From 96255877a8e836e75bd81351ff182bfaef2ea87b Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Tue, 16 Sep 2025 14:34:23 +0530 Subject: [PATCH 001/161] Initial commit --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6acc570d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +.DS_Store \ No newline at end of file From f3ab024d6a9cf0dade48c9ca4d1bd0b1ec93bbdb Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Tue, 16 Sep 2025 14:36:27 +0530 Subject: [PATCH 002/161] Added package.json --- package.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 00000000..ae658d65 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "opencollection", + "private": true, + "workspaces": [ + "packages/oc-converters", + "packages/oc-schema", + "packages/oc-core", + "packages/oc-express" + ], + "homepage": "https://uwww.opencollection.com" +} From 890eb44d806d5c2b6630cd98053e171b1d10323c Mon Sep 17 00:00:00 2001 From: naman-bruno Date: Thu, 18 Sep 2025 16:47:04 +0530 Subject: [PATCH 003/161] init --- .gitignore | 3 +- examples/express-server/package-lock.json | 951 +++ examples/express-server/package.json | 28 + examples/express-server/server-cjs.cjs | 117 + examples/express-server/server.js | 247 + examples/react/index.html | 12 + examples/react/package-lock.json | 1304 ++++ examples/react/package.json | 20 + examples/react/src/App.jsx | 102 + examples/react/src/main.jsx | 12 + examples/react/vite.config.js | 9 + examples/vanilla-html/index.html | 244 + main.json | 4476 ++++++++++++ package-lock.json | 6230 +++++++++++++++++ packages/oc-core/eslint.config.js | 30 + packages/oc-core/index.html | 14 + packages/oc-core/package.json | 108 + packages/oc-core/postcss.config.cjs | 6 + packages/oc-core/src/App.tsx | 19 + packages/oc-core/src/assets/react.svg | 1 + .../oc-core/src/core/FileCollectionLoader.tsx | 76 + .../src/core/OpenCollectionPlayground.tsx | 142 + packages/oc-core/src/express.ts | 97 + packages/oc-core/src/hooks/index.ts | 9 + .../oc-core/src/hooks/useCollectionData.ts | 103 + packages/oc-core/src/hooks/useCustomPages.ts | 63 + .../oc-core/src/hooks/useItemFiltering.ts | 60 + .../oc-core/src/hooks/useMarkdownRenderer.ts | 55 + .../oc-core/src/hooks/usePageNavigation.ts | 173 + packages/oc-core/src/hooks/useResponsive.ts | 43 + packages/oc-core/src/hooks/useRunnerMode.ts | 15 + packages/oc-core/src/hooks/useSearchModal.ts | 169 + .../oc-core/src/hooks/useSectionTracking.ts | 424 ++ packages/oc-core/src/hooks/useTheme.ts | 13 + packages/oc-core/src/index.ts | 31 + .../oc-core/src/layouts/ContentLayout.tsx | 56 + .../oc-core/src/layouts/DesktopLayout.tsx | 245 + packages/oc-core/src/main.tsx | 10 + .../oc-core/src/runner/RequestExecutor.ts | 219 + packages/oc-core/src/runner/ScriptRunner.ts | 332 + .../src/runner/VariableInterpolator.ts | 103 + packages/oc-core/src/runner/index.ts | 109 + packages/oc-core/src/server.ts | 116 + packages/oc-core/src/standalone.ts | 103 + packages/oc-core/src/styles/App.css | 36 + packages/oc-core/src/styles/index.css | 1324 ++++ packages/oc-core/src/test.ts | 1662 +++++ packages/oc-core/src/types/index.ts | 147 + packages/oc-core/src/ui/BodyCard.tsx | 71 + packages/oc-core/src/ui/Card.tsx | 127 + packages/oc-core/src/ui/CodeBlock.tsx | 177 + packages/oc-core/src/ui/CodeEditor.tsx | 81 + packages/oc-core/src/ui/ContentView.tsx | 344 + .../oc-core/src/ui/CustomPageRenderer.tsx | 71 + packages/oc-core/src/ui/DataTable.tsx | 222 + .../src/ui/EnvironmentVariablesSection.tsx | 119 + .../oc-core/src/ui/ExpandableFormTable.tsx | 170 + packages/oc-core/src/ui/ItemComponent.tsx | 1396 ++++ packages/oc-core/src/ui/MobileLayout.tsx | 145 + packages/oc-core/src/ui/Overview.tsx | 204 + packages/oc-core/src/ui/PageNavigation.tsx | 124 + packages/oc-core/src/ui/SearchModal.tsx | 136 + packages/oc-core/src/ui/Sidebar.tsx | 426 ++ .../oc-core/src/ui/SinglePageRenderer.tsx | 164 + packages/oc-core/src/ui/TableHelpers.tsx | 221 + packages/oc-core/src/ui/index.ts | 9 + .../src/ui/request-runner/QueryBar.tsx | 90 + .../src/ui/request-runner/RequestHeader.tsx | 81 + .../src/ui/request-runner/RequestPane.tsx | 682 ++ .../src/ui/request-runner/RequestRunner.tsx | 121 + .../src/ui/request-runner/ResponsePane.tsx | 201 + .../oc-core/src/ui/request-runner/index.ts | 5 + packages/oc-core/src/utils/fileUtils.ts | 93 + packages/oc-core/src/utils/itemUtils.ts | 45 + packages/oc-core/src/utils/yamlUtils.ts | 66 + packages/oc-core/src/vite-env.d.ts | 1 + packages/oc-core/standalone.d.ts | 15 + packages/oc-core/tsconfig.json | 28 + packages/oc-core/vite.config.standalone.ts | 39 + packages/oc-core/vite.config.ts | 37 + packages/oc-proxy/package-lock.json | 967 +++ packages/oc-proxy/package.json | 23 + packages/oc-proxy/src/index.js | 127 + 83 files changed, 26695 insertions(+), 1 deletion(-) create mode 100644 examples/express-server/package-lock.json create mode 100644 examples/express-server/package.json create mode 100644 examples/express-server/server-cjs.cjs create mode 100644 examples/express-server/server.js create mode 100644 examples/react/index.html create mode 100644 examples/react/package-lock.json create mode 100644 examples/react/package.json create mode 100644 examples/react/src/App.jsx create mode 100644 examples/react/src/main.jsx create mode 100644 examples/react/vite.config.js create mode 100644 examples/vanilla-html/index.html create mode 100644 main.json create mode 100644 package-lock.json create mode 100644 packages/oc-core/eslint.config.js create mode 100644 packages/oc-core/index.html create mode 100644 packages/oc-core/package.json create mode 100644 packages/oc-core/postcss.config.cjs create mode 100644 packages/oc-core/src/App.tsx create mode 100644 packages/oc-core/src/assets/react.svg create mode 100644 packages/oc-core/src/core/FileCollectionLoader.tsx create mode 100644 packages/oc-core/src/core/OpenCollectionPlayground.tsx create mode 100644 packages/oc-core/src/express.ts create mode 100644 packages/oc-core/src/hooks/index.ts create mode 100644 packages/oc-core/src/hooks/useCollectionData.ts create mode 100644 packages/oc-core/src/hooks/useCustomPages.ts create mode 100644 packages/oc-core/src/hooks/useItemFiltering.ts create mode 100644 packages/oc-core/src/hooks/useMarkdownRenderer.ts create mode 100644 packages/oc-core/src/hooks/usePageNavigation.ts create mode 100644 packages/oc-core/src/hooks/useResponsive.ts create mode 100644 packages/oc-core/src/hooks/useRunnerMode.ts create mode 100644 packages/oc-core/src/hooks/useSearchModal.ts create mode 100644 packages/oc-core/src/hooks/useSectionTracking.ts create mode 100644 packages/oc-core/src/hooks/useTheme.ts create mode 100644 packages/oc-core/src/index.ts create mode 100644 packages/oc-core/src/layouts/ContentLayout.tsx create mode 100644 packages/oc-core/src/layouts/DesktopLayout.tsx create mode 100644 packages/oc-core/src/main.tsx create mode 100644 packages/oc-core/src/runner/RequestExecutor.ts create mode 100644 packages/oc-core/src/runner/ScriptRunner.ts create mode 100644 packages/oc-core/src/runner/VariableInterpolator.ts create mode 100644 packages/oc-core/src/runner/index.ts create mode 100644 packages/oc-core/src/server.ts create mode 100644 packages/oc-core/src/standalone.ts create mode 100644 packages/oc-core/src/styles/App.css create mode 100644 packages/oc-core/src/styles/index.css create mode 100644 packages/oc-core/src/test.ts create mode 100644 packages/oc-core/src/types/index.ts create mode 100644 packages/oc-core/src/ui/BodyCard.tsx create mode 100644 packages/oc-core/src/ui/Card.tsx create mode 100644 packages/oc-core/src/ui/CodeBlock.tsx create mode 100644 packages/oc-core/src/ui/CodeEditor.tsx create mode 100644 packages/oc-core/src/ui/ContentView.tsx create mode 100644 packages/oc-core/src/ui/CustomPageRenderer.tsx create mode 100644 packages/oc-core/src/ui/DataTable.tsx create mode 100644 packages/oc-core/src/ui/EnvironmentVariablesSection.tsx create mode 100644 packages/oc-core/src/ui/ExpandableFormTable.tsx create mode 100644 packages/oc-core/src/ui/ItemComponent.tsx create mode 100644 packages/oc-core/src/ui/MobileLayout.tsx create mode 100644 packages/oc-core/src/ui/Overview.tsx create mode 100644 packages/oc-core/src/ui/PageNavigation.tsx create mode 100644 packages/oc-core/src/ui/SearchModal.tsx create mode 100644 packages/oc-core/src/ui/Sidebar.tsx create mode 100644 packages/oc-core/src/ui/SinglePageRenderer.tsx create mode 100644 packages/oc-core/src/ui/TableHelpers.tsx create mode 100644 packages/oc-core/src/ui/index.ts create mode 100644 packages/oc-core/src/ui/request-runner/QueryBar.tsx create mode 100644 packages/oc-core/src/ui/request-runner/RequestHeader.tsx create mode 100644 packages/oc-core/src/ui/request-runner/RequestPane.tsx create mode 100644 packages/oc-core/src/ui/request-runner/RequestRunner.tsx create mode 100644 packages/oc-core/src/ui/request-runner/ResponsePane.tsx create mode 100644 packages/oc-core/src/ui/request-runner/index.ts create mode 100644 packages/oc-core/src/utils/fileUtils.ts create mode 100644 packages/oc-core/src/utils/itemUtils.ts create mode 100644 packages/oc-core/src/utils/yamlUtils.ts create mode 100644 packages/oc-core/src/vite-env.d.ts create mode 100644 packages/oc-core/standalone.d.ts create mode 100644 packages/oc-core/tsconfig.json create mode 100644 packages/oc-core/vite.config.standalone.ts create mode 100644 packages/oc-core/vite.config.ts create mode 100644 packages/oc-proxy/package-lock.json create mode 100644 packages/oc-proxy/package.json create mode 100644 packages/oc-proxy/src/index.js diff --git a/.gitignore b/.gitignore index 6acc570d..1d144f69 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules dist -.DS_Store \ No newline at end of file +.DS_Store +dist-standalone \ No newline at end of file diff --git a/examples/express-server/package-lock.json b/examples/express-server/package-lock.json new file mode 100644 index 00000000..02c94549 --- /dev/null +++ b/examples/express-server/package-lock.json @@ -0,0 +1,951 @@ +{ + "name": "opencollection-express-example", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "opencollection-express-example", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "express": "^4.18.2" + }, + "devDependencies": { + "@types/express": "^4.17.21" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", + "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.0.tgz", + "integrity": "sha512-y1dMvuvJspJiPSDZUQ+WMBvF7dpnEqN4x9DDC9ie5Fs/HUZJA3wFp7EhHoVaKX/iI0cRoECV8X2jL8zi0xrHCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.12.0" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undici-types": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", + "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/examples/express-server/package.json b/examples/express-server/package.json new file mode 100644 index 00000000..93077f6e --- /dev/null +++ b/examples/express-server/package.json @@ -0,0 +1,28 @@ +{ + "name": "opencollection-express-example", + "version": "1.0.0", + "description": "Express server example using OpenCollection playground", + "main": "server.js", + "type": "module", + "scripts": { + "start": "node server.js", + "start:cjs": "node server-cjs.cjs", + "dev": "node --watch server.js", + "dev:cjs": "node --watch server-cjs.cjs", + "test": "node test.js" + }, + "dependencies": { + "express": "^4.18.2" + }, + "devDependencies": { + "@types/express": "^4.17.21" + }, + "keywords": [ + "opencollection", + "express", + "api-docs", + "playground" + ], + "author": "OpenCollection", + "license": "MIT" +} \ No newline at end of file diff --git a/examples/express-server/server-cjs.cjs b/examples/express-server/server-cjs.cjs new file mode 100644 index 00000000..1df77472 --- /dev/null +++ b/examples/express-server/server-cjs.cjs @@ -0,0 +1,117 @@ +const express = require('express'); +const { playgroundHandler, setupPlaygroundAssets } = require('../../packages/oc-playground/dist/express.cjs'); + +const app = express(); +const PORT = process.env.PORT || 3001; + +setupPlaygroundAssets(app, express.static, { + playgroundDistPath: '../../packages/oc-playground/dist-standalone' +}); + +const apiCollection = { + name: "My Express API (CommonJS)", + description: "Example API documentation using OpenCollection playground with Express and CommonJS", + docs: "This collection demonstrates different request types, bodies, auth, variables, and assertions for an Express-based API using CommonJS imports.", + environments: [ + { + name: "Development", + description: "Local development environment", + variables: [ + { name: "baseUrl", value: `http://localhost:${PORT}` }, + { name: "authToken", value: "dev-token-123" } + ] + } + ], + base: { + headers: [ + { name: "Content-Type", value: "application/json" } + ], + auth: { + type: "bearer", + token: "{{authToken}}" + } + }, + items: [ + { + name: "Get Users", + type: "http", + url: "{{baseUrl}}/api/users", + method: "GET", + docs: "Retrieve a list of all users", + params: [ + { name: "limit", value: "10", type: "query" } + ], + assertions: [ + { expression: "response.status", operator: "==", value: "200" } + ] + }, + { + name: "Create User", + type: "http", + url: "{{baseUrl}}/api/users", + method: "POST", + docs: "Create a new user", + body: { + type: "json", + data: JSON.stringify({ + name: "Bob Wilson", + email: "bob@example.com" + }, null, 2) + } + } + ] +}; + +app.get('/api-docs', playgroundHandler({ + collection: apiCollection, + theme: 'dark', + title: 'My Express API Documentation (CommonJS)' +})); + +app.get('/simple-docs', playgroundHandler({ + name: "Simple API (CommonJS)", + description: "My awesome API documentation using CommonJS", + items: [{ + type: "http", + method: "GET", + url: "https://api.example.com/users", + docs: "Get all users" + }] +})); + +app.get('/api/users', (req, res) => { + res.json({ + users: [ + { id: 1, name: 'Bob Wilson', email: 'bob@example.com' }, + { id: 2, name: 'Carol Davis', email: 'carol@example.com' } + ] + }); +}); + +app.post('/api/users', express.json(), (req, res) => { + const { name, email } = req.body; + res.status(201).json({ + id: Date.now(), + name, + email, + createdAt: new Date().toISOString() + }); +}); + +app.get('/', (req, res) => { + res.send(` +

OpenCollection Express Example (CommonJS)

+

Visit the API documentation:

+ + `); +}); + +app.listen(PORT, () => { + console.log(`🚀 CommonJS Server running on http://localhost:${PORT}`); + console.log(`📚 API docs available at:`); + console.log(` - http://localhost:${PORT}/api-docs`); + console.log(` - http://localhost:${PORT}/simple-docs`); +}); \ No newline at end of file diff --git a/examples/express-server/server.js b/examples/express-server/server.js new file mode 100644 index 00000000..c421bca5 --- /dev/null +++ b/examples/express-server/server.js @@ -0,0 +1,247 @@ +import express from 'express'; +import { playgroundHandler, setupPlaygroundAssets } from '../../packages/oc-playground/dist/express.js'; + +const app = express(); +const PORT = process.env.PORT || 3000; + +setupPlaygroundAssets(app, express.static, { + playgroundDistPath: '../../packages/oc-playground/dist-standalone' +}); + +const apiCollection = { + name: "My Express API", + description: "Example API documentation using OpenCollection playground with Express", + docs: "This collection demonstrates different request types, bodies, auth, variables, and assertions for an Express-based API.", + environments: [ + { + name: "Development", + description: "Local development environment", + variables: [ + { name: "baseUrl", value: `http://localhost:${PORT}` }, + { name: "authToken", value: "dev-token-123" } + ] + }, + { + name: "Production", + variables: [ + { name: "baseUrl", value: "https://api.example.com" }, + { name: "authToken", value: "prod-token-456" } + ] + } + ], + base: { + headers: [ + { name: "Content-Type", value: "application/json" } + ], + auth: { + type: "bearer", + token: "{{authToken}}" + }, + variables: [ + { name: "globalVar", value: "global-value" } + ] + }, + items: [ + { + name: "Get Users", + type: "http", + url: "{{baseUrl}}/api/users", + method: "GET", + docs: "Retrieve a list of all users", + params: [ + { name: "limit", value: "10", type: "query" }, + { name: "offset", value: "0", type: "query" } + ], + assertions: [ + { expression: "response.status", operator: "==", value: "200" } + ] + }, + { + name: "Get User by ID", + type: "http", + url: "{{baseUrl}}/api/users/:id", + method: "GET", + docs: "Get a specific user by their ID", + params: [ + { name: "id", value: "123", type: "path" } + ], + headers: [ + { name: "X-Custom-Header", value: "CustomValue" } + ] + }, + { + name: "Create User", + type: "http", + url: "{{baseUrl}}/api/users", + method: "POST", + docs: "Create a new user", + body: { + type: "json", + data: JSON.stringify({ + name: "Alice Johnson", + email: "alice@example.com", + role: "user" + }, null, 2) + }, + assertions: [ + { expression: "response.body.name", operator: "==", value: "Alice Johnson" }, + { expression: "response.status", operator: "==", value: "201" } + ] + }, + { + name: "Update User", + type: "http", + url: "{{baseUrl}}/api/users/:id", + method: "PUT", + docs: "Update an existing user", + params: [ + { name: "id", value: "123", type: "path" } + ], + body: [ + { name: "name", value: "Alice Smith" }, + { name: "email", value: "alice.smith@example.com" } + ] + }, + { + name: "Delete User", + type: "http", + url: "{{baseUrl}}/api/users/:id", + method: "DELETE", + docs: "Delete a user by ID", + params: [ + { name: "id", value: "123", type: "path" } + ], + auth: { + type: "apikey", + key: "X-API-Key", + value: "secret-key-999", + placement: "header" + } + }, + { + name: "User Management", + type: "folder", + docs: "Folder containing advanced user management endpoints", + variables: [ + { name: "adminUserId", value: "admin-123" } + ], + headers: [ + { name: "X-Admin-Header", value: "AdminHeaderValue" } + ], + auth: { + type: "basic", + username: "admin", + password: "password" + } + }, + { + name: "Get Admin Dashboard", + type: "http", + url: "{{baseUrl}}/api/admin/dashboard", + method: "GET", + docs: "Access the admin dashboard (requires admin auth)" + } + ] +}; + +app.get('/api-docs', playgroundHandler({ + collection: apiCollection, + theme: 'light', + title: 'My Express API Documentation' +})); + +app.get('/simple-docs', playgroundHandler({ + name: "Simple API", + description: "My awesome API documentation", + items: [{ + type: "http", + method: "GET", + url: "https://api.example.com/users", + docs: "Get all users" + }], + theme: 'dark' +})); + +app.get('/api/users', (req, res) => { + const { limit = 10, offset = 0 } = req.query; + res.json({ + users: [ + { id: 1, name: 'John Doe', email: 'john@example.com' }, + { id: 2, name: 'Jane Smith', email: 'jane@example.com' } + ].slice(offset, offset + parseInt(limit)), + total: 2, + limit: parseInt(limit), + offset: parseInt(offset) + }); +}); + +app.get('/api/users/:id', (req, res) => { + const { id } = req.params; + res.json({ + id: parseInt(id), + name: 'John Doe', + email: 'john@example.com', + role: 'user' + }); +}); + +app.post('/api/users', express.json(), (req, res) => { + const { name, email, role = 'user' } = req.body; + res.status(201).json({ + id: Date.now(), + name, + email, + role, + createdAt: new Date().toISOString() + }); +}); + +app.put('/api/users/:id', express.json(), (req, res) => { + const { id } = req.params; + const { name, email } = req.body; + res.json({ + id: parseInt(id), + name, + email, + updatedAt: new Date().toISOString() + }); +}); + +app.delete('/api/users/:id', (req, res) => { + const { id } = req.params; + res.status(204).send(); +}); + +app.get('/api/admin/dashboard', (req, res) => { + res.json({ + stats: { + totalUsers: 42, + activeUsers: 38, + newUsersToday: 5 + }, + message: 'Admin dashboard data' + }); +}); + +app.get('/', (req, res) => { + res.send(` +

OpenCollection Express Example

+

Visit the API documentation:

+ +

Try the API endpoints:

+ + `); +}); + +app.listen(PORT, () => { + console.log(`🚀 Server running on http://localhost:${PORT}`); + console.log(`📚 API docs available at:`); + console.log(` - http://localhost:${PORT}/api-docs`); + console.log(` - http://localhost:${PORT}/simple-docs`); +}); \ No newline at end of file diff --git a/examples/react/index.html b/examples/react/index.html new file mode 100644 index 00000000..87d1bf83 --- /dev/null +++ b/examples/react/index.html @@ -0,0 +1,12 @@ + + + + + + OpenCollection Playground - React Example + + +
+ + + \ No newline at end of file diff --git a/examples/react/package-lock.json b/examples/react/package-lock.json new file mode 100644 index 00000000..591843c4 --- /dev/null +++ b/examples/react/package-lock.json @@ -0,0 +1,1304 @@ +{ + "name": "opencollection-react-example", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "opencollection-react-example", + "version": "1.0.0", + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@vitejs/plugin-react": "^4.0.0", + "vite": "^4.4.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.24", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz", + "integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.4.tgz", + "integrity": "sha512-L+YvJwGAgwJBV1p6ffpSTa2KRc69EeeYGYjRVWKs0GKrK+LON0GC0gV+rKSNtALEDvMDqkvCFq9r1r94/Gjwxw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/browserslist": { + "version": "4.26.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", + "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.3", + "caniuse-lite": "^1.0.30001741", + "electron-to-chromium": "^1.5.218", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001743", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz", + "integrity": "sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.218", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.218.tgz", + "integrity": "sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", + "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "4.5.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.14.tgz", + "integrity": "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/examples/react/package.json b/examples/react/package.json new file mode 100644 index 00000000..cce434ae --- /dev/null +++ b/examples/react/package.json @@ -0,0 +1,20 @@ +{ + "name": "opencollection-react-example", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@vitejs/plugin-react": "^4.0.0", + "vite": "^4.4.0" + } +} \ No newline at end of file diff --git a/examples/react/src/App.jsx b/examples/react/src/App.jsx new file mode 100644 index 00000000..e337e055 --- /dev/null +++ b/examples/react/src/App.jsx @@ -0,0 +1,102 @@ +import React from 'react'; +import { OpenCollectionPlayground } from '../../../packages/oc-playground/dist/oc-playground.es.js'; +import '../../../packages/oc-playground/dist/playground.css'; + +const sampleCollection = { + name: "Sample API Collection", + description: "A comprehensive OpenCollection demonstration showcasing various API endpoints and configurations", + items: [ + { + type: "http", + name: "Get Users", + method: "GET", + url: "https://jsonplaceholder.typicode.com/users", + docs: "Retrieves a list of all users from the JSONPlaceholder API", + headers: [ + { + name: "Accept", + value: "application/json" + } + ] + }, + { + type: "http", + name: "Create User", + method: "POST", + url: "https://jsonplaceholder.typicode.com/users", + docs: "Creates a new user in the system", + headers: [ + { + name: "Content-Type", + value: "application/json" + } + ], + body: { + type: "json", + data: JSON.stringify({ + name: "John Doe", + email: "john@example.com" + }, null, 2) + } + }, + { + type: "http", + name: "Get All Posts", + method: "GET", + url: "https://jsonplaceholder.typicode.com/posts", + docs: "Retrieves all blog posts" + }, + { + type: "http", + name: "Get Post by ID", + method: "GET", + url: "https://jsonplaceholder.typicode.com/posts/1", + docs: "Retrieves a specific post by its ID" + }, + { + type: "script", + script: "// Utility functions for API testing\nfunction generateRandomId() {\n return Math.floor(Math.random() * 1000) + 1;\n}\n\nconsole.log('Utility functions loaded');" + } + ], + environments: [ + { + name: "Production", + description: "Production environment configuration", + variables: [ + { + name: "baseUrl", + value: "https://api.example.com" + } + ] + }, + { + name: "Development", + description: "Development environment for testing", + variables: [ + { + name: "baseUrl", + value: "http://localhost:3000" + } + ] + } + ] +}; + +function App() { + return ( + + 🚀 + + } + /> + ); +} + +export default App; \ No newline at end of file diff --git a/examples/react/src/main.jsx b/examples/react/src/main.jsx new file mode 100644 index 00000000..7c3fd8c2 --- /dev/null +++ b/examples/react/src/main.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.jsx'; + +const container = document.getElementById('root'); +const root = createRoot(container); + +root.render( + + + +); \ No newline at end of file diff --git a/examples/react/vite.config.js b/examples/react/vite.config.js new file mode 100644 index 00000000..4568ffba --- /dev/null +++ b/examples/react/vite.config.js @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { + port: 3001 + } +}); \ No newline at end of file diff --git a/examples/vanilla-html/index.html b/examples/vanilla-html/index.html new file mode 100644 index 00000000..ae53b620 --- /dev/null +++ b/examples/vanilla-html/index.html @@ -0,0 +1,244 @@ + + + + + + OpenCollection Playground - Vanilla HTML Example + + + +
+ + + + + + + \ No newline at end of file diff --git a/main.json b/main.json new file mode 100644 index 00000000..31efaa74 --- /dev/null +++ b/main.json @@ -0,0 +1,4476 @@ +{ + "name": "bruno-testbench", + "version": "1", + "items": [ + { + "type": "http", + "name": "ping", + "filename": "ping.bru", + "seq": 1, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "bru.runner.stopExecution();" + }, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "folder", + "name": "graphql", + "filename": "graphql", + "items": [ + { + "type": "graphql", + "name": "spacex", + "filename": "spacex.bru", + "seq": 1, + "request": { + "url": "https://spacex-production.up.railway.app/", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "graphql", + "graphql": { + "query": "{\n company {\n ceo\n }\n}\n" + }, + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "cgAc6fGnMoVZmVUmttac9" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "echo", + "filename": "echo", + "items": [ + { + "type": "http", + "name": "echo bom json", + "filename": "echo bom json.bru", + "seq": 1, + "request": { + "url": "{{host}}/api/echo/bom-json-test", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "echo form-url-encoded", + "filename": "echo form-url-encoded.bru", + "seq": 9, + "request": { + "url": "{{echo-host}}", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "formUrlEncoded", + "formUrlEncoded": [ + { + "name": "form-data-key", + "value": "{{form-data-key}}", + "enabled": true + }, + { + "name": "form-data-stringified-object", + "value": "{{form-data-stringified-object}}", + "enabled": true + } + ], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "let obj = JSON.stringify({foo:123});\nbru.setVar('form-data-key', 'form-data-value');\nbru.setVar('form-data-stringified-object', obj);" + }, + "vars": {}, + "assertions": [ + { + "name": "res.body", + "value": "eq form-data-key=form-data-value&form-data-stringified-object=%7B%22foo%22%3A123%7D", + "enabled": true, + "uid": "GXP3iOow05yWkFbazZh97" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "echo json", + "filename": "echo json.bru", + "seq": 2, + "request": { + "url": "{{host}}/api/echo/json", + "method": "POST", + "headers": [ + { + "name": "foo", + "value": "bar", + "enabled": true + } + ], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"hello\": \"bruno\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "bru.setVar(\"foo\", \"foo-world-2\");" + }, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "NtmLz1fS7Ms6eQ6ZrDRN0" + } + ], + "tests": "test(\"should return json\", function() {\n const data = res.getBody();\n expect(res.getBody()).to.eql({\n \"hello\": \"bruno\"\n });\n}); ", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "echo multipart via scripting", + "filename": "echo multipart scripting.bru", + "seq": 10, + "request": { + "url": "{{echo-host}}", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "multipartForm", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "const FormData = require(\"form-data\");\nconst form = new FormData();\nform.append('form-data-key', 'form-data-value');\nreq.setBody(form);" + }, + "vars": {}, + "assertions": [ + { + "name": "res.body", + "value": "contains form-data-value", + "enabled": true, + "uid": "6jqCWTqQtH6GSgpFNU3ep" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "echo multipart", + "filename": "echo multipart.bru", + "seq": 8, + "request": { + "url": "{{echo-host}}", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "multipartForm", + "formUrlEncoded": [], + "multipartForm": [ + { + "type": "text", + "name": "foo", + "value": "{\"bar\":\"baz\"}", + "enabled": true + }, + { + "type": "text", + "name": "form-data-key", + "value": "{{form-data-key}}", + "enabled": true + }, + { + "type": "text", + "name": "form-data-stringified-object", + "value": "{{form-data-stringified-object}}", + "enabled": true + }, + { + "type": "file", + "name": "file", + "value": [ + "bruno.png" + ], + "enabled": true + } + ], + "file": [] + }, + "script": { + "req": "let obj = JSON.stringify({foo:123});\nbru.setVar('form-data-key', 'form-data-value');\nbru.setVar('form-data-stringified-object', obj);" + }, + "vars": {}, + "assertions": [ + { + "name": "res.body", + "value": "contains form-data-value", + "enabled": true, + "uid": "mvYX54Lmd3i79iB48LEyz" + }, + { + "name": "res.body", + "value": "contains {\"foo\":123}", + "enabled": true, + "uid": "CAEG8PjR6KPYsIbxz1N0f" + }, + { + "name": "res.body", + "value": "contains Content-Type: application/json--test", + "enabled": true, + "uid": "aZHXzHRJMkWaVqA0kFLJV" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "echo numbers", + "filename": "echo numbers.bru", + "seq": 1, + "request": { + "url": "{{echo-host}}", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"integer\": 123,\n \"negativeInteger\": -99,\n \"zero\": 0,\n \"float\": 2.718,\n \"negativeFloat\": -1.618,\n \"largeDouble\": 12345.678901234567,\n \"smallDouble\": 9.876e-12,\n \"booleanTrue\": true,\n \"booleanFalse\": false\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body.integer", + "value": "eq 123", + "enabled": true, + "uid": "ZtVS9gUgc5iSmDq0gNIaT" + }, + { + "name": "res.body.integer", + "value": "isNumber", + "enabled": true, + "uid": "fqLAWWm8pqG2YacqLa58R" + }, + { + "name": "res.body.negativeInteger", + "value": "eq -99", + "enabled": true, + "uid": "ULU5rgNpJBc4t1OspKAIi" + }, + { + "name": "res.body.negativeInteger", + "value": "isNumber", + "enabled": true, + "uid": "gpZjNNkPr2AXn6GzkhkN8" + }, + { + "name": "res.body.zero", + "value": "eq 0", + "enabled": true, + "uid": "D2pXZJB9aTOAwqwXmTVEB" + }, + { + "name": "res.body.zero", + "value": "isNumber", + "enabled": true, + "uid": "TE6Q5jhRkjg0Rsp0zNLl7" + }, + { + "name": "res.body.float", + "value": "eq 2.718", + "enabled": true, + "uid": "izHjCWoESD3JaoDgQBwa0" + }, + { + "name": "res.body.float", + "value": "isNumber", + "enabled": true, + "uid": "MRZ9PsV8AogLzvgUolu68" + }, + { + "name": "res.body.negativeFloat", + "value": "eq -1.618", + "enabled": true, + "uid": "fzzJP0kj71WFqZ0DDMeIo" + }, + { + "name": "res.body.negativeFloat", + "value": "isNumber", + "enabled": true, + "uid": "cm2c5v40ilCZsraGiR564" + }, + { + "name": "res.body.largeDouble", + "value": "eq 12345.678901234567", + "enabled": true, + "uid": "6gnCn1c8TSHigYy1taFhN" + }, + { + "name": "res.body.largeDouble", + "value": "isNumber", + "enabled": true, + "uid": "qEHQ1qRt6HyCzMqhUrdIT" + }, + { + "name": "res.body.smallDouble", + "value": "eq 9.876e-12", + "enabled": true, + "uid": "ZKrwMPCjBEGqZ4yb1KxoG" + }, + { + "name": "res.body.smallDouble", + "value": "isNumber", + "enabled": true, + "uid": "xRWLvDJJZSzmpz5vubw8p" + }, + { + "name": "res.body.booleanTrue", + "value": "eq true", + "enabled": true, + "uid": "FENKG4gsPZtHyYYBLNKie" + }, + { + "name": "res.body.booleanFalse", + "value": "eq false", + "enabled": true, + "uid": "6SoMig8CeBBIW590VwX5K" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "echo plaintext", + "filename": "echo plaintext.bru", + "seq": 3, + "request": { + "url": "{{host}}/api/echo/text", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "text", + "text": "hello", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "uWsY31SAmtyc2JIpTxqzO" + } + ], + "tests": "test(\"should return plain text\", function() {\n const data = res.getBody();\n expect(res.getBody()).to.eql(\"hello\");\n});\n", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "echo xml parsed(self closing tags)", + "filename": "echo xml parsed(self closing tags).bru", + "seq": 6, + "request": { + "url": "{{host}}/api/echo/xml-parsed", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "xml", + "xml": "\n bruno\n \n", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "F4K7K3z239O2WvhcYc8cu" + } + ], + "tests": "test(\"should return parsed xml\", function() {\n const data = res.getBody();\n expect(res.getBody()).to.eql({\n \"hello\": {\n \"world\": [\n \"bruno\",\n \"\"\n ]\n }\n });\n});\n", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "echo xml parsed", + "filename": "echo xml parsed.bru", + "seq": 4, + "request": { + "url": "{{host}}/api/echo/xml-parsed", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "xml", + "xml": "\n bruno\n", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "4OxoP1d1Sn7J0Rr5A5MoR" + } + ], + "tests": "test(\"should return parsed xml\", function() {\n const data = res.getBody();\n expect(res.getBody()).to.eql({\n \"hello\": {\n \"world\": [\"bruno\"]\n }\n });\n});\n", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "echo xml raw", + "filename": "echo xml raw.bru", + "seq": 5, + "request": { + "url": "{{host}}/api/echo/xml-raw", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "xml", + "xml": "bruno", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test echo any", + "filename": "test echo any.bru", + "seq": 11, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"headers\": { \"content-type\": \"text/plain\" },\n \"content\": \"hello\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body", + "value": "eq hello", + "enabled": true, + "uid": "7Plt7ZGJQhoOFXFKnTQce" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test echo-any json", + "filename": "test echo-any json.bru", + "seq": 12, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"type\": \"application/json\",\n \"contentJSON\": {\"x\": 42}\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body.x", + "value": "eq 42", + "enabled": true, + "uid": "wU1YDNe5ZYZLi1noaCsHK" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "folder", + "name": "multiline", + "filename": "multiline", + "items": [ + { + "type": "http", + "name": "echo binary", + "filename": "echo binary.bru", + "seq": 1, + "request": { + "url": "{{echo-host}}", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "file", + "formUrlEncoded": [], + "multipartForm": [], + "file": [ + { + "filePath": "bruno.png", + "contentType": "image/png", + "selected": true + } + ] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + } + ] + }, + { + "type": "folder", + "name": "auth", + "filename": "auth", + "items": [ + { + "type": "folder", + "name": "inherit auth", + "filename": "inherit auth", + "items": [ + { + "type": "http", + "name": "inherit Bearer Auth 200", + "filename": "inherit Bearer Auth 200.bru", + "seq": 2, + "request": { + "url": "{{host}}/api/auth/bearer/protected", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "res": "bru.setEnvVar(\"foo\", \"bar\");" + }, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "200", + "enabled": true, + "uid": "60I5u9Am5kDPd7juwvKV5" + }, + { + "name": "res.body.message", + "value": "Authentication successful", + "enabled": true, + "uid": "fkQViSlIxJJ0RyhCN5tp3" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "inherit" + } + } + } + ] + }, + { + "type": "folder", + "name": "basic", + "filename": "basic", + "items": [ + { + "type": "folder", + "name": "via auth", + "filename": "via auth", + "items": [ + { + "type": "http", + "name": "Basic Auth 200", + "filename": "Basic Auth 200.bru", + "seq": 1, + "request": { + "url": "{{host}}/api/auth/basic/protected", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "200", + "enabled": true, + "uid": "d5CVuD1HpRVtyUWziE6Y7" + }, + { + "name": "res.body.message", + "value": "Authentication successful", + "enabled": true, + "uid": "kSZPZekosG0Uci7SE8Hju" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "basic", + "basic": { + "username": "bruno", + "password": "{{basic_auth_password}}" + } + } + } + }, + { + "type": "http", + "name": "Basic Auth 400", + "filename": "Basic Auth 401.bru", + "seq": 2, + "request": { + "url": "{{host}}/api/auth/basic/protected", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "401", + "enabled": true, + "uid": "xnlyXClGdlx7gGLwzaniZ" + }, + { + "name": "res.body", + "value": "Unauthorized", + "enabled": true, + "uid": "20H5jGbawcioeVKPLMsHn" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "via script", + "filename": "via script", + "items": [ + { + "type": "http", + "name": "Basic Auth 401", + "filename": "Basic Auth 401.bru", + "seq": 2, + "request": { + "url": "{{host}}/api/auth/basic/protected", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "const username = \"bruno\";\nconst password = \"invalid\";\n\nconst authString = `${username}:${password}`;\nconst encodedAuthString = require('btoa')(authString);\n\nreq.setHeader(\"Authorization\", `Basic ${encodedAuthString}`);" + }, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "401", + "enabled": true, + "uid": "96LEDffmtAmfKTsfemJaB" + }, + { + "name": "res.body", + "value": "Unauthorized", + "enabled": true, + "uid": "7B66oHgquEiW39PCRcf5A" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "Basic Auth 200", + "filename": "Basic Auth 200.bru", + "seq": 1, + "request": { + "url": "{{host}}/api/auth/basic/protected", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "const username = \"bruno\";\nconst password = \"della\";\n\nconst authString = `${username}:${password}`;\nconst encodedAuthString = require('btoa')(authString);\n\nreq.setHeader(\"Authorization\", `Basic ${encodedAuthString}`);" + }, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "C7EXFxZSx9MTjrPvbZK9h" + }, + { + "name": "res.body.message", + "value": "Authentication successful", + "enabled": true, + "uid": "zXFBjrNaXojNhRdJVJ86w" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + } + ] + }, + { + "type": "folder", + "name": "cookie", + "filename": "cookie", + "items": [ + { + "type": "http", + "name": "Check", + "filename": "Check.bru", + "seq": 2, + "request": { + "url": "{{host}}/api/auth/cookie/protected", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "Login", + "filename": "Login.bru", + "seq": 1, + "request": { + "url": "{{host}}/api/auth/cookie/login", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "bearer", + "filename": "bearer", + "items": [ + { + "type": "folder", + "name": "via headers", + "filename": "via headers", + "items": [ + { + "type": "http", + "name": "Bearer Auth 200", + "filename": "Bearer Auth 200.bru", + "seq": 1, + "request": { + "url": "{{host}}/api/auth/bearer/protected", + "method": "GET", + "headers": [ + { + "name": "Authorization", + "value": "Bearer your_secret_token", + "enabled": true + } + ], + "params": [], + "body": { + "mode": "json", + "json": "", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "res": "bru.setEnvVar(\"foo\", \"bar\");" + }, + "vars": { + "req": [ + { + "name": "a-c", + "value": "foo", + "enabled": true, + "local": false + } + ] + }, + "assertions": [ + { + "name": "res.status", + "value": "200", + "enabled": true, + "uid": "uV4xNx780Qk3ff8zj0xVX" + }, + { + "name": "res.body.message", + "value": "Authentication successful", + "enabled": true, + "uid": "0t9sjKI6l5tFRfLYcFLXv" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "via auth", + "filename": "via auth", + "items": [ + { + "type": "http", + "name": "Bearer Auth 200", + "filename": "Bearer Auth 200.bru", + "seq": 1, + "request": { + "url": "{{host}}/api/auth/bearer/protected", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "res": "bru.setEnvVar(\"foo\", \"bar\");" + }, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "200", + "enabled": true, + "uid": "nD6fKpZaZcqky2EMS3hhQ" + }, + { + "name": "res.body.message", + "value": "Authentication successful", + "enabled": true, + "uid": "HRp5kskhIHPPXXDesRKj0" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "bearer", + "bearer": { + "token": "{{bearer_auth_token}}" + } + } + } + } + ] + } + ] + } + ] + }, + { + "type": "folder", + "name": "multipart", + "filename": "multipart" + }, + { + "type": "folder", + "name": "lib", + "filename": "lib", + "items": [ + { + "type": "js", + "name": "constants", + "filename": "constants.js", + "seq": 1, + "fileContent": "const PI = 3.14;\n\nmodule.exports = {\n PI\n};" + }, + { + "type": "js", + "name": "math", + "filename": "math.js", + "seq": 1, + "fileContent": "const { PI } = require('./constants');\n\nconst sum = (a, b) => a + b;\nconst areaOfCircle = (radius) => PI * radius * radius;\n\nmodule.exports = {\n sum,\n areaOfCircle\n};\n" + }, + { + "type": "js", + "name": "notes", + "filename": "notes.js", + "seq": 1, + "fileContent": "const visualizeNotes = (res) => {\n let response = res.body;\n\n let notes = response?.notes || {};\n let responseRuntime = notes.runtime_sec || 0;\n\n notes.runtime = new Date(responseRuntime * 1000).toISOString().substr(11, 8);\n\n if (typeof response?.data === 'undefined' && typeof response?.rows === 'object') {\n response.data = response?.rows?.map(function (data) {\n return data?.values;\n });\n }\n\n const templateScript = `\n \n `;\n\n const mainScript = `\n \n `;\n\n const style = `\n \n `;\n\n const htmlString = `\n \n \n ${style}\n \n \n \n ${templateScript}\n ${mainScript}\n \n \n `;\n\n return htmlString;\n};\n\nmodule.exports = visualizeNotes;\n" + } + ] + }, + { + "type": "folder", + "name": "string interpolation", + "filename": "string interpolation", + "root": { + "request": { + "vars": { + "req": [ + { + "name": "folder_pre_var", + "value": "folder_pre_var_value", + "enabled": true, + "local": false, + "uid": "V3bTRQFGwkKI4UoahPd2V" + }, + { + "name": "folder_pre_var_2", + "value": "{{env.var1}}", + "enabled": true, + "local": false, + "uid": "av2XViQpOS4JZDWur8dK2" + } + ] + } + }, + "meta": { + "name": "string interpolation" + } + }, + "items": [ + { + "type": "http", + "name": "env vars", + "filename": "env vars.bru", + "seq": 2, + "request": { + "url": "{{host}}/api/echo/json", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"envVar1\": \"{{env.var1}}\",\n \"envVar2\": \"{{env-var2}}\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "u2yA2zeeglSqFZxTDHEiZ" + } + ], + "tests": "test(\"should return json\", function() {\n expect(res.getBody()).to.eql({\n \"envVar1\": \"envVar1\",\n \"envVar2\": \"envVar2\"\n });\n});\n", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "missing values", + "filename": "missing values.bru", + "seq": 1, + "request": { + "url": "{{host}}/api/echo/json?foo={{undefinedVar}}", + "method": "POST", + "headers": [], + "params": [ + { + "name": "foo", + "value": "{{undefinedVar}}", + "type": "query", + "enabled": true + } + ], + "body": { + "mode": "json", + "json": "{\n \"hello\": \"{{undefinedVar2}}\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "BwlJRndmkVnlQgCdpdNoE" + } + ], + "tests": "test(\"should return json\", function() {\n const url = req.getUrl();\n const query = url.split(\"?\")[1];\n expect(query).to.equal(\"foo={{undefinedVar}}\");\n\n const data = res.getBody();\n expect(res.getBody()).to.eql({\n \"hello\": \"{{undefinedVar2}}\"\n });\n});\n", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "process env vars", + "filename": "process env vars.bru", + "seq": 4, + "request": { + "url": "{{host}}/api/echo/json", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"bark\": \"{{bark}}\",\n \"bark2\": \"{{process.env.PROC_ENV_VAR}}\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "VDoqOFucl5B4UdBGqGCAl" + } + ], + "tests": "test(\"should return json\", function() {\n expect(res.getBody()).to.eql({\n \"bark\": \"woof\",\n \"bark2\": \"woof\"\n });\n});\n", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "runtime vars", + "filename": "runtime vars.bru", + "seq": 3, + "request": { + "url": "{{host}}/api/echo/text", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "text", + "json": "{\n \"envVar1\": \"{{env.var1}}\",\n \"envVar2\": \"{{env-var2}}\"\n}", + "text": "Hi, I am {{rUser.full_name}},\nI am {{rUser.age}} years old.\nMy favorite food is {{rUser.fav-food[0]}} and {{rUser.fav-food[1]}}.\nI like attention: {{rUser.want.attention}}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "bru.setVar(\"rUser\", {\n full_name: 'Bruno',\n age: 5,\n 'fav-food': ['egg', 'meat'],\n 'want.attention': true\n});" + }, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "EodEloyMBhRCH8mPoLR8E" + } + ], + "tests": "test(\"should return json\", function() {\n const expectedResponse = `Hi, I am Bruno,\nI am 5 years old.\nMy favorite food is egg and meat.\nI like attention: true`;\n expect(res.getBody()).to.equal(expectedResponse);\n});\n", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "redirects", + "filename": "redirects", + "items": [ + { + "type": "http", + "name": "Disable Redirect", + "filename": "Disable Redirect.bru", + "seq": 1, + "request": { + "url": "{{host}}/redirect-to-ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "req.setMaxRedirects(0);" + }, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "302", + "enabled": true, + "uid": "Kqu9TpenOeiPke7gM6e2W" + } + ], + "tests": "test(\"should disable redirect to ping\", function() {\n const data = res.getBody();\n expect(data).to.equal('Found. Redirecting to /ping');\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "Test Redirect", + "filename": "Test Redirect.bru", + "seq": 2, + "request": { + "url": "{{host}}/redirect-to-ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "200", + "enabled": true, + "uid": "IDIABjMsPFmyysPy3DDLG" + }, + { + "name": "res.body", + "value": "pong", + "enabled": true, + "uid": "d74g0aRt3Mlk1yaTa2BD1" + } + ], + "tests": "test(\"should redirect to ping\", function() {\n const data = res.getBody();\n expect(data).to.equal('pong');\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "asserts", + "filename": "asserts", + "items": [ + { + "type": "http", + "name": "test-assert-combinations", + "filename": "test-assert-combinations.bru", + "seq": 1, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"type\": \"application/json\",\n \"contentJSON\": {\n \"string\": \"foo\",\n \"stringWithSQuotes\": \"'foo'\",\n \"stringWithDQuotes\": \"\\\"foo\\\"\",\n \"number\": 123,\n \"numberAsString\": \"123\",\n \"numberAsStringWithSQuotes\": \"'123'\",\n \"numberAsStringWithDQuotes\": \"\\\"123\\\"\",\n \"numberAsStringWithLeadingZero\": \"0123\",\n \"numberBig\": 9007199254740992000,\n \"numberBigAsString\": \"9007199254740991999\",\n \"null\": null,\n \"nullAsString\": \"null\",\n \"nullAsStringWithSQuotes\": \"'null'\",\n \"nullAsStringWithDQuotes\": \"\\\"null\\\"\",\n \"true\": true,\n \"trueAsString\": \"true\",\n \"trueAsStringWithSQuotes\": \"'true'\",\n \"trueAsStringWithDQuotes\": \"\\\"true\\\"\",\n \"false\": false,\n \"falseAsString\": \"false\",\n \"falseAsStringWithSQuotes\": \"'false'\",\n \"falseAsStringWithDQuotes\": \"\\\"false\\\"\",\n \"stringWithCurlyBraces\": \"{foo}\",\n \"stringWithDoubleCurlyBraces\": \"{{foobar}}\"\n }\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body.string", + "value": "eq foo", + "enabled": true, + "uid": "BIAZhDa8r5jiaeUMC9oJx" + }, + { + "name": "res.body.string", + "value": "eq 'foo'", + "enabled": true, + "uid": "MmDv72YGFukil4LAZ6H1f" + }, + { + "name": "res.body.string", + "value": "eq \"foo\"", + "enabled": true, + "uid": "p0YuYI8JJhOLnQmb1OJyO" + }, + { + "name": "res.body.stringWithSQuotes", + "value": "eq \"'foo'\"", + "enabled": true, + "uid": "6vBjUYmjT6w680kXw5GN8" + }, + { + "name": "res.body.stringWithDQuotes", + "value": "eq '\"foo\"'", + "enabled": true, + "uid": "6SlJJOabN6dqU6zzBGJ5V" + }, + { + "name": "res.body.number", + "value": "eq 123", + "enabled": true, + "uid": "qsTsccOFzdaTad54qLRPD" + }, + { + "name": "res.body.numberAsString", + "value": "eq '123'", + "enabled": true, + "uid": "acr4c0DbVSMHybtmPfpAK" + }, + { + "name": "res.body.numberAsString", + "value": "eq \"123\"", + "enabled": true, + "uid": "j2UBEitg7trKqERvHFonB" + }, + { + "name": "res.body.numberAsStringWithSQuotes", + "value": "eq \"'123'\"", + "enabled": true, + "uid": "HkhFuTMDlPTtjdRH1JtDq" + }, + { + "name": "res.body.numberAsStringWithDQuotes", + "value": "eq '\"123\"'", + "enabled": true, + "uid": "FOJ9Il1Mw26AVh3mtfH2A" + }, + { + "name": "res.body.numberAsStringWithLeadingZero", + "value": "eq \"0123\"", + "enabled": true, + "uid": "PYqZgMwa8utODeQu6jGZ8" + }, + { + "name": "res.body.numberBig.toString()", + "value": "eq '9007199254740992000'", + "enabled": true, + "uid": "ceH22BYjeONOfjQzCQuQ5" + }, + { + "name": "res.body.numberBigAsString", + "value": "eq \"9007199254740991999\"", + "enabled": true, + "uid": "ISsGCwNYz2gO1OiwCrSzj" + }, + { + "name": "res.body.null", + "value": "eq null", + "enabled": true, + "uid": "ymthap5UgbSWYrA2KrhSf" + }, + { + "name": "res.body.nullAsString", + "value": "eq \"null\"", + "enabled": true, + "uid": "70dQ4jmZ3NObBuSgNms4U" + }, + { + "name": "res.body.nullAsStringWithSQuotes", + "value": "eq \"'null'\"", + "enabled": true, + "uid": "8sHCgezylhO5WobuMF6x5" + }, + { + "name": "res.body.nullAsStringWithDQuotes", + "value": "eq '\"null\"'", + "enabled": true, + "uid": "Beo0GPeJjGChOUW3ULJFq" + }, + { + "name": "res.body.true", + "value": "eq true", + "enabled": true, + "uid": "xyKYxUigaRbnsM3NUCGqo" + }, + { + "name": "res.body.trueAsString", + "value": "eq \"true\"", + "enabled": true, + "uid": "TpEuGS0KamPW2P5WlNKIz" + }, + { + "name": "res.body.trueAsStringWithSQuotes", + "value": "eq \"'true'\"", + "enabled": true, + "uid": "NGOStfvaFasU1BnDQBeNw" + }, + { + "name": "res.body.trueAsStringWithDQuotes", + "value": "eq '\"true\"'", + "enabled": true, + "uid": "1aQu1geER6NzyRyvn6YKL" + }, + { + "name": "res.body.false", + "value": "eq false", + "enabled": true, + "uid": "5hYV1aZexcmTnvWVGukhR" + }, + { + "name": "res.body.falseAsString", + "value": "eq \"false\"", + "enabled": true, + "uid": "0nlBWyhHLkqwJnnQvntpH" + }, + { + "name": "res.body.falseAsStringWithSQuotes", + "value": "eq \"'false'\"", + "enabled": true, + "uid": "6SyZkAdoR7nymmSuxEAVm" + }, + { + "name": "res.body.falseAsStringWithDQuotes", + "value": "eq '\"false\"'", + "enabled": true, + "uid": "ikW1dRvg543wlg4OBVOj4" + }, + { + "name": "res.body.nonexistent", + "value": "eq undefined", + "enabled": true, + "uid": "18cmPWFrjuhO9w3r37QbC" + }, + { + "name": "res.body.stringWithCurlyBraces", + "value": "eq \"{foo}\"", + "enabled": true, + "uid": "MG7kIGiDRpVMWFsUNJwwL" + }, + { + "name": "res.body.stringWithDoubleCurlyBraces", + "value": "eq \"{{foobar}}\"", + "enabled": true, + "uid": "lc8H3nkOK6oH9EU8pfGAB" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "response-parsing", + "filename": "response-parsing", + "items": [ + { + "type": "http", + "name": "test JSON false response", + "filename": "test JSON false response.bru", + "seq": 11, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"headers\": { \"content-type\": \"application/json\" },\n \"content\": \"false\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body", + "value": "eq false", + "enabled": true, + "uid": "44ByglOpNCcWR39kkFqUB" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test JSON null response", + "filename": "test JSON null response.bru", + "seq": 6, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"headers\": { \"content-type\": \"application/json\" },\n \"content\": \"null\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body", + "value": "eq null", + "enabled": true, + "uid": "SoMSHwOxrNF0mO7vjtMIq" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test JSON number response", + "filename": "test JSON number response.bru", + "seq": 12, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"headers\": { \"content-type\": \"application/json\" },\n \"content\": \"3.1\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body", + "value": "eq 3.1", + "enabled": true, + "uid": "PXORG50VO2DQhpxTe4Z9b" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test JSON response", + "filename": "test JSON response.bru", + "seq": 2, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"headers\": { \"content-type\": \"application/json\" },\n \"contentJSON\": { \"message\": \"hello\" }\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body.message", + "value": "eq hello", + "enabled": true, + "uid": "ghYaoqOIqNkzYySyevIbS" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test JSON string response", + "filename": "test JSON string response.bru", + "seq": 7, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"headers\": { \"content-type\": \"application/json\" },\n \"content\": \"\\\"ok\\\"\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body", + "value": "eq ok", + "enabled": true, + "uid": "lrb7s0Cy5fSBmpsnQS2hC" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test JSON string with quotes response", + "filename": "test JSON string with quotes response.bru", + "seq": 8, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"headers\": { \"content-type\": \"application/json\" },\n \"contentJSON\": \"\\\"ok\\\"\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body", + "value": "eq '\"ok\"'", + "enabled": true, + "uid": "0vtvad5pJzWJ82EkoP1tL" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test JSON true response", + "filename": "test JSON true response.bru", + "seq": 10, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"headers\": { \"content-type\": \"application/json\" },\n \"content\": \"true\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body", + "value": "eq true", + "enabled": true, + "uid": "gWf2HHCyZEfJDjF9epFgh" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test JSON unsafe-int response", + "filename": "test JSON unsafe-int response.bru", + "seq": 13, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"headers\": { \"content-type\": \"application/json\" },\n \"content\": \"90071992547409919876\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body.toString()", + "value": "eq 90071992547409920000", + "enabled": true, + "uid": "VPxENgWx5laoc8a8AbPSt" + } + ], + "tests": "", + "docs": "Note: This test is not perfect, we should match the unparsed raw-response with the expected string version of the unsafe-integer", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test binary response", + "filename": "test binary response.bru", + "seq": 4, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"type\": \"application/octet-stream\",\n \"contentBase64\": \"+Z1P82iH1wmbILfvnhvjQVbVAktP4TzltpxYD74zNyA=\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "test(\"response matches the expectation after utf-8 decoding(needs improvement)\", function () {\n expect(res.getStatus()).to.equal(200);\n const dataBinary = Buffer.from(\"+Z1P82iH1wmbILfvnhvjQVbVAktP4TzltpxYD74zNyA=\", \"base64\"); \n expect(res.body).to.equal(dataBinary.toString(\"utf-8\"));\n});", + "docs": "Note:\n\nThis test is not perfect and needs to be improved by direclty matching expected binary data with raw-response.\n\nCurrently res.body is decoded with `utf-8` by default and looses data in the process. We need some property in `res` which gives access to raw-data/Buffer.", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test html response", + "filename": "test html response.bru", + "seq": 5, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"headers\": { \"content-type\": \"text/html\" },\n \"content\": \"

hello

\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body", + "value": "eq

hello

", + "enabled": true, + "uid": "2ljMHST9vdVDcGsDYeea5" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test image response", + "filename": "test image response.bru", + "seq": 3, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"type\": \"image/png\",\n \"contentBase64\": \"iVBORw0KGgoAAAANSUhEUgAAAGQAAABkAQMAAABKLAcXAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGUExURQCqAP///59OGOoAAAABYktHRAH/Ai3eAAAAB3RJTUUH6QMHCwUNKHvFmgAAABRJREFUOMtjYBgFo2AUjIJRQE8AAAV4AAEpcbn8AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDI1LTAzLTA3VDExOjA1OjEzKzAwOjAwQkgGWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyNS0wMy0wN1QxMTowNToxMyswMDowMDMVvuYAAAAodEVYdGRhdGU6dGltZXN0YW1wADIwMjUtMDMtMDdUMTE6MDU6MTMrMDA6MDBkAJ85AAAAAElFTkSuQmCC\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test plain text response with formatting", + "filename": "test plain text response with formatting.bru", + "seq": 18, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"headers\": { \"content-type\": \"text/plain\" },\n \"content\": \"hello\\n\\tworld\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body", + "value": "eq hello\\n\\tworld", + "enabled": true, + "uid": "krtHsmpnhOUT4SP575BjE" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test plain text response", + "filename": "test plain text response.bru", + "seq": 1, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"headers\": { \"content-type\": \"text/plain\" },\n \"content\": \"hello\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body", + "value": "eq hello", + "enabled": true, + "uid": "iiduau7JWqfG721cU41GX" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test invalid JSON response with formatting", + "filename": "test invalid JSON response with formatting.bru", + "seq": 19, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"headers\": { \"content-type\": \"application/json\" },\n \"content\": \"hello\\n\\tworld\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body", + "value": "eq hello\\n\\tworld", + "enabled": true, + "uid": "2Lgh5484D4lpF5w2d0y4M" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test plain text utf16 response", + "filename": "test plain text utf16 response.bru", + "seq": 14, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"headers\": { \"content-type\": \"text/plain; charset=utf-16\" },\n \"contentBase64\": \"dABoAGkAcwAgAGkAcwAgAGUAbgBjAG8AZABlAGQAIAB3AGkAdABoACAAdQB0AGYAMQA2AA==\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body", + "value": "eq \"this is encoded with utf16\"", + "enabled": true, + "uid": "bTudYyt3VYDJBGUQMaBrn" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test plain text utf16-be with BOM response", + "filename": "test plain text utf16-be with BOM response.bru", + "seq": 15, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"headers\": { \"content-type\": \"text/plain; charset=utf-16\" },\n \"contentBase64\": \"/v8AdABoAGkAcwAgAGkAcwAgAGUAbgBjAG8AZABlAGQAIAB3AGkAdABoACAAdQB0AGYAMQA2AC0AYgBlACAAdwBpAHQAaAAgAEIATwBN\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body", + "value": "eq \"this is encoded with utf16-be with BOM\"", + "enabled": true, + "uid": "2y4vvfKmEIiPbazx9WcI7" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test plain text utf16-le with BOM response", + "filename": "test plain text utf16-le with BOM response.bru", + "seq": 16, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"headers\": { \"content-type\": \"text/plain; charset=utf-16\" },\n \"contentBase64\": \"//50AGgAaQBzACAAaQBzACAAZQBuAGMAbwBkAGUAZAAgAHcAaQB0AGgAIAB1AHQAZgAxADYALQBsAGUAIAB3AGkAdABoACAAQgBPAE0A\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body", + "value": "eq \"this is encoded with utf16-le with BOM\"", + "enabled": true, + "uid": "DoCOfg20CxCaNv59xBeMK" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test plain text utf8 with BOM response", + "filename": "test plain text utf8 with BOM response.bru", + "seq": 17, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"headers\": { \"content-type\": \"text/plain; charset=utf8\" },\n \"contentBase64\": \"77u/dGhpcyBpcyB1dGY4IGVuY29kZWQgd2l0aCBCT00sIHdoeSBub3Q/\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body", + "value": "eq \"this is utf8 encoded with BOM, why not?\"", + "enabled": true, + "uid": "W2wEieY0CyJnZPTS7QFdi" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "test xml response", + "filename": "test xml response.bru", + "seq": 9, + "request": { + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"headers\": { \"content-type\": \"application/xml\" },\n \"content\": \"hello\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.body", + "value": "eq hello", + "enabled": true, + "uid": "LpkeJWArhseIjWVvrRSsY" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "preview", + "filename": "preview", + "items": [ + { + "type": "folder", + "name": "html", + "filename": "html", + "items": [ + { + "type": "http", + "name": "bruno", + "filename": "bruno.bru", + "seq": 1, + "request": { + "url": "https://www.usebruno.com", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "Lq3jQdo5w6Sj5DmK7RR4e" + } + ], + "tests": "test(\"should return parsed xml\", function() {\n const headers = res.getHeaders();\n expect(headers['content-type']).to.eql(\"text/html; charset=utf-8\");\n});\n", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "image", + "filename": "image", + "items": [ + { + "type": "http", + "name": "bruno", + "filename": "bruno.bru", + "seq": 1, + "request": { + "url": "https://www.usebruno.com/favicon.ico", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "test(\"should return parsed xml\", function() {\n const headers = res.getHeaders();\n expect(headers['content-type']).to.eql(\"image/x-icon\");\n});\n", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + } + ] + }, + { + "type": "folder", + "name": "scripting", + "filename": "scripting", + "items": [ + { + "type": "folder", + "name": "js", + "filename": "js", + "root": { + "request": { + "headers": [ + { + "name": "folder-header", + "value": "folder-header-value", + "enabled": true, + "uid": "IbjpKtt6OSIEfbEpq63i5" + } + ], + "script": { + "req": "// used by `scripting/js/folder-collection script-tests`\nconst shouldTestFolderScripts = bru.getVar('should-test-folder-scripts');\nif(shouldTestFolderScripts) {\n bru.setVar('folder-var-set-by-folder-script', 'folder-var-value-set-by-folder-script');\n}" + }, + "tests": "// used by `scripting/js/folder-collection script-tests`\nconst shouldTestFolderScripts = bru.getVar('should-test-folder-scripts');\nconst folderVar = bru.getVar(\"folder-var-set-by-folder-script\");\nif (shouldTestFolderScripts && folderVar) {\n test(\"folder level test - should get the var that was set by the folder script\", function() {\n expect(folderVar).to.equal(\"folder-var-value-set-by-folder-script\");\n }); \n bru.setVar('folder-var-set-by-folder-script', null); \n bru.setVar('should-test-folder-scripts', null);\n}" + }, + "meta": { + "name": "js" + } + }, + "items": [ + { + "type": "http", + "name": "data types - request vars", + "filename": "data types - request vars.bru", + "seq": 3, + "request": { + "url": "{{host}}/api/echo/json", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"boolean\": false,\n \"number_1\": 1,\n \"number_2\": 0,\n \"number_3\": -1,\n \"string\": \"bruno\",\n \"array\": [1, 2, 3, 4, 5],\n \"object\": {\n \"hello\": \"bruno\"\n },\n \"null\": null\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "req.body.boolean", + "value": "isBoolean false", + "enabled": true, + "uid": "zEDwisxV9J5rpkVFsogOa" + }, + { + "name": "req.body.number_1", + "value": "isNumber 1", + "enabled": true, + "uid": "dl2kC9vNfZZstXR4T7m82" + }, + { + "name": "req.body.undefined", + "value": "isUndefined undefined", + "enabled": true, + "uid": "IgJsHATg2BunQdduIPWj7" + }, + { + "name": "req.body.string", + "value": "isString bruno", + "enabled": true, + "uid": "ZkjsCtU9U3CXoUXQR22wr" + }, + { + "name": "req.body.null", + "value": "isNull null", + "enabled": true, + "uid": "QK3P1uBuD9DwmMgQfeEIU" + }, + { + "name": "req.body.array", + "value": "isArray", + "enabled": true, + "uid": "SS4pel9Lxy2zXJplAwdGl" + }, + { + "name": "req.body.boolean", + "value": "eq false", + "enabled": true, + "uid": "bgDu625CBaYhGfZdDUPW0" + }, + { + "name": "req.body.number_1", + "value": "eq 1", + "enabled": true, + "uid": "H0iOZFFLufmyelF7JT66C" + }, + { + "name": "req.body.undefined", + "value": "eq undefined", + "enabled": true, + "uid": "NH5NrTa3rDBUovinP39vc" + }, + { + "name": "req.body.string", + "value": "eq bruno", + "enabled": true, + "uid": "BWf3Y60ywGiLMP3BdtjF8" + }, + { + "name": "req.body.null", + "value": "eq null", + "enabled": true, + "uid": "mz2qdTyufiVSGmDAsdDJD" + }, + { + "name": "req.body.number_2", + "value": "eq 0", + "enabled": true, + "uid": "Fj8Je4fBjHQaBn9MlcyRY" + }, + { + "name": "req.body.number_3", + "value": "eq -1", + "enabled": true, + "uid": "8nH1gowUVSlPE5B7zXjIu" + }, + { + "name": "req.body.number_2", + "value": "isNumber", + "enabled": true, + "uid": "bZXbYyYOrxtfq3rcK4T0R" + }, + { + "name": "req.body.number_3", + "value": "isNumber", + "enabled": true, + "uid": "WUpdAAGdYgyumiueofKzX" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "data types", + "filename": "data types.bru", + "seq": 2, + "request": { + "url": "{{host}}/api/echo/json", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"boolean\": false,\n \"number\": 1,\n \"string\": \"bruno\",\n \"array\": [1, 2, 3, 4, 5],\n \"object\": {\n \"hello\": \"bruno\"\n },\n \"null\": null\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "const reqBody = req.getBody();\n\nbru.setVar(\"dataTypeVarTest\", {\n ...reqBody,\n \"undefined\": undefined\n});" + }, + "vars": {}, + "assertions": [], + "tests": "test(\"data types check via bru var\", function() {\n let v = bru.getVar(\"dataTypeVarTest\");\n v = {\n ...v,\n \"undefined\": undefined\n };\n expect(v).to.eql({\n \"boolean\": false,\n \"number\": 1,\n \"string\": \"bruno\",\n \"array\": [1, 2, 3, 4, 5],\n \"object\": {\n \"hello\": \"bruno\"\n },\n \"null\": null,\n \"undefined\": undefined\n })\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "folder-collection script-tests pre", + "filename": "folder-collection script-tests pre.bru", + "seq": 4, + "request": { + "url": "{{echo-host}}", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "bru.setVar('should-test-collection-scripts', true);\nbru.setVar('should-test-folder-scripts', true);" + }, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "folder-collection script-tests", + "filename": "folder-collection script-tests.bru", + "seq": 5, + "request": { + "url": "{{echo-host}}", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "// do not delete - the collection/folder scripts/tests run during this request execution" + }, + "vars": {}, + "assertions": [], + "tests": "const collectionHeader = req.getHeader(\"collection-header\");\nconst folderHeader = req.getHeader(\"folder-header\");\n\ntest(\"should get the header value set at collection level\", function() {\n expect(collectionHeader).to.equal(\"collection-header-value\");\n});\n\ntest(\"should get the header value set at folder level\", function() {\n expect(folderHeader).to.equal(\"folder-header-value\");\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "setTimeout", + "filename": "setTimeout.bru", + "seq": 1, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "bru.setVar(\"test-js-set-timeout\", \"\");\nawait new Promise((resolve, reject) => {\n setTimeout(() => {\n bru.setVar(\"test-js-set-timeout\", \"bruno\");\n resolve();\n }, 1000);\n});\n\nconst v = bru.getVar(\"test-js-set-timeout\");\nbru.setVar(\"test-js-set-timeout\", v + \"-is-awesome\");\n" + }, + "vars": {}, + "assertions": [], + "tests": "test(\"setTimeout()\", function() {\n const v = bru.getVar(\"test-js-set-timeout\")\n expect(v).to.eql(\"bruno-is-awesome\");\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "npm modules", + "filename": "npm modules", + "items": [ + { + "type": "http", + "name": "fakerjs", + "filename": "fakerjs.bru", + "seq": 1, + "request": { + "url": "{{host}}/api/echo/json", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"hello\": \"bruno\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "const { faker } = require('@faker-js/faker');\nconst uuid = faker.string.uuid();\n\nconst data = req.getBody();\ndata.uuid = uuid;\n\nreq.setBody(data);" + }, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "lD0O3YJPd70wfjyybkyd5" + } + ], + "tests": "test(\"should return json\", function() {\n const data = res.getBody();\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n const isUUID = (inputString) => {\n return uuidRegex.test(inputString);\n };\n \n expect(data.hello).to.equal(\"bruno\");\n expect(isUUID(data.uuid)).to.be.true;\n});\n", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "local modules", + "filename": "local modules", + "items": [ + { + "type": "http", + "name": "invalid and valid module imports", + "filename": "invalid and valid module imports.bru", + "seq": 3, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "try {\n bru.setVar('invalid_module_error_thrown', false);\n // should throw an error\n const invalid = require(\"./lib/invalid\");\n}\ncatch(error) {\n bru.setVar('invalid_module_error_thrown', true);\n}\n\n\ntry {\n bru.setVar('valid_module_no_error', true);\n // should not throw an error\n const math = require(\"./lib/math\");\n}\ncatch(error) {\n bru.setVar('valid_module_no_error', false);\n}" + }, + "vars": {}, + "assertions": [ + { + "name": "invalid_module_error_thrown", + "value": "eq true", + "enabled": true, + "uid": "3imk4DIFjOQdXZL7RKBN0" + }, + { + "name": "valid_module_no_error", + "value": "eq true", + "enabled": true, + "uid": "bGJX4qgETYERw2hvg1i3U" + } + ], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "sum (without js extn)", + "filename": "sum (without js extn).bru", + "seq": 2, + "request": { + "url": "{{host}}/api/echo/json", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"a\": 1,\n \"b\": 2\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "const math = require(\"./lib/math\");\nconsole.log(math, 'math');\n\nconst body = req.getBody();\nbody.sum = math.sum(body.a, body.b);\nbody.areaOfCircle = math.areaOfCircle(2);\n\nreq.setBody(body);" + }, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "ckHfAfPNwcmxcCRBrj9sN" + } + ], + "tests": "test(\"should return json\", function() {\n const data = res.getBody();\n expect(res.getBody()).to.eql({\n \"a\": 1,\n \"b\": 2,\n \"sum\": 3,\n \"areaOfCircle\": 12.56\n });\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "sum", + "filename": "sum.bru", + "seq": 1, + "request": { + "url": "{{host}}/api/echo/json", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"a\": 1,\n \"b\": 2\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "const math = require(\"./lib/math.js\"); \nconst body = req.getBody();\nbody.sum = math.sum(body.a, body.b);\n\nreq.setBody(body);" + }, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "51KAG1UxDubFYXJrXSLCR" + } + ], + "tests": "test(\"should return json\", function() {\n const data = res.getBody();\n expect(res.getBody()).to.eql({\n \"a\": 1,\n \"b\": 2,\n \"sum\": 3\n });\n});\n\ntest(\"should return json\", function() {\n const data = res.getBody();\n expect(res.getBody()).to.eql({\n \"a\": 1,\n \"b\": 2,\n \"sum\": 3\n });\n});\n\ntest(\"should return json\", function() {\n const data = res.getBody();\n expect(res.getBody()).to.eql({\n \"a\": 1,\n \"b\": 2,\n \"sum\": 3\n });\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "inbuilt modules", + "filename": "inbuilt modules", + "items": [ + { + "type": "folder", + "name": "axios", + "filename": "axios", + "items": [ + { + "type": "http", + "name": "axios-pre-req-script", + "filename": "axios-pre-req-script.bru", + "seq": 1, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "const axios = require(\"axios\");\n\nconst url = \"https://testbench-sanity.usebruno.com/api/echo/json\";\nconst response = await axios.post(url, {\n \"hello\": \"bruno\"\n});\n\nreq.setBody(response.data);\nreq.setMethod(\"POST\");\nreq.setUrl(url);" + }, + "vars": {}, + "assertions": [], + "tests": "test(\"req.getBody()\", function() {\n const data = res.getBody();\n expect(data).to.eql({\n \"hello\": \"bruno\"\n });\n});\n", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "cheerio", + "filename": "cheerio", + "items": [ + { + "type": "http", + "name": "cheerio", + "filename": "cheerio.bru", + "seq": 1, + "request": { + "url": "https://echo.usebruno.com", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "text", + "text": "

Hello Bruno!

", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "const cheerio = require('cheerio');\n\nconst $ = cheerio.load('

Hello world

');\n\n$('h2.title').text('Hello there!');\n$('h2').addClass('welcome');\n\nbru.setVar(\"cheerio-test-html\", $.html());" + }, + "vars": {}, + "assertions": [], + "tests": "const cheerio = require('cheerio');\n\ntest(\"cheerio html - from scripts\", function() {\n const expected = '

Hello there!

';\n const html = bru.getVar('cheerio-test-html');\n expect(html).to.eql(expected);\n});\n\ntest(\"cheerio html - from tests\", function() {\n const expected = '

Hello Bruno!

';\n const $ = cheerio.load(res.body);\n expect($.html()).to.eql(expected);\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "crypto-js", + "filename": "crypto-js", + "items": [ + { + "type": "http", + "name": "crypto-js-pre-request-script", + "filename": "crypto-js-pre-request-script.bru", + "seq": 1, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "var CryptoJS = require(\"crypto-js\");\n\n// Encrypt\nvar ciphertext = CryptoJS.AES.encrypt('my message', 'secret key 123').toString();\n\n// Decrypt\nvar bytes = CryptoJS.AES.decrypt(ciphertext, 'secret key 123');\nvar originalText = bytes.toString(CryptoJS.enc.Utf8);\n\nbru.setVar('crypto-test-message', originalText);" + }, + "vars": {}, + "assertions": [], + "tests": "test(\"crypto message\", function() {\n const data = bru.getVar('crypto-test-message');\n bru.setVar('crypto-test-message', null);\n expect(data).to.eql('my message');\n});\n", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "nanoid", + "filename": "nanoid", + "items": [ + { + "type": "http", + "name": "nanoid", + "filename": "nanoid.bru", + "seq": 1, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "const { nanoid } = require(\"nanoid\");\n \nbru.setVar(\"nanoid-test-id\", nanoid());" + }, + "vars": {}, + "assertions": [], + "tests": "test(\"nanoid var\", function() {\n const id = bru.getVar('nanoid-test-id');\n let isValidNanoid = /^[a-zA-Z0-9_-]{21}$/.test(id)\n bru.setVar('nanoid-test-id', null);\n expect(isValidNanoid).to.eql(true);\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "xml2js", + "filename": "xml2js", + "items": [ + { + "type": "http", + "name": "xml2js", + "filename": "xml2js.bru", + "seq": 1, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "var parseString = require('xml2js').parseString;\nvar xml = \"Hello xml2js!\"\nparseString(xml, function (err, result) {\n bru.setVar(\"xml2js-test-result\", result); \n});" + }, + "vars": {}, + "assertions": [], + "tests": "var parseString = require('xml2js').parseString;\n\ntest(\"xml2js parseString in scripts\", function() {\n const expected = {\n root: 'Hello xml2js!'\n };\n const result = bru.getVar('xml2js-test-result');\n expect(result).to.eql(expected);\n});\n\ntest(\"xml2js parseString in tests\", async function() {\n var xml = \"Hello inside test!\"\n const expected = {\n root: 'Hello inside test!'\n };\n parseString(xml, function (err, result) {\n expect(result).to.eql(expected);\n });\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "uuid", + "filename": "uuid", + "items": [ + { + "type": "http", + "name": "uuid", + "filename": "uuid.bru", + "seq": 1, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "const { v4 } = require(\"uuid\");\n \nbru.setVar(\"uuid-test-id\", v4());" + }, + "vars": {}, + "assertions": [], + "tests": "test(\"uuid var\", function() {\n const id = bru.getVar('uuid-test-id');\n let isValidUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id);\n bru.setVar('uuid-test-id', null);\n expect(isValidUuid).to.eql(true);\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + } + ] + }, + { + "type": "folder", + "name": "api", + "filename": "api", + "items": [ + { + "type": "folder", + "name": "bru", + "filename": "bru", + "root": { + "request": { + "vars": { + "req": [ + { + "name": "folder-var", + "value": "folder-var-value", + "enabled": true, + "local": false, + "uid": "AqPWEUdm2LylmjMIt3lkw" + } + ] + } + }, + "meta": { + "name": "bru" + } + }, + "items": [ + { + "type": "http", + "name": "getCollectionVar", + "filename": "getCollectionVar.bru", + "seq": 9, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "test(\"should get collection var in scripts\", function() {\n const testVar = bru.getCollectionVar(\"collection-var\");\n expect(testVar).to.equal(\"collection-var-value\");\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "getEnvName", + "filename": "getEnvName.bru", + "seq": 1, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "const envName = bru.getEnvName();\nbru.setVar(\"testEnvName\", envName);" + }, + "vars": {}, + "assertions": [], + "tests": "test(\"should get env name in scripts\", function() {\n const testEnvName = bru.getVar(\"testEnvName\");\n expect(testEnvName).to.equal(\"Prod\");\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "getEnvVar", + "filename": "getEnvVar.bru", + "seq": 2, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "test(\"should get env var in scripts\", function() {\n const host = bru.getEnvVar(\"host\")\n expect(host).to.equal(\"https://testbench-sanity.usebruno.com\");\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "getFolderVar", + "filename": "getFolderVar.bru", + "seq": 8, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "test(\"should get folder var in scripts\", function() {\n const testVar = bru.getFolderVar(\"folder-var\");\n expect(testVar).to.equal(\"folder-var-value\");\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "getProcessEnv", + "filename": "getProcessEnv.bru", + "seq": 6, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "test(\"bru.getProcessEnv()\", function() {\n const v = bru.getProcessEnv(\"PROC_ENV_VAR\");\n expect(v).to.equal(\"woof\");\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "getRequestVar", + "filename": "getRequestVar.bru", + "seq": 7, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": { + "req": [ + { + "name": "request-var", + "value": "request-var-value", + "enabled": true, + "local": false + } + ] + }, + "assertions": [], + "tests": "test(\"should get request var in scripts\", function() {\n const testVar = bru.getRequestVar(\"request-var\");\n expect(testVar).to.equal(\"request-var-value\");\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "getVar", + "filename": "getVar.bru", + "seq": 5, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "test(\"should get var in scripts\", function() {\n const testSetVar = bru.getVar(\"testSetVar\");\n expect(testSetVar).to.equal(\"bruno-test-87267\");\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "runRequest-1", + "filename": "runRequest-1.bru", + "seq": 10, + "request": { + "url": "{{echo-host}}", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "text", + "text": "bruno", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "// reset values\nbru.setVar('run-request-runtime-var', null);\nbru.setEnvVar('run-request-env-var', null);\nbru.setGlobalEnvVar('run-request-global-env-var', null);\n\n// the above vars will be set in the below request\nconst resp = await bru.runRequest('scripting/api/bru/runRequest-2');\n\nbru.setVar('run-request-resp', {\n data: resp?.data,\n statusText: resp?.statusText,\n status: resp?.status\n});" + }, + "vars": {}, + "assertions": [], + "tests": "test(\"should get runtime var set in runRequest-2\", function() {\n const val = bru.getVar(\"run-request-runtime-var\");\n expect(val).to.equal(\"run-request-runtime-var-value\");\n});\n\ntest(\"should get env var set in runRequest-2\", function() {\n const val = bru.getEnvVar(\"run-request-env-var\");\n expect(val).to.equal(\"run-request-env-var-value\");\n});\n\ntest(\"should get global env var set in runRequest-2\", function() {\n const val = bru.getGlobalEnvVar(\"run-request-global-env-var\");\n const executionMode = req.getExecutionMode();\n if (executionMode == 'runner') {\n expect(val).to.equal(\"run-request-global-env-var-value\");\n }\n});\n\ntest(\"should get response of runRequest-2\", function() {\n const val = bru.getVar('run-request-resp');\n expect(JSON.stringify(val)).to.equal(JSON.stringify({\n \"data\": \"bruno\",\n \"statusText\": \"OK\",\n \"status\": 200\n }));\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "runRequest-2", + "filename": "runRequest-2.bru", + "seq": 11, + "request": { + "url": "{{echo-host}}", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "text", + "text": "bruno", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "bru.setVar('run-request-runtime-var', 'run-request-runtime-var-value');\nbru.setEnvVar('run-request-env-var', 'run-request-env-var-value');\nbru.setGlobalEnvVar('run-request-global-env-var', 'run-request-global-env-var-value');" + }, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "runRequest", + "filename": "runRequest.bru", + "seq": 2, + "request": { + "url": "{{host}}/api/echo/json", + "method": "POST", + "headers": [ + { + "name": "foo", + "value": "bar", + "enabled": true + } + ], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"hello\": \"bruno\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "bru.setVar(\"runRequest-ping-res-1\", null);\nbru.setVar(\"runRequest-ping-res-2\", null);\nbru.setVar(\"runRequest-ping-res-3\", null);\n\nlet pingRes = await bru.runRequest('ping');\nbru.setVar('runRequest-ping-res-1', {\n data: pingRes?.data,\n statusText: pingRes?.statusText,\n status: pingRes?.status\n});", + "res": "let pingRes = await bru.runRequest('ping');\nbru.setVar('runRequest-ping-res-2', {\n data: pingRes?.data,\n statusText: pingRes?.statusText,\n status: pingRes?.status\n});" + }, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "XiYVLMGIQARuIZRmXYS65" + } + ], + "tests": "const pingRes = await bru.runRequest('ping');\nbru.setVar('runRequest-ping-res-3', {\n data: pingRes?.data,\n statusText: pingRes?.statusText,\n status: pingRes?.status\n});\n\ntest(\"should run request and return valid response in pre-request script\", function() {\n const expectedPingRes = {\n data: \"pong\",\n statusText: \"OK\",\n status: 200\n };\n const pingRes = bru.getVar('runRequest-ping-res-1');\n expect(pingRes).to.eql(expectedPingRes);\n});\n\ntest(\"should run request and return valid response in post-response script\", function() {\n const expectedPingRes = {\n data: \"pong\",\n statusText: \"OK\",\n status: 200\n };\n const pingRes = bru.getVar('runRequest-ping-res-2');\n expect(pingRes).to.eql(expectedPingRes);\n});\n\ntest(\"should run request and return valid response in tests script\", function() {\n const expectedPingRes = {\n data: \"pong\",\n statusText: \"OK\",\n status: 200\n };\n const pingRes = bru.getVar('runRequest-ping-res-3');\n expect(pingRes).to.eql(expectedPingRes);\n});\n", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "setEnvVar", + "filename": "setEnvVar.bru", + "seq": 3, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "res": "bru.setEnvVar(\"testSetEnvVar\", \"bruno-29653\")" + }, + "vars": {}, + "assertions": [], + "tests": "test(\"should set env var in scripts\", function() {\n const testSetEnvVar = bru.getEnvVar(\"testSetEnvVar\")\n expect(testSetEnvVar).to.equal(\"bruno-29653\");\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "setVar", + "filename": "setVar.bru", + "seq": 4, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "res": "bru.setVar(\"testSetVar\", \"bruno-test-87267\")" + }, + "vars": {}, + "assertions": [], + "tests": "test(\"should get var in scripts\", function() {\n const testSetVar = bru.getVar(\"testSetVar\");\n expect(testSetVar).to.equal(\"bruno-test-87267\");\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "folder", + "name": "runner", + "filename": "runner", + "items": [ + { + "type": "http", + "name": "1", + "filename": "1.bru", + "seq": 1, + "request": { + "url": "https://echo.usebruno.com", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "bru.setVar('bru-runner-req', 1);", + "res": "bru.setVar('bru.runner.skipRequest', true);" + }, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "2", + "filename": "2.bru", + "seq": 2, + "request": { + "url": "https://echo.usebruno.com", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "bru.runner.skipRequest();", + "res": "bru.setVar('bru.runner.skipRequest', false);" + }, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "3", + "filename": "3.bru", + "seq": 3, + "request": { + "url": "https://echo.usebruno.com", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + } + ] + }, + { + "type": "folder", + "name": "req", + "filename": "req", + "items": [ + { + "type": "http", + "name": "getBody", + "filename": "getBody.bru", + "seq": 9, + "request": { + "url": "{{host}}/api/echo/json", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"hello\": \"bruno\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "YACUsQsYlX6yvIkkHfGeM" + } + ], + "tests": "test(\"req.getBody()\", function() {\n const data = res.getBody();\n expect(data).to.eql({\n \"hello\": \"bruno\"\n });\n});\n", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "getHeader", + "filename": "getHeader.bru", + "seq": 5, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [ + { + "name": "bruno", + "value": "is-awesome", + "enabled": true + } + ], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "1fFLd8VtOZKujvtAkGUZj" + }, + { + "name": "res.body", + "value": "eq pong", + "enabled": true, + "uid": "QD11VDqnqq5jTcL88Y3D1" + } + ], + "tests": "test(\"req.getHeader(name)\", function() {\n const h = req.getHeader('bruno');\n expect(h).to.equal(\"is-awesome\");\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "getHeaders", + "filename": "getHeaders.bru", + "seq": 7, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [ + { + "name": "bruno", + "value": "is-awesome", + "enabled": true + }, + { + "name": "della", + "value": "is-beautiful", + "enabled": true + } + ], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "04R4EJbusCqY89KXTKajO" + }, + { + "name": "res.body", + "value": "eq pong", + "enabled": true, + "uid": "CKd1EZjgvkeFB6a25BpDc" + } + ], + "tests": "test(\"req.getHeaders()\", function() {\n const h = req.getHeaders();\n expect(h.bruno).to.equal(\"is-awesome\");\n expect(h.della).to.equal(\"is-beautiful\");\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "getMethod", + "filename": "getMethod.bru", + "seq": 3, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "5ItoSs1VqOfB0sll7wqpN" + }, + { + "name": "res.body", + "value": "eq pong", + "enabled": true, + "uid": "JdEtqrNa8XTVM8bwHYNLq" + } + ], + "tests": "test(\"req.getMethod()()\", function() {\n const method = req.getMethod();\n expect(method).to.equal(\"GET\");\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "getUrl", + "filename": "getUrl.bru", + "seq": 1, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "OkR2RNGAnwkPvXPex4huK" + }, + { + "name": "res.body", + "value": "eq pong", + "enabled": true, + "uid": "T7L4EFpqrauldg2JOGshM" + } + ], + "tests": "test(\"req.getUrl()\", function() {\n const url = req.getUrl();\n expect(url).to.equal(\"https://testbench-sanity.usebruno.com/ping\");\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "setBody", + "filename": "setBody.bru", + "seq": 10, + "request": { + "url": "{{host}}/api/echo/json", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"hello\": \"bruno\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "req.setBody({\n \"bruno\": \"is awesome\"\n});" + }, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "1tuWeypKcGgbuLEXYF54S" + } + ], + "tests": "test(\"req.setBody()\", function() {\n const data = res.getBody();\n expect(data).to.eql({\n \"bruno\": \"is awesome\"\n });\n});\n", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "setHeader", + "filename": "setHeader.bru", + "seq": 6, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [ + { + "name": "bruno", + "value": "is-awesome", + "enabled": true + } + ], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "req.setHeader('bruno', 'is-the-future');" + }, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "jLHIdvmg0VXn4PLW5Ch3j" + }, + { + "name": "res.body", + "value": "eq pong", + "enabled": true, + "uid": "PLKiqwDU1HsEkwchjyNky" + } + ], + "tests": "test(\"req.setHeader(name)\", function() {\n const h = req.getHeader('bruno');\n expect(h).to.equal(\"is-the-future\");\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "setHeaders", + "filename": "setHeaders.bru", + "seq": 8, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [ + { + "name": "bruno", + "value": "is-awesome", + "enabled": true + }, + { + "name": "della", + "value": "is-beautiful", + "enabled": true + } + ], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "req.setHeaders({\n \"content-type\": \"application/text\",\n \"transaction-id\": \"foobar\"\n});" + }, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "cFZM1lk0lnaR845ZQscl9" + }, + { + "name": "res.body", + "value": "eq pong", + "enabled": true, + "uid": "Gt07qor0o6lnGiMGvtPF3" + } + ], + "tests": "test(\"req.setHeaders()\", function() {\n const h = req.getHeaders();\n expect(h['content-type']).to.equal(\"application/text\");\n expect(h['transaction-id']).to.equal(\"foobar\");\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "setMethod", + "filename": "setMethod.bru", + "seq": 4, + "request": { + "url": "{{host}}/ping", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "req.setMethod(\"GET\");" + }, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "cJZfmbnZVfdaenvhz4wfq" + }, + { + "name": "res.body", + "value": "eq pong", + "enabled": true, + "uid": "wHTmbMRx9ZYWSZ8QivM8h" + } + ], + "tests": "test(\"req.setMethod()()\", function() {\n const method = req.getMethod();\n expect(method).to.equal(\"GET\");\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "setUrl", + "filename": "setUrl.bru", + "seq": 2, + "request": { + "url": "{{host}}/ping/invalid", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "req": "req.setUrl(\"https://testbench-sanity.usebruno.com/ping\");" + }, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "rVolqUH1aCO0NurDPCA0T" + }, + { + "name": "res.body", + "value": "eq pong", + "enabled": true, + "uid": "5ynLdeD42v7nQlvI8ORXZ" + } + ], + "tests": "test(\"req.setUrl()\", function() {\n const url = req.getUrl();\n expect(url).to.equal(\"https://testbench-sanity.usebruno.com/ping\");\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "res", + "filename": "res", + "items": [ + { + "type": "http", + "name": "getBody", + "filename": "getBody.bru", + "seq": 4, + "request": { + "url": "{{host}}/api/echo/json", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"hello\": \"bruno\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "QAa35PSfpwk267ompkUyX" + } + ], + "tests": "test(\"res.getBody()\", function() {\n const data = res.getBody();\n expect(data).to.eql({\n \"hello\": \"bruno\"\n });\n});\n", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "getHeader", + "filename": "getHeader.bru", + "seq": 2, + "request": { + "url": "{{host}}/api/echo/json", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"hello\": \"bruno\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "QfXtdGakjK897gTqaU4PE" + } + ], + "tests": "test(\"res.getHeader(name)\", function() {\n const server = res.getHeader('x-powered-by');\n expect(server).to.eql('Express');\n});\n", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "getHeaders", + "filename": "getHeaders.bru", + "seq": 3, + "request": { + "url": "{{host}}/api/echo/json", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"hello\": \"bruno\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "WCmqMcNZlAoJdZ9g2KLVe" + } + ], + "tests": "test(\"res.getHeaders(name)\", function() {\n const h = res.getHeaders();\n expect(h['x-powered-by']).to.eql('Express');\n expect(h['content-length']).to.eql('17');\n});\n", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "getResponseTime", + "filename": "getResponseTime.bru", + "seq": 5, + "request": { + "url": "{{host}}/api/echo/json", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"hello\": \"bruno\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "DpDzW3VB5cIFdxtvxe6Og" + } + ], + "tests": "test(\"res.getResponseTime()\", function() {\n const responseTime = res.getResponseTime();\n expect(typeof responseTime).to.eql(\"number\");\n expect(responseTime > 0).to.be.true;\n});\n", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "getStatus", + "filename": "getStatus.bru", + "seq": 1, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.status", + "value": "eq 200", + "enabled": true, + "uid": "9lIBr7KIWkUTYANiSBNAW" + }, + { + "name": "res.body", + "value": "eq pong", + "enabled": true, + "uid": "TnsOCt0EUxB7meZTOkEdb" + } + ], + "tests": "test(\"res.getStatus()\", function() {\n const status = res.getStatus()\n expect(status).to.equal(200);\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "getStatusText", + "filename": "getStatusText.bru", + "seq": 6, + "request": { + "url": "{{host}}/ping", + "method": "GET", + "headers": [], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [ + { + "name": "res.statusText", + "value": "eq OK", + "enabled": true, + "uid": "bhhg2wMYFSh060HKl8Vgq" + }, + { + "name": "res.body", + "value": "eq pong", + "enabled": true, + "uid": "DTYW4zxmKrtsbPxqn8U5E" + } + ], + "tests": "test(\"res.getStatusText()\", function() {\n const statusText = res.getStatusText()\n expect(statusText).to.equal('OK');\n});", + "docs": "", + "auth": { + "mode": "none" + } + } + } + ] + } + ] + } + ] + } + ], + "activeEnvironmentUid": "epJ1gynipcQgb5uJLBHZr", + "environments": [ + { + "variables": [ + { + "name": "host", + "value": "http://localhost:8080", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "httpfaker", + "value": "https://www.httpfaker.org", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "bearer_auth_token", + "value": "your_secret_token", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "basic_auth_password", + "value": "della", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "env.var1", + "value": "envVar1", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "env-var2", + "value": "envVar2", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "bark", + "value": "{{process.env.PROC_ENV_VAR}}", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "foo", + "value": "bar", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "testSetEnvVar", + "value": "bruno-29653", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "echo-host", + "value": "https://echo.usebruno.com", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "client_id", + "value": "client_id_1", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "client_secret", + "value": "client_secret_1", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "auth_url", + "value": "http://localhost:8080/api/auth/oauth2/authorization_code/authorize", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "callback_url", + "value": "http://localhost:8080/api/auth/oauth2/authorization_code/callback", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "access_token_url", + "value": "http://localhost:8080/api/auth/oauth2/authorization_code/token", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "passwordCredentials_username", + "value": "foo", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "passwordCredentials_password", + "value": "bar", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "github_authorize_url", + "value": "https://github.com/login/oauth/authorize", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "github_access_token_url", + "value": "https://github.com/login/oauth/access_token", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "google_auth_url", + "value": "https://accounts.google.com/o/oauth2/auth", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "google_access_token_url", + "value": "https://accounts.google.com/o/oauth2/token", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "google_scope", + "value": "https://www.googleapis.com/auth/userinfo.email", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "github_client_secret", + "value": "", + "enabled": true, + "secret": true, + "type": "text" + }, + { + "name": "github_client_id", + "value": "", + "enabled": true, + "secret": true, + "type": "text" + }, + { + "name": "google_client_id", + "value": "", + "enabled": true, + "secret": true, + "type": "text" + }, + { + "name": "google_client_secret", + "value": "", + "enabled": true, + "secret": true, + "type": "text" + }, + { + "name": "github_authorization_code", + "value": "", + "enabled": true, + "secret": true, + "type": "text" + }, + { + "name": "passwordCredentials_access_token", + "value": "", + "enabled": true, + "secret": true, + "type": "text" + }, + { + "name": "client_credentials_access_token", + "value": "", + "enabled": true, + "secret": true, + "type": "text" + }, + { + "name": "authorization_code_access_token", + "value": "", + "enabled": true, + "secret": true, + "type": "text" + }, + { + "name": "github_access_token", + "value": "", + "enabled": true, + "secret": true, + "type": "text" + } + ], + "name": "Local" + }, + { + "variables": [ + { + "name": "host", + "value": "https://testbench-sanity.usebruno.com", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "httpfaker", + "value": "https://www.httpfaker.org", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "bearer_auth_token", + "value": "your_secret_token", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "basic_auth_password", + "value": "della", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "env.var1", + "value": "envVar1", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "env-var2", + "value": "envVar2", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "bark", + "value": "{{process.env.PROC_ENV_VAR}}", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "foo", + "value": "bar", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "testSetEnvVar", + "value": "bruno-29653", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "echo-host", + "value": "https://echo.usebruno.com", + "enabled": true, + "secret": false, + "type": "text" + } + ], + "name": "Prod" + } + ], + "root": { + "request": { + "auth": { + "mode": "bearer", + "bearer": { + "token": "{{bearer_auth_token}}" + } + }, + "headers": [ + { + "name": "check", + "value": "again", + "enabled": true, + "uid": "x5ic2SWqv4uOlkzzUaK6u" + }, + { + "name": "token", + "value": "{{collection_pre_var_token}}", + "enabled": true, + "uid": "MdFY4Vekah6cfuxuClOCu" + }, + { + "name": "collection-header", + "value": "collection-header-value", + "enabled": true, + "uid": "wCrMSPJUrX9QljaY6kadq" + } + ], + "script": { + "req": "// used by `scripting/js/folder-collection script-tests`\nconst shouldTestCollectionScripts = bru.getVar('should-test-collection-scripts');\nif(shouldTestCollectionScripts) {\n bru.setVar('collection-var-set-by-collection-script', 'collection-var-value-set-by-collection-script');\n}" + }, + "vars": { + "req": [ + { + "name": "collection_pre_var", + "value": "collection_pre_var_value", + "enabled": true, + "local": false, + "uid": "6K19B5ZyhSBTB1ilbx2Sx" + }, + { + "name": "collection_pre_var_token", + "value": "{{request_pre_var_token}}", + "enabled": true, + "local": false, + "uid": "zdoL9ayNeJnNY7ot6RHDj" + }, + { + "name": "collection-var", + "value": "collection-var-value", + "enabled": true, + "local": false, + "uid": "6tgTaZw6HTazsyAMWrVfL" + } + ] + }, + "tests": "// used by `scripting/js/folder-collection script-tests`\nconst shouldTestCollectionScripts = bru.getVar('should-test-collection-scripts');\nconst collectionVar = bru.getVar(\"collection-var-set-by-collection-script\");\nif (shouldTestCollectionScripts && collectionVar) {\n test(\"collection level test - should get the var that was set by the collection script\", function() {\n expect(collectionVar).to.equal(\"collection-var-value-set-by-collection-script\");\n }); \n bru.setVar('collection-var-set-by-collection-script', null); \n bru.setVar('should-test-collection-scripts', null);\n}" + }, + "docs": "# bruno-testbench 🐶\n\nThis is a test collection that I am using to test various functionalities around bruno" + }, + "brunoConfig": { + "version": "1", + "name": "bruno-testbench", + "type": "collection", + "proxy": { + "enabled": false, + "protocol": "http", + "hostname": "{{proxyHostname}}", + "port": 4000, + "auth": { + "enabled": false, + "username": "anoop" + }, + "bypassProxy": "" + }, + "scripts": { + "moduleWhitelist": [ + "crypto", + "buffer", + "form-data" + ], + "filesystemAccess": { + "allow": true + } + }, + "clientCertificates": { + "enabled": true, + "certs": [] + }, + "presets": { + "requestType": "http", + "requestUrl": "http://localhost:6000" + }, + "ignore": [ + "node_modules", + ".git" + ], + "size": 0.046390533447265625, + "filesCount": 104 + } + } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..e5bd1f01 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6230 @@ +{ + "name": "opencollection", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "opencollection", + "workspaces": [ + "packages/oc-converters", + "packages/oc-schema", + "packages/oc-core", + "packages/oc-express" + ] + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@codemirror/autocomplete": { + "version": "6.18.7", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.7.tgz", + "integrity": "sha512-8EzdeIoWPJDsMBwz3zdzwXnUpCzMiCyz5/A3FIPpriaclFCGDkAzK13sMcnsu5rowqiyeQN2Vs2TsOcoDPZirQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz", + "integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz", + "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.8.5", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz", + "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.5.11", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz", + "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", + "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.38.2", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.2.tgz", + "integrity": "sha512-bTWAJxL6EOFLPzTx+O5P5xAO3gTqpatQ2b/ARQ8itfU/v2LlpS3pH2fkL0A3E/Fx8Y2St2KES7ZEV0sHTsSW/A==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@lezer/common": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", + "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", + "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", + "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, + "node_modules/@monaco-editor/loader": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz", + "integrity": "sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==", + "license": "MIT", + "dependencies": { + "state-local": "^1.0.6" + } + }, + "node_modules/@monaco-editor/react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz", + "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==", + "license": "MIT", + "dependencies": { + "@monaco-editor/loader": "^1.5.0" + }, + "peerDependencies": { + "monaco-editor": ">= 0.25.0 < 1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@opencollection/playground": { + "resolved": "packages/oc-core", + "link": true + }, + "node_modules/@prantlf/jsonlint": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@prantlf/jsonlint/-/jsonlint-16.0.0.tgz", + "integrity": "sha512-L0jFtcsBRJZOr4T6sbePb1R6XYF6Nofj6kmEAxqTKCHEr50uvyxBFnB1UKaehWaMhHnvtyqWfTR5Go25RywXIQ==", + "license": "MIT", + "dependencies": { + "ajv": "8.17.1", + "ajv-draft-04": "1.0.0", + "cosmiconfig": "9.0.0", + "diff": "5.2.0", + "fast-glob": "3.3.2" + }, + "bin": { + "jsonlint": "lib/cli.js" + }, + "engines": { + "node": ">=16.9" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.2.tgz", + "integrity": "sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.2.tgz", + "integrity": "sha512-oEouqQk2/zxxj22PNcGSskya+3kV0ZKH+nQxuCCOGJ4oTXBdNTbv+f/E3c74cNLeMO1S5wVWacSws10TTSB77g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.2.tgz", + "integrity": "sha512-OZuTVTpj3CDSIxmPgGH8en/XtirV5nfljHZ3wrNwvgkT5DQLhIKAeuFSiwtbMto6oVexV0k1F1zqURPKf5rI1Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.2.tgz", + "integrity": "sha512-Wa/Wn8RFkIkr1vy1k1PB//VYhLnlnn5eaJkfTQKivirOvzu5uVd2It01ukeQstMursuz7S1bU+8WW+1UPXpa8A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.2.tgz", + "integrity": "sha512-QkzxvH3kYN9J1w7D1A+yIMdI1pPekD+pWx7G5rXgnIlQ1TVYVC6hLl7SOV9pi5q9uIDF9AuIGkuzcbF7+fAhow==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.2.tgz", + "integrity": "sha512-dkYXB0c2XAS3a3jmyDkX4Jk0m7gWLFzq1C3qUnJJ38AyxIF5G/dyS4N9B30nvFseCfgtCEdbYFhk0ChoCGxPog==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.2.tgz", + "integrity": "sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.2.tgz", + "integrity": "sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.2.tgz", + "integrity": "sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.2.tgz", + "integrity": "sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.50.2.tgz", + "integrity": "sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.2.tgz", + "integrity": "sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.2.tgz", + "integrity": "sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.2.tgz", + "integrity": "sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.2.tgz", + "integrity": "sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.2.tgz", + "integrity": "sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.2.tgz", + "integrity": "sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.2.tgz", + "integrity": "sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.2.tgz", + "integrity": "sha512-eFUvvnTYEKeTyHEijQKz81bLrUQOXKZqECeiWH6tb8eXXbZk+CXSG2aFrig2BQ/pjiVRj36zysjgILkqarS2YA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.2.tgz", + "integrity": "sha512-cBaWmXqyfRhH8zmUxK3d3sAhEWLrtMjWBRwdMMHJIXSjvjLKvv49adxiEz+FJ8AP90apSDDBx2Tyd/WylV6ikA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.2.tgz", + "integrity": "sha512-APwKy6YUhvZaEoHyM+9xqmTpviEI+9eL7LoCH+aLcvWYHJ663qG5zx7WzWZY+a9qkg5JtzcMyJ9z0WtQBMDmgA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-1.0.6.tgz", + "integrity": "sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==", + "license": "MIT" + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", + "integrity": "sha512-41U72MB56TfUMGndAKK8vJ78eooOD4Z5NOL4xEfjc0c23s+6EYKXlXsmACBVclLP1yOfWCgEganVzddVrSNoTg==", + "license": "MIT", + "dependencies": { + "exit": "0.1.2", + "glob": "^7.1.1" + }, + "engines": { + "node": ">=0.2.5" + } + }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha512-duS7VP5pvfsNLDvL1O4VOEbw37AI3A4ZUQYemvDlnpGrNu9tprR7BYWpDYwC0Xia0Zxz5ZupdiIrUp0GH1aXfg==", + "dependencies": { + "date-now": "^0.1.4" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, + "node_modules/date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha512-AsElvov3LoNB7tf5k37H2jYSB+ZZPMT5sG2QjJCcdlV5chIv6htBUBUui2IKRjgtKAKtCBN7Zbwa+MtwLjSeNw==" + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/dom-serializer/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", + "integrity": "sha512-q9bUwjfp7Eif8jWxxxPSykdRZAb6GkguBGSgvvCrhI9wB71W2K/Kvv4E61CF/mcCfnVJDeDWx/Vb/uAqbDj6UQ==", + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/entities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha512-LbLqfXgJMmy81t+7c14mnulFHJ170cM6E+0vMXR9k/ZiZwgX8i5pNgjTCX3SO4VeUsFLV+8InixoretwU+MjBQ==", + "license": "BSD-like" + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/esbuild": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/htmlparser2": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "integrity": "sha512-hBxEg3CYXe+rPIua8ETe7tmG3XDn9B0edOE/e9wH2nLczxzgdu0m0aNHY+5wFZiviLWLdANPJTssa92dMcXQ5Q==", + "license": "MIT", + "dependencies": { + "domelementtype": "1", + "domhandler": "2.3", + "domutils": "1.5", + "entities": "1.0", + "readable-stream": "1.1" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jshint": { + "version": "2.13.6", + "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.13.6.tgz", + "integrity": "sha512-IVdB4G0NTTeQZrBoM8C5JFVLjV2KtZ9APgybDA1MK73xb09qFs0jCXyQLnCOp1cSZZZbvhq/6mfXHUTaDkffuQ==", + "license": "MIT", + "dependencies": { + "cli": "~1.0.0", + "console-browserify": "1.1.x", + "exit": "0.1.x", + "htmlparser2": "3.8.x", + "lodash": "~4.17.21", + "minimatch": "~3.0.2", + "strip-json-comments": "1.0.x" + }, + "bin": { + "jshint": "bin/jshint" + } + }, + "node_modules/jshint/node_modules/strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha512-AOPG8EBc5wAikaG1/7uFCNFJwnKOuQwFTpYBdTW6OvWHeZBQBrAA/amefHGrEiOnCPcLFZK6FUPtWVKpQVIRgg==", + "license": "MIT", + "bin": { + "strip-json-comments": "cli.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/monaco-editor": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.53.0.tgz", + "integrity": "sha512-0WNThgC6CMWNXXBxTbaYYcunj08iB5rnx4/G56UOPeL9UVIUGGHA1GR0EWIh9Ebabj7NpCRawQ5b0hfN1jQmYQ==", + "license": "MIT", + "dependencies": { + "@types/trusted-types": "^1.0.6" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.2.tgz", + "integrity": "sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.50.2", + "@rollup/rollup-android-arm64": "4.50.2", + "@rollup/rollup-darwin-arm64": "4.50.2", + "@rollup/rollup-darwin-x64": "4.50.2", + "@rollup/rollup-freebsd-arm64": "4.50.2", + "@rollup/rollup-freebsd-x64": "4.50.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.50.2", + "@rollup/rollup-linux-arm-musleabihf": "4.50.2", + "@rollup/rollup-linux-arm64-gnu": "4.50.2", + "@rollup/rollup-linux-arm64-musl": "4.50.2", + "@rollup/rollup-linux-loong64-gnu": "4.50.2", + "@rollup/rollup-linux-ppc64-gnu": "4.50.2", + "@rollup/rollup-linux-riscv64-gnu": "4.50.2", + "@rollup/rollup-linux-riscv64-musl": "4.50.2", + "@rollup/rollup-linux-s390x-gnu": "4.50.2", + "@rollup/rollup-linux-x64-gnu": "4.50.2", + "@rollup/rollup-linux-x64-musl": "4.50.2", + "@rollup/rollup-openharmony-arm64": "4.50.2", + "@rollup/rollup-win32-arm64-msvc": "4.50.2", + "@rollup/rollup-win32-ia32-msvc": "4.50.2", + "@rollup/rollup-win32-x64-msvc": "4.50.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/state-local": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", + "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==", + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT" + }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/vite": { + "version": "6.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz", + "integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "packages/oc-core": { + "name": "@opencollection/playground", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@monaco-editor/react": "^4.7.0", + "@prantlf/jsonlint": "^16.0.0", + "@tailwindcss/vite": "^4.0.9", + "@types/js-yaml": "^4.0.9", + "@types/markdown-it": "^14.1.2", + "@types/prismjs": "^1.26.5", + "ajv": "^8.12.0", + "codemirror": "^6.0.2", + "js-yaml": "^4.1.0", + "jshint": "^2.13.6", + "markdown-it": "^14.1.0", + "monaco-editor": "^0.53.0", + "prismjs": "^1.29.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-markdown": "^10.0.0", + "react-router-dom": "^7.3.0", + "remark-gfm": "^4.0.1", + "strip-json-comments": "^3.1.1" + }, + "devDependencies": { + "@eslint/js": "^9.21.0", + "@tailwindcss/postcss": "^4.1.13", + "@tailwindcss/typography": "^0.5.10", + "@types/express": "^4.17.21", + "@types/prismjs": "^1.26.3", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@types/uuid": "^10.0.0", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "concurrently": "^9.1.2", + "eslint": "^9.21.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^15.15.0", + "postcss": "^8.5.3", + "rimraf": "^6.0.1", + "rollup": "^4.50.2", + "tailwindcss": "^4.0.9", + "terser": "^5.44.0", + "typescript": "~5.7.2", + "typescript-eslint": "^8.24.1", + "vite": "^6.2.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "packages/oc-core/node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/oc-core/node_modules/@babel/compat-data": { + "version": "7.28.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "packages/oc-core/node_modules/@babel/core": { + "version": "7.28.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "packages/oc-core/node_modules/@babel/generator": { + "version": "7.28.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "packages/oc-core/node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "packages/oc-core/node_modules/@babel/helper-globals": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "packages/oc-core/node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "packages/oc-core/node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "packages/oc-core/node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "packages/oc-core/node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "packages/oc-core/node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "packages/oc-core/node_modules/@babel/helpers": { + "version": "7.28.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "packages/oc-core/node_modules/@babel/parser": { + "version": "7.28.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "packages/oc-core/node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "packages/oc-core/node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "packages/oc-core/node_modules/@babel/template": { + "version": "7.27.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "packages/oc-core/node_modules/@babel/traverse": { + "version": "7.28.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "packages/oc-core/node_modules/@babel/types": { + "version": "7.28.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "packages/oc-core/node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "packages/oc-core/node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "packages/oc-core/node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "packages/oc-core/node_modules/@eslint/config-array": { + "version": "0.21.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "packages/oc-core/node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "packages/oc-core/node_modules/@eslint/core": { + "version": "0.15.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "packages/oc-core/node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "packages/oc-core/node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "packages/oc-core/node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/oc-core/node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/@eslint/js": { + "version": "9.35.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "packages/oc-core/node_modules/@eslint/object-schema": { + "version": "2.1.6", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "packages/oc-core/node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "packages/oc-core/node_modules/@humanfs/core": { + "version": "0.19.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "packages/oc-core/node_modules/@humanfs/node": { + "version": "0.16.7", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "packages/oc-core/node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "packages/oc-core/node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "packages/oc-core/node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "packages/oc-core/node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "packages/oc-core/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "packages/oc-core/node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/oc-core/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "packages/oc-core/node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "packages/oc-core/node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "packages/oc-core/node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "packages/oc-core/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "license": "MIT" + }, + "packages/oc-core/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "packages/oc-core/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/@tailwindcss/node": { + "version": "4.1.13", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.5.1", + "lightningcss": "1.30.1", + "magic-string": "^0.30.18", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.13" + } + }, + "packages/oc-core/node_modules/@tailwindcss/oxide": { + "version": "4.1.13", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.4.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.13", + "@tailwindcss/oxide-darwin-arm64": "4.1.13", + "@tailwindcss/oxide-darwin-x64": "4.1.13", + "@tailwindcss/oxide-freebsd-x64": "4.1.13", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.13", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.13", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.13", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.13", + "@tailwindcss/oxide-linux-x64-musl": "4.1.13", + "@tailwindcss/oxide-wasm32-wasi": "4.1.13", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.13", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.13" + } + }, + "packages/oc-core/node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.13", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "packages/oc-core/node_modules/@tailwindcss/postcss": { + "version": "4.1.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.13", + "@tailwindcss/oxide": "4.1.13", + "postcss": "^8.4.41", + "tailwindcss": "4.1.13" + } + }, + "packages/oc-core/node_modules/@tailwindcss/typography": { + "version": "0.5.16", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "packages/oc-core/node_modules/@tailwindcss/vite": { + "version": "4.1.13", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.13", + "@tailwindcss/oxide": "4.1.13", + "tailwindcss": "4.1.13" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "packages/oc-core/node_modules/@types/babel__core": { + "version": "7.20.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "packages/oc-core/node_modules/@types/babel__generator": { + "version": "7.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "packages/oc-core/node_modules/@types/babel__template": { + "version": "7.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "packages/oc-core/node_modules/@types/babel__traverse": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "packages/oc-core/node_modules/@types/body-parser": { + "version": "1.19.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "packages/oc-core/node_modules/@types/connect": { + "version": "3.4.38", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "packages/oc-core/node_modules/@types/debug": { + "version": "4.1.12", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "packages/oc-core/node_modules/@types/estree-jsx": { + "version": "1.0.5", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "packages/oc-core/node_modules/@types/express": { + "version": "4.17.23", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "packages/oc-core/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "packages/oc-core/node_modules/@types/hast": { + "version": "3.0.4", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "packages/oc-core/node_modules/@types/http-errors": { + "version": "2.0.5", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/@types/js-yaml": { + "version": "4.0.9", + "license": "MIT" + }, + "packages/oc-core/node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/@types/linkify-it": { + "version": "5.0.0", + "license": "MIT" + }, + "packages/oc-core/node_modules/@types/markdown-it": { + "version": "14.1.2", + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "packages/oc-core/node_modules/@types/mdast": { + "version": "4.0.4", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "packages/oc-core/node_modules/@types/mdurl": { + "version": "2.0.0", + "license": "MIT" + }, + "packages/oc-core/node_modules/@types/mime": { + "version": "1.3.5", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/@types/ms": { + "version": "2.1.0", + "license": "MIT" + }, + "packages/oc-core/node_modules/@types/node": { + "version": "24.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.12.0" + } + }, + "packages/oc-core/node_modules/@types/prismjs": { + "version": "1.26.5", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/@types/qs": { + "version": "6.14.0", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/@types/range-parser": { + "version": "1.2.7", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/@types/react": { + "version": "19.1.13", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "packages/oc-core/node_modules/@types/react-dom": { + "version": "19.1.9", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "packages/oc-core/node_modules/@types/send": { + "version": "0.17.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "packages/oc-core/node_modules/@types/serve-static": { + "version": "1.15.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "packages/oc-core/node_modules/@types/unist": { + "version": "3.0.3", + "license": "MIT" + }, + "packages/oc-core/node_modules/@types/uuid": { + "version": "10.0.0", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.44.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.44.0", + "@typescript-eslint/type-utils": "8.44.0", + "@typescript-eslint/utils": "8.44.0", + "@typescript-eslint/visitor-keys": "8.44.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.44.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "packages/oc-core/node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "packages/oc-core/node_modules/@typescript-eslint/parser": { + "version": "8.44.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.44.0", + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/typescript-estree": "8.44.0", + "@typescript-eslint/visitor-keys": "8.44.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "packages/oc-core/node_modules/@typescript-eslint/project-service": { + "version": "8.44.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.44.0", + "@typescript-eslint/types": "^8.44.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "packages/oc-core/node_modules/@typescript-eslint/scope-manager": { + "version": "8.44.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/visitor-keys": "8.44.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "packages/oc-core/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.44.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "packages/oc-core/node_modules/@typescript-eslint/type-utils": { + "version": "8.44.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/typescript-estree": "8.44.0", + "@typescript-eslint/utils": "8.44.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "packages/oc-core/node_modules/@typescript-eslint/types": { + "version": "8.44.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "packages/oc-core/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.44.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.44.0", + "@typescript-eslint/tsconfig-utils": "8.44.0", + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/visitor-keys": "8.44.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "packages/oc-core/node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "packages/oc-core/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/oc-core/node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.2", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "packages/oc-core/node_modules/@typescript-eslint/utils": { + "version": "8.44.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.44.0", + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/typescript-estree": "8.44.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "packages/oc-core/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.44.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.44.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "packages/oc-core/node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "license": "ISC" + }, + "packages/oc-core/node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "packages/oc-core/node_modules/acorn": { + "version": "8.15.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "packages/oc-core/node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "packages/oc-core/node_modules/ansi-regex": { + "version": "6.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "packages/oc-core/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "packages/oc-core/node_modules/autoprefixer": { + "version": "10.4.21", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "packages/oc-core/node_modules/bail": { + "version": "2.0.2", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/baseline-browser-mapping": { + "version": "2.8.4", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "packages/oc-core/node_modules/browserslist": { + "version": "4.26.0", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.2", + "caniuse-lite": "^1.0.30001741", + "electron-to-chromium": "^1.5.218", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "packages/oc-core/node_modules/buffer-from": { + "version": "1.1.2", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/caniuse-lite": { + "version": "1.0.30001741", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "packages/oc-core/node_modules/ccount": { + "version": "2.0.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "packages/oc-core/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/character-entities": { + "version": "2.0.2", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/character-entities-html4": { + "version": "2.1.0", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/character-entities-legacy": { + "version": "3.0.0", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/character-reference-invalid": { + "version": "2.0.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/chownr": { + "version": "3.0.0", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "packages/oc-core/node_modules/cliui": { + "version": "8.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "packages/oc-core/node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "packages/oc-core/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "packages/oc-core/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/comma-separated-tokens": { + "version": "2.0.3", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/commander": { + "version": "2.20.3", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/concurrently": { + "version": "9.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "packages/oc-core/node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "packages/oc-core/node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "packages/oc-core/node_modules/csstype": { + "version": "3.1.3", + "license": "MIT" + }, + "packages/oc-core/node_modules/debug": { + "version": "4.4.3", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "packages/oc-core/node_modules/decode-named-character-reference": { + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/dequal": { + "version": "2.0.3", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "packages/oc-core/node_modules/detect-libc": { + "version": "2.1.0", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/devlop": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/eastasianwidth": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/electron-to-chromium": { + "version": "1.5.218", + "dev": true, + "license": "ISC" + }, + "packages/oc-core/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/enhanced-resolve": { + "version": "5.18.3", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "packages/oc-core/node_modules/entities": { + "version": "4.5.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "packages/oc-core/node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "packages/oc-core/node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/oc-core/node_modules/eslint": { + "version": "9.35.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.35.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "packages/oc-core/node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "packages/oc-core/node_modules/eslint-plugin-react-refresh": { + "version": "0.4.20", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "packages/oc-core/node_modules/eslint-scope": { + "version": "8.4.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "packages/oc-core/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "packages/oc-core/node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "packages/oc-core/node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/espree": { + "version": "10.4.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "packages/oc-core/node_modules/esquery": { + "version": "1.6.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "packages/oc-core/node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "packages/oc-core/node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "packages/oc-core/node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "packages/oc-core/node_modules/extend": { + "version": "3.0.2", + "license": "MIT" + }, + "packages/oc-core/node_modules/fast-glob": { + "version": "3.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "packages/oc-core/node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "packages/oc-core/node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/file-entry-cache": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "packages/oc-core/node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/oc-core/node_modules/flat-cache": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "packages/oc-core/node_modules/flatted": { + "version": "3.3.3", + "dev": true, + "license": "ISC" + }, + "packages/oc-core/node_modules/foreground-child": { + "version": "3.3.1", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/oc-core/node_modules/fraction.js": { + "version": "4.3.7", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "packages/oc-core/node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "packages/oc-core/node_modules/get-caller-file": { + "version": "2.0.5", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "packages/oc-core/node_modules/glob": { + "version": "11.0.3", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/oc-core/node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "packages/oc-core/node_modules/glob/node_modules/minimatch": { + "version": "10.0.3", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/oc-core/node_modules/globals": { + "version": "15.15.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/oc-core/node_modules/graceful-fs": { + "version": "4.2.11", + "license": "ISC" + }, + "packages/oc-core/node_modules/graphemer": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/hast-util-whitespace": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/html-url-attributes": { + "version": "3.0.1", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "packages/oc-core/node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "packages/oc-core/node_modules/inline-style-parser": { + "version": "0.2.4", + "license": "MIT" + }, + "packages/oc-core/node_modules/is-alphabetical": { + "version": "2.0.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/is-alphanumerical": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/is-decimal": { + "version": "2.0.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/is-hexadecimal": { + "version": "2.0.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/is-plain-obj": { + "version": "4.1.0", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/oc-core/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "packages/oc-core/node_modules/jackspeak": { + "version": "4.1.1", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/oc-core/node_modules/jiti": { + "version": "2.5.1", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "packages/oc-core/node_modules/jsesc": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "packages/oc-core/node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "packages/oc-core/node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "packages/oc-core/node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "packages/oc-core/node_modules/lightningcss": { + "version": "1.30.1", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "packages/oc-core/node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "packages/oc-core/node_modules/linkify-it": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "packages/oc-core/node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/oc-core/node_modules/lodash.castarray": { + "version": "4.4.0", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/lodash.isplainobject": { + "version": "4.0.6", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/longest-streak": { + "version": "3.1.0", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "packages/oc-core/node_modules/magic-string": { + "version": "0.30.19", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "packages/oc-core/node_modules/markdown-it": { + "version": "14.1.0", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "packages/oc-core/node_modules/markdown-table": { + "version": "3.0.4", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/oc-core/node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/mdast-util-gfm": { + "version": "3.1.0", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/mdurl": { + "version": "2.0.0", + "license": "MIT" + }, + "packages/oc-core/node_modules/micromark": { + "version": "4.0.2", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "packages/oc-core/node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "packages/oc-core/node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/micromark-factory-destination": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "packages/oc-core/node_modules/micromark-factory-label": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "packages/oc-core/node_modules/micromark-factory-space": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "packages/oc-core/node_modules/micromark-factory-title": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "packages/oc-core/node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "packages/oc-core/node_modules/micromark-util-character": { + "version": "2.1.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "packages/oc-core/node_modules/micromark-util-chunked": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "packages/oc-core/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "packages/oc-core/node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "packages/oc-core/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "packages/oc-core/node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "packages/oc-core/node_modules/micromark-util-encode": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "packages/oc-core/node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "packages/oc-core/node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "packages/oc-core/node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "packages/oc-core/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "packages/oc-core/node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "packages/oc-core/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "packages/oc-core/node_modules/micromark-util-types": { + "version": "2.0.2", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "packages/oc-core/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "packages/oc-core/node_modules/minipass": { + "version": "7.1.2", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "packages/oc-core/node_modules/minizlib": { + "version": "3.0.2", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "packages/oc-core/node_modules/mkdirp": { + "version": "3.0.1", + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/oc-core/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "packages/oc-core/node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/node-releases": { + "version": "2.0.21", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/normalize-range": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "packages/oc-core/node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "packages/oc-core/node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/oc-core/node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/oc-core/node_modules/package-json-from-dist": { + "version": "1.0.1", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "packages/oc-core/node_modules/parse-entities": { + "version": "4.0.2", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "license": "MIT" + }, + "packages/oc-core/node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/path-scurry": { + "version": "2.0.0", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/oc-core/node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.2.1", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "packages/oc-core/node_modules/postcss-selector-parser": { + "version": "6.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "packages/oc-core/node_modules/postcss-value-parser": { + "version": "4.2.0", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "packages/oc-core/node_modules/prismjs": { + "version": "1.30.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "packages/oc-core/node_modules/property-information": { + "version": "7.1.0", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "packages/oc-core/node_modules/punycode.js": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "packages/oc-core/node_modules/react-markdown": { + "version": "10.1.0", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "packages/oc-core/node_modules/react-refresh": { + "version": "0.17.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "packages/oc-core/node_modules/react-router": { + "version": "7.9.1", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "packages/oc-core/node_modules/react-router-dom": { + "version": "7.9.1", + "license": "MIT", + "dependencies": { + "react-router": "7.9.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "packages/oc-core/node_modules/react-router/node_modules/cookie": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "packages/oc-core/node_modules/remark-gfm": { + "version": "4.0.1", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/remark-parse": { + "version": "11.0.0", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/remark-rehype": { + "version": "11.1.2", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/remark-stringify": { + "version": "11.0.0", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/require-directory": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "packages/oc-core/node_modules/rimraf": { + "version": "6.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/oc-core/node_modules/rxjs": { + "version": "7.8.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "packages/oc-core/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "packages/oc-core/node_modules/set-cookie-parser": { + "version": "2.7.1", + "license": "MIT" + }, + "packages/oc-core/node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/shell-quote": { + "version": "1.8.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "packages/oc-core/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/oc-core/node_modules/source-map": { + "version": "0.6.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "packages/oc-core/node_modules/source-map-support": { + "version": "0.5.21", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "packages/oc-core/node_modules/space-separated-tokens": { + "version": "2.0.2", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/oc-core/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/stringify-entities": { + "version": "4.0.4", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/strip-ansi": { + "version": "7.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "packages/oc-core/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/strip-json-comments": { + "version": "3.1.1", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/oc-core/node_modules/style-to-js": { + "version": "1.1.17", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.9" + } + }, + "packages/oc-core/node_modules/style-to-object": { + "version": "1.0.9", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, + "packages/oc-core/node_modules/supports-color": { + "version": "8.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "packages/oc-core/node_modules/tailwindcss": { + "version": "4.1.13", + "license": "MIT" + }, + "packages/oc-core/node_modules/tapable": { + "version": "2.2.3", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "packages/oc-core/node_modules/tar": { + "version": "7.4.3", + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "packages/oc-core/node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "packages/oc-core/node_modules/terser": { + "version": "5.44.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "packages/oc-core/node_modules/tree-kill": { + "version": "1.2.2", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "packages/oc-core/node_modules/trim-lines": { + "version": "3.0.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/trough": { + "version": "2.2.0", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/oc-core/node_modules/ts-api-utils": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "packages/oc-core/node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "license": "0BSD" + }, + "packages/oc-core/node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "packages/oc-core/node_modules/typescript": { + "version": "5.7.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "packages/oc-core/node_modules/typescript-eslint": { + "version": "8.44.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.44.0", + "@typescript-eslint/parser": "8.44.0", + "@typescript-eslint/typescript-estree": "8.44.0", + "@typescript-eslint/utils": "8.44.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "packages/oc-core/node_modules/uc.micro": { + "version": "2.1.0", + "license": "MIT" + }, + "packages/oc-core/node_modules/undici-types": { + "version": "7.12.0", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/unified": { + "version": "11.0.5", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/unist-util-is": { + "version": "6.0.0", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/unist-util-position": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/unist-util-visit": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/update-browserslist-db": { + "version": "1.1.3", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "packages/oc-core/node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "packages/oc-core/node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/vfile": { + "version": "6.0.3", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/vfile-message": { + "version": "4.0.3", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/oc-core/node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "packages/oc-core/node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "packages/oc-core/node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "packages/oc-core/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "packages/oc-core/node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "packages/oc-core/node_modules/y18n": { + "version": "5.0.8", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "packages/oc-core/node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + }, + "packages/oc-core/node_modules/yargs": { + "version": "17.7.2", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "packages/oc-core/node_modules/yargs-parser": { + "version": "21.1.1", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "packages/oc-core/node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "packages/oc-core/node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "packages/oc-core/node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/oc-core/node_modules/zwitch": { + "version": "2.0.4", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/packages/oc-core/eslint.config.js b/packages/oc-core/eslint.config.js new file mode 100644 index 00000000..edc60c7a --- /dev/null +++ b/packages/oc-core/eslint.config.js @@ -0,0 +1,30 @@ +import js from '@eslint/js'; +import globals from 'globals'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { ignores: ['dist', 'dist-standalone', 'dist-server'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + '@typescript-eslint/no-explicit-any': 'warn', + }, + }, +); \ No newline at end of file diff --git a/packages/oc-core/index.html b/packages/oc-core/index.html new file mode 100644 index 00000000..ce4b2ef5 --- /dev/null +++ b/packages/oc-core/index.html @@ -0,0 +1,14 @@ + + + + + + + OpenCollection Playground - Interactive API Documentation + + + +
+ + + diff --git a/packages/oc-core/package.json b/packages/oc-core/package.json new file mode 100644 index 00000000..bfdb30e7 --- /dev/null +++ b/packages/oc-core/package.json @@ -0,0 +1,108 @@ +{ + "name": "@opencollection/playground", + "version": "1.0.0", + "description": "Interactive API documentation component for OpenCollection - supports both React components and standalone JavaScript", + "type": "module", + "main": "dist/oc-playground.umd.js", + "module": "dist/oc-playground.es.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/oc-playground.es.js", + "require": "./dist/oc-playground.umd.js" + }, + "./standalone": { + "types": "./standalone.d.ts", + "import": "./dist-standalone/playground.es.js", + "require": "./dist-standalone/playground.umd.js" + }, + "./express": { + "types": "./dist/express.d.ts", + "import": "./dist/express.js", + "require": "./dist/express.js" + }, + "./server": { + "types": "./dist/server.d.ts", + "import": "./dist/server.js", + "require": "./dist/server.js" + } + }, + "files": [ + "dist", + "dist-standalone", + "standalone.d.ts" + ], + "scripts": { + "dev": "vite", + "server": "node server.js", + "dev:all": "concurrently \"npm run dev\" \"npm run server\"", + "build": "npm run clean && tsc --emitDeclarationOnly && vite build", + "build:standalone": "npm run clean:standalone && vite build --config vite.config.standalone.ts", + "build:all": "npm run build && npm run build:standalone", + "prepublishOnly": "npm run build:all", + "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview", + "clean": "rimraf dist", + "clean:standalone": "rimraf dist-standalone" + }, + "dependencies": { + "@monaco-editor/react": "^4.7.0", + "@prantlf/jsonlint": "^16.0.0", + "@tailwindcss/vite": "^4.0.9", + "@types/js-yaml": "^4.0.9", + "@types/markdown-it": "^14.1.2", + "@types/prismjs": "^1.26.5", + "ajv": "^8.12.0", + "codemirror": "^6.0.2", + "js-yaml": "^4.1.0", + "jshint": "^2.13.6", + "markdown-it": "^14.1.0", + "monaco-editor": "^0.53.0", + "prismjs": "^1.29.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-markdown": "^10.0.0", + "react-router-dom": "^7.3.0", + "remark-gfm": "^4.0.1", + "strip-json-comments": "^3.1.1" + }, + "devDependencies": { + "@eslint/js": "^9.21.0", + "@tailwindcss/postcss": "^4.1.13", + "@tailwindcss/typography": "^0.5.10", + "@types/express": "^4.17.21", + "@types/prismjs": "^1.26.3", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@types/uuid": "^10.0.0", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "concurrently": "^9.1.2", + "eslint": "^9.21.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^15.15.0", + "postcss": "^8.5.3", + "rimraf": "^6.0.1", + "rollup": "^4.50.2", + "tailwindcss": "^4.0.9", + "terser": "^5.44.0", + "typescript": "~5.7.2", + "typescript-eslint": "^8.24.1", + "vite": "^6.2.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "keywords": [ + "opencollection", + "api", + "documentation", + "playground", + "interactive" + ], + "author": "OpenCollection", + "license": "MIT" +} diff --git a/packages/oc-core/postcss.config.cjs b/packages/oc-core/postcss.config.cjs new file mode 100644 index 00000000..90c78136 --- /dev/null +++ b/packages/oc-core/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + '@tailwindcss/postcss': {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/packages/oc-core/src/App.tsx b/packages/oc-core/src/App.tsx new file mode 100644 index 00000000..971d5467 --- /dev/null +++ b/packages/oc-core/src/App.tsx @@ -0,0 +1,19 @@ +import OpenCollectionPlayground from './core/OpenCollectionPlayground'; +import './styles/App.css'; +import { test } from './test'; + +function App() { + const theme = 'dark'; + + return ( +
+ +
+ ); +} + +export default App; diff --git a/packages/oc-core/src/assets/react.svg b/packages/oc-core/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/packages/oc-core/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/oc-core/src/core/FileCollectionLoader.tsx b/packages/oc-core/src/core/FileCollectionLoader.tsx new file mode 100644 index 00000000..8b3cfbc9 --- /dev/null +++ b/packages/oc-core/src/core/FileCollectionLoader.tsx @@ -0,0 +1,76 @@ +import React, { useState } from 'react'; +import OpenCollectionPlayground from './OpenCollectionPlayground'; + +const FileCollectionLoader: React.FC = () => { + const [selectedFile, setSelectedFile] = useState(null); + const [theme] = useState<'light' | 'dark' | 'auto'>('light'); + + const handleFileChange = (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (file) { + setSelectedFile(file); + } + }; + + return ( +
+ +
+
+

+ Collection Loader +

+
+ + + {selectedFile && ( + + Selected: {selectedFile.name} ({selectedFile.type || 'unknown type'}) + + )} +
+

+ Supports JSON collections and YAML collections following the OpenCollection schema. +

+
+
+ + +
+ {selectedFile ? ( + + ) : ( +
+
+ + + +

No collection loaded

+

Select a JSON or YAML collection file to get started.

+
+
+ )} +
+
+ ); +}; + +export default FileCollectionLoader; \ No newline at end of file diff --git a/packages/oc-core/src/core/OpenCollectionPlayground.tsx b/packages/oc-core/src/core/OpenCollectionPlayground.tsx new file mode 100644 index 00000000..8134a3bd --- /dev/null +++ b/packages/oc-core/src/core/OpenCollectionPlayground.tsx @@ -0,0 +1,142 @@ +import React, { useRef } from 'react'; +import { + useCollectionData, + useCustomPages, + useMarkdownRenderer, + useResponsive, + useTheme, + useItemFiltering, + useSearchModal, + usePageNavigation, + useRunnerMode +} from '../hooks'; +import { OpenCollectionPlaygroundProps } from '../types'; +import MobileLayout from '../ui/MobileLayout'; +import DesktopLayout from '../layouts/DesktopLayout'; + +const OpenCollectionPlayground: React.FC = ({ + collection, + theme = 'light', + logo, + customPages, + hideSidebar = false, + hideHeader = false, + onlyShow, + proxyUrl, +}) => { + const containerRef = useRef(null); + + const { collectionData, isLoading, error } = useCollectionData(collection); + const { validCustomPages, customPageContents } = useCustomPages(customPages); + const md = useMarkdownRenderer(); + const { isMobile, mobileView, setMobileView } = useResponsive(hideSidebar); + + useTheme(theme); + + const { + filteredCollectionItems, + filteredCustomPages, + shouldShowOverview + } = useItemFiltering(collectionData, validCustomPages, onlyShow); + + const { + currentPageId, + currentPageItem, + currentPageIndex, + totalPages, + canGoNext, + canGoPrevious, + goToNext, + goToPrevious, + goToPage, + allPageItems + } = usePageNavigation( + collectionData, + filteredCustomPages, + filteredCollectionItems, + shouldShowOverview, + isMobile, + setMobileView + ); + + const { + isSearchOpen, + setIsSearchOpen, + searchQuery, + setSearchQuery, + searchResults, + searchInputRef, + handleSearchResultSelect: handleSearchResultSelectHook + } = useSearchModal(collectionData, validCustomPages, goToPage); + + const { isRunnerMode, toggleRunnerMode } = useRunnerMode(); + + const handleSearchResultSelect = (result: {id: string; path?: string; type: string}) => { + handleSearchResultSelectHook({ ...result, name: result.id }); + }; + + if (isLoading) { + return
Loading...
; + } + + if (error) { + return
Error: {error}
; + } + + const commonProps = { + collectionData, + hideSidebar, + theme, + currentPageId, + currentPageItem, + currentPageIndex, + totalPages, + canGoNext, + canGoPrevious, + goToNext, + goToPrevious, + goToPage, + allPageItems, + onSelectItem: goToPage, + filteredCustomPages, + onlyShow, + containerRef, + shouldShowOverview, + filteredCollectionItems, + md, + customPageContents + }; + + const desktopProps = { + ...commonProps, + logo, + isSearchOpen, + setIsSearchOpen, + searchQuery, + setSearchQuery, + searchResults, + searchInputRef, + handleSearchResultSelect, + isRunnerMode, + toggleRunnerMode, + proxyUrl + }; + + const mobileProps = { + ...commonProps, + mobileView, + setMobileView + }; + + return ( +
+ {isMobile ? ( + + ) : ( + + )} +
+ ); +}; + +export default OpenCollectionPlayground; \ No newline at end of file diff --git a/packages/oc-core/src/express.ts b/packages/oc-core/src/express.ts new file mode 100644 index 00000000..e98c8642 --- /dev/null +++ b/packages/oc-core/src/express.ts @@ -0,0 +1,97 @@ +import { Request, Response, Express } from 'express'; +import { renderPlayground, ServerRenderOptions } from './server'; + +export interface PlaygroundHandlerOptions extends Omit { + collection?: any; + name?: string; + description?: string; + docs?: string; + environments?: any[]; + base?: any; + items?: any[]; +} + +export interface PlaygroundMiddlewareOptions { + /** Path to serve playground assets from (default: '/playground-assets') */ + assetsPath?: string; + /** Custom path to playground dist-standalone directory (required) */ + playgroundDistPath: string; +} + +export function playgroundHandler(options: PlaygroundHandlerOptions) { + return (req: Request, res: Response) => { + try { + let collection = options.collection; + + if (!collection && (options.name || options.description || options.items)) { + collection = { + name: options.name || 'API Documentation', + description: options.description, + docs: options.docs, + environments: options.environments, + base: options.base, + items: options.items || [] + }; + } + + if (!collection) { + throw new Error('No collection data provided. Please provide either a "collection" object or individual properties like "name", "items", etc.'); + } + + const renderOptions: ServerRenderOptions = { + opencollection: collection, + theme: options.theme, + logo: options.logo, + customPages: options.customPages, + hideSidebar: options.hideSidebar, + hideHeader: options.hideHeader, + onlyShow: options.onlyShow, + title: options.title || collection.name || 'API Documentation', + cdnUrl: options.cdnUrl, + assetsPath: options.assetsPath || '/playground-assets', + useLocalAssets: options.useLocalAssets !== false // Default to true for Express + }; + + const html = renderPlayground(renderOptions); + + res.setHeader('Content-Type', 'text/html'); + res.send(html); + } catch (error) { + console.error('Error rendering playground:', error); + res.status(500).send(` + + Error + +

Error rendering API playground

+

${error instanceof Error ? error.message : 'Unknown error'}

+ + + `); + } + }; +} + +/** + * Sets up static asset serving for the playground files. + * Call this before using playgroundHandler to ensure assets are available. + * + * @param app Express application instance + * @param expressStatic Express static middleware function (pass express.static) + * @param options Configuration options for asset serving + */ +export function setupPlaygroundAssets(app: Express, expressStatic: any, options: PlaygroundMiddlewareOptions) { + const { + assetsPath = '/playground-assets', + playgroundDistPath + } = options; + + // Serve static assets using the provided express.static function + try { + app.use(assetsPath, expressStatic(playgroundDistPath)); + } catch (error) { + throw new Error(`Failed to setup static assets: ${error instanceof Error ? error.message : 'Unknown error'}`); + } +} + +// For CommonJS compatibility +export default playgroundHandler; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/index.ts b/packages/oc-core/src/hooks/index.ts new file mode 100644 index 00000000..44a0a285 --- /dev/null +++ b/packages/oc-core/src/hooks/index.ts @@ -0,0 +1,9 @@ +export { useCollectionData } from './useCollectionData'; +export { useCustomPages } from './useCustomPages'; +export { useSearchModal } from './useSearchModal'; +export { useTheme } from './useTheme'; +export { useResponsive } from './useResponsive'; +export { useItemFiltering } from './useItemFiltering'; +export { useMarkdownRenderer } from './useMarkdownRenderer'; +export { usePageNavigation } from './usePageNavigation'; +export { useRunnerMode } from './useRunnerMode'; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/useCollectionData.ts b/packages/oc-core/src/hooks/useCollectionData.ts new file mode 100644 index 00000000..ec58c4a9 --- /dev/null +++ b/packages/oc-core/src/hooks/useCollectionData.ts @@ -0,0 +1,103 @@ +import { useEffect, useState } from 'react'; +import { isYamlFile, parseYaml } from '../utils/yamlUtils'; +import { OpenCollectionCollection } from '../types'; + +const loadOpenCollectionData = async (source: string | File): Promise => { + let content: string; + + if (source instanceof File) { + content = await source.text(); + } else if (typeof source === 'string') { + if (source.startsWith('http://') || source.startsWith('https://')) { + const response = await fetch(source); + if (!response.ok) { + throw new Error(`Failed to fetch collection: ${response.statusText}`); + } + content = await response.text(); + } else { + content = source; + } + } else { + throw new Error('Invalid source type for collection'); + } + + try { + return parseYaml(content) as OpenCollectionCollection; + } catch (yamlError) { + try { + return JSON.parse(content) as OpenCollectionCollection; + } catch (jsonError) { + throw new Error('Failed to parse collection as YAML or JSON'); + } + } +}; + +interface UseCollectionDataReturn { + collectionData: OpenCollectionCollection | null; + isLoading: boolean; + error: string | null; +} + +export const useCollectionData = (collection: OpenCollectionCollection | string | File): UseCollectionDataReturn => { + const [collectionData, setCollectionData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const loadCollection = async () => { + try { + setIsLoading(true); + + if (collection instanceof File) { + // Handle File objects + const fileName = collection.name.toLowerCase(); + + let data; + if (isYamlFile(fileName)) { + data = await loadOpenCollectionData(collection); + } else { + // Assume JSON + const content = await collection.text(); + data = JSON.parse(content); + } + + setCollectionData(data as OpenCollectionCollection); + } else if (typeof collection === 'string') { + // Handle URLs + const response = await fetch(collection); + if (!response.ok) { + throw new Error(`Failed to load API collection: ${response.statusText}`); + } + + // Check if the URL or content suggests YAML format + const contentType = response.headers.get('content-type') || ''; + const isYamlUrl = isYamlFile(collection); + const isYamlContentType = contentType.includes('yaml') || contentType.includes('yml'); + + let data; + if (isYamlUrl || isYamlContentType) { + // Handle YAML content + const yamlText = await response.text(); + data = await loadOpenCollectionData(yamlText); + } else { + // Default to JSON + data = await response.json(); + } + + setCollectionData(data as OpenCollectionCollection); + } else { + // Handle collection objects + setCollectionData(collection as OpenCollectionCollection); + } + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load API collection'); + } finally { + setIsLoading(false); + } + }; + + loadCollection(); + }, [collection]); + + return { collectionData, isLoading, error }; +}; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/useCustomPages.ts b/packages/oc-core/src/hooks/useCustomPages.ts new file mode 100644 index 00000000..c83e366b --- /dev/null +++ b/packages/oc-core/src/hooks/useCustomPages.ts @@ -0,0 +1,63 @@ +import { useState, useEffect } from 'react'; +import { CustomPage } from '../types'; + +interface UseCustomPagesReturn { + validCustomPages: CustomPage[]; + customPageContents: Record; +} + +export const useCustomPages = (customPages?: CustomPage[]): UseCustomPagesReturn => { + const [validCustomPages, setValidCustomPages] = useState([]); + const [customPageContents, setCustomPageContents] = useState>({}); + + useEffect(() => { + if (!customPages || customPages.length === 0) { + setValidCustomPages([]); + return; + } + + const valid = customPages.filter(page => { + if (!page.name) { + console.error('Custom page missing required "name" property'); + return false; + } + + if ((!page.content && !page.contentPath) || (page.content && page.contentPath)) { + console.error(`Custom page "${page.name}" must have either content or contentPath, but not both`); + return false; + } + + return true; + }); + + setValidCustomPages(valid); + + const loadPageContents = async () => { + const contents: Record = {}; + + for (const page of valid) { + if (page.content) { + contents[page.name] = page.content; + } else if (page.contentPath) { + try { + const response = await fetch(page.contentPath); + if (!response.ok) { + throw new Error(`Failed to load content from ${page.contentPath}`); + } + contents[page.name] = await response.text(); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + console.error(`Error loading content for custom page "${page.name}":`, error); + contents[page.name] = `Error loading content: ${errorMessage}`; + } + } + } + + setCustomPageContents(contents); + }; + + loadPageContents(); + }, [customPages]); + + return { validCustomPages, customPageContents }; +}; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/useItemFiltering.ts b/packages/oc-core/src/hooks/useItemFiltering.ts new file mode 100644 index 00000000..9357a29f --- /dev/null +++ b/packages/oc-core/src/hooks/useItemFiltering.ts @@ -0,0 +1,60 @@ +import { useMemo, useCallback } from 'react'; +import { OpenCollectionCollection, OpenCollectionItem, HttpRequest, CustomPage } from '../types'; + +interface UseItemFilteringReturn { + shouldShowItem: (itemId: string) => boolean; + filteredCollectionItems: OpenCollectionItem[]; + filteredCustomPages: CustomPage[]; + shouldShowOverview: boolean; +} + +export const useItemFiltering = ( + collectionData: OpenCollectionCollection | null, + validCustomPages: CustomPage[], + onlyShow?: string[] +): UseItemFilteringReturn => { + const shouldShowItem = useCallback((itemId: string): boolean => { + if (!onlyShow || onlyShow.length === 0) { + return true; + } + return onlyShow.includes(itemId); + }, [onlyShow]); + + const filteredCollectionItems = useMemo(() => { + if (!collectionData?.items) { + return []; + } + + if (!onlyShow || onlyShow.length === 0) { + return collectionData.items; + } + + const filterItems = (items: OpenCollectionItem[]): OpenCollectionItem[] => { + return items.filter(item => { + const itemId = item.type === 'http' ? (item as HttpRequest).name || '' : item.type; + return shouldShowItem(itemId); + }); + }; + + return filterItems(collectionData.items); + }, [collectionData, onlyShow, shouldShowItem]); + + const filteredCustomPages = useMemo(() => { + if (!validCustomPages || !onlyShow || onlyShow.length === 0) { + return validCustomPages; + } + + return validCustomPages.filter(page => shouldShowItem(page.name)); + }, [validCustomPages, onlyShow, shouldShowItem]); + + const shouldShowOverview = useMemo(() => { + return shouldShowItem('overview'); + }, [shouldShowItem]); + + return { + shouldShowItem, + filteredCollectionItems, + filteredCustomPages, + shouldShowOverview + }; +}; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/useMarkdownRenderer.ts b/packages/oc-core/src/hooks/useMarkdownRenderer.ts new file mode 100644 index 00000000..ba233d14 --- /dev/null +++ b/packages/oc-core/src/hooks/useMarkdownRenderer.ts @@ -0,0 +1,55 @@ +import { useMemo } from 'react'; +import MarkdownIt from 'markdown-it'; +import Prism from 'prismjs'; + +export const useMarkdownRenderer = () => { + const md = useMemo(() => { + const markdownIt = new MarkdownIt({ + html: true, + linkify: true, + typographer: true, + breaks: true, + highlight: function (str, lang) { + if (lang && Prism.languages[lang]) { + try { + return Prism.highlight(str, Prism.languages[lang], lang); + } catch (__) { } + } + return ''; + }, + }); + + const defaultRender = + markdownIt.renderer.rules.heading_open || + function (tokens, idx, options, env, self) { + return self.renderToken(tokens, idx, options); + }; + + markdownIt.renderer.rules.heading_open = function ( + tokens, + idx, + options, + env, + self + ) { + const token = tokens[idx]; + const level = token.tag.substr(1); + + if (level === '1') { + token.attrJoin('class', 'text-2xl font-bold mt-6 mb-3 heading-1'); + } else if (level === '2') { + token.attrJoin('class', 'text-xl font-semibold mt-5 mb-2 heading-2'); + } else if (level === '3') { + token.attrJoin('class', 'text-lg font-semibold mt-4 mb-2 heading-3'); + } else { + token.attrJoin('class', 'font-semibold mt-3 mb-1 heading-4'); + } + + return defaultRender(tokens, idx, options, env, self); + }; + + return markdownIt; + }, []); + + return md; +}; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/usePageNavigation.ts b/packages/oc-core/src/hooks/usePageNavigation.ts new file mode 100644 index 00000000..20510a2e --- /dev/null +++ b/packages/oc-core/src/hooks/usePageNavigation.ts @@ -0,0 +1,173 @@ +import { useState, useEffect, useCallback, useMemo } from 'react'; +import { OpenCollectionCollection, OpenCollectionItem, CustomPage } from '../types'; +import { getItemId, generateSafeId } from '../utils/itemUtils'; + +interface UsePageNavigationReturn { + currentPageId: string | null; + currentPageItem: OpenCollectionItem | CustomPage | null; + currentPageIndex: number; + totalPages: number; + canGoNext: boolean; + canGoPrevious: boolean; + goToNext: () => void; + goToPrevious: () => void; + goToPage: (id: string) => void; + allPageItems: Array<{id: string; item: OpenCollectionItem | CustomPage; type: 'item' | 'custom' | 'overview'}>; +} + +export const usePageNavigation = ( + collectionData: OpenCollectionCollection | null, + filteredCustomPages: CustomPage[], + filteredCollectionItems: OpenCollectionItem[], + shouldShowOverview: boolean, + isMobile: boolean, + setMobileView: (view: 'sidebar' | 'content') => void +): UsePageNavigationReturn => { + const [currentPageId, setCurrentPageId] = useState(null); + + // Memoize all available pages + const allPageItems = useMemo(() => { + const pages: Array<{id: string; item: OpenCollectionItem | CustomPage; type: 'item' | 'custom' | 'overview'}> = []; + + // Add overview if it should be shown + if (shouldShowOverview && collectionData) { + pages.push({ + id: 'overview', + item: { name: 'Overview', type: 'overview' } as any, + type: 'overview' + }); + } + + // Add custom pages + filteredCustomPages.forEach(page => { + pages.push({ + id: page.name, + item: page, + type: 'custom' + }); + }); + + // Add collection items (flatten nested items in folders) + const flattenItems = (items: OpenCollectionItem[], parentPath: string = ''): OpenCollectionItem[] => { + const result: OpenCollectionItem[] = []; + items.forEach(item => { + // Always include the item itself (folders are also pages now) + result.push(item); + + // If it's a folder with nested items, recursively flatten them + if (item.type === 'folder' && (item as any).items) { + const nestedItems = flattenItems((item as any).items, parentPath ? `${parentPath}/${item.name || 'folder'}` : item.name || 'folder'); + result.push(...nestedItems); + } + }); + return result; + }; + + const flatItems = flattenItems(filteredCollectionItems); + flatItems.forEach(item => { + const itemId = getItemId(item); + const safeId = generateSafeId(itemId); + pages.push({ + id: safeId, + item, + type: 'item' + }); + }); + + return pages; + }, [collectionData, filteredCustomPages, filteredCollectionItems, shouldShowOverview]); + + // Get current page index and item + const currentPageIndex = useMemo(() => { + if (!currentPageId) return -1; + return allPageItems.findIndex(page => page.id === currentPageId); + }, [currentPageId, allPageItems]); + + const currentPageItem = useMemo(() => { + if (currentPageIndex === -1) return null; + return allPageItems[currentPageIndex]?.item || null; + }, [currentPageIndex, allPageItems]); + + // Navigation state + const canGoNext = currentPageIndex < allPageItems.length - 1 && currentPageIndex !== -1; + const canGoPrevious = currentPageIndex > 0; + const totalPages = allPageItems.length; + + // Update URL parameters + const updateUrlParams = useCallback((pageId: string) => { + if (typeof window !== 'undefined') { + const url = new URL(window.location.href); + url.searchParams.set('page', pageId); + window.history.replaceState({}, '', url.toString()); + } + }, []); + + // Read initial page from URL parameters + useEffect(() => { + if (typeof window !== 'undefined' && allPageItems.length > 0) { + const url = new URL(window.location.href); + const pageParam = url.searchParams.get('page'); + + if (pageParam && allPageItems.some(page => page.id === pageParam)) { + setCurrentPageId(pageParam); + } else if (!currentPageId) { + // Set first page as default + const firstPageId = allPageItems[0]?.id; + if (firstPageId) { + setCurrentPageId(firstPageId); + updateUrlParams(firstPageId); + } + } + } + }, [allPageItems, currentPageId, updateUrlParams]); + + // Navigation functions + const goToNext = useCallback(() => { + if (canGoNext) { + const nextPageId = allPageItems[currentPageIndex + 1]?.id; + if (nextPageId) { + setCurrentPageId(nextPageId); + updateUrlParams(nextPageId); + if (isMobile) { + setMobileView('content'); + } + } + } + }, [canGoNext, currentPageIndex, allPageItems, updateUrlParams, isMobile, setMobileView]); + + const goToPrevious = useCallback(() => { + if (canGoPrevious) { + const prevPageId = allPageItems[currentPageIndex - 1]?.id; + if (prevPageId) { + setCurrentPageId(prevPageId); + updateUrlParams(prevPageId); + if (isMobile) { + setMobileView('content'); + } + } + } + }, [canGoPrevious, currentPageIndex, allPageItems, updateUrlParams, isMobile, setMobileView]); + + const goToPage = useCallback((id: string) => { + if (allPageItems.some(page => page.id === id)) { + setCurrentPageId(id); + updateUrlParams(id); + if (isMobile) { + setMobileView('content'); + } + } + }, [allPageItems, updateUrlParams, isMobile, setMobileView]); + + return { + currentPageId, + currentPageItem, + currentPageIndex, + totalPages, + canGoNext, + canGoPrevious, + goToNext, + goToPrevious, + goToPage, + allPageItems + }; +}; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/useResponsive.ts b/packages/oc-core/src/hooks/useResponsive.ts new file mode 100644 index 00000000..8595f176 --- /dev/null +++ b/packages/oc-core/src/hooks/useResponsive.ts @@ -0,0 +1,43 @@ +import { useState, useEffect } from 'react'; + +interface UseResponsiveReturn { + isMobile: boolean; + mobileView: 'sidebar' | 'content'; + setMobileView: (view: 'sidebar' | 'content') => void; + availableTabs: Array<'sidebar' | 'content'>; +} + +export const useResponsive = (hideSidebar: boolean = false): UseResponsiveReturn => { + const [isMobile, setIsMobile] = useState(false); + const [mobileView, setMobileView] = useState<'sidebar' | 'content'>('sidebar'); + + const availableTabs: Array<'sidebar' | 'content'> = []; + if (!hideSidebar) availableTabs.push('sidebar'); + availableTabs.push('content'); + + useEffect(() => { + const handleResize = () => { + setIsMobile(window.innerWidth < 768); + }; + + handleResize(); + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); + + useEffect(() => { + if (!availableTabs.includes(mobileView)) { + setMobileView(availableTabs.includes('content') ? 'content' : availableTabs[0]); + } + }, [hideSidebar, mobileView, availableTabs]); + + return { + isMobile, + mobileView, + setMobileView, + availableTabs + }; +}; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/useRunnerMode.ts b/packages/oc-core/src/hooks/useRunnerMode.ts new file mode 100644 index 00000000..3bb97cc5 --- /dev/null +++ b/packages/oc-core/src/hooks/useRunnerMode.ts @@ -0,0 +1,15 @@ +import { useState } from 'react'; + +export const useRunnerMode = () => { + const [isRunnerMode, setIsRunnerMode] = useState(false); + + const toggleRunnerMode = () => { + setIsRunnerMode(prev => !prev); + }; + + return { + isRunnerMode, + setIsRunnerMode, + toggleRunnerMode + }; +}; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/useSearchModal.ts b/packages/oc-core/src/hooks/useSearchModal.ts new file mode 100644 index 00000000..697b0f71 --- /dev/null +++ b/packages/oc-core/src/hooks/useSearchModal.ts @@ -0,0 +1,169 @@ +import { useState, useEffect, useRef } from 'react'; +import { OpenCollectionCollection, OpenCollectionItem, HttpRequest, CustomPage } from '../types'; +import { getItemId, generateSafeId } from '../utils/itemUtils'; + +interface SearchResult { + id: string; + name: string; + path?: string; + type: string; +} + +interface UseSearchModalReturn { + isSearchOpen: boolean; + setIsSearchOpen: (open: boolean) => void; + searchQuery: string; + setSearchQuery: (query: string) => void; + searchResults: SearchResult[]; + selectedResultIndex: number; + searchInputRef: React.RefObject; + handleSearchResultSelect: (result: SearchResult) => void; +} + +export const useSearchModal = ( + collectionData: OpenCollectionCollection | null, + validCustomPages: CustomPage[], + onSelectItem: (id: string, path?: string) => void +): UseSearchModalReturn => { + const [isSearchOpen, setIsSearchOpen] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); + const [searchResults, setSearchResults] = useState([]); + const [selectedResultIndex, setSelectedResultIndex] = useState(0); + const searchInputRef = useRef(null); + + const performSearch = (query: string) => { + if (!query.trim() || !collectionData) { + setSearchResults([]); + return; + } + + const results: SearchResult[] = []; + const addedIds = new Set(); // Track added items to avoid duplicates + + const searchInItems = (items: OpenCollectionItem[], currentPath: string = '') => { + items.forEach(item => { + const itemId = getItemId(item); + const itemName = itemId; + const itemPath = currentPath ? `${currentPath}/${itemName}` : itemName; + + const lowerQuery = query.toLowerCase(); + let matchFound = false; + + // Search in item name + if (itemName.toLowerCase().includes(lowerQuery)) { + matchFound = true; + } + + // Search in URL for HTTP requests + if (item.type === 'http' && (item as any).url && (item as any).url.toLowerCase().includes(lowerQuery)) { + matchFound = true; + } + + // Search in method for HTTP requests + if (item.type === 'http' && (item as any).method && (item as any).method.toLowerCase().includes(lowerQuery)) { + matchFound = true; + } + + // Search in documentation + if ((item as any).docs && (item as any).docs.toLowerCase().includes(lowerQuery)) { + matchFound = true; + } + + // Add to results if match found and not already added + const safeId = generateSafeId(itemId); + if (matchFound && !addedIds.has(safeId)) { + addedIds.add(safeId); + + // Create a descriptive name based on item type + let displayName = itemName; + if (item.type === 'http') { + const method = (item as any).method || 'GET'; + const url = (item as any).url || ''; + displayName = `${method} ${itemName}`; + if (url && !itemName.includes(url)) { + displayName += ` - ${url}`; + } + } else if (item.type === 'folder') { + const itemCount = (item as any).items?.length || 0; + displayName = `${itemName} (${itemCount} items)`; + } else if (item.type === 'script') { + displayName = `📜 ${itemName}`; + } + + results.push({ + id: safeId, + name: displayName, + path: itemPath, + type: item.type + }); + } + + // Recursively search in folder items + if (item.type === 'folder' && (item as any).items && (item as any).items.length > 0) { + searchInItems((item as any).items, itemPath); + } + }); + }; + + if (collectionData.items) { + searchInItems(collectionData.items); + } + + validCustomPages.forEach(page => { + if (page.name.toLowerCase().includes(query.toLowerCase()) && !addedIds.has(page.name)) { + addedIds.add(page.name); + results.push({ + id: page.name, + name: `📄 ${page.name}`, + type: 'page' + }); + } + }); + + setSearchResults(results); + }; + + const handleSearchResultSelect = (result: SearchResult) => { + setIsSearchOpen(false); + setSearchQuery(''); + onSelectItem(result.id, result.path); + }; + + useEffect(() => { + performSearch(searchQuery); + }, [searchQuery, collectionData, validCustomPages]); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key === 'k') { + e.preventDefault(); + setIsSearchOpen(true); + setTimeout(() => { + if (searchInputRef.current) { + searchInputRef.current.focus(); + } + }, 10); + } + + if (e.key === 'Escape' && isSearchOpen) { + setIsSearchOpen(false); + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [isSearchOpen]); + + return { + isSearchOpen, + setIsSearchOpen, + searchQuery, + setSearchQuery, + searchResults, + selectedResultIndex, + searchInputRef, + handleSearchResultSelect + }; +}; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/useSectionTracking.ts b/packages/oc-core/src/hooks/useSectionTracking.ts new file mode 100644 index 00000000..8b459990 --- /dev/null +++ b/packages/oc-core/src/hooks/useSectionTracking.ts @@ -0,0 +1,424 @@ +import { useState, useEffect, useRef, useCallback } from 'react'; +import { OpenCollectionCollection, OpenCollectionItem, HttpRequest } from '../types'; +import { getItemId, generateSafeId } from '../utils/itemUtils'; + +interface UseSectionTrackingReturn { + activeItemId: string | null; + activeItem: OpenCollectionItem | null; + sectionRefs: React.MutableRefObject>; + registerSectionRef: (id: string, ref: HTMLDivElement | null) => void; + handleSelectItem: (id: string, path?: string) => void; +} + +export const useSectionTracking = ( + collectionData: OpenCollectionCollection | null, + containerRef: React.RefObject, + isMobile: boolean, + setMobileView: (view: 'sidebar' | 'content') => void +): UseSectionTrackingReturn => { + const [activeItemId, setActiveItemId] = useState(null); + const [activeItem, setActiveItem] = useState(null); + const sectionRefs = useRef>({}); + + const findItemById = useCallback(( + items: OpenCollectionItem[], + id: string + ): OpenCollectionItem | null => { + for (const item of items) { + const itemId = getItemId(item); + if (itemId === id) { + return item; + } + } + return null; + }, []); + + const registerSectionRef = useCallback((id: string, ref: HTMLDivElement | null) => { + if (ref) { + sectionRefs.current[id] = ref; + } + }, []); + + const handleSelectItem = useCallback((id: string, path?: string) => { + console.log('handleSelectItem called with id:', id, 'path:', path); + + if (isMobile) { + setMobileView('content'); + } + + setActiveItemId(id); + + console.log('Available section refs:', Object.keys(sectionRefs.current)); + + if (path) { + const fullPathId = path.replace(/^\//, ''); + console.log('Trying with fullPathId:', fullPathId); + + if (sectionRefs.current[fullPathId]) { + console.log('Found section ref match for fullPathId:', fullPathId); + sectionRefs.current[fullPathId].scrollIntoView({ behavior: 'smooth' }); + return; + } + } + + if (sectionRefs.current[id]) { + console.log('Found exact section ref match:', id); + sectionRefs.current[id].scrollIntoView({ behavior: 'smooth' }); + return; + } + + if (id.includes('/')) { + console.log('Trying to find section for path:', id); + + const normalizedId = id.replace(/\/+/g, '/').replace(/^\/|\/$/g, ''); + const allSectionIds = Object.keys(sectionRefs.current); + + const variations = [ + normalizedId, + normalizedId.replace(/\//g, '//'), + normalizedId.split('/').pop() || '', + normalizedId.split('/').slice(-2).join('/') + ]; + + console.log('Trying path variations:', variations); + + for (const variant of variations) { + if (sectionRefs.current[variant]) { + console.log('Found section ref match with variant:', variant); + sectionRefs.current[variant].scrollIntoView({ behavior: 'smooth' }); + return; + } + } + + const pathParts = normalizedId.split('/'); + const lastPart = pathParts[pathParts.length - 1]; + + const matchingLastPart = allSectionIds.filter(sectionId => + sectionId.endsWith(`/${lastPart}`) || sectionId === lastPart + ); + + if (matchingLastPart.length === 1) { + console.log('Found single match by last part:', matchingLastPart[0]); + sectionRefs.current[matchingLastPart[0]].scrollIntoView({ behavior: 'smooth' }); + return; + } + + if (matchingLastPart.length > 1 || matchingLastPart.length === 0) { + console.log('Multiple or no matches by last part, trying path similarity scoring'); + + const scoredMatches = allSectionIds.map(sectionId => { + if (!sectionId.includes(lastPart)) { + return { id: sectionId, score: 0 }; + } + + const sectionParts = sectionId.split('/'); + let score = 0; + let consecutiveMatches = 0; + + for (let i = 1; i <= Math.min(pathParts.length, sectionParts.length); i++) { + if (pathParts[pathParts.length - i] === sectionParts[sectionParts.length - i]) { + score += 10; + consecutiveMatches++; + score += consecutiveMatches * 5; + } else { + consecutiveMatches = 0; + } + } + + if (sectionId.includes(lastPart)) { + score += 20; + } + + const lengthDiff = Math.abs(sectionId.length - normalizedId.length); + score -= lengthDiff * 0.5; + + return { id: sectionId, score }; + }) + .filter(match => match.score > 0) + .sort((a, b) => b.score - a.score); + + console.log('Scored matches:', scoredMatches); + + if (scoredMatches.length > 0) { + const bestMatch = scoredMatches[0].id; + console.log('Best match by scoring:', bestMatch, 'with score:', scoredMatches[0].score); + sectionRefs.current[bestMatch].scrollIntoView({ behavior: 'smooth' }); + return; + } + } + } + + const baseId = id.includes('/') ? id.split('/').pop() : id; + if (baseId && sectionRefs.current[baseId]) { + console.log('Falling back to base ID:', baseId); + sectionRefs.current[baseId].scrollIntoView({ behavior: 'smooth' }); + } else { + console.log('No matching section found for:', id); + } + }, [isMobile, setMobileView]); + + useEffect(() => { + if (collectionData && !activeItemId) { + const findFirstApiItem = (items: OpenCollectionItem[]): string | null => { + for (const item of items) { + if (item.type === 'http') { + const itemId = getItemId(item); + return generateSafeId(itemId); + } + } + return null; + }; + + const initialItemId = findFirstApiItem(collectionData.items || []); + + if (initialItemId) { + setActiveItemId(initialItemId); + } + } + }, [collectionData, activeItemId]); + + useEffect(() => { + if (!activeItemId || !collectionData || !collectionData.items) return; + + const item = findItemById(collectionData.items, activeItemId); + setActiveItem(item); + }, [activeItemId, collectionData, findItemById]); + + useEffect(() => { + if (!collectionData || !containerRef.current) return; + + console.log("Setting up robust section visibility tracking"); + + const observedSections = new Map(); + let observers: IntersectionObserver[] = []; + let isInitialized = false; + let initializationAttempts = 0; + let initTimer: ReturnType | null = null; + let updateTimer: ReturnType | null = null; + let mutationObserver: MutationObserver | null = null; + + const clearObservers = () => { + observers.forEach(observer => observer.disconnect()); + observers = []; + if (mutationObserver) { + mutationObserver.disconnect(); + mutationObserver = null; + } + }; + + const updateActiveSection = () => { + const visibleSections = Array.from(observedSections.entries()) + .filter(([id, entry]) => { + if (!entry.isIntersecting) return false; + + if (id.includes('/') && collectionData && 'items' in collectionData && collectionData.items) { + const item = findItemById(collectionData.items, id); + if (item && item.type === 'folder') { + return false; + } + } + + return true; + }) + .map(([id, entry]) => { + const rect = entry.boundingClientRect; + const elementCenter = rect.top + (rect.height / 2); + + const containerRect = containerRef.current?.getBoundingClientRect(); + const containerCenter = containerRect + ? containerRect.top + (containerRect.height / 2) + : window.innerHeight / 2; + + const distanceFromCenter = Math.abs(elementCenter - containerCenter); + + const visibleHeight = entry.intersectionRect.height; + const visiblePercent = visibleHeight / rect.height; + + const score = distanceFromCenter * 0.8 - (visiblePercent * 0.2); + + return { id, entry, score, distanceFromCenter, visiblePercent }; + }) + .sort((a, b) => a.score - b.score); + + if (visibleSections.length > 0) { + const bestSection = visibleSections[0]; + + if (bestSection.id !== activeItemId) { + console.log(`Changing active item to ${bestSection.id} (distance: ${bestSection.distanceFromCenter.toFixed(2)}px, visible: ${(bestSection.visiblePercent * 100).toFixed(1)}%)`); + console.log('All visible sections:', visibleSections.map(s => ({ id: s.id, score: s.score, distance: s.distanceFromCenter, visible: s.visiblePercent }))); + + setActiveItemId(bestSection.id); + } + } else { + console.log('No visible sections found'); + } + }; + + const setupObservers = () => { + clearObservers(); + + const sections = Object.entries(sectionRefs.current); + + if (sections.length === 0) { + console.log("No sections found yet, will retry"); + return false; + } + + console.log(`Setting up observers for ${sections.length} sections`); + + const centerObserver = new IntersectionObserver( + (entries) => { + entries.forEach(entry => { + const id = entry.target.id.startsWith('section-') + ? entry.target.id.replace(/^section-/, '') + : entry.target.id; + + if (id) { + observedSections.set(id, entry); + } + }); + + if (isInitialized) { + if (updateTimer) clearTimeout(updateTimer); + updateTimer = setTimeout(updateActiveSection, 50); + } + }, + { + root: containerRef.current, + rootMargin: '-40% 0px -40% 0px', + threshold: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] + } + ); + + const visibilityObserver = new IntersectionObserver( + (entries) => { + entries.forEach(entry => { + const id = entry.target.id.startsWith('section-') + ? entry.target.id.replace(/^section-/, '') + : entry.target.id; + + if (id) { + observedSections.set(id, entry); + } + }); + + if (isInitialized) { + if (updateTimer) clearTimeout(updateTimer); + updateTimer = setTimeout(updateActiveSection, 50); + } + }, + { + root: containerRef.current, + rootMargin: '0px', + threshold: [0, 0.1, 0.5, 1.0] + } + ); + + observers.push(centerObserver, visibilityObserver); + + sections.forEach(([id, element]) => { + if (!element || !element.isConnected) return; + + centerObserver.observe(element); + visibilityObserver.observe(element); + }); + + mutationObserver = new MutationObserver((mutations) => { + let needsReinit = false; + + mutations.forEach(mutation => { + if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { + mutation.addedNodes.forEach(node => { + if (node.nodeType === 1) { + const element = node as Element; + if (element.id && element.id.startsWith('section-')) { + needsReinit = true; + } else if (element.querySelector('[id^="section-"]')) { + needsReinit = true; + } + } + }); + } + }); + + if (needsReinit) { + console.log("DOM changed, reinitializing observers"); + setupObservers(); + } + }); + + if (containerRef.current) { + mutationObserver.observe(containerRef.current, { + childList: true, + subtree: true + }); + } + + return sections.length > 0; + }; + + const initializeTracking = () => { + initializationAttempts++; + + console.log(`Initializing section tracking (attempt ${initializationAttempts})`); + + const success = setupObservers(); + + if (success) { + console.log("Successfully set up section tracking"); + + setTimeout(() => { + isInitialized = true; + + updateActiveSection(); + + if (!activeItemId && observedSections.size > 0) { + const firstSection = Array.from(observedSections.keys())[0]; + setActiveItemId(firstSection); + } + }, 300); + + return true; + } else if (initializationAttempts < 10) { + const delay = Math.min(5000, Math.pow(1.5, initializationAttempts) * 100); + console.log(`Retrying in ${delay}ms`); + + initTimer = setTimeout(initializeTracking, delay); + return false; + } else { + console.error("Failed to initialize section tracking after multiple attempts"); + return false; + } + }; + + const handleScroll = () => { + if (!isInitialized) return; + + if (updateTimer) clearTimeout(updateTimer); + updateTimer = setTimeout(updateActiveSection, 50); + }; + + if (containerRef.current) { + containerRef.current.addEventListener('scroll', handleScroll); + } + + initializeTracking(); + + return () => { + clearObservers(); + if (initTimer) clearTimeout(initTimer); + if (updateTimer) clearTimeout(updateTimer); + if (containerRef.current) { + containerRef.current.removeEventListener('scroll', handleScroll); + } + }; + }, [collectionData, containerRef, activeItemId, findItemById]); + + return { + activeItemId, + activeItem, + sectionRefs, + registerSectionRef, + handleSelectItem + }; +}; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/useTheme.ts b/packages/oc-core/src/hooks/useTheme.ts new file mode 100644 index 00000000..52a3a7de --- /dev/null +++ b/packages/oc-core/src/hooks/useTheme.ts @@ -0,0 +1,13 @@ +import { useEffect } from 'react'; + +export const useTheme = (theme: 'light' | 'dark' | 'auto') => { + useEffect(() => { + const root = document.documentElement; + + root.classList.remove('dark', 'light'); + + if (theme === 'dark' || theme === 'light') { + root.classList.add(theme); + } + }, [theme]); +}; \ No newline at end of file diff --git a/packages/oc-core/src/index.ts b/packages/oc-core/src/index.ts new file mode 100644 index 00000000..7190e8a8 --- /dev/null +++ b/packages/oc-core/src/index.ts @@ -0,0 +1,31 @@ +import OpenCollectionPlayground from './core/OpenCollectionPlayground'; +import FileCollectionLoader from './core/FileCollectionLoader'; +import Sidebar from './ui/Sidebar'; + +import './styles/index.css'; + +export { + OpenCollectionPlayground, + FileCollectionLoader, + Sidebar, +}; + +export * from './types'; +export * from './hooks'; +export { + PageNavigation, + SinglePageRenderer, + CodeEditor, + RequestRunner, + RequestHeader as RequestHeaderComponent, + QueryBar, + RequestPane, + ResponsePane +} from './ui'; +export { requestRunner, RequestRunner as RequestRunnerClass } from './runner'; + +// Server-side exports +export { renderPlayground } from './server'; +export type { ServerRenderOptions } from './server'; +export { playgroundHandler } from './express'; +export type { PlaygroundHandlerOptions } from './express'; \ No newline at end of file diff --git a/packages/oc-core/src/layouts/ContentLayout.tsx b/packages/oc-core/src/layouts/ContentLayout.tsx new file mode 100644 index 00000000..646708de --- /dev/null +++ b/packages/oc-core/src/layouts/ContentLayout.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import MarkdownIt from 'markdown-it'; + +const ContentLayout = ({ + title, + subtitle, + description, + badge, + badgeColor, + leftContent, + rightContent, + theme +}: { + title: string; + subtitle?: React.ReactNode; + description?: string; + badge?: string; + badgeColor?: string; + leftContent: React.ReactNode; + rightContent: React.ReactNode; + theme: 'light' | 'dark' | 'auto'; +}) => { + return ( +
+
+

{title}

+ {badge && ( +
+ + {badge} + + {subtitle && {subtitle}} +
+ )} + {!badge && subtitle &&
{subtitle}
} + + {description && ( +
+
+
+ )} +
+ +
+
+ {leftContent} +
+
+ {rightContent} +
+
+
+ ); +}; + +export default ContentLayout; \ No newline at end of file diff --git a/packages/oc-core/src/layouts/DesktopLayout.tsx b/packages/oc-core/src/layouts/DesktopLayout.tsx new file mode 100644 index 00000000..7ef30164 --- /dev/null +++ b/packages/oc-core/src/layouts/DesktopLayout.tsx @@ -0,0 +1,245 @@ +import React from 'react'; +import { OpenCollectionCollection, CustomPage, OpenCollectionItem } from '../types'; +import SearchModal from '../ui/SearchModal'; +import Sidebar from '../ui/Sidebar'; +import SinglePageRenderer from '../ui/SinglePageRenderer'; + +interface DesktopLayoutProps { + collectionData: OpenCollectionCollection | null; + hideSidebar: boolean; + logo: React.ReactNode; + theme: 'light' | 'dark' | 'auto'; + currentPageId: string | null; + currentPageItem: OpenCollectionItem | CustomPage | null; + currentPageIndex: number; + totalPages: number; + canGoNext: boolean; + canGoPrevious: boolean; + goToNext: () => void; + goToPrevious: () => void; + goToPage: (id: string) => void; + allPageItems: Array<{id: string; item: OpenCollectionItem | CustomPage; type: 'item' | 'custom' | 'overview'}>; + onSelectItem: (id: string, path?: string) => void; + filteredCustomPages: CustomPage[]; + onlyShow?: string[]; + isSearchOpen: boolean; + setIsSearchOpen: (open: boolean) => void; + searchQuery: string; + setSearchQuery: (query: string) => void; + searchResults: Array<{id: string; name: string; path?: string; type: string}>; + searchInputRef: React.RefObject; + handleSearchResultSelect: (result: {id: string; path?: string; type: string}) => void; + containerRef: React.RefObject; + shouldShowOverview: boolean; + filteredCollectionItems: any[]; + md: any; + customPageContents: Record; + children?: React.ReactNode; + isRunnerMode?: boolean; + toggleRunnerMode?: () => void; + proxyUrl?: string; +} + +const DesktopLayout: React.FC = ({ + collectionData, + hideSidebar, + logo, + theme, + currentPageId, + currentPageItem, + currentPageIndex, + totalPages, + canGoNext, + canGoPrevious, + goToNext, + goToPrevious, + goToPage, + allPageItems, + onSelectItem, + filteredCustomPages, + onlyShow, + isSearchOpen, + setIsSearchOpen, + searchQuery, + setSearchQuery, + searchResults, + searchInputRef, + handleSearchResultSelect, + containerRef, + shouldShowOverview, + filteredCollectionItems, + md, + customPageContents, + children, + isRunnerMode, + toggleRunnerMode, + proxyUrl +}) => { + return ( +
+ + + + {!hideSidebar && ( +
+
+ {logo ? ( +
{logo}
+ ) : ( + + + + + + + + )} +

+ {collectionData?.name} +

+
+
+ )} + +
+ +
+ +
+ +
+ {/* {collectionData?.version && ( + + v{collectionData.version} + + )} */} + + + Open in Bruno + +
+
+ + + {!hideSidebar && ( +
+ +
+ )} + + +
+
+ p.id === currentPageId)?.type || 'item'} + theme={theme} + md={md} + collection={collectionData} + customPageContents={customPageContents} + currentPageIndex={currentPageIndex} + totalPages={totalPages} + canGoNext={canGoNext} + canGoPrevious={canGoPrevious} + goToNext={goToNext} + goToPrevious={goToPrevious} + isRunnerMode={isRunnerMode} + proxyUrl={proxyUrl} + /> +
+
+
+ ); +}; + +export default DesktopLayout; \ No newline at end of file diff --git a/packages/oc-core/src/main.tsx b/packages/oc-core/src/main.tsx new file mode 100644 index 00000000..77257fa1 --- /dev/null +++ b/packages/oc-core/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './styles/index.css' +import App from './App' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/packages/oc-core/src/runner/RequestExecutor.ts b/packages/oc-core/src/runner/RequestExecutor.ts new file mode 100644 index 00000000..cc8e140f --- /dev/null +++ b/packages/oc-core/src/runner/RequestExecutor.ts @@ -0,0 +1,219 @@ +import { HttpRequest } from '../types'; +import { RunRequestResponse } from './index'; + +export class RequestExecutor { + constructor(private proxyUrl?: string) {} + + async executeRequest(request: HttpRequest): Promise { + if (this.proxyUrl) { + return this.executeViaProxy(request); + } + return this.executeDirectly(request); + } + + private async executeViaProxy(request: HttpRequest): Promise { + try { + const proxyPayload = { + url: request.url, + method: request.method || 'GET', + headers: this.buildHeaders(request), + body: await this.buildBody(request), + timeout: 30000 + }; + + const response = await fetch(`${this.proxyUrl}/proxy`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(proxyPayload) + }); + + if (!response.ok) { + throw new Error(`Proxy error: ${response.statusText}`); + } + + const result = await response.json(); + + if (result.error) { + return { error: result.error }; + } + + return result; + } catch (error) { + return { + error: error instanceof Error ? error.message : 'Proxy request failed' + }; + } + } + + private async executeDirectly(request: HttpRequest): Promise { + const startTime = Date.now(); + + try { + const fetchOptions = await this.buildFetchOptions(request); + const response = await fetch(request.url || '', fetchOptions); + const endTime = Date.now(); + + const responseData = await this.parseResponse(response); + const responseHeaders = this.parseHeaders(response.headers); + + return { + status: response.status, + statusText: response.statusText, + headers: responseHeaders, + data: responseData.data, + size: responseData.size, + duration: endTime - startTime, + url: response.url + }; + } catch (error) { + return { + error: error instanceof Error ? error.message : 'Request failed' + }; + } + } + + private async buildFetchOptions(request: HttpRequest): Promise { + const options: RequestInit = { + method: request.method || 'GET', + headers: this.buildHeaders(request), + signal: AbortSignal.timeout(30000) + }; + + if (request.body && ['POST', 'PUT', 'PATCH'].includes(request.method || '')) { + options.body = await this.buildBody(request); + } + + return options; + } + + private buildHeaders(request: HttpRequest): HeadersInit { + const headers: Record = {}; + + if (request.headers) { + request.headers.forEach(header => { + if (!header.disabled && header.name && header.value) { + headers[header.name] = header.value; + } + }); + } + + // Auto-set Content-Type for JSON bodies if not already set + if (request.body && 'type' in request.body && request.body.type === 'json') { + const hasContentType = request.headers?.some(h => + !h.disabled && h.name.toLowerCase() === 'content-type' + ); + if (!hasContentType) { + headers['Content-Type'] = 'application/json'; + } + } + + if (request.auth) { + this.setAuthHeaders(headers, request.auth); + } + + return headers; + } + + private setAuthHeaders(headers: Record, auth: any) { + switch (auth.type) { + case 'basic': + if (auth.username && auth.password) { + const credentials = btoa(`${auth.username}:${auth.password}`); + headers['Authorization'] = `Basic ${credentials}`; + } + break; + case 'bearer': + if (auth.token) { + headers['Authorization'] = `Bearer ${auth.token}`; + } + break; + case 'apikey': + if (auth.key && auth.value) { + if (auth.placement === 'header') { + headers[auth.key] = auth.value; + } + } + break; + } + } + + private async buildBody(request: HttpRequest): Promise { + if (!request.body) return null; + + if ('type' in request.body) { + switch (request.body.type) { + case 'json': + return request.body.data; + case 'text': + case 'xml': + case 'sparql': + return request.body.data; + default: + return null; + } + } else if (Array.isArray(request.body)) { + if (request.headers?.some(h => h.name.toLowerCase() === 'content-type' && h.value === 'application/x-www-form-urlencoded')) { + return this.buildUrlEncodedBody(request.body); + } else { + return this.buildFormDataBody(request.body); + } + } + + return null; + } + + private buildUrlEncodedBody(data: any[]): string { + const params = new URLSearchParams(); + data.forEach(item => { + if (item.enabled !== false && item.name) { + params.append(item.name, item.value || ''); + } + }); + return params.toString(); + } + + private buildFormDataBody(data: any[]): FormData { + const formData = new FormData(); + data.forEach(item => { + if (item.enabled !== false && item.name) { + if (item.type === 'file' && item.value instanceof File) { + formData.append(item.name, item.value); + } else { + formData.append(item.name, item.value || ''); + } + } + }); + return formData; + } + + private async parseResponse(response: Response) { + const contentType = response.headers.get('content-type') || ''; + let data: any; + let size = 0; + + if (contentType.includes('application/json')) { + const text = await response.text(); + size = new Blob([text]).size; + try { + data = JSON.parse(text); + } catch { + data = text; + } + } else { + data = await response.text(); + size = new Blob([data]).size; + } + + return { data, size }; + } + + private parseHeaders(headers: Headers): Record { + const result: Record = {}; + headers.forEach((value, key) => { + result[key] = value; + }); + return result; + } +} \ No newline at end of file diff --git a/packages/oc-core/src/runner/ScriptRunner.ts b/packages/oc-core/src/runner/ScriptRunner.ts new file mode 100644 index 00000000..5ffe6abf --- /dev/null +++ b/packages/oc-core/src/runner/ScriptRunner.ts @@ -0,0 +1,332 @@ +import { HttpRequest, OpenCollectionCollection } from '../types'; +import { RunRequestResponse } from './index'; + +interface ScriptContext { + req: { + url?: string; + method?: string; + headers: Record; + body: any; + timeout: number; + name: string; + getUrl: () => string; + setUrl: (url: string) => void; + getMethod: () => string; + setMethod: (method: string) => void; + getName: () => string; + getAuthMode: () => string; + getHeaders: () => Record; + setHeaders: (headers: Record) => void; + getHeader: (name: string) => string; + setHeader: (name: string, value: string) => void; + getBody: () => any; + setBody: (data: any) => void; + getTimeout: () => number; + setTimeout: (timeout: number) => void; + setMaxRedirects: (maxRedirects: number) => void; + disableParsingResponseJson: () => void; + getExecutionMode: () => string; + }; + res?: any; + bru: { + setVar: (key: string, value: string) => void; + getVar: (key: string) => string | undefined; + setEnvVar: (key: string, value: string) => void; + getEnvVar: (key: string) => string | undefined; + }; + test: (name: string, fn: () => void) => void; + expect: (actual: any) => any; + console: { + log: (...args: any[]) => void; + }; + __variables: Record; + __testResults: Array<{ name: string; passed: boolean; error?: string }>; +} + +export class ScriptRunner { + private createSandboxedContext( + request: HttpRequest, + response: RunRequestResponse | null, + variables: Record, + collection: OpenCollectionCollection + ): ScriptContext { + const requestObj = { + url: request.url, + method: request.method, + headers: this.headersToObject(request.headers), + body: this.getRequestBody(request), + timeout: 30000, + name: request.name || '', + + getUrl: () => request.url || '', + setUrl: (url: string) => { request.url = url; }, + getMethod: () => request.method || 'GET', + setMethod: (method: string) => { request.method = method; }, + getName: () => request.name || '', + getAuthMode: () => request.auth?.type || 'none', + getHeaders: () => this.headersToObject(request.headers), + setHeaders: (headers: Record) => { + request.headers = Object.entries(headers).map(([name, value]) => ({ + name, + value, + disabled: false + })); + }, + getHeader: (name: string) => { + const header = request.headers?.find(h => h.name.toLowerCase() === name.toLowerCase()); + return header?.value || ''; + }, + setHeader: (name: string, value: string) => { + if (!request.headers) request.headers = []; + const existingIndex = request.headers.findIndex(h => h.name.toLowerCase() === name.toLowerCase()); + if (existingIndex >= 0) { + request.headers[existingIndex].value = value; + } else { + request.headers.push({ name, value, disabled: false }); + } + }, + getBody: () => this.getRequestBody(request), + setBody: (data: any) => { + if (typeof data === 'string') { + try { + JSON.parse(data); + request.body = { type: 'json', data }; + } catch { + request.body = { type: 'text', data }; + } + } else if (typeof data === 'object') { + request.body = { type: 'json', data: JSON.stringify(data, null, 2) }; + } + }, + getTimeout: () => 30000, + setTimeout: (timeout: number) => {}, + setMaxRedirects: (maxRedirects: number) => {}, + disableParsingResponseJson: () => {}, + getExecutionMode: () => 'normal' + }; + + const context: ScriptContext = { + req: requestObj, + bru: { + setVar: (key: string, value: string) => { + context.__variables[key] = value; + variables[key] = value; + }, + getVar: (key: string) => { + return context.__variables[key] || variables[key]; + }, + setEnvVar: (key: string, value: string) => { + context.__variables[key] = value; + variables[key] = value; + }, + getEnvVar: (key: string) => { + return context.__variables[key] || variables[key]; + } + }, + test: (name: string, fn: () => void) => { + try { + fn(); + context.__testResults.push({ name, passed: true }); + console.log(`✓ ${name}`); + } catch (error) { + context.__testResults.push({ + name, + passed: false, + error: error instanceof Error ? error.message : String(error) + }); + console.log(`✗ ${name}: ${error instanceof Error ? error.message : error}`); + } + }, + expect: (actual: any) => ({ + to: { + equal: (expected: any) => { + if (actual !== expected) { + throw new Error(`Expected ${actual} to equal ${expected}`); + } + }, + be: { + greaterThan: (expected: any) => { + if (actual <= expected) { + throw new Error(`Expected ${actual} to be greater than ${expected}`); + } + }, + lessThan: (expected: any) => { + if (actual >= expected) { + throw new Error(`Expected ${actual} to be less than ${expected}`); + } + } + }, + contain: (expected: any) => { + if (typeof actual === 'string' && actual.indexOf(expected) === -1) { + throw new Error(`Expected ${actual} to contain ${expected}`); + } + } + } + }), + console: { + log: (...args: any[]) => console.log('[Script]', ...args) + }, + __variables: { ...variables }, + __testResults: [] + }; + + if (response) { + const responseObj = { + status: response.status, + statusText: response.statusText, + headers: response.headers || {}, + body: response.data, + responseTime: response.duration || 0, + url: response.url || '', + + getStatus: () => response.status || 0, + getStatusText: () => response.statusText || '', + getHeader: (name: string) => { + if (!response.headers) return ''; + const key = Object.keys(response.headers).find(k => k.toLowerCase() === name.toLowerCase()); + return key ? String(response.headers[key]) : ''; + }, + getHeaders: () => response.headers || {}, + getBody: () => response.data, + setBody: (data: any) => { response.data = data; }, + getResponseTime: () => response.duration || 0, + getUrl: () => response.url || '', + getSize: () => response.size || 0 + }; + + // Add function call capability like Bruno's res() + const resFunction = (exprStr: string) => { + try { + if (exprStr.startsWith('.')) { + const path = exprStr.substring(1); + return this.getNestedProperty(response.data, path); + } + return response.data; + } catch { + return null; + } + }; + + Object.assign(resFunction, responseObj); + context.res = resFunction; + } + + return context; + } + + private headersToObject(headers?: any[]): Record { + if (!headers) return {}; + return headers.reduce((obj, header) => { + if (!header.disabled && header.name && header.value) { + obj[header.name] = header.value; + } + return obj; + }, {}); + } + + private getRequestBody(request: HttpRequest): any { + if (!request.body) return null; + + if ('type' in request.body && typeof request.body.data === 'string') { + try { + return request.body.type === 'json' ? JSON.parse(request.body.data) : request.body.data; + } catch { + return request.body.data; + } + } + + return request.body; + } + + private executeScript(script: string, context: ScriptContext): void { + const sanitizedScript = this.sanitizeScript(script); + + try { + const func = new Function( + 'req', 'res', 'bru', 'test', 'expect', 'console', '__variables', + sanitizedScript + ); + + func( + context.req, + context.res, + context.bru, + context.test, + context.expect, + context.console, + context.__variables + ); + } catch (error) { + console.error('Script execution error:', error); + throw error; + } + } + + private sanitizeScript(script: string): string { + const dangerousPatterns = [ + /import\s+/g, + /require\s*\(/g, + /eval\s*\(/g, + /Function\s*\(/g, + /setTimeout\s*\(/g, + /setInterval\s*\(/g, + /fetch\s*\(/g, + /XMLHttpRequest/g, + /document\./g, + /window\./g, + /global\./g, + /process\./g, + /__proto__/g, + /constructor/g + ]; + + let sanitized = script; + dangerousPatterns.forEach(pattern => { + sanitized = sanitized.replace(pattern, '/* blocked */'); + }); + + return sanitized; + } + + async runPreRequestScript( + script: string, + request: HttpRequest, + variables: Record, + collection: OpenCollectionCollection + ): Promise { + const context = this.createSandboxedContext(request, null, variables, collection); + this.executeScript(script, context); + Object.assign(variables, context.__variables); + } + + async runPostResponseScript( + script: string, + request: HttpRequest, + response: RunRequestResponse, + variables: Record, + collection: OpenCollectionCollection + ): Promise { + const context = this.createSandboxedContext(request, response, variables, collection); + this.executeScript(script, context); + Object.assign(variables, context.__variables); + } + + async runTests( + script: string, + request: HttpRequest, + response: RunRequestResponse, + variables: Record, + collection: OpenCollectionCollection + ): Promise { + const context = this.createSandboxedContext(request, response, variables, collection); + this.executeScript(script, context); + Object.assign(variables, context.__variables); + return context.__testResults; + } + + private getNestedProperty(obj: any, path: string): any { + return path.split('.').reduce((current, key) => { + return current && typeof current === 'object' ? current[key] : undefined; + }, obj); + } +} \ No newline at end of file diff --git a/packages/oc-core/src/runner/VariableInterpolator.ts b/packages/oc-core/src/runner/VariableInterpolator.ts new file mode 100644 index 00000000..766de843 --- /dev/null +++ b/packages/oc-core/src/runner/VariableInterpolator.ts @@ -0,0 +1,103 @@ +import { HttpRequest } from '../types'; + +export class VariableInterpolator { + interpolateRequest(request: HttpRequest, variables: Record): HttpRequest { + const interpolated = JSON.parse(JSON.stringify(request)); + + if (interpolated.url) { + interpolated.url = this.interpolateString(interpolated.url, variables); + } + + if (interpolated.headers) { + interpolated.headers = interpolated.headers.map((header: any) => ({ + ...header, + name: this.interpolateString(header.name, variables), + value: this.interpolateString(header.value, variables) + })); + } + + if (interpolated.params) { + interpolated.params = interpolated.params.map((param: any) => ({ + ...param, + name: this.interpolateString(param.name, variables), + value: this.interpolateString(param.value, variables) + })); + + interpolated.url = this.interpolateUrlParams(interpolated.url, interpolated.params, variables); + } + + if (interpolated.body) { + interpolated.body = this.interpolateBody(interpolated.body, variables); + } + + if (interpolated.auth) { + interpolated.auth = this.interpolateAuth(interpolated.auth, variables); + } + + return interpolated; + } + + private interpolateString(str: string, variables: Record): string { + if (!str) return str; + + return str.replace(/\{\{([^}]+)\}\}/g, (match, varName) => { + const trimmedVarName = varName.trim(); + return variables[trimmedVarName] !== undefined ? String(variables[trimmedVarName]) : match; + }); + } + + private interpolateUrlParams(url: string, params: any[], variables: Record): string { + if (!params || !url) return url; + + let interpolatedUrl = url; + const urlObj = new URL(interpolatedUrl.startsWith('http') ? interpolatedUrl : `http://${interpolatedUrl}`); + + params.forEach(param => { + if (param.enabled !== false && param.name) { + const paramName = this.interpolateString(param.name, variables); + const paramValue = this.interpolateString(param.value || '', variables); + + if (param.type === 'path') { + interpolatedUrl = interpolatedUrl.replace(`:${paramName}`, paramValue); + } else if (param.type === 'query') { + urlObj.searchParams.set(paramName, paramValue); + } + } + }); + + return urlObj.toString(); + } + + private interpolateBody(body: any, variables: Record): any { + if (!body) return body; + + if ('type' in body && typeof body.data === 'string') { + return { + ...body, + data: this.interpolateString(body.data, variables) + }; + } else if (Array.isArray(body)) { + return body.map(item => ({ + ...item, + name: this.interpolateString(item.name, variables), + value: this.interpolateString(item.value || '', variables) + })); + } + + return body; + } + + private interpolateAuth(auth: any, variables: Record): any { + if (!auth) return auth; + + const interpolated = { ...auth }; + + Object.keys(interpolated).forEach(key => { + if (typeof interpolated[key] === 'string') { + interpolated[key] = this.interpolateString(interpolated[key], variables); + } + }); + + return interpolated; + } +} \ No newline at end of file diff --git a/packages/oc-core/src/runner/index.ts b/packages/oc-core/src/runner/index.ts new file mode 100644 index 00000000..17e9c460 --- /dev/null +++ b/packages/oc-core/src/runner/index.ts @@ -0,0 +1,109 @@ +import { HttpRequest, OpenCollectionCollection, Environment } from '../types'; +import { RequestExecutor } from './RequestExecutor'; +import { ScriptRunner } from './ScriptRunner'; +import { VariableInterpolator } from './VariableInterpolator'; + +export interface RunRequestOptions { + item: HttpRequest; + collection: OpenCollectionCollection; + environment?: Environment; + runtimeVariables?: Record; +} + +export interface RunRequestResponse { + status?: number; + statusText?: string; + headers?: Record; + data?: any; + size?: number; + duration?: number; + url?: string; + error?: string; + isCancel?: boolean; +} + +export class RequestRunner { + private executor: RequestExecutor; + private scriptRunner: ScriptRunner; + private interpolator: VariableInterpolator; + + constructor(proxyUrl?: string) { + this.executor = new RequestExecutor(proxyUrl); + this.scriptRunner = new ScriptRunner(); + this.interpolator = new VariableInterpolator(); + } + + async runRequest(options: RunRequestOptions): Promise { + const { item, collection, environment, runtimeVariables = {} } = options; + + try { + const envVars = this.getEnvironmentVariables(environment); + const allVars = { ...envVars, ...runtimeVariables }; + + const processedRequest = await this.preprocessRequest(item, collection, allVars); + + if (processedRequest.scripts?.preRequest) { + await this.scriptRunner.runPreRequestScript( + processedRequest.scripts.preRequest, + processedRequest, + allVars, + collection + ); + } + + const interpolatedRequest = this.interpolator.interpolateRequest(processedRequest, allVars); + const response = await this.executor.executeRequest(interpolatedRequest); + + if (processedRequest.scripts?.postResponse) { + await this.scriptRunner.runPostResponseScript( + processedRequest.scripts.postResponse, + interpolatedRequest, + response, + allVars, + collection + ); + } + + if (processedRequest.scripts?.tests) { + await this.scriptRunner.runTests( + processedRequest.scripts.tests, + interpolatedRequest, + response, + allVars, + collection + ); + } + + return response; + } catch (error) { + return { + error: error instanceof Error ? error.message : 'Unknown error occurred' + }; + } + } + + private getEnvironmentVariables(environment?: Environment): Record { + if (!environment?.variables) return {}; + + return environment.variables.reduce((vars, variable) => { + if (variable.name && !variable.disabled) { + vars[variable.name] = variable.value || variable.default || ''; + } + return vars; + }, {} as Record); + } + + private async preprocessRequest( + item: HttpRequest, + collection: OpenCollectionCollection, + variables: Record + ): Promise { + const processed = { ...item }; + + return processed; + } +} + +export const createRequestRunner = (proxyUrl?: string) => new RequestRunner(proxyUrl); + +export const requestRunner = new RequestRunner(); \ No newline at end of file diff --git a/packages/oc-core/src/server.ts b/packages/oc-core/src/server.ts new file mode 100644 index 00000000..90236949 --- /dev/null +++ b/packages/oc-core/src/server.ts @@ -0,0 +1,116 @@ + +export interface ServerRenderOptions { + opencollection: any; + theme?: 'light' | 'dark' | 'auto'; + logo?: string; + customPages?: Array<{ + name: string; + content?: string; + contentPath?: string; + }>; + hideSidebar?: boolean; + hideHeader?: boolean; + onlyShow?: string[]; + title?: string; + cdnUrl?: string; + /** Base path for serving static assets locally (e.g., '/static/playground') */ + assetsPath?: string; + /** Whether to use local assets instead of CDN */ + useLocalAssets?: boolean; +} + +export function renderPlayground(options: ServerRenderOptions): string { + const { + opencollection, + theme = 'light', + logo, + customPages, + hideSidebar = false, + hideHeader = false, + onlyShow, + title = 'API Playground', + cdnUrl = 'https://cdn.jsdelivr.net/npm/@opencollection/playground@latest/dist-standalone', + assetsPath = '/playground-assets', + useLocalAssets = false + } = options; + + const processedCollection = opencollection; + + const config = { + opencollection: processedCollection, + theme, + logo, + customPages, + hideSidebar, + hideHeader, + onlyShow + }; + + const configJson = JSON.stringify(config).replace(/ + + + + + ${title} + + + + +
+
Loading API Playground...
+
+ + + + + +`; +} + +// For CommonJS compatibility +export default renderPlayground; \ No newline at end of file diff --git a/packages/oc-core/src/standalone.ts b/packages/oc-core/src/standalone.ts new file mode 100644 index 00000000..16080960 --- /dev/null +++ b/packages/oc-core/src/standalone.ts @@ -0,0 +1,103 @@ +import React from 'react'; +import { createRoot, Root } from 'react-dom/client'; +import './styles/index.css'; +import OpenCollectionPlayground from './core/OpenCollectionPlayground'; +import { OpenCollectionCollection } from './types'; + +export interface OpenCollectionPlaygroundOptions { + target: HTMLElement; + opencollection: any; + theme?: 'light' | 'dark' | 'auto'; + logo?: string; + customPages?: Array<{ + name: string; + content?: string; + contentPath?: string; + }>; + hideSidebar?: boolean; + hideHeader?: boolean; + onlyShow?: string[]; +} + +export class OpenCollectionPlaygroundRenderer { + private root: Root | null = null; + private options: OpenCollectionPlaygroundOptions; + + constructor(options: OpenCollectionPlaygroundOptions) { + this.options = options; + this.init(); + } + + private init() { + if (!this.options.target) { + throw new Error('Target element is required'); + } + + this.root = createRoot(this.options.target); + this.render(); + } + + private convertCollection(opencollection: any): OpenCollectionCollection { + if (typeof opencollection === 'string') { + try { + return JSON.parse(opencollection) as OpenCollectionCollection; + } catch (error) { + console.error('Failed to parse collection JSON:', error); + throw new Error('Invalid JSON format for OpenCollection'); + } + } + + return opencollection as OpenCollectionCollection; + } + + private createLogoElement(): React.ReactNode { + if (!this.options.logo) return undefined; + + return React.createElement('img', { + src: this.options.logo, + alt: 'Logo', + style: { height: '32px', width: 'auto' } + }); + } + + private render() { + if (!this.root) return; + + const collection = this.convertCollection(this.options.opencollection); + + const playgroundElement = React.createElement(OpenCollectionPlayground, { + collection, + theme: this.options.theme || 'light', + logo: this.createLogoElement(), + customPages: this.options.customPages, + hideSidebar: this.options.hideSidebar, + hideHeader: this.options.hideHeader, + onlyShow: this.options.onlyShow + }); + + this.root.render(playgroundElement); + } + + public updateCollection(opencollection: any) { + this.options.opencollection = opencollection; + this.render(); + } + + public updateTheme(theme: 'light' | 'dark' | 'auto') { + this.options.theme = theme; + this.render(); + } + + public destroy() { + if (this.root) { + this.root.unmount(); + this.root = null; + } + } +} + +export default OpenCollectionPlaygroundRenderer; + +if (typeof window !== 'undefined') { + (window as any).OpenCollectionPlayground = OpenCollectionPlaygroundRenderer; +} \ No newline at end of file diff --git a/packages/oc-core/src/styles/App.css b/packages/oc-core/src/styles/App.css new file mode 100644 index 00000000..2dc33638 --- /dev/null +++ b/packages/oc-core/src/styles/App.css @@ -0,0 +1,36 @@ + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/packages/oc-core/src/styles/index.css b/packages/oc-core/src/styles/index.css new file mode 100644 index 00000000..f4204913 --- /dev/null +++ b/packages/oc-core/src/styles/index.css @@ -0,0 +1,1324 @@ +@import "tailwindcss"; + +:root { + /* Core theme colors */ + --primary-color: #546de5; + --secondary-color: #0ea5e9; + + /* Background colors */ + --background-light: #fff; + --background-dark: #1e1e1e; + + /* Text colors */ + --text-light: rgb(52, 52, 52); + --text-dark: #d4d4d4; + + /* Border colors */ + --border-light: #efefef; + --border-dark: #333333; + + /* Code colors */ + --code-bg-light: #f3f3f3; + --code-bg-dark: #2d2d2d; + --code-text-light: #515151; + --code-text-dark: rgb(204, 204, 204); + + /* Sidebar colors */ + --sidebar-bg-light: #F3F3F3; + --sidebar-bg-dark: #252526; + --sidebar-color-light: rgb(52, 52, 52); + --sidebar-color-dark: #ccc; + + /* Table colors */ + --table-header-bg-light: rgb(249, 250, 251); + --table-header-bg-dark: #333333; + --table-row-odd-bg-light: #ffffff; + --table-row-odd-bg-dark: #2A2D2F; + --table-row-even-bg-light: #ffffff; + --table-row-even-bg-dark: #1e1e1e; + + /* Status colors */ + --success-color: #22c55e; + --warning-color: #eab308; + --info-color: #3b82f6; + --error-color: #ef4444; + + /* Method colors */ + --method-get-bg: #61affe; + --method-post-bg: #49cc90; + --method-put-bg: #fca130; + --method-delete-bg: #f93e3e; + --method-patch-bg: #50e3c2; + --method-options-bg: #a47fdc; + + /* Input and badge colors */ + --input-bg-light: #f9fafb; + --input-bg-dark: #2d2d2d; + --badge-bg-light: #f3f4f6; + --badge-bg-dark: #374151; + --badge-text-light: #4b5563; + --badge-text-dark: #d1d5db; + + /* Apple colors for method badges */ + --apple-blue-100: #e6f1ff; + --apple-blue-900: #0050d4; + --apple-green-100: #e6f7ed; + --apple-green-900: #056e3c; + --apple-orange-100: #fef4e6; + --apple-orange-900: #804c00; + --apple-red-100: #fee6e6; + --apple-red-900: #b01212; + --apple-purple-100: #f3e6ff; + --apple-purple-900: #6b0099; + --apple-gray-100: #f0f0f0; + --apple-gray-900: #333333; + + /* Prose colors */ + --prose-links: #0071e3; + --prose-links-hover: #0058b0; + --prose-blockquote-border: rgba(0, 0, 0, 0.1); + --prose-hr-color: rgba(0, 0, 0, 0.1); + + /* Layout dimensions */ + --sidebar-width: 280px; + --header-height: 60px; + + /* Z-index layers */ + --z-header: 10; + --z-sidebar: 5; + --z-overlay: 50; + + /* Shadows */ + --shadow-sm: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.08); + --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06); + + /* Fonts */ + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif; + + /* Current theme variables (default to light) */ + --background-color: var(--background-light); + --text-primary: var(--text-light); + --text-secondary: #6e6e6e; + --border-color: var(--border-light); + --code-bg: var(--code-bg-light); + --code-text: var(--code-text-light); + --prose-code-bg: var(--code-bg); + --prose-code-text: var(--code-text); + --table-header-bg: var(--table-header-bg-light); + --table-row-odd-bg: var(--table-row-odd-bg-light); + --table-row-even-bg: var(--table-row-even-bg-light); + --input-bg: var(--input-bg-light); + --badge-bg: var(--badge-bg-light); + --badge-text: var(--badge-text-light); +} + +/* Dark theme variables */ +.dark { + --background-color: var(--background-dark); + --text-primary: var(--text-dark); + --text-secondary: #a0a0a0; + --border-color: var(--border-dark); + --code-bg: var(--code-bg-dark); + --code-text: var(--code-text-dark); + --prose-code-bg: var(--code-bg); + --prose-code-text: var(--code-text); + --table-header-bg: var(--table-header-bg-dark); + --table-row-odd-bg: var(--table-row-odd-bg-dark); + --table-row-even-bg: var(--table-row-even-bg-dark); + --input-bg: var(--input-bg-dark); + --badge-bg: var(--badge-bg-dark); + --badge-text: var(--badge-text-dark); +} + +/* Auto theme detection */ +@media (prefers-color-scheme: dark) { + .theme-auto { + --background-color: var(--background-dark); + --text-primary: var(--text-dark); + --text-secondary: #a0a0a0; + --border-color: var(--border-dark); + --code-bg: var(--code-bg-dark); + --code-text: var(--code-text-dark); + --prose-code-bg: var(--code-bg); + --prose-code-text: var(--code-text); + --table-header-bg: var(--table-header-bg-dark); + --table-row-odd-bg: var(--table-row-odd-bg-dark); + --table-row-even-bg: var(--table-row-even-bg-dark); + } +} + +@media (prefers-color-scheme: light) { + .theme-auto { + --background-color: var(--background-light); + --text-primary: var(--text-light); + --text-secondary: #6e6e6e; + --border-color: var(--border-light); + --code-bg: var(--code-bg-light); + --code-text: var(--code-text-light); + --prose-code-bg: var(--code-bg); + --prose-code-text: var(--code-text); + --table-header-bg: var(--table-header-bg-light); + --table-row-odd-bg: var(--table-row-odd-bg-light); + --table-row-even-bg: var(--table-row-even-bg-light); + } +} + +/* Main playground container */ +.oc-playground { + width: 100%; + height: 100vh !important; + overflow: hidden; + font-family: -apple-system, BlinkMacSystemFont, "San Francisco", "Segoe UI", Roboto, "Helvetica Neue", sans-serif; + color: var(--text-primary); + background-color: var(--background-color); + position: relative; + display: flex; + flex-direction: column; + box-sizing: border-box; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-size: 14px; + line-height: 1.5; +} + +/* Theme-specific overrides */ +.oc-playground.light { + --background-color: var(--background-light); + --text-primary: var(--text-light); + --text-secondary: #6e6e6e; + --border-color: var(--border-light); + --code-bg: var(--code-bg-light); + --code-text: var(--code-text-light); + --prose-code-bg: var(--code-bg); + --prose-code-text: var(--code-text); + --table-header-bg: var(--table-header-bg-light); + --table-row-odd-bg: var(--table-row-odd-bg-light); + --table-row-even-bg: var(--table-row-even-bg-light); + --input-bg: var(--input-bg-light); + --badge-bg: var(--badge-bg-light); + --badge-text: var(--badge-text-light); +} + +.oc-playground.dark { + --background-color: var(--background-dark); + --text-primary: var(--text-dark); + --text-secondary: #a0a0a0; + --border-color: var(--border-dark); + --code-bg: var(--code-bg-dark); + --code-text: var(--code-text-dark); + --prose-code-bg: var(--code-bg); + --prose-code-text: var(--code-text); + --table-header-bg: var(--table-header-bg-dark); + --table-row-odd-bg: var(--table-row-odd-bg-dark); + --table-row-even-bg: var(--table-row-even-bg-dark); + --input-bg: var(--input-bg-dark); + --badge-bg: var(--badge-bg-dark); + --badge-text: var(--badge-text-dark); +} + +@media (prefers-color-scheme: dark) { + .oc-playground.auto { + --background-color: var(--background-dark); + --text-primary: var(--text-dark); + --text-secondary: #a0a0a0; + --border-color: var(--border-dark); + --code-bg: var(--code-bg-dark); + --code-text: var(--code-text-dark); + --prose-code-bg: var(--code-bg); + --prose-code-text: var(--code-text); + --table-header-bg: var(--table-header-bg-dark); + --table-row-odd-bg: var(--table-row-odd-bg-dark); + --table-row-even-bg: var(--table-row-even-bg-dark); + } +} + +@media (prefers-color-scheme: light) { + .oc-playground.auto { + --background-color: var(--background-light); + --text-primary: var(--text-light); + --text-secondary: #6e6e6e; + --border-color: var(--border-light); + --code-bg: var(--code-bg-light); + --code-text: var(--code-text-light); + --prose-code-bg: var(--code-bg); + --prose-code-text: var(--code-text); + --table-header-bg: var(--table-header-bg-light); + } +} + +/* Header styling */ +.glass-header { + height: var(--header-height); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + background-color: rgba(255, 255, 255, 0.8); + border-bottom: 1px solid var(--border-color); +} + +.dark .glass-header { + background-color: rgba(30, 30, 30, 0.8); +} + +/* Desktop Layout */ +@media (min-width: 1024px) { + .oc-playground { + display: flex; + flex-direction: column; + height: 100vh !important; + } + + .playground-header { + height: var(--header-height); + z-index: var(--z-header); + } + + .playground-sidebar { + width: var(--sidebar-width); + min-width: var(--sidebar-width); + max-width: var(--sidebar-width); + height: calc(100vh - var(--header-height)) !important; + overflow-y: auto; + background-color: var(--sidebar-bg-light); + color: var(--sidebar-color-light); + } + + .dark .playground-sidebar { + background-color: var(--sidebar-bg-dark); + color: var(--sidebar-color-dark); + border-right: 1px solid var(--border-color); + } + + .playground-content { + flex: 1; + overflow-y: auto; + height: calc(100vh - var(--header-height)) !important; + } + + .playground-console { + overflow: hidden; + height: calc(100vh - var(--header-height)) !important; + backdrop-filter: saturate(180%) blur(20px); + -webkit-backdrop-filter: saturate(180%) blur(20px); + background-color: var(--background-color); + transition: width 0.3s ease; + } +} + +/* Mobile Layout */ +@media (max-width: 1023px) { + .oc-playground { + flex-direction: column; + } + + .mobile-tabs { + display: flex; + padding: 0.5rem; + background-color: var(--background-color); + border-top: 1px solid var(--border-color); + } + + .mobile-tab { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.875rem; + padding: 0.625rem; + border-radius: 0.5rem; + transition: all 0.2s ease; + } + + .mobile-tab.active { + background-color: var(--primary-color); + color: white; + } + + .mobile-tab:not(.active) { + color: var(--text-secondary); + } + + .mobile-panel { + flex: 1; + overflow: auto; + } +} + +/* Method badge styling */ +.method-badge { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + font-weight: 600; + border-radius: 0.375rem; + text-transform: uppercase; + letter-spacing: 0.05em; + font-family: var(--font-mono); +} + +.method-badge.get { + background-color: var(--method-get-bg); + color: white; +} + +.method-badge.post { + background-color: var(--method-post-bg); + color: white; +} + +.method-badge.put { + background-color: var(--method-put-bg); + color: white; +} + +.method-badge.delete { + background-color: var(--method-delete-bg); + color: white; +} + +.method-badge.patch { + background-color: var(--method-patch-bg); + color: white; +} + +.method-badge.options, +.method-badge.head { + background-color: var(--method-options-bg); + color: white; +} + +/* Dark mode method badges */ +.dark .method-badge.get { + background-color: var(--apple-blue-900); + color: var(--apple-blue-100); +} + +.dark .method-badge.post { + background-color: var(--apple-green-900); + color: var(--apple-green-100); +} + +.dark .method-badge.put { + background-color: var(--apple-orange-900); + color: var(--apple-orange-100); +} + +.dark .method-badge.delete { + background-color: var(--apple-red-900); + color: var(--apple-red-100); +} + +.dark .method-badge.patch { + background-color: var(--apple-purple-900); + color: var(--apple-purple-100); +} + +.dark .method-badge.options, +.dark .method-badge.head { + background-color: var(--apple-gray-900); + color: var(--apple-gray-100); +} + +/* Tab styling */ +.tabs { + display: flex; + border-bottom: 1px solid var(--border-color); + position: relative; +} + +.tab { + padding: 0.75rem 1rem; + font-size: 0.875rem; + font-weight: 500; + color: var(--text-secondary); + position: relative; + transition: color 0.2s ease; +} + +.tab:hover { + color: var(--text-primary); +} + +.tab.active { + color: var(--primary-color); + font-weight: 600; +} + +.tab.active::after { + content: ''; + position: absolute; + bottom: -1px; + left: 0; + width: 100%; + height: 2px; + background-color: var(--primary-color); + border-radius: 1px 1px 0 0; +} + +/* Code block styling */ +.code-block { + border-radius: 8px; + background: var(--code-bg); + position: relative; + transition: all 0.2s ease; + overflow: hidden; + color: var(--code-text); + min-height: 100px; +} + +.dark .code-block { + background-color: var(--code-bg-dark); + border: 1px solid var(--border-dark); + color: var(--code-text-dark); +} + +.code-block pre { + margin: 0; + padding: 1rem; + overflow: auto; + font-family: var(--font-mono); + font-size: 13px; + line-height: 1.5; + scrollbar-width: thin; +} + +/* Copy button styling */ +.copy-button { + opacity: 0.7; + transition: all 0.2s ease; + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + color: var(--text-primary); +} + +.copy-button:hover { + opacity: 1; + transform: scale(1.05); +} + +/* API table styling */ +.api-table { + width: 100%; + border-collapse: collapse; +} + +.api-table th { + text-align: left; + padding: 0.75rem 1rem; + font-weight: 500; + background-color: var(--table-header-bg); + border-bottom: 1px solid var(--border-color); + color: var(--text-primary); + font-size: 0.875rem; +} + +.api-table td { + padding: 0.75rem 1rem; + border-bottom: 1px solid var(--border-color); + font-size: 0.875rem; +} + +.api-table tr:last-child td { + border-bottom: none; +} + +.dark .api-table th { + background-color: var(--table-header-bg-dark); + color: var(--code-text-dark); + border-bottom: 1px solid var(--border-dark); +} + +.dark .api-table td { + border-bottom: 1px solid var(--border-dark); + color: var(--text-dark); +} + +.dark .api-table tr:nth-child(odd) { + background-color: var(--table-row-odd-bg-dark); +} + +.dark .api-table tr:nth-child(even) { + background-color: var(--table-row-even-bg-dark); +} + +.dark .api-table input { + background-color: var(--input-bg-dark); + color: var(--code-text-dark); + border: 1px solid var(--border-dark); +} + +/* Table row styling */ +tr.themed-row:nth-child(odd) { + background-color: var(--table-row-odd-bg) !important; +} + +tr.themed-row:nth-child(even) { + background-color: var(--table-row-even-bg) !important; +} + +/* Dark row styling for backward compatibility */ +tr.dark-row:nth-child(odd) { + background-color: var(--table-row-odd-bg-dark) !important; +} + +tr.dark-row:nth-child(even) { + background-color: var(--table-row-even-bg-dark) !important; +} + +/* Apple-style rounded corners */ +.rounded-apple { + border-radius: 10px; +} + +/* Apple-inspired shadows */ +.shadow-apple-sm { + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04), 0 0.5px 2px rgba(0, 0, 0, 0.06); +} + +.dark .shadow-apple-sm { + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2), 0 0.5px 2px rgba(0, 0, 0, 0.3); +} + +.shadow-apple-md { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05), 0 1px 3px rgba(0, 0, 0, 0.08); +} + +.dark .shadow-apple-md { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3), 0 1px 3px rgba(0, 0, 0, 0.4); +} + +/* Apple-style scrollbars */ +.oc-playground ::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +.oc-playground ::-webkit-scrollbar-track { + background: transparent; +} + +.oc-playground ::-webkit-scrollbar-thumb { + background-color: #d1d5db; + border-radius: 20px; + border: 2px solid transparent; + background-clip: content-box; +} + +.oc-playground.dark ::-webkit-scrollbar-thumb { + background-color: #4d4d4d; + border-radius: 4px; +} + +.oc-playground ::-webkit-scrollbar-thumb:hover { + background-color: #5a5a5a; +} + +/* Skeleton loading effect */ +.skeleton { + background: linear-gradient( + 90deg, + rgba(0, 0, 0, 0.06) 25%, + rgba(0, 0, 0, 0.12) 37%, + rgba(0, 0, 0, 0.06) 63% + ); + background-size: 400% 100%; + animation: skeleton 1.4s ease infinite; +} + +.dark .skeleton { + background: linear-gradient( + 90deg, + rgba(45, 45, 45, 0.8) 25%, + rgba(55, 55, 55, 0.8) 37%, + rgba(45, 45, 45, 0.8) 63% + ); + background-size: 400% 100%; + animation: skeleton 1.4s ease infinite; +} + +@keyframes skeleton { + 0% { background-position: 100% 50%; } + 100% { background-position: 0 50%; } +} + +/* Hover lift effect */ +.hover-lift { + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.hover-lift:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05), 0 1px 3px rgba(0, 0, 0, 0.1); +} + +/* Ensure consistent box-sizing */ +.oc-playground * { + box-sizing: border-box; +} + +/* Animation for tab indicator */ +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +.fade-in { + animation: fadeIn 0.3s ease-in-out; +} + +/* Syntax highlighting for better Apple-like appearance */ +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #8e908c; +} + +.token.punctuation { + color: #5E5E5E; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol { + color: #0071e3; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin { + color: #718C00; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string, +.token.variable { + color: #9a6e3a; +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #d73a49; +} + +.token.function, +.token.class-name { + color: #6f42c1; +} + +.token.regex, +.token.important { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} + +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + +.dark .token.comment, +.dark .token.prolog, +.dark .token.doctype, +.dark .token.cdata { + color: #8a8a8a; +} + +.dark .token.punctuation { + color: #9e9e9e; +} + +.dark .token.property, +.dark .token.tag, +.dark .token.boolean, +.dark .token.number, +.dark .token.constant, +.dark .token.symbol { + color: #4dabf7; +} + +.dark .token.selector, +.dark .token.attr-name, +.dark .token.string, +.dark .token.char, +.dark .token.builtin { + color: #a8e34b; +} + +.dark .token.operator, +.dark .token.entity, +.dark .token.url, +.dark .language-css .token.string, +.dark .style .token.string, +.dark .token.variable { + color: #d19a66; +} + +.dark .token.atrule, +.dark .token.attr-value, +.dark .token.keyword { + color: #f97583; +} + +.dark .token.function, +.dark .token.class-name { + color: #b392f0; +} + +.dark .token.regex, +.dark .token.important { + color: #e6c07b; +} + +/* Focus styles for accessibility */ +:focus-visible { + outline: 2px solid var(--primary-color); + outline-offset: 2px; +} + +/* Button styling */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + font-weight: 500; + padding: 0.5rem 1rem; + border-radius: 0.5rem; + transition: all 0.2s ease; + cursor: pointer; +} + +.btn-primary { + background-color: var(--primary-color); + color: white; +} + +.btn-primary:hover { + background-color: #005bbf; +} + +.btn-secondary { + background-color: #f3f4f6; + color: #1f2937; +} + +.btn-secondary:hover { + background-color: #e5e7eb; +} + +.dark .btn-secondary { + background-color: var(--input-bg-dark); + color: var(--text-dark); + border: 1px solid var(--border-dark); +} + +.dark .btn-secondary:hover { + background-color: #3a3a3a; +} + +/* Sidebar item styles */ +.sidebar-item { + cursor: pointer; + transition: all 0.08s ease-out; + border-radius: 0.375rem; + margin-bottom: 2px; + position: relative; + overflow: hidden; + will-change: background-color, color; +} + +.sidebar-item:hover, .sidebar-item.hovered { + background-color: rgba(0, 0, 0, 0.05); +} + +.dark .sidebar-item:hover, .dark .sidebar-item.hovered { + background-color: rgba(255, 255, 255, 0.07); +} + +.sidebar-item.active { + background-color: rgba(84, 109, 229, 0.1); + color: var(--primary-color); + font-weight: 500; + box-shadow: inset 3px 0 0 var(--primary-color); +} + +.dark .sidebar-item.active { + background-color: rgba(84, 109, 229, 0.25); + color: #ffffff; + border-left: 3px solid var(--primary-color); +} + +.sidebar-item.active:hover, .sidebar-item.active.hovered { + background-color: rgba(84, 109, 229, 0.3); +} + +.sidebar-item.folder { + font-weight: 500; +} + +.sidebar-item .method-badge { + font-size: 0.7rem; + padding: 0.15rem 0.5rem; + border-radius: 0.25rem; + font-weight: 600; + min-width: 45px; + text-align: center; + transition: all 0.08s ease-out; +} + +.sidebar-items { + padding: 0 0.5rem; +} + +/* Fix for click events */ +.sidebar-item * { + pointer-events: none; +} +.sidebar-item svg { + pointer-events: all; +} + +/* Prose customization for markdown content */ +.prose { + color: var(--text-primary) !important; + max-width: none !important; + line-height: 1.6 !important; +} + +/* Specific heading styles */ +.markdown-documentation h1, +.markdown-documentation h2, +.markdown-documentation h3, +.markdown-documentation h4, +.markdown-documentation h5, +.markdown-documentation h6 { + color: var(--text-primary) !important; + margin-top: 1.5rem !important; + margin-bottom: 1rem !important; + font-weight: 600 !important; +} + +.markdown-documentation h1.heading-1 { + font-size: 1.75rem !important; + line-height: 2.25rem !important; + font-weight: 700 !important; + margin-top: 0 !important; + margin-bottom: 1rem !important; + color: var(--text-primary) !important; +} + +.markdown-documentation h2.heading-2 { + font-size: 1.5rem !important; + line-height: 2rem !important; + font-weight: 600 !important; + margin-top: 1.5rem !important; + margin-bottom: 0.8rem !important; +} + +.markdown-documentation h3.heading-3 { + font-size: 1.25rem !important; + font-weight: 600 !important; + margin-top: 1rem !important; + margin-bottom: 0.5rem !important; +} + +.markdown-documentation h4.heading-4 { + font-size: 1.1rem !important; + font-weight: 600 !important; + margin-top: 0.8rem !important; + margin-bottom: 0.4rem !important; +} + +.prose a { + text-decoration: none !important; + transition: color 0.15s ease-in-out !important; + color: var(--prose-links) !important; +} + +.prose a:hover { + text-decoration: underline !important; + color: var(--prose-links-hover) !important; +} + +.prose code { + font-family: var(--font-mono) !important; + padding: 0.2em 0.4em !important; + border-radius: 4px !important; + font-size: 0.9em !important; + background-color: var(--prose-code-bg) !important; + color: var(--prose-code-text) !important; +} + +.prose pre { + padding: 1em !important; + background-color: var(--prose-code-bg) !important; + border-radius: 10px !important; + overflow-x: auto !important; + margin: 1em 0 !important; + color: var(--prose-code-text) !important; +} + +.prose pre code { + background-color: transparent !important; + padding: 0 !important; + border-radius: 0 !important; + font-size: 0.9em !important; + color: inherit !important; +} + +.prose img { + margin: 1.5em 0 !important; + border-radius: 8px !important; +} + +.prose ul, .prose ol { + margin-left: 1.5em !important; + margin-top: 1em !important; + margin-bottom: 1em !important; +} + +.prose li { + margin-top: 0.25em !important; + margin-bottom: 0.25em !important; +} + +.prose blockquote { + border-left-width: 0.25em !important; + border-left-color: var(--prose-blockquote-border) !important; + padding-left: 1em !important; + font-style: italic !important; +} + +.prose hr { + margin: 2em 0 !important; + border-color: var(--prose-hr-color) !important; +} + +/* Console animations */ +.console-container { + transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out; + will-change: opacity, transform; + min-height: 300px; + display: flex; + flex-direction: column; + height: 100%; +} + +.console-container.fade-in { + animation: fadeIn 0.3s ease forwards; +} + +.console-container.fade-out { + opacity: 0; + transition: opacity 0.2s ease; +} + +/* Make console sticky in appropriate places */ +.playground-console .sticky { + position: sticky; + top: 0; + max-height: 100vh; + overflow-y: auto; + z-index: 10; +} + +/* Flash effect for code updates */ +@keyframes codeFlash { + 0% { + background-color: var(--primary-color); + opacity: 0.2; + } + 100% { + background-color: transparent; + opacity: 1; + } +} + +.code-updated { + animation: codeFlash 0.5s ease-out; +} + +/* Loading indicator for console */ +.console-view.is-loading .code-block::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0,0,0,0.1); + display: flex; + justify-content: center; + align-items: center; + z-index: 10; +} + +.dark .console-view.is-loading .code-block::after { + background: rgba(255,255,255,0.05); +} + +/* Endpoint info styling */ +.endpoint-info { + padding: 8px 0; + border-top: 1px solid var(--border-color); + margin-top: 12px; +} + +/* Section highlight animation */ +@keyframes sectionHighlight { + 0% { + background-color: rgba(59, 130, 246, 0.08); + box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.08); + } + 50% { + background-color: rgba(59, 130, 246, 0.12); + box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.12); + } + 100% { + background-color: transparent; + box-shadow: 0 0 0 0 transparent; + } +} + +@keyframes sectionHighlightDark { + 0% { + background-color: rgba(84, 109, 229, 0.3); + } + 100% { + background-color: transparent; + } +} + +.section-highlight { + animation: sectionHighlightDark 2s ease-out; + border-radius: 8px; +} + +/* Hide scrollbar but keep functionality */ +.hide-scrollbar::-webkit-scrollbar { + width: 0; + height: 0; + display: none; +} + +.hide-scrollbar { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + +.endpoint-content { + max-width: 100%; +} + +/* ContentLayout styles */ +.content-layout { + padding: 1.5rem; + max-width: 100%; + overflow-x: hidden; +} + +.content-header { + margin-bottom: 1.5rem; + border-bottom: 1px solid var(--border-color, #e2e8f0); + padding-bottom: 1rem; +} + +.content-title { + font-size: 1.8rem; + font-weight: 700; + color: var(--text-primary); +} + +.content-badge-container { + display: flex; + align-items: center; + font-size: 1.1rem; + margin: 0.5rem 0 1rem; + flex-wrap: wrap; +} + +.content-badge { + padding: 0.2rem 0.5rem; + border-radius: 4px; + font-weight: 600; + font-size: 0.85rem; + margin-right: 0.75rem; + text-transform: uppercase; +} + +.content-subtitle { + font-family: monospace; + padding: 0.35rem 0.5rem; + background: var(--code-bg); + border-radius: 4px; + overflow-x: auto; + word-break: break-all; + max-width: 100%; + color: var(--code-text); +} + +.content-description { + margin-top: 1rem; + line-height: 1.6; +} + +.content-grid { + display: grid; + grid-template-columns: 2fr 1fr; + gap: 1.5rem; + margin-top: 1rem; +} + +.content-main, +.content-side { + display: flex; + flex-direction: column; + gap: 1.5rem; + overflow-x: hidden; +} + +.content-side { + width: 350px; + min-width: 350px; + max-width: 350px; +} + +@media (max-width: 1100px) { + .content-grid { + grid-template-columns: 1fr; + } + + .content-side { + width: 100%; + min-width: 100%; + max-width: 100%; + } +} + +/* Original styles */ +.endpoint-header { + margin-bottom: 1.5rem; + border-bottom: 1px solid var(--border-color, #e2e8f0); + padding-bottom: 1rem; +} + +.endpoint-name { + font-size: 1.8rem; + font-weight: 700; + color: var(--text-primary); +} + +.endpoint-method-url { + display: flex; + align-items: center; + font-size: 1.1rem; + flex-wrap: wrap; +} + +.endpoint-url { + font-family: monospace; + background: var(--code-bg); + border-radius: 4px; + margin-left: 0.25rem; + overflow-x: auto; + word-break: break-all; + max-width: 100%; + color: var(--code-text); +} + +.endpoint-description { + margin-top: 1rem; + line-height: 1.6; +} + +.endpoint-grid { + display: grid; + grid-template-columns: 1fr 37%; + gap: 1.5rem; + margin-top: 1rem; + width: 100%; + max-width: 100%; +} + +.endpoint-details { + width: 100%; + max-width: 100%; + overflow: hidden; +} + +.code-samples { + width: 100%; + min-width: 30%; +} + +.code-samples-frame { + width: 100%; + min-width: 30%; + height: fit-content; +} + +.code-samples-container { + width: 100%; + overflow-x: auto; +} + +.code-block { + overflow: hidden; + transition: all 0.2s ease; +} + +.code-tab { + transition: all 0.2s ease; +} + +.code-tab:hover { + opacity: 0.9; +} + +@media (max-width: 1100px) { + .endpoint-grid { + grid-template-columns: 1fr; + } + + .code-samples-container { + position: static; + } +} + +.param-name, .param-value { + font-family: monospace; + word-break: break-all; +} + +.param-name { + font-weight: 600; +} + +/* Dark theme adjustments */ +.dark { + --heading-color: var(--text-dark); + --border-color: var(--border-dark); + --card-bg: var(--background-dark); + --code-bg: var(--code-bg); + --table-header-bg: var(--table-header-bg-dark); + --badge-bg: var(--badge-bg-dark); + --badge-color: var(--badge-text-dark); +} \ No newline at end of file diff --git a/packages/oc-core/src/test.ts b/packages/oc-core/src/test.ts new file mode 100644 index 00000000..15a8f938 --- /dev/null +++ b/packages/oc-core/src/test.ts @@ -0,0 +1,1662 @@ +export const test = { + "name": "bruno-testbench", + + "environments": [ + { + "name": "Local", + "description": null, + "variables": [ + { + "name": "host", + "value": "http://localhost:8080", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "httpfaker", + "value": "https://www.httpfaker.org", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "bearer_auth_token", + "value": "your_secret_token", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "basic_auth_password", + "value": "della", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "env.var1", + "value": "envVar1", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "env-var2", + "value": "envVar2", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "bark", + "value": "{{process.env.PROC_ENV_VAR}}", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "foo", + "value": "bar", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "testSetEnvVar", + "value": "bruno-29653", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "echo-host", + "value": "https://echo.usebruno.com", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "client_id", + "value": "client_id_1", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "client_secret", + "value": "client_secret_1", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "auth_url", + "value": "http://localhost:8080/api/auth/oauth2/authorization_code/authorize", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "callback_url", + "value": "http://localhost:8080/api/auth/oauth2/authorization_code/callback", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "access_token_url", + "value": "http://localhost:8080/api/auth/oauth2/authorization_code/token", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "passwordCredentials_username", + "value": "foo", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "passwordCredentials_password", + "value": "bar", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "github_authorize_url", + "value": "https://github.com/login/oauth/authorize", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "github_access_token_url", + "value": "https://github.com/login/oauth/access_token", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "google_auth_url", + "value": "https://accounts.google.com/o/oauth2/auth", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "google_access_token_url", + "value": "https://accounts.google.com/o/oauth2/token", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "google_scope", + "value": "https://www.googleapis.com/auth/userinfo.email", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "github_client_secret", + "value": "", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "github_client_id", + "value": "", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "google_client_id", + "value": "", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "google_client_secret", + "value": "", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "github_authorization_code", + "value": "", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "passwordCredentials_access_token", + "value": "", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "client_credentials_access_token", + "value": "", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "authorization_code_access_token", + "value": "", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "github_access_token", + "value": "", + "description": null, + "disabled": false, + "transient": null, + "default": null + } + ] + }, + { + "name": "Prod", + "description": null, + "variables": [ + { + "name": "host", + "value": "https://testbench-sanity.usebruno.com", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "httpfaker", + "value": "https://www.httpfaker.org", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "bearer_auth_token", + "value": "your_secret_token", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "basic_auth_password", + "value": "della", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "env.var1", + "value": "envVar1", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "env-var2", + "value": "envVar2", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "bark", + "value": "{{process.env.PROC_ENV_VAR}}", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "foo", + "value": "bar", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "testSetEnvVar", + "value": "bruno-29653", + "description": null, + "disabled": false, + "transient": null, + "default": null + }, + { + "name": "echo-host", + "value": "https://echo.usebruno.com", + "description": null, + "disabled": false, + "transient": null, + "default": null + } + ] + } + ], + "items": [ + { + "name": "ping", + "type": "http", + "url": "{{host}}/ping", + "method": "GET", + "params": [], + "headers": [], + "body": null, + "auth": null, + "scripts": { + "preRequest": "bru.runner.stopExecution();" + }, + "variables": [], + "assertions": [], + "docs": "" + }, + { + "type": "folder", + "name": "graphql", + "headers": [], + "auth": null, + "variables": [], + "scripts": {}, + "docs": null, + "items": [ + { + "name": "spacex", + "type": "graphql", + "url": "https://spacex-production.up.railway.app/", + "method": "POST", + "headers": [], + "params": [], + "body": { + "type": "json", + "data": "{\n \"company\": {\n \"ceo\": \"Elon Musk\"\n }\n}" + }, + "auth": null, + "scripts": {}, + "variables": [], + "assertions": [ + { + "expression": "res.status", + "operator": "eq", + "value": "200", + "enabled": true, + + } + ], + "docs": "" + } + ] + }, + { + "type": "folder", + "name": "echo", + "headers": [], + "auth": null, + "variables": [], + "scripts": {}, + "docs": null, + "items": [ + { + "name": "echo bom json", + "type": "http", + "url": "{{host}}/api/echo/bom-json-test", + "method": "GET", + "params": [], + "headers": [], + "body": null, + "auth": null, + "scripts": {}, + "variables": [], + "assertions": [], + "docs": "" + }, + { + "name": "echo form-url-encoded", + "type": "http", + "url": "{{echo-host}}", + "method": "POST", + "params": [], + "headers": [], + "body": [ + { + "name": "form-data-key", + "value": "{{form-data-key}}", + + "enabled": true + }, + { + "name": "form-data-stringified-object", + "value": "{{form-data-stringified-object}}", + + "enabled": true + } + ], + "auth": null, + "scripts": { + "preRequest": "let obj = JSON.stringify({foo:123});\nbru.setVar('form-data-key', 'form-data-value');\nbru.setVar('form-data-stringified-object', obj);" + }, + "variables": [], + "assertions": [ + { + "expression": "res.body", + "operator": "eq", + "value": "form-data-key=form-data-value&form-data-stringified-object=%7B%22foo%22%3A123%7D", + "enabled": true, + + } + ], + "docs": "" + }, + { + "name": "echo json", + "type": "http", + "url": "{{host}}/api/echo/json", + "method": "POST", + "params": [], + "headers": [ + { + "name": "foo", + "value": "bar", + + "disabled": null + } + ], + "body": { + "type": "json", + "data": "{\n \"hello\": \"bruno\"\n}" + }, + "auth": null, + "scripts": { + "preRequest": "bru.setVar(\"foo\", \"foo-world-2\");", + "tests": "test(\"should return json\", function() {\n const data = res.getBody();\n expect(res.getBody()).to.eql({\n \"hello\": \"bruno\"\n });\n}); " + }, + "variables": [], + "assertions": [ + { + "expression": "res.status", + "operator": "eq", + "value": "200", + "enabled": true, + + } + ], + "docs": "" + }, + { + "name": "echo multipart via scripting", + "type": "http", + "url": "{{echo-host}}", + "method": "POST", + "params": [], + "headers": [], + "body": null, + "auth": null, + "scripts": { + "preRequest": "const FormData = require(\"form-data\");\nconst form = new FormData();\nform.append('form-data-key', 'form-data-value');\nreq.setBody(form);" + }, + "variables": [], + "assertions": [ + { + "expression": "res.body", + "operator": "contains", + "value": "form-data-value", + "enabled": true, + + } + ], + "docs": "" + }, + { + "name": "echo multipart", + "type": "http", + "url": "{{echo-host}}", + "method": "POST", + "params": [], + "headers": [], + "body": [ + { + "name": "foo", + "type": "text", + "value": "{\"bar\":\"baz\"}", + + "enabled": true + }, + { + "name": "form-data-key", + "type": "text", + "value": "{{form-data-key}}", + + "enabled": true + }, + { + "name": "form-data-stringified-object", + "type": "text", + "value": "{{form-data-stringified-object}}", + + "enabled": true + }, + { + "name": "file", + "type": "file", + "value": [ + "bruno.png" + ], + + "enabled": true + } + ], + "auth": null, + "scripts": { + "preRequest": "let obj = JSON.stringify({foo:123});\nbru.setVar('form-data-key', 'form-data-value');\nbru.setVar('form-data-stringified-object', obj);" + }, + "variables": [], + "assertions": [ + { + "expression": "res.body", + "operator": "contains", + "value": "form-data-value", + "enabled": true, + + }, + { + "expression": "res.body", + "operator": "contains", + "value": "{\"foo\":123}", + "enabled": true, + + }, + { + "expression": "res.body", + "operator": "contains", + "value": "Content-Type: application/json--test", + "enabled": true, + + } + ], + "docs": "" + }, + { + "name": "echo numbers", + "type": "http", + "url": "{{echo-host}}", + "method": "POST", + "params": [], + "headers": [], + "body": { + "type": "json", + "data": "{\n \"integer\": 123,\n \"negativeInteger\": -99,\n \"zero\": 0,\n \"float\": 2.718,\n \"negativeFloat\": -1.618,\n \"largeDouble\": 12345.678901234567,\n \"smallDouble\": 9.876e-12,\n \"booleanTrue\": true,\n \"booleanFalse\": false\n}" + }, + "auth": null, + "scripts": {}, + "variables": [], + "assertions": [ + { + "expression": "res.body.integer", + "operator": "eq", + "value": "123", + "enabled": true, + + }, + { + "expression": "res.body.integer", + "operator": "isNumber", + "value": null, + "enabled": true, + + }, + { + "expression": "res.body.negativeInteger", + "operator": "eq", + "value": "-99", + "enabled": true, + + }, + { + "expression": "res.body.negativeInteger", + "operator": "isNumber", + "value": null, + "enabled": true, + + }, + { + "expression": "res.body.zero", + "operator": "eq", + "value": "0", + "enabled": true, + + }, + { + "expression": "res.body.zero", + "operator": "isNumber", + "value": null, + "enabled": true, + + }, + { + "expression": "res.body.float", + "operator": "eq", + "value": "2.718", + "enabled": true, + + }, + { + "expression": "res.body.float", + "operator": "isNumber", + "value": null, + "enabled": true, + + }, + { + "expression": "res.body.negativeFloat", + "operator": "eq", + "value": "-1.618", + "enabled": true, + + }, + { + "expression": "res.body.negativeFloat", + "operator": "isNumber", + "value": null, + "enabled": true, + + }, + { + "expression": "res.body.largeDouble", + "operator": "eq", + "value": "12345.678901234567", + "enabled": true, + + }, + { + "expression": "res.body.largeDouble", + "operator": "isNumber", + "value": null, + "enabled": true, + + }, + { + "expression": "res.body.smallDouble", + "operator": "eq", + "value": "9.876e-12", + "enabled": true, + + }, + { + "expression": "res.body.smallDouble", + "operator": "isNumber", + "value": null, + "enabled": true, + + }, + { + "expression": "res.body.booleanTrue", + "operator": "eq", + "value": "true", + "enabled": true, + + }, + { + "expression": "res.body.booleanFalse", + "operator": "eq", + "value": "false", + "enabled": true, + + } + ], + "docs": "" + }, + { + "name": "echo plaintext", + "type": "http", + "url": "{{host}}/api/echo/text", + "method": "POST", + "params": [], + "headers": [], + "body": { + "type": "text", + "data": "hello" + }, + "auth": null, + "scripts": { + "tests": "test(\"should return plain text\", function() {\n const data = res.getBody();\n expect(res.getBody()).to.eql(\"hello\");\n});\n" + }, + "variables": [], + "assertions": [ + { + "expression": "res.status", + "operator": "eq", + "value": "200", + "enabled": true, + + } + ], + "docs": "" + }, + { + "name": "echo xml parsed(self closing tags)", + "type": "http", + "url": "{{host}}/api/echo/xml-parsed", + "method": "POST", + "params": [], + "headers": [], + "body": { + "type": "xml", + "data": "\n bruno\n \n" + }, + "auth": null, + "scripts": { + "tests": "test(\"should return parsed xml\", function() {\n const data = res.getBody();\n expect(res.getBody()).to.eql({\n \"hello\": {\n \"world\": [\n \"bruno\",\n \"\"\n ]\n }\n });\n});\n" + }, + "variables": [], + "assertions": [ + { + "expression": "res.status", + "operator": "eq", + "value": "200", + "enabled": true, + + } + ], + "docs": "" + }, + { + "name": "echo xml parsed", + "type": "http", + "url": "{{host}}/api/echo/xml-parsed", + "method": "POST", + "params": [], + "headers": [], + "body": { + "type": "xml", + "data": "\n bruno\n" + }, + "auth": null, + "scripts": { + "tests": "test(\"should return parsed xml\", function() {\n const data = res.getBody();\n expect(res.getBody()).to.eql({\n \"hello\": {\n \"world\": [\"bruno\"]\n }\n });\n});\n" + }, + "variables": [], + "assertions": [ + { + "expression": "res.status", + "operator": "eq", + "value": "200", + "enabled": true, + + } + ], + "docs": "" + }, + { + "name": "echo xml raw", + "type": "http", + "url": "{{host}}/api/echo/xml-raw", + "method": "POST", + "params": [], + "headers": [], + "body": { + "type": "xml", + "data": "bruno" + }, + "auth": null, + "scripts": {}, + "variables": [], + "assertions": [], + "docs": "" + }, + { + "name": "test echo any", + "type": "http", + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "params": [], + "headers": [], + "body": { + "type": "json", + "data": "{\n \"headers\": { \"content-type\": \"text/plain\" },\n \"content\": \"hello\"\n}" + }, + "auth": null, + "scripts": {}, + "variables": [], + "assertions": [ + { + "expression": "res.body", + "operator": "eq", + "value": "hello", + "enabled": true, + + } + ], + "docs": "" + }, + { + "name": "test echo-any json", + "type": "http", + "url": "{{httpfaker}}/api/echo/custom", + "method": "POST", + "params": [], + "headers": [], + "body": { + "type": "json", + "data": "{\n \"type\": \"application/json\",\n \"contentJSON\": {\"x\": 42}\n}" + }, + "auth": null, + "scripts": {}, + "variables": [], + "assertions": [ + { + "expression": "res.body.x", + "operator": "eq", + "value": "42", + "enabled": true, + + } + ], + "docs": "" + }, + { + "type": "folder", + "name": "multiline", + "headers": [], + "auth": null, + "variables": [], + "scripts": {}, + "docs": null, + "items": [ + { + "name": "echo binary", + "type": "http", + "url": "{{echo-host}}", + "method": "POST", + "params": [], + "headers": [], + "body": [ + { + "filePath": "bruno.png", + "contentType": "image/png", + "selected": true + } + ], + "auth": null, + "scripts": {}, + "variables": [], + "assertions": [], + "docs": "" + } + ] + } + ] + }, + { + "type": "folder", + "name": "auth", + "headers": [], + "auth": null, + "variables": [], + "scripts": {}, + "docs": null, + "items": [ + { + "type": "folder", + "name": "inherit auth", + "headers": [], + "auth": null, + "variables": [], + "scripts": {}, + "docs": null, + "items": [ + { + "name": "inherit Bearer Auth 200", + "type": "http", + "url": "{{host}}/api/auth/bearer/protected", + "method": "GET", + "params": [], + "headers": [], + "body": null, + "auth": null, + "scripts": { + "postResponse": "bru.setEnvVar(\"foo\", \"bar\");" + }, + "variables": [], + "assertions": [ + { + "expression": "res.status", + "operator": "eq", + "value": "200", + "enabled": true, + + }, + { + "expression": "res.body.message", + "operator": "eq", + "value": "Authentication successful", + "enabled": true, + + } + ], + "docs": "" + } + ] + }, + { + "type": "folder", + "name": "basic", + "headers": [], + "auth": null, + "variables": [], + "scripts": {}, + "docs": null, + "items": [ + { + "type": "folder", + "name": "via auth", + "headers": [], + "auth": null, + "variables": [], + "scripts": {}, + "docs": null, + "items": [ + { + "name": "Basic Auth 200", + "type": "http", + "url": "{{host}}/api/auth/basic/protected", + "method": "POST", + "params": [], + "headers": [], + "body": null, + "auth": { + "type": "basic", + "username": "bruno", + "password": "{{basic_auth_password}}" + }, + "scripts": {}, + "variables": [], + "assertions": [ + { + "expression": "res.status", + "operator": "eq", + "value": "200", + "enabled": true, + + }, + { + "expression": "res.body.message", + "operator": "eq", + "value": "Authentication successful", + "enabled": true, + + } + ], + "docs": "" + }, + { + "name": "Basic Auth 400", + "type": "http", + "url": "{{host}}/api/auth/basic/protected", + "method": "POST", + "params": [], + "headers": [], + "body": null, + "auth": null, + "scripts": {}, + "variables": [], + "assertions": [ + { + "expression": "res.status", + "operator": "eq", + "value": "401", + "enabled": true, + + }, + { + "expression": "res.body", + "operator": "eq", + "value": "Unauthorized", + "enabled": true, + + } + ], + "docs": "" + } + ] + }, + { + "type": "folder", + "name": "via script", + "headers": [], + "auth": null, + "variables": [], + "scripts": {}, + "docs": null, + "items": [ + { + "name": "Basic Auth 401", + "type": "http", + "url": "{{host}}/api/auth/basic/protected", + "method": "POST", + "params": [], + "headers": [], + "body": null, + "auth": null, + "scripts": { + "preRequest": "const username = \"bruno\";\nconst password = \"invalid\";\n\nconst authString = `${username}:${password}`;\nconst encodedAuthString = require('btoa')(authString);\n\nreq.setHeader(\"Authorization\", `Basic ${encodedAuthString}`);" + }, + "variables": [], + "assertions": [ + { + "expression": "res.status", + "operator": "eq", + "value": "401", + "enabled": true, + + }, + { + "expression": "res.body", + "operator": "eq", + "value": "Unauthorized", + "enabled": true, + + } + ], + "docs": "" + }, + { + "name": "Basic Auth 200", + "type": "http", + "url": "{{host}}/api/auth/basic/protected", + "method": "POST", + "params": [], + "headers": [], + "body": null, + "auth": null, + "scripts": { + "preRequest": "const username = \"bruno\";\nconst password = \"della\";\n\nconst authString = `${username}:${password}`;\nconst encodedAuthString = require('btoa')(authString);\n\nreq.setHeader(\"Authorization\", `Basic ${encodedAuthString}`);" + }, + "variables": [], + "assertions": [ + { + "expression": "res.status", + "operator": "eq", + "value": "200", + "enabled": true, + + }, + { + "expression": "res.body.message", + "operator": "eq", + "value": "Authentication successful", + "enabled": true, + + } + ], + "docs": "" + } + ] + } + ] + }, + { + "type": "folder", + "name": "cookie", + "headers": [], + "auth": null, + "variables": [], + "scripts": {}, + "docs": null, + "items": [ + { + "name": "Check", + "type": "http", + "url": "{{host}}/api/auth/cookie/protected", + "method": "GET", + "params": [], + "headers": [], + "body": null, + "auth": null, + "scripts": {}, + "variables": [], + "assertions": [], + "docs": "" + }, + { + "name": "Login", + "type": "http", + "url": "{{host}}/api/auth/cookie/login", + "method": "POST", + "params": [], + "headers": [], + "body": null, + "auth": null, + "scripts": {}, + "variables": [], + "assertions": [], + "docs": "" + } + ] + }, + { + "type": "folder", + "name": "bearer", + "headers": [], + "auth": null, + "variables": [], + "scripts": {}, + "docs": null, + "items": [ + { + "type": "folder", + "name": "via headers", + "headers": [], + "auth": null, + "variables": [], + "scripts": {}, + "docs": null, + "items": [ + { + "name": "Bearer Auth 200", + "type": "http", + "url": "{{host}}/api/auth/bearer/protected", + "method": "GET", + "params": [], + "headers": [ + { + "name": "Authorization", + "value": "Bearer your_secret_token", + + "disabled": null + } + ], + "body": null, + "auth": null, + "scripts": { + "postResponse": "bru.setEnvVar(\"foo\", \"bar\");" + }, + "variables": [ + { + "name": "a-c", + "value": "foo", + + "disabled": false, + "transient": false + } + ], + "assertions": [ + { + "expression": "res.status", + "operator": "eq", + "value": "200", + "enabled": true, + + }, + { + "expression": "res.body.message", + "operator": "eq", + "value": "Authentication successful", + "enabled": true, + + } + ], + "docs": "" + } + ] + }, + { + "type": "folder", + "name": "via auth", + "headers": [], + "auth": null, + "variables": [], + "scripts": {}, + "docs": null, + "items": [ + { + "name": "Bearer Auth 200", + "type": "http", + "url": "{{host}}/api/auth/bearer/protected", + "method": "GET", + "params": [], + "headers": [], + "body": null, + "auth": { + "type": "bearer", + "token": "{{bearer_token}}" + }, + "scripts": {}, + "variables": [], + "assertions": [ + { + "expression": "res.status", + "operator": "eq", + "value": "200", + "enabled": true, + + }, + { + "expression": "res.body.message", + "operator": "eq", + "value": "Authentication successful", + "enabled": true, + + } + ], + "docs": "" + } + ] + } + ] + }, + { + "name": "oauth", + "type": "folder", + "headers": [], + "auth": null, + "variables": [], + "scripts": {}, + "docs": null, + "items": [ + { + "name": "authorization code grant", + "type": "http", + "url": "https://accounts.google.com/o/oauth2/v2/auth", + "method": "GET", + "params": [ + { + "name": "scope", + "value": "https://www.googleapis.com/auth/cloud-platform", + + "type": "query", + "enabled": true + }, + { + "name": "access_type", + "value": "offline", + + "type": "query", + "enabled": true + }, + { + "name": "include_granted_scopes", + "value": "true", + + "type": "query", + "enabled": true + }, + { + "name": "response_type", + "value": "code", + + "type": "query", + "enabled": true + }, + { + "name": "redirect_uri", + "value": "http://localhost:3000", + + "type": "query", + "enabled": true + }, + { + "name": "client_id", + "value": "{{client_id}}", + + "type": "query", + "enabled": true + } + ], + "headers": [], + "body": null, + "auth": null, + "scripts": {}, + "variables": [], + "assertions": [], + "docs": "" + }, + { + "name": "get access token", + "type": "http", + "url": "https://oauth2.googleapis.com/token", + "method": "POST", + "params": [], + "headers": [ + { + "name": "Content-Type", + "value": "application/x-www-form-urlencoded", + + "disabled": null + } + ], + "body": [ + { + "name": "code", + "value": "{{auth_code}}", + + "enabled": true + }, + { + "name": "client_id", + "value": "{{client_id}}", + + "enabled": true + }, + { + "name": "client_secret", + "value": "{{client_secret}}", + + "enabled": true + }, + { + "name": "redirect_uri", + "value": "http://localhost:3000", + + "enabled": true + }, + { + "name": "grant_type", + "value": "authorization_code", + + "enabled": true + } + ], + "auth": null, + "scripts": {}, + "variables": [], + "assertions": [], + "docs": "" + } + ] + }, + { + "type": "folder", + "name": "api key", + "headers": [], + "auth": null, + "variables": [], + "scripts": {}, + "docs": null, + "items": [ + { + "name": "api key in headers", + "type": "http", + "url": "{{host}}/api/auth/api-key", + "method": "GET", + "params": [], + "headers": [], + "body": null, + "auth": { + "type": "apikey", + "key": "x-api-key", + "value": "your_api_key_here", + "placement": "header" + }, + "scripts": {}, + "variables": [], + "assertions": [], + "docs": "" + }, + { + "name": "api key in query", + "type": "http", + "url": "{{host}}/api/auth/api-key", + "method": "GET", + "params": [], + "headers": [], + "body": null, + "auth": { + "type": "apikey", + "key": "x-api-key", + "value": "your_api_key_here", + "placement": "query" + }, + "scripts": {}, + "variables": [], + "assertions": [], + "docs": "" + } + ] + }, + { + "name": "digest", + "type": "folder", + "headers": [], + "auth": null, + "variables": [], + "scripts": {}, + "docs": null, + "items": [ + { + "name": "digest auth 200", + "type": "http", + "url": "{{host}}/api/auth/digest/protected", + "method": "POST", + "params": [], + "headers": [], + "body": null, + "auth": { + "type": "digest", + "username": "bruno", + "password": "della" + }, + "scripts": {}, + "variables": [], + "assertions": [ + { + "expression": "res.status", + "operator": "eq", + "value": "200", + "enabled": true, + + }, + { + "expression": "res.body.message", + "operator": "eq", + "value": "Authentication successful", + "enabled": true, + + } + ], + "docs": "" + } + ] + }, + { + "type": "folder", + "name": "ntlm", + "headers": [], + "auth": null, + "variables": [], + "scripts": {}, + "docs": null, + "items": [ + { + "name": "ntlm auth 200", + "type": "http", + "url": "{{host}}/api/auth/ntlm/protected", + "method": "POST", + "params": [], + "headers": [], + "body": null, + "auth": { + "type": "ntlm", + "username": "bruno", + "password": "della", + "domain": "acme" + }, + "scripts": {}, + "variables": [], + "assertions": [ + { + "expression": "res.status", + "operator": "eq", + "value": "200", + "enabled": true, + + }, + { + "expression": "res.body.message", + "operator": "eq", + "value": "Authentication successful", + "enabled": true, + + } + ], + "docs": "" + } + ] + }, + { + "type": "folder", + "name": "awsv4", + "headers": [], + "auth": null, + "variables": [], + "scripts": {}, + "docs": null, + "items": [ + { + "name": "awsv4 auth 200", + "type": "http", + "url": "{{host}}/api/auth/awsv4/protected", + "method": "GET", + "params": [], + "headers": [], + "body": null, + "auth": { + "type": "awsv4", + "accessKeyId": "AKIAIOSFODNN7EXAMPLE", + "secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "sessionToken": null, + "service": "execute-api", + "region": "us-east-1", + "profileName": null + }, + "scripts": {}, + "variables": [], + "assertions": [ + { + "expression": "res.status", + "operator": "eq", + "value": "200", + "enabled": true, + + }, + { + "expression": "res.body.message", + "operator": "eq", + "value": "Authentication successful", + "enabled": true, + + } + ], + "docs": "" + } + ] + } + ] + } + ], + "base": { + "headers": [], + "auth": null, + "variables": [ + { + "name": "host", + "value": "http://localhost:3001", + + "disabled": null, + "transient": null + }, + { + "name": "echo-host", + "value": "http://localhost:3002", + + "disabled": null, + "transient": null + }, + { + "name": "should-test-collection-scripts", + "value": null, + + "disabled": null, + "transient": true + }, + { + "name": "collection-var-set-by-collection-script", + "value": null, + + "disabled": null, + "transient": true + }, + { + "name": "httpfaker", + "value": "http://localhost:3003", + + "disabled": null, + "transient": null + }, + { + "name": "auth_code", + "value": null, + + "disabled": null, + "transient": true + }, + { + "name": "access_token", + "value": null, + + "disabled": null, + "transient": true + }, + { + "name": "refresh_token", + "value": null, + + "disabled": null, + "transient": true + }, + { + "name": "client_id", + "value": "your_client_id_here", + + "disabled": null, + "transient": null + }, + { + "name": "client_secret", + "value": "your_client_secret_here", + + "disabled": null, + "transient": null + }, + { + "name": "basic_auth_password", + "value": "della", + + "disabled": null, + "transient": null + }, + { + "name": "bearer_token", + "value": "this-is-my-secret-token", + + "disabled": null, + "transient": null + } + ], + "scripts": { + "preRequest": "const varName = 'collection-var-set-by-collection-script';\nbru.setVar(varName, `collection-var-value-set-by-collection-script`);", + "postResponse": "const varName = bru.getVar(\"collection-var-set-by-collection-script\");\nconsole.log(varName);\n", + "tests": "// check if collection scripts work\nconst shouldTestCollectionScripts = bru.getVar('should-test-collection-scripts');\nconst collectionVar = bru.getVar(\"collection-var-set-by-collection-script\");\nif (shouldTestCollectionScripts && collectionVar) {\n test(\"collection level test - should get the var that was set by the collection script\", function() {\n expect(collectionVar).to.equal(\"collection-var-value-set-by-collection-script\");\n }); \n bru.setVar('collection-var-set-by-collection-script', null); \n bru.setVar('should-test-collection-scripts', null);\n}", + "hooks": null + } + }, + "docs": "# bruno-testbench 🐶\n\nThis is a test collection that I am using to test various functionalities around bruno" + } as any \ No newline at end of file diff --git a/packages/oc-core/src/types/index.ts b/packages/oc-core/src/types/index.ts new file mode 100644 index 00000000..826f694b --- /dev/null +++ b/packages/oc-core/src/types/index.ts @@ -0,0 +1,147 @@ +import React from 'react'; + +export interface RequestHeader { + name: string; + value: string; + description?: string; + disabled?: boolean; +} + +export interface RequestParam { + name: string; + value: string; + description?: string; + type: 'query' | 'path'; + enabled?: boolean; +} + +export interface RawBody { + type: 'json' | 'text' | 'xml' | 'sparql'; + data: string; +} + +export interface FormUrlEncodedBody extends Array<{ + name: string; + value: string; + description?: string; + enabled?: boolean; +}> {} + +export interface MultipartFormBody extends Array<{ + name: string; + type: 'text' | 'file'; + value: string | string[]; + description?: string; + enabled?: boolean; +}> {} + +export interface FileBody extends Array<{ + filePath: string; + contentType: string; + selected: boolean; +}> {} + +export type RequestBody = RawBody | FormUrlEncodedBody | MultipartFormBody | FileBody | null; + +export interface Auth { + type: 'awsv4' | 'basic' | 'wsse' | 'bearer' | 'digest' | 'ntlm' | 'apikey'; + [key: string]: any; +} + +export interface Scripts { + preRequest?: string; + postResponse?: string; + tests?: string; + hooks?: string; +} + +export interface Variable { + name: string; + value?: string | null; + description?: string; + disabled?: boolean; + transient?: boolean; + default?: string | null; +} + +export interface Assertion { + expression: string; + operator: string; + value: string; + enabled?: boolean; + description?: string; +} + +export interface HttpRequest { + type: 'http'; + name?: string; + url?: string; + method?: string; + params?: RequestParam[]; + headers?: RequestHeader[]; + body?: RequestBody; + auth?: Auth; + scripts?: Scripts; + variables?: Variable[]; + assertions?: Assertion[]; + docs?: string; +} + +export interface Folder { + type: 'folder'; + name?: string; + items?: OpenCollectionItem[]; + headers?: RequestHeader[]; + auth?: Auth; + variables?: Variable[]; + scripts?: Scripts; + docs?: string; +} + +export interface Script { + type: 'script'; + name?: string; + script?: string; +} + +export type OpenCollectionItem = HttpRequest | Folder | Script; + +export interface Environment { + name: string; + description?: string; + variables?: Variable[]; +} + +export interface BaseConfiguration { + headers?: RequestHeader[]; + auth?: Auth; + variables?: Variable[]; + scripts?: Scripts; +} + +export interface OpenCollectionCollection { + name?: string; + description?: string; + environments?: Environment[]; + items?: OpenCollectionItem[]; + base?: BaseConfiguration; + docs?: string; +} + +export interface CustomPage { + name: string; + content?: string; + contentPath?: string; + consoleView?: React.ReactNode; +} + +export interface OpenCollectionPlaygroundProps { + collection: OpenCollectionCollection | string | File; + theme?: 'light' | 'dark' | 'auto'; + logo?: React.ReactNode; + customPages?: CustomPage[]; + hideSidebar?: boolean; + hideHeader?: boolean; + onlyShow?: string[]; + proxyUrl?: string; +} \ No newline at end of file diff --git a/packages/oc-core/src/ui/BodyCard.tsx b/packages/oc-core/src/ui/BodyCard.tsx new file mode 100644 index 00000000..79e216fd --- /dev/null +++ b/packages/oc-core/src/ui/BodyCard.tsx @@ -0,0 +1,71 @@ +import React, { useEffect, useRef } from 'react'; +import Prism from 'prismjs'; +import 'prismjs/components/prism-json'; +import 'prismjs/components/prism-xml-doc'; +import 'prismjs/components/prism-graphql'; + +interface BodyCardProps { + children: React.ReactNode; + className?: string; + language?: string; +} + +const BodyCard: React.FC = ({ + children, + className = '', + language = 'text' +}) => { + const codeRef = useRef(null); + + useEffect(() => { + if (codeRef.current) { + Prism.highlightAllUnder(codeRef.current); + } + }, [children, language]); + + return ( +
+ +
+ +
+
+            
+              {children}
+            
+          
+
+
+
+ ); +}; + +export default BodyCard; \ No newline at end of file diff --git a/packages/oc-core/src/ui/Card.tsx b/packages/oc-core/src/ui/Card.tsx new file mode 100644 index 00000000..a4a9c73e --- /dev/null +++ b/packages/oc-core/src/ui/Card.tsx @@ -0,0 +1,127 @@ +import React from 'react'; + +interface CardProps { + title?: string; + subtitle?: string; + badge?: { + text: string; + variant?: 'primary' | 'secondary' | 'success' | 'warning' | 'error'; + }; + children: React.ReactNode; + className?: string; + headerActions?: React.ReactNode; +} + +const Card: React.FC = ({ + title, + subtitle, + badge, + children, + className = '', + headerActions +}) => { + const getBadgeStyles = (variant: string = 'primary') => { + const baseStyles = { + display: 'inline-flex', + alignItems: 'center', + padding: '4px 8px', + borderRadius: '6px', + fontSize: '12px', + fontWeight: '500', + textTransform: 'uppercase' as const, + letterSpacing: '0.025em' + }; + + const variantStyles = { + primary: { + backgroundColor: 'var(--primary-color, #3b82f6)', + color: 'white' + }, + secondary: { + backgroundColor: 'var(--secondary-color, #6b7280)', + color: 'white' + }, + success: { + backgroundColor: 'var(--success-color, #10b981)', + color: 'white' + }, + warning: { + backgroundColor: 'var(--warning-color, #f59e0b)', + color: 'white' + }, + error: { + backgroundColor: 'var(--error-color, #ef4444)', + color: 'white' + } + }; + + return { ...baseStyles, ...variantStyles[variant as keyof typeof variantStyles] }; + }; + + return ( +
+ {(title || subtitle || badge || headerActions) && ( +
+
+ {title && ( +

+ {title} +

+ )} + + {badge && ( + + {badge.text} + + )} + + {subtitle && ( + + {subtitle} + + )} +
+ + {headerActions && ( +
+ {headerActions} +
+ )} +
+ )} + +
+ {children} +
+
+ ); +}; + +export default Card; \ No newline at end of file diff --git a/packages/oc-core/src/ui/CodeBlock.tsx b/packages/oc-core/src/ui/CodeBlock.tsx new file mode 100644 index 00000000..f0e2b342 --- /dev/null +++ b/packages/oc-core/src/ui/CodeBlock.tsx @@ -0,0 +1,177 @@ +import React from 'react'; + +interface CodeBlockProps { + code: string; + language?: string; + title?: string; + badge?: { + text: string; + variant?: 'primary' | 'secondary' | 'success' | 'warning' | 'error'; + }; + maxHeight?: string; + className?: string; + showLineNumbers?: boolean; +} + +const CodeBlock: React.FC = ({ + code, + language = 'text', + title, + badge, + maxHeight = '400px', + className = '', + showLineNumbers = false +}) => { + const getBadgeStyles = (variant: string = 'primary') => { + const baseStyles = { + display: 'inline-flex', + alignItems: 'center', + padding: '4px 8px', + borderRadius: '6px', + fontSize: '12px', + fontWeight: '500', + textTransform: 'uppercase' as const, + letterSpacing: '0.025em' + }; + + const variantStyles = { + primary: { + backgroundColor: 'var(--primary-color, #3b82f6)', + color: 'white' + }, + secondary: { + backgroundColor: 'var(--secondary-color, #6b7280)', + color: 'white' + }, + success: { + backgroundColor: 'var(--success-color, #10b981)', + color: 'white' + }, + warning: { + backgroundColor: 'var(--warning-color, #f59e0b)', + color: 'white' + }, + error: { + backgroundColor: 'var(--error-color, #ef4444)', + color: 'white' + } + }; + + return { ...baseStyles, ...variantStyles[variant as keyof typeof variantStyles] }; + }; + + const lines = code.split('\n'); + + return ( +
+ {(title || badge) && ( +
+ {title && ( + {title} + )} + + {badge && ( + + {badge.text} + + )} +
+ )} + +
+
+          {showLineNumbers ? (
+            
+              
+                {lines.map((line, index) => (
+                  
+                    
+                    
+                  
+                ))}
+              
+            
+ {index + 1} + + + {line || ' '} + +
+ ) : ( + + {code} + + )} +
+
+
+ ); +}; + +export default CodeBlock; \ No newline at end of file diff --git a/packages/oc-core/src/ui/CodeEditor.tsx b/packages/oc-core/src/ui/CodeEditor.tsx new file mode 100644 index 00000000..27654dd3 --- /dev/null +++ b/packages/oc-core/src/ui/CodeEditor.tsx @@ -0,0 +1,81 @@ +import React, { useEffect, useState } from 'react'; +import Editor from '@monaco-editor/react'; + +interface CodeEditorProps { + value: string; + onChange: (value: string) => void; + language?: string; + readOnly?: boolean; + height?: string; +} + +const CodeEditor: React.FC = ({ + value, + onChange, + language = 'json', + readOnly = false, + height = '300px' +}) => { + const [theme, setTheme] = useState<'light' | 'dark'>('light'); + + useEffect(() => { + // Detect current theme from document classes + const root = document.documentElement; + const isDark = root.classList.contains('dark'); + setTheme(isDark ? 'dark' : 'light'); + + // Listen for theme changes + const observer = new MutationObserver(() => { + const isDark = root.classList.contains('dark'); + setTheme(isDark ? 'dark' : 'light'); + }); + + observer.observe(root, { + attributes: true, + attributeFilter: ['class'] + }); + + return () => observer.disconnect(); + }, []); + + const handleChange = (newValue: string | undefined) => { + onChange(newValue || ''); + }; + + return ( +
+ +
+ ); +}; + +export default CodeEditor; \ No newline at end of file diff --git a/packages/oc-core/src/ui/ContentView.tsx b/packages/oc-core/src/ui/ContentView.tsx new file mode 100644 index 00000000..b267b60a --- /dev/null +++ b/packages/oc-core/src/ui/ContentView.tsx @@ -0,0 +1,344 @@ +import React, { useMemo } from 'react'; +import MarkdownIt from 'markdown-it'; +import '../styles/index.css'; +import { OpenCollectionItem, HttpRequest } from '../types'; + +interface ApiEndpoint { + id: string; + method: string; + path: string; + name?: string; + description?: string; + headers?: Record; + queryParams?: Record; + requestBody?: any; + brunoUrl?: string; +} + +interface ApiResponse { + status: number; + description?: string; + body?: any; +} + +interface ApiEndpointWithResponses extends ApiEndpoint { + responses?: ApiResponse[]; + isFolder?: boolean; +} + +export interface ContentViewProps { + endpoint: ApiEndpointWithResponses | OpenCollectionItem; + theme?: 'light' | 'dark' | 'auto'; + className?: string; +} + +const ContentView: React.FC = ({ endpoint, theme = 'light', className = '' }) => { + const processedEndpoint = useMemo(() => { + if (endpoint && 'type' in endpoint) { + const item = endpoint as OpenCollectionItem; + const itemName = item.type === 'http' ? (item as HttpRequest).name || item.type : item.type; + const itemId = itemName.replace(/\s+/g, '-').toLowerCase(); + + if (item.type !== 'http') { + return { + id: itemId, + method: item.type.toUpperCase(), + path: itemName, + name: itemName, + description: item.type === 'folder' ? (item as any).docs || '' : '', + isFolder: item.type === 'folder', + } as ApiEndpointWithResponses & { isFolder?: boolean }; + } + + const httpItem = item as HttpRequest; + const normalizedEndpoint: ApiEndpointWithResponses = { + id: itemId, + name: httpItem.name || 'Untitled', + method: httpItem.method || 'GET', + path: httpItem.url || '', + description: httpItem.docs || '', + headers: {}, + queryParams: {}, + }; + + if (httpItem.headers) { + normalizedEndpoint.headers = httpItem.headers.reduce( + (acc: Record, header: any) => { + if (header.disabled !== true) { + acc[header.name] = header.value; + } + return acc; + }, {} + ); + } + + if (httpItem.params) { + normalizedEndpoint.queryParams = httpItem.params.reduce( + (acc: Record, param: any) => { + if (param.enabled !== false) { + acc[param.name] = param.value; + } + return acc; + }, {} + ); + } + + if (httpItem.body && typeof httpItem.body === 'object' && 'data' in httpItem.body) { + const rawBody = httpItem.body as any; + if (rawBody.type === 'json') { + try { + normalizedEndpoint.requestBody = JSON.parse(rawBody.data || '{}'); + } catch { + normalizedEndpoint.requestBody = rawBody.data; + } + } else { + normalizedEndpoint.requestBody = rawBody.data; + } + } + + return normalizedEndpoint; + } + + return endpoint as ApiEndpointWithResponses; + }, [endpoint]); + + const md = useMemo(() => { + const markdownIt = new MarkdownIt({ + html: true, + linkify: true, + typographer: true, + breaks: true, + highlight: function (str: string, lang: string) { + return `
${str}
`; + } + }); + + const defaultRender = markdownIt.renderer.rules.heading_open || function(tokens: any, idx: number, options: any, env: any, self: any) { + return self.renderToken(tokens, idx, options); + }; + + markdownIt.renderer.rules.heading_open = function(tokens: any, idx: number, options: any, env: any, self: any) { + const token = tokens[idx]; + const level = token.tag.substr(1); // h1 -> 1, h2 -> 2, etc. + + // Add custom classes based on heading level + if (level === '1') { + token.attrJoin('class', 'text-2xl font-bold mt-6 mb-3 heading-1'); + } else if (level === '2') { + token.attrJoin('class', 'text-xl font-semibold mt-5 mb-2 heading-2'); + } else if (level === '3') { + token.attrJoin('class', 'text-lg font-semibold mt-4 mb-2 heading-3'); + } else { + token.attrJoin('class', 'font-semibold mt-3 mb-1 heading-4'); + } + + return defaultRender(tokens, idx, options, env, self); + }; + + return markdownIt; + }, []); + + return ( +
+ +
+
+ {processedEndpoint.isFolder ? ( +
+ + + +
+ ) : ( +
+ {processedEndpoint.method} +
+ )} +

+ {processedEndpoint.name || processedEndpoint.path.split('/').pop()} +

+ {processedEndpoint.brunoUrl && ( + + Open in Bruno + + )} +
+
+ {processedEndpoint.path} +
+
+ + +
+ + {processedEndpoint.description && ( +
+

Documentation

+
+
+ )} + + +
+

Request

+ + + {processedEndpoint.headers && Object.keys(processedEndpoint.headers).length > 0 && ( +
+

Headers

+
+ + + + + + + + + + {Object.entries(processedEndpoint.headers).map(([key, value]) => ( + + + + + + ))} + +
NameValueDescription
{key} + + {String(value)} + +
+
+
+ )} + + + {processedEndpoint.queryParams && Object.keys(processedEndpoint.queryParams).length > 0 && ( +
+

Query Parameters

+
+ + + + + + + + + + {Object.entries(processedEndpoint.queryParams).map(([key, value]) => ( + + + + + + ))} + +
NameValueDescription
{key} + + {String(value)} + +
+
+
+ )} + + + {processedEndpoint.requestBody && ( +
+

Request Body

+
+
{JSON.stringify(processedEndpoint.requestBody, null, 2)}
+
+
+ )} +
+ + + {processedEndpoint.responses && processedEndpoint.responses.length > 0 && ( +
+

Responses

+ + {processedEndpoint.responses.map((response: ApiResponse, index: number) => ( +
+
+

+ Status: {response.status} +

+ {response.description && ( + + {response.description} + + )} +
+ + {response.body && ( +
+
{JSON.stringify(response.body, null, 2)}
+
+ )} +
+ ))} +
+ )} +
+
+ ); +}; + +export default ContentView; \ No newline at end of file diff --git a/packages/oc-core/src/ui/CustomPageRenderer.tsx b/packages/oc-core/src/ui/CustomPageRenderer.tsx new file mode 100644 index 00000000..8dd6907a --- /dev/null +++ b/packages/oc-core/src/ui/CustomPageRenderer.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { CustomPage } from '../types'; +import ContentLayout from '../layouts/ContentLayout'; + +const CustomPageRenderer = ({ + pageName, + customPage, + content, + theme, + md, + registerSectionRef +}: { + pageName: string; + customPage?: CustomPage; + content: string; + theme: 'light' | 'dark' | 'auto'; + md: any; + registerSectionRef: (id: string, ref: HTMLDivElement | null) => void; +}) => { + const leftColumnContent = ( +
+
+
+ ); + + const rightColumnContent = customPage?.consoleView ? ( +
+ {customPage.consoleView} +
+ ) : ( +
+
+

+ Custom Page Information +

+
+ Name: + {pageName} +
+
+ Content Source: + {customPage?.contentPath ? 'External File' : 'Inline Content'} +
+
+
+ ); + + return ( +
registerSectionRef(pageName, ref)}> + +
+ ); +}; + +export default CustomPageRenderer; \ No newline at end of file diff --git a/packages/oc-core/src/ui/DataTable.tsx b/packages/oc-core/src/ui/DataTable.tsx new file mode 100644 index 00000000..52e71227 --- /dev/null +++ b/packages/oc-core/src/ui/DataTable.tsx @@ -0,0 +1,222 @@ +import React, { useState } from 'react'; + +interface Column { + key: string; + label: string; + render?: (value: any, row: any) => React.ReactNode; + width?: string; +} + +interface DataTableProps { + columns: Column[]; + data: any[]; + title?: string; + icon?: React.ReactNode; + maxVisibleRows?: number; + emptyMessage?: string; + className?: string; +} + +const DataTable: React.FC = ({ + columns, + data, + title, + icon, + maxVisibleRows = 10, + emptyMessage = 'No data available', + className = '' +}) => { + const [isExpanded, setIsExpanded] = useState(false); + const hasLotsOfRows = data.length > maxVisibleRows; + const displayData = hasLotsOfRows && !isExpanded ? data.slice(0, maxVisibleRows) : data; + + const toggleExpansion = () => { + setIsExpanded(prev => !prev); + }; + + if (!data.length) { + return ( +
+ {emptyMessage} +
+ ); + } + + return ( +
+ {(title || icon) && ( +
+ {icon} + {title} +
+ )} + +
+
+ + + + {columns.map((column) => ( + + ))} + + + + {displayData.map((row, rowIndex) => ( + + {columns.map((column) => ( + + ))} + + ))} + +
+ {column.label} +
+ {column.render ? column.render(row[column.key], row) : row[column.key]} +
+
+ + {hasLotsOfRows && !isExpanded && ( +
+
+
+ +
+
+ )} + + {hasLotsOfRows && isExpanded && ( +
+ +
+ )} +
+
+ ); +}; + +export default DataTable; \ No newline at end of file diff --git a/packages/oc-core/src/ui/EnvironmentVariablesSection.tsx b/packages/oc-core/src/ui/EnvironmentVariablesSection.tsx new file mode 100644 index 00000000..542e4bdb --- /dev/null +++ b/packages/oc-core/src/ui/EnvironmentVariablesSection.tsx @@ -0,0 +1,119 @@ +import React from 'react'; +import DataTable from './DataTable'; + +interface EnvironmentVariable { + name: string; + value: string; + enabled?: boolean; + local?: boolean; + uid?: string; +} + +interface Environment { + name: string; + uid?: string; + variables: EnvironmentVariable[]; +} + +interface EnvironmentVariablesSectionProps { + environments?: Array<{ + name: string; + variables: Array<{ + name: string; + value: string; + enabled: boolean; + type: string; + secret?: boolean; + }>; + uid?: string; + }>; + activeEnvironmentUid?: string; +} + +const EnvironmentVariablesSection: React.FC = ({ + environments, + activeEnvironmentUid +}) => { + if (!environments || environments.length === 0) { + return null; + } + + const columns = [ + { + key: 'name', + label: 'Name', + width: '40%' + }, + { + key: 'value', + label: 'Value', + width: '45%' + }, + { + key: 'enabled', + label: 'Status', + width: '15%', + render: (enabled: boolean) => ( + + {enabled !== false ? 'Enabled' : 'Disabled'} + + ) + } + ]; + + return ( +
+

+ Environment Variables +

+ +
+ {environments.map((env, envIndex) => { + const envId = env.uid || `env-${envIndex}`; + const isActive = activeEnvironmentUid === envId; + + return ( + + Active + + ) : undefined + } + columns={columns} + data={env.variables} + maxVisibleRows={8} + emptyMessage={`No variables defined for ${env.name}`} + /> + ); + })} +
+
+ ); +}; + +export default EnvironmentVariablesSection; \ No newline at end of file diff --git a/packages/oc-core/src/ui/ExpandableFormTable.tsx b/packages/oc-core/src/ui/ExpandableFormTable.tsx new file mode 100644 index 00000000..5fa05fc1 --- /dev/null +++ b/packages/oc-core/src/ui/ExpandableFormTable.tsx @@ -0,0 +1,170 @@ +import React, { useState } from 'react'; + +interface FormItem { + name: string; + value: string; + enabled?: boolean; + description?: string; + uid?: string; +} + +interface ExpandableFormTableProps { + items: FormItem[]; + title: string; + icon: React.ReactNode; + theme: 'light' | 'dark' | 'auto'; + maxVisibleRows?: number; +} + +const ExpandableFormTable: React.FC = ({ + items, + title, + icon, + theme, + maxVisibleRows = 10 +}) => { + const [isExpanded, setIsExpanded] = useState(false); + const hasLotsOfItems = items.length > maxVisibleRows; + + const toggleExpansion = () => { + setIsExpanded(prev => !prev); + }; + + return ( +
+
+ {icon} + {title} +
+
+
+ + + + + + + + + + {items.map((item, index) => ( + + + + + + ))} + +
NameValueStatus
{item.name}{item.value} + + {item.enabled !== false ? 'Enabled' : 'Disabled'} + +
+
+ + + {hasLotsOfItems && !isExpanded && ( +
+
+
+ +
+
+ )} + + + {hasLotsOfItems && isExpanded && ( +
+ +
+ )} +
+
+ ); +}; + +export default ExpandableFormTable; \ No newline at end of file diff --git a/packages/oc-core/src/ui/ItemComponent.tsx b/packages/oc-core/src/ui/ItemComponent.tsx new file mode 100644 index 00000000..db44008d --- /dev/null +++ b/packages/oc-core/src/ui/ItemComponent.tsx @@ -0,0 +1,1396 @@ +import React, { useState, useEffect, useRef, memo, useCallback } from 'react'; +import Prism from 'prismjs'; +import 'prismjs/components/prism-bash'; +import 'prismjs/components/prism-http'; +import 'prismjs/components/prism-graphql'; +import 'prismjs/components/prism-json'; +import 'prismjs/components/prism-xml-doc'; +import { OpenCollectionItem, OpenCollectionCollection, HttpRequest, Script, Folder } from '../types'; +import { generateSectionId, getItemId, generateSafeId } from '../utils/itemUtils'; +import { + HeadersTable, + VarsTable, + AssertionsTable, + MultipartFormTable, + FormUrlEncodedTable +} from './TableHelpers'; +import BodyCard from './BodyCard'; +import CodeBlock from './CodeBlock'; + +// This is a memoized component to render individual items +const ItemComponent = memo(({ + item, + registerSectionRef, + theme, + md, + parentPath = '', + collection +}: { + item: OpenCollectionItem; + registerSectionRef: (id: string, ref: HTMLDivElement | null) => void; + theme: 'light' | 'dark' | 'auto'; + md: any; + parentPath?: string; + collection?: OpenCollectionCollection; +}) => { + const [activeCodeTab, setActiveCodeTab] = useState<'curl' | 'fetch' | 'http'>('curl'); + const [codeCopied, setCodeCopied] = useState(false); + const codeRef = useRef(null); + + // Script-related hooks (moved from renderScripts to fix hook rendering error) + const [activeScriptTab, setActiveScriptTab] = useState<'req' | 'res'>('req'); + const scriptCodeRef = useRef(null); + + const itemId = getItemId(item); + const sectionId = generateSectionId(item, parentPath); + + const trackingId = parentPath ? `${parentPath}/${itemId}` : itemId; + + const sectionRefCallback = useCallback((element: HTMLDivElement | null) => { + if (element) { + console.log('Registering section ref for:', sectionId, 'with element ID:', element.id); + registerSectionRef(sectionId, element); + } + }, [sectionId, registerSectionRef]); + + // Effect for script syntax highlighting + useEffect(() => { + if (scriptCodeRef.current) { + Prism.highlightAllUnder(scriptCodeRef.current); + } + }, [activeScriptTab]); + + const renderHeadersTable = (headers: Array<{ name: string; value: string; enabled?: boolean; description?: string; uid?: string }>) => { + if (!headers || !headers.length) { + return null; + } + return ; + }; + + const renderVarsTable = (vars: Array<{ name: string; value: string; enabled?: boolean; local?: boolean; uid?: string }>) => { + if (!vars || !vars.length) { + return null; + } + return ; + }; + + const renderScripts = (script: Record) => { + if (!script || Object.keys(script).length === 0) { + return null; + } + + const scriptMap = { + req: script.req || script.pre || '', + res: script.res || script.post || '' + }; + + if (!scriptMap.req && !scriptMap.res) { + return null; + } + + return ( +
+
+ +
+
+ +
+ + + + + + +
+
+ + +
+ {activeScriptTab === 'req' && scriptMap.req && ( +
+
+                  {scriptMap.req}
+                
+
+ )} + + {activeScriptTab === 'res' && scriptMap.res && ( +
+
+                  {scriptMap.res}
+                
+
+ )} + + {activeScriptTab === 'req' && !scriptMap.req && ( +
+ No pre-request script available +
+ )} + + {activeScriptTab === 'res' && !scriptMap.res && ( +
+ No post-response script available +
+ )} +
+
+
+ ); + }; + + const formatCurlHeaders = (headers: Record = {}) => { + return Object.entries(headers) + .map(([key, value]) => `-H "${key}: ${value}"`) + .join(" \\\n "); + }; + + const formatQueryParams = (queryParams: Record = {}) => { + const params = Object.entries(queryParams) + .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) + .join("&"); + return params ? `?${params}` : ""; + }; + + const formatRequestBody = (requestBody: any, contentType: string = "application/json") => { + if (!requestBody) return ""; + + if (typeof requestBody === "string") { + if (contentType === "application/json") { + try { + const parsed = JSON.parse(requestBody); + return JSON.stringify(parsed, null, 2); + } catch { + return requestBody; + } + } else if (contentType === "application/xml") { + return requestBody; + } else { + return requestBody; + } + } + + return JSON.stringify(requestBody, null, 2); + }; + + const getNormalizedEndpoint = () => { + if (item.type !== 'http') return null; + + const httpItem = item as HttpRequest; + const headers: Record = {}; + if (httpItem.headers) { + httpItem.headers.forEach((header: any) => { + if (header.disabled !== true) { + headers[header.name] = header.value; + } + }); + } + + const queryParams: Record = {}; + if (httpItem.params) { + httpItem.params.forEach((param: any) => { + if (param.enabled !== false) { + queryParams[param.name] = param.value; + } + }); + } + + let requestBody = null; + let contentType = "application/json"; + + if (httpItem.body) { + if (typeof httpItem.body === 'object' && 'data' in httpItem.body) { + const rawBody = httpItem.body as any; + if (rawBody.type === 'json') { + try { + requestBody = JSON.parse(rawBody.data); + contentType = "application/json"; + } catch { + requestBody = rawBody.data; + } + } else if (rawBody.type === 'text') { + requestBody = rawBody.data; + contentType = "text/plain"; + } else if (rawBody.type === 'xml') { + requestBody = rawBody.data; + contentType = "application/xml"; + } + } + } + + const itemName = httpItem.name || httpItem.type; + return { + id: itemName, + method: httpItem.method || 'GET', + path: httpItem.url || '', + description: httpItem.docs || '', + headers: headers, + queryParams: queryParams, + requestBody: requestBody, + contentType: contentType + }; + }; + + const generateCodeSample = () => { + const endpoint = getNormalizedEndpoint(); + if (!endpoint) return ''; + + const { method, path, headers, queryParams, requestBody, contentType } = endpoint; + const url = `${path}${formatQueryParams(queryParams)}`; + + const headersWithContentType = { ...headers }; + if (requestBody && !Object.keys(headers).some(key => key.toLowerCase() === 'content-type')) { + headersWithContentType['Content-Type'] = contentType; + } + + switch (activeCodeTab) { + case 'curl': + let curlCommand = `curl -X ${method} "${url}"`; + if (Object.keys(headersWithContentType).length) { + curlCommand += ` \\\n ${formatCurlHeaders(headersWithContentType)}`; + } + if (requestBody) { + const bodyStr = formatRequestBody(requestBody, contentType); + curlCommand += ` \\\n -d '${bodyStr.replace(/'/g, "\\'")}'`; + } + return curlCommand; + + case 'fetch': + let fetchCode = `const options = {\n method: "${method}"`; + if (Object.keys(headersWithContentType).length) { + fetchCode += `,\n headers: {\n ${Object.entries(headersWithContentType) + .map(([key, value]) => `"${key}": "${value}"`) + .join(",\n ")}\n }`; + } + if (requestBody) { + if (contentType === "application/json") { + fetchCode += `,\n body: JSON.stringify(${formatRequestBody(requestBody, contentType)})`; + } else { + fetchCode += `,\n body: \`${formatRequestBody(requestBody, contentType).replace(/`/g, "\\`")}\``; + } + } + + let responseHandling = ""; + if (contentType === "application/json") { + responseHandling = ".then(response => response.json())\n .then(json => console.log(json))"; + } else if (contentType === "application/xml") { + responseHandling = ".then(response => response.text())\n .then(text => console.log(text))"; + } else { + responseHandling = ".then(response => response.text())\n .then(text => console.log(text))"; + } + + fetchCode += `\n};\n\nfetch("${url}", options)\n ${responseHandling}\n .catch(err => console.error('error', err));`; + return fetchCode; + + case 'http': + let httpRequest = `${method} ${url} HTTP/1.1\n`; + if (Object.keys(headersWithContentType).length) { + httpRequest += Object.entries(headersWithContentType) + .map(([key, value]) => `${key}: ${value}`) + .join("\n"); + } + if (requestBody) { + httpRequest += `\n\n${formatRequestBody(requestBody, contentType)}`; + } + return httpRequest; + + default: + return ''; + } + }; + + const handleCopyCode = () => { + const code = generateCodeSample(); + navigator.clipboard.writeText(code); + setCodeCopied(true); + setTimeout(() => setCodeCopied(false), 2000); + }; + + const renderAssertionsTable = (assertions: Array<{ name: string; value: string; enabled?: boolean; uid?: string }>) => { + if (!assertions || !assertions.length) { + return null; + } + return ; + }; + + const renderCodeSamples = () => { + if (item.type !== 'http') return null; + + const code = generateCodeSample(); + const codeLanguage = activeCodeTab === 'curl' ? 'bash' : activeCodeTab === 'fetch' ? 'javascript' : 'http'; + + const languageOptions = [ + { + id: 'curl', label: 'cURL', icon: ( + + + + ) + }, + { + id: 'fetch', label: 'Fetch API', icon: ( + + + + + + ) + }, + { + id: 'http', label: 'HTTP', icon: ( + + + + + ) + } + ]; + + const selectedLanguage = languageOptions.find(lang => lang.id === activeCodeTab) || languageOptions[0]; + + const [dropdownOpen, setDropdownOpen] = useState(false); + + const toggleDropdown = () => { + setDropdownOpen(!dropdownOpen); + }; + + useEffect(() => { + const handleClickOutside = () => { + setDropdownOpen(false); + }; + + if (dropdownOpen) { + document.addEventListener('click', handleClickOutside); + } + + return () => { + document.removeEventListener('click', handleClickOutside); + }; + }, [dropdownOpen]); + + return ( +
+
+ +
e.stopPropagation()}> + + + {dropdownOpen && ( +
+ {languageOptions.map(option => ( + + ))} +
+ )} +
+
+ +
+
+          {code}
+        
+
+ ); + }; + + // Highlight code when it changes or tab changes + useEffect(() => { + if (codeRef.current) { + Prism.highlightAllUnder(codeRef.current); + } + }, [activeCodeTab, item]); + + if (item.type === 'folder') { + const folderItem = item as Folder; + + return ( +
+
+

{folderItem.name || 'Untitled Folder'}

+
+ + + + + FOLDER + +
+ + {folderItem.docs && ( +
+
+
+ )} +
+ +
+ {!folderItem.docs && ( +
+ This folder contains configuration items. OpenCollection folders are containers for organizing requests and other items. +
+ )} + + {/* Folder configuration details */} +
+ {folderItem.headers && folderItem.headers.length > 0 && ( +
+

Folder Headers

+ {renderHeadersTable(folderItem.headers)} +
+ )} + + {folderItem.variables && folderItem.variables.length > 0 && ( +
+

Folder Variables

+ {renderVarsTable(folderItem.variables.map(v => ({ + name: v.name, + value: v.value || v.default || '', + enabled: !v.disabled, + local: v.transient + })))} +
+ )} + + {folderItem.auth && Object.keys(folderItem.auth).length > 0 && ( +
+

Folder Authentication

+
+ Type: {folderItem.auth.type} +
+
+ )} + + {folderItem.scripts && Object.keys(folderItem.scripts).length > 0 && ( +
+

Folder Scripts

+ {renderScripts({ + req: folderItem.scripts.preRequest || '', + res: folderItem.scripts.postResponse || '' + })} +
+ )} + + {/* Show nested items if they exist */} + {folderItem.items && folderItem.items.length > 0 && ( +
+
+ + + + + + Folder Contents ({folderItem.items.length}) +
+ +
+ {folderItem.items.map((nestedItem, index) => { + const nestedItemId = getItemId(nestedItem); + const safeId = generateSafeId(nestedItemId); + + const getMethodColor = (method: string) => { + const colors: Record = { + 'GET': '#10b981', + 'POST': '#3b82f6', + 'PUT': '#f59e0b', + 'PATCH': '#8b5cf6', + 'DELETE': '#ef4444', + 'HEAD': '#6b7280', + 'OPTIONS': '#6b7280' + }; + return colors[method?.toUpperCase()] || '#6b7280'; + }; + + return ( +
{ + // Navigate to the nested item page + if (typeof window !== 'undefined') { + const url = new URL(window.location.href); + url.searchParams.set('page', safeId); + window.history.pushState({}, '', url.toString()); + window.location.reload(); // Simple way to trigger navigation + } + }} + > + {/* Card Header */} +
+
+ {nestedItem.type === 'http' && ( +
+ + {(nestedItem as any).method || 'GET'} + + + + + +
+ )} + + {nestedItem.type === 'folder' && ( +
+
+ +
+ + FOLDER + +
+ )} + + {nestedItem.type === 'script' && ( +
+
+ +
+ + SCRIPT + +
+ )} +
+ + {/* Navigate Arrow */} + + + +
+ + {/* Card Content */} +
+

+ {nestedItem.name || nestedItemId} +

+ + {nestedItem.type === 'http' && (nestedItem as any).url && ( +

+ {(nestedItem as any).url} +

+ )} + + {nestedItem.type === 'http' && (nestedItem as any).docs && ( +

+ {(nestedItem as any).docs} +

+ )} + + {nestedItem.type === 'folder' && ( +

+ Contains {(nestedItem as any).items?.length || 0} items +

+ )} + + {nestedItem.type === 'script' && ( +

+ JavaScript execution script +

+ )} +
+ + {/* Hover Effect Overlay */} +
+
+ ); + })} +
+ +
+
+ + + + + + Click on any card to navigate to that item's dedicated page + +
+
+
+ )} +
+
+
+ ); + } + else if (item.type === 'script') { + const scriptItem = item as Script; + + return ( +
+
+

{scriptItem.name || 'Untitled Script'}

+
+ + + + + + + + + SCRIPT + +
+
+ +
+ {scriptItem.script && ( +
+
+
+ Script Content +
+
+                  {scriptItem.script}
+                
+
+
+ )} + + {!scriptItem.script && ( +
+ No script content available. +
+ )} +
+
+ ); + } + else if (item.type === 'http') { + const httpItem = item as HttpRequest; + const endpoint = { + id: itemId, + name: httpItem.name || 'Untitled', + method: httpItem.method || 'GET', + url: httpItem.url || '', + description: httpItem.docs || '', + headers: httpItem.headers || [], + body: httpItem.body || { mode: 'none' }, + params: httpItem.params || [], + auth: httpItem.auth || { mode: 'none' }, + vars: httpItem.variables || {}, + assertions: httpItem.assertions || [], + tests: '', + script: httpItem.scripts || {} + }; + + return ( +
+ +
+

{endpoint.name}

+
+ + {endpoint.method} + + {endpoint.url} +
+ + {endpoint.description && ( +
+
+
+ )} +
+ +
+ +
+ + {false && endpoint.auth && ( +
+

Authentication

+
+ {(endpoint.auth as any)?.mode === 'basic' && ( +
+
+ + + + + Basic Authentication +
+
+ {(endpoint.auth as any)?.username && ( +
+ Username: + {(endpoint.auth as any)?.username} +
+ )} + {(endpoint.auth as any)?.password && ( +
+ Password: + •••••••• +
+ )} +
+
+ )} + {(endpoint.auth as any)?.mode === 'bearer' && ( +
+
+ + + + + Bearer Token Authentication +
+
+ {(endpoint.auth as any)?.bearer.token && ( +
+ Token: + {(endpoint.auth as any)?.token || 'token here'} +
+ )} +
+
+ )} + {(endpoint.auth as any)?.mode === 'oauth2' && ( +
+
+ + + + + + + + OAuth 2.0 +
+
+ {(endpoint.auth as any)?.grantType && ( +
+ Grant Type: + {(endpoint.auth as any)?.grantType} +
+ )} + {(endpoint.auth as any)?.accessTokenUrl && ( +
+ Access Token URL: + {(endpoint.auth as any)?.accessTokenUrl} +
+ )} + {(endpoint.auth as any)?.clientId && ( +
+ Client ID: + {(endpoint.auth as any)?.clientId} +
+ )} + {(endpoint.auth as any)?.scope && ( +
+ Scope: + {(endpoint.auth as any)?.scope} +
+ )} +
+
+ )} +
+
+ )} + + + {endpoint.params && endpoint.params.length > 0 && ( +
+

Params

+
+ + + + + + + + + + {endpoint.params.map((param: any, index: number) => ( + + + + + + ))} + +
NameValueStatus
{param.name} + {param.value} + + + {param.enabled !== false ? 'Enabled' : 'Disabled'} + +
+
+
+ )} + + + {endpoint.headers && endpoint.headers.length > 0 && renderHeadersTable(endpoint.headers)} + + + {endpoint.body && ( +
+
+
+ + Body +
+ + {typeof endpoint.body === 'object' && 'type' in endpoint.body && 'data' in endpoint.body && ( + + {(endpoint.body as any).type === 'json' ? + JSON.stringify(JSON.parse((endpoint.body as any).data || '{}'), null, 2) : + (endpoint.body as any).data + } + + )} + + + {Array.isArray(endpoint.body) && endpoint.body.length > 0 && + endpoint.body[0] && 'name' in endpoint.body[0] && 'value' in endpoint.body[0] && + !('type' in endpoint.body[0]) && ( + + )} + + + {Array.isArray(endpoint.body) && endpoint.body.length > 0 && + endpoint.body[0] && 'type' in endpoint.body[0] && ( + + )} + + + {Array.isArray(endpoint.body) && endpoint.body.length > 0 && + endpoint.body[0] && 'filePath' in endpoint.body[0] && ( +
+

Files:

+ {(endpoint.body as any).map((file: any, index: number) => ( +
+ {file.filePath} ({file.contentType}) +
+ ))} +
+ )} + +
+

+ {typeof endpoint.body === 'object' && 'type' in endpoint.body ? + (endpoint.body as any).type : + Array.isArray(endpoint.body) ? 'form-data' : 'body' + } +

+
+
+
+ )} + + + {endpoint.vars && Array.isArray(endpoint.vars) && renderVarsTable(endpoint.vars)} + + + {endpoint.assertions && endpoint.assertions.length > 0 && renderAssertionsTable(endpoint.assertions.map((assertion: any) => ({ + name: assertion.expression || 'assertion', + value: assertion.value || '', + enabled: assertion.enabled !== false, + uid: assertion.expression + })))} + + + {endpoint.tests && ( + + )} + + + {endpoint.script && Object.keys(endpoint.script).length > 0 && renderScripts({ + req: (endpoint.script as any).preRequest || '', + res: (endpoint.script as any).postResponse || '', + test: (endpoint.script as any).tests || '', + hooks: (endpoint.script as any).hooks || '' + })} +
+ + +
+
+ {renderCodeSamples()} +
+
+
+
+ ); + } + + // Default case for unsupported item types + return ( +
+
+

{(item as any).name || 'Untitled Item'}

+
+ Unsupported item type: {(item as any).type} +
+
+
+ ); +}, (prevProps, nextProps) => { + if (prevProps.item.type !== nextProps.item.type) { + return false; + } + + const prevItemId = getItemId(prevProps.item); + const nextItemId = getItemId(nextProps.item); + if (prevItemId !== nextItemId) { + return false; + } + + return ( + prevProps.theme === nextProps.theme && + prevProps.parentPath === nextProps.parentPath && + prevProps.collection === nextProps.collection + ); +}); + +export default ItemComponent; \ No newline at end of file diff --git a/packages/oc-core/src/ui/MobileLayout.tsx b/packages/oc-core/src/ui/MobileLayout.tsx new file mode 100644 index 00000000..37d35ca5 --- /dev/null +++ b/packages/oc-core/src/ui/MobileLayout.tsx @@ -0,0 +1,145 @@ +import React from 'react'; +import { OpenCollectionCollection, CustomPage, OpenCollectionItem } from '../types'; +import Sidebar from './Sidebar'; +import SinglePageRenderer from './SinglePageRenderer'; +import Overview from './Overview'; + +interface MobileLayoutProps { + collectionData: OpenCollectionCollection | null; + hideSidebar: boolean; + theme: 'light' | 'dark' | 'auto'; + currentPageId: string | null; + currentPageItem: OpenCollectionItem | CustomPage | null; + currentPageIndex: number; + totalPages: number; + canGoNext: boolean; + canGoPrevious: boolean; + goToNext: () => void; + goToPrevious: () => void; + goToPage: (id: string) => void; + allPageItems: Array<{id: string; item: OpenCollectionItem | CustomPage; type: 'item' | 'custom' | 'overview'}>; + onSelectItem: (id: string, path?: string) => void; + filteredCustomPages: CustomPage[]; + onlyShow?: string[]; + mobileView: 'sidebar' | 'content'; + setMobileView: (view: 'sidebar' | 'content') => void; + containerRef: React.RefObject; + shouldShowOverview: boolean; + filteredCollectionItems: any[]; + md: any; + customPageContents: Record; +} + +const MobileLayout: React.FC = ({ + collectionData, + hideSidebar, + theme, + currentPageId, + currentPageItem, + currentPageIndex, + totalPages, + canGoNext, + canGoPrevious, + goToNext, + goToPrevious, + goToPage, + allPageItems, + onSelectItem, + filteredCustomPages, + onlyShow, + mobileView, + setMobileView, + containerRef, + shouldShowOverview, + filteredCollectionItems, + md, + customPageContents +}) => { + if (hideSidebar) { + return ( +
+
+ p.id === currentPageId)?.type || 'item'} + theme={theme} + md={md} + collection={collectionData} + customPageContents={customPageContents} + currentPageIndex={currentPageIndex} + totalPages={totalPages} + canGoNext={canGoNext} + canGoPrevious={canGoPrevious} + goToNext={goToNext} + goToPrevious={goToPrevious} + /> +
+
+ ); + } + + return ( +
+
+ {!hideSidebar && ( + + )} + +
+ + {!hideSidebar && ( +
+ +
+ )} + +
+
+ p.id === currentPageId)?.type || 'item'} + theme={theme} + md={md} + collection={collectionData} + customPageContents={customPageContents} + currentPageIndex={currentPageIndex} + totalPages={totalPages} + canGoNext={canGoNext} + canGoPrevious={canGoPrevious} + goToNext={goToNext} + goToPrevious={goToPrevious} + /> +
+
+
+ ); +}; + +export default MobileLayout; \ No newline at end of file diff --git a/packages/oc-core/src/ui/Overview.tsx b/packages/oc-core/src/ui/Overview.tsx new file mode 100644 index 00000000..7b2d57fb --- /dev/null +++ b/packages/oc-core/src/ui/Overview.tsx @@ -0,0 +1,204 @@ +import React from 'react'; +import { OpenCollectionCollection, OpenCollectionItem } from '../types'; +import ContentLayout from '../layouts/ContentLayout'; + +const Overview = ({ collection, theme }: { collection: OpenCollectionCollection; theme: 'light' | 'dark' | 'auto' }) => { + if (!collection) return null; + + const sectionHeadingStyle = { + fontSize: '1.25rem', + fontWeight: '600', + marginBottom: '0.75rem', + color: 'var(--text-primary)' + }; + + const tableHeaderStyle = { + backgroundColor: 'var(--table-header-bg)', + borderBottom: '1px solid var(--border-color)', + padding: '0.75rem 1rem', + textAlign: 'left' as const, + fontWeight: '600', + color: 'var(--text-primary)' + }; + + const tableCellStyle = { + padding: '0.75rem 1rem', + borderBottom: '1px solid var(--border-color)' + }; + + const tableContainerStyle = { + border: '1px solid var(--border-color)', + borderRadius: '0.375rem', + overflow: 'hidden', + backgroundColor: 'var(--background-color)', + boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' + }; + + const itemCounts = collection.items?.reduce((acc, item) => { + acc[item.type] = (acc[item.type] || 0) + 1; + return acc; + }, {} as Record) || {}; + + const leftColumnContent = ( + <> + {collection.description && ( +
+

Description

+
+ {collection.description} +
+
+ )} + + +
+

Collection Statistics

+
+ + + + + + + + + {Object.entries(itemCounts).map(([type, count]) => ( + + + + + ))} + + + + + +
TypeCount
+ + {type} + + + + {count} + +
+ Total Items + + + {collection.items?.length || 0} + +
+
+
+ + ); + + // Right column content - Environments + const rightColumnContent = ( + <> + {collection.environments && collection.environments.length > 0 && ( +
+

Environments

+
+ + + + + + + + + {collection.environments.map((env, index) => ( + + + + + ))} + +
NameVariables
+ + {env.name} + + {env.description && ( +
+ {env.description} +
+ )} +
+ + {env.variables?.length || 0} + +
+
+
+ )} + + + {collection.base && ( +
+

Base Configuration

+
+ + + {collection.base.headers && collection.base.headers.length > 0 && ( + + + + + )} + {collection.base.variables && collection.base.variables.length > 0 && ( + + + + + )} + {collection.base.auth && ( + + + + + )} + +
+ Headers + + + {collection.base.headers.length} + +
+ Variables + + + {collection.base.variables.length} + +
+ Authentication + + + Configured + +
+
+
+ )} + + ); + + return ( + + ); +}; + +export default Overview; \ No newline at end of file diff --git a/packages/oc-core/src/ui/PageNavigation.tsx b/packages/oc-core/src/ui/PageNavigation.tsx new file mode 100644 index 00000000..2fe613db --- /dev/null +++ b/packages/oc-core/src/ui/PageNavigation.tsx @@ -0,0 +1,124 @@ +import React, { useEffect } from 'react'; + +interface PageNavigationProps { + currentPageIndex: number; + totalPages: number; + canGoNext: boolean; + canGoPrevious: boolean; + goToNext: () => void; + goToPrevious: () => void; + currentPageName?: string; +} + +const PageNavigation: React.FC = ({ + currentPageIndex, + totalPages, + canGoNext, + canGoPrevious, + goToNext, + goToPrevious, + currentPageName +}) => { + // Keyboard navigation + useEffect(() => { + const handleKeyPress = (event: KeyboardEvent) => { + if (event.key === 'ArrowLeft' && canGoPrevious) { + goToPrevious(); + } else if (event.key === 'ArrowRight' && canGoNext) { + goToNext(); + } + }; + + document.addEventListener('keydown', handleKeyPress); + return () => document.removeEventListener('keydown', handleKeyPress); + }, [canGoNext, canGoPrevious, goToNext, goToPrevious]); + if (totalPages <= 1) { + return null; + } + + return ( +
+
+ + +
+
+
+ Page {currentPageIndex + 1} of {totalPages} +
+ {currentPageName && ( + + {currentPageName} + + )} +
+ + +
+
+ ); +}; + +export default PageNavigation; \ No newline at end of file diff --git a/packages/oc-core/src/ui/SearchModal.tsx b/packages/oc-core/src/ui/SearchModal.tsx new file mode 100644 index 00000000..aaa8b04c --- /dev/null +++ b/packages/oc-core/src/ui/SearchModal.tsx @@ -0,0 +1,136 @@ +import React, { useEffect } from 'react'; + +// Search Modal Component +const SearchModal = ({ + isSearchOpen, + setIsSearchOpen, + searchQuery, + setSearchQuery, + searchResults, + searchInputRef, + handleSearchResultSelect +}: { + isSearchOpen: boolean; + setIsSearchOpen: (open: boolean) => void; + searchQuery: string; + setSearchQuery: (query: string) => void; + searchResults: Array<{id: string; name: string; path?: string; type: string}>; + searchInputRef: React.RefObject; + handleSearchResultSelect: (result: {id: string; path?: string; type: string}) => void; +}) => { + if (!isSearchOpen) return null; + + // Use useEffect to maintain focus on the input when typing + useEffect(() => { + if (isSearchOpen && searchInputRef.current) { + searchInputRef.current.focus(); + } + }, [isSearchOpen, searchQuery, searchInputRef]); + + return ( +
setIsSearchOpen(false)} + > +
e.stopPropagation()} + > + +
+
+ +
+ setSearchQuery(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter' && searchResults.length > 0) { + handleSearchResultSelect(searchResults[0]); + } + }} + /> +
+ ESC +
+
+ + +
+ {searchResults.length > 0 ? ( +
    + {searchResults.map((result, index) => ( +
  • handleSearchResultSelect(result)} + > + + + {result.type === 'folder' ? ( + + + + ) : result.type === 'page' ? ( + + + + ) : result.type === 'script' ? ( + + + + + + + + ) : ( + + + + )} + + + +
    +
    {result.name}
    + {result.path && result.path !== result.name && ( +
    {result.path}
    + )} +
    +
  • + ))} +
+ ) : searchQuery ? ( +
+ No results found for "{searchQuery}" +
+ ) : ( +
+ Type to search for endpoints, folders, scripts, and pages +
+ )} +
+
+
+ ); +}; + +export default SearchModal; \ No newline at end of file diff --git a/packages/oc-core/src/ui/Sidebar.tsx b/packages/oc-core/src/ui/Sidebar.tsx new file mode 100644 index 00000000..4de9ca31 --- /dev/null +++ b/packages/oc-core/src/ui/Sidebar.tsx @@ -0,0 +1,426 @@ +import React, { useState, useMemo, useEffect, useCallback } from 'react'; +import { OpenCollectionCollection, OpenCollectionItem, HttpRequest, CustomPage, Folder } from '../types'; +import { getItemId, generateSafeId } from '../utils/itemUtils'; + +interface ApiEndpoint { + id: string; + method: string; + path: string; + description?: string; +} + +interface ApiCollection { + name: string; + description?: string; + version?: string; + endpoints: ApiEndpoint[]; +} + +export interface SidebarProps { + collection: OpenCollectionCollection | ApiCollection | null; + activeItemId: string | null; + onSelectItem: (id: string, parentPath?: string) => void; + logo?: React.ReactNode; + className?: string; + theme?: 'light' | 'dark' | 'auto'; + customPages?: CustomPage[]; + onlyShow?: string[]; +} + +const Sidebar: React.FC = ({ + collection, + activeItemId, + onSelectItem, + logo, + className = '', + theme = 'light', + customPages = [], + onlyShow = [] +}) => { + const [expandedFolders, setExpandedFolders] = useState>({}); + const [hoveredItemId, setHoveredItemId] = useState(null); + + const shouldShowItem = useCallback((itemId: string): boolean => { + if (!onlyShow.length) { + return true; + } + return onlyShow.includes(itemId); + }, [onlyShow]); + + useEffect(() => { + if (!collection) return; + + const initExpandedFolders: Record = {}; + + if ('items' in collection) { + const items = collection.items || []; + items.forEach((item) => { + if (item.type === 'folder') { + const itemId = getItemId(item); + initExpandedFolders[itemId] = true; + } + }); + } + + setExpandedFolders(initExpandedFolders); + }, [collection]); + + const toggleFolder = useCallback((folderPath: string) => { + setExpandedFolders((prev) => ({ + ...prev, + [folderPath]: !prev[folderPath] + })); + }, []); + + const handleMouseEnter = useCallback((itemId: string) => { + setHoveredItemId(itemId); + }, []); + + const handleMouseLeave = useCallback(() => { + setHoveredItemId(null); + }, []); + + const handleItemSelect = (id: string, parentPath: string = '') => { + console.log('handleItemSelect', id, 'parentPath:', parentPath); + onSelectItem(id, parentPath); + }; + + const normalizedItems = useMemo(() => { + if (!collection) return []; + + if ('endpoints' in collection) { + return collection.endpoints.map((endpoint: ApiEndpoint, index: number) => ({ + type: 'http' as const, + name: endpoint.path, + method: endpoint.method, + url: endpoint.path + })); + } else if ('items' in collection) { + return collection.items; + } + + return []; + }, [collection]); + + const filteredItems = useMemo(() => { + let items = normalizedItems; + + if (onlyShow.length && collection !== null && 'items' in collection) { + const filterItemsByOnlyShow = (items: OpenCollectionItem[]): OpenCollectionItem[] => { + return items.filter(item => { + const itemName = item.type === 'http' ? (item as HttpRequest).name || item.type : item.type; + if (shouldShowItem(itemName)) { + return true; + } + + + return false; + }); + }; + + items = filterItemsByOnlyShow(items as OpenCollectionItem[]); + } + + return items; + }, [normalizedItems, onlyShow, collection, shouldShowItem]); + + const filteredEndpoints = useMemo(() => { + if (!collection || !('endpoints' in collection) || !collection.endpoints) { + return []; + } + + let endpoints = collection.endpoints; + if (onlyShow.length) { + endpoints = endpoints.filter((endpoint: ApiEndpoint) => + shouldShowItem(endpoint.id) || shouldShowItem(endpoint.path) + ); + } + + return endpoints; + }, [collection, onlyShow, shouldShowItem]); + + const filteredCustomPages = useMemo(() => { + let pages = customPages; + if (onlyShow.length) { + pages = pages.filter(page => shouldShowItem(page.name)); + } + + return pages + }, [customPages, onlyShow, shouldShowItem]); + + const renderFolderIcon = (isExpanded: boolean) => ( + + + + ); + + const renderItem = (item: OpenCollectionItem, level = 0, parentPath = '') => { + const itemId = getItemId(item); + const itemName = itemId; + const itemPath = parentPath ? `${parentPath}/${itemName}` : itemName; + + const fullPathId = parentPath ? `${parentPath.replace(/^\//, '')}/${itemId}` : itemId; + + const isFolder = item.type === 'folder'; + const safeItemId = generateSafeId(itemId); + const isActive = !isFolder && (activeItemId === safeItemId || activeItemId === itemId); + + // Use fullPathId for hover state tracking + const isHovered = hoveredItemId === fullPathId; + const isExpanded = expandedFolders[itemId] || false; + + // Don't render if this item should be filtered out + if (!shouldShowItem(itemId)) { + return null; + } + + return ( +
+
isFolder ? toggleFolder(itemId) : handleItemSelect(safeItemId, itemPath)} + onMouseEnter={() => setHoveredItemId(fullPathId)} + onMouseLeave={() => setHoveredItemId(null)} + id={`sidebar-item-${fullPathId}`} + data-item-id={fullPathId} + > + + {level > 0 && ( +
+ )} + + + {isFolder ? ( +
+ {renderFolderIcon(isExpanded)} +
+ ) : ( +
+ {item.type === "http" ? (item as HttpRequest).method || 'GET' : 'GET'} +
+ )} + + +
+ {itemName} +
+ + + {isFolder && (item as Folder).items && (item as Folder).items!.length > 0 && ( +
+ {(item as Folder).items!.length} +
+ )} +
+ + + {isFolder && isExpanded && (item as Folder).items && ( +
+ +
+ + + {((item as Folder).items || []).map((child: OpenCollectionItem) => renderItem(child, level + 1, itemPath))} +
+ )} +
+ ); + }; + + return ( +
+ {logo && ( +
+ {logo} +
+ )} + +
+ + {collection && (!onlyShow.length || shouldShowItem('overview')) && ( +
+
handleItemSelect('overview', '')} + onMouseEnter={() => setHoveredItemId('overview')} + onMouseLeave={() => setHoveredItemId(null)} + id="sidebar-item-overview" + data-testid="overview" + > +
+ + + + +
+
Overview
+
+
+ )} + + + {filteredCustomPages.length > 0 && ( +
+ {filteredCustomPages.map((page) => { + const pageId = page.name; + const safePageId = generateSafeId(pageId); + const isActive = activeItemId === safePageId; + const isHovered = hoveredItemId === pageId; + + return ( +
+
handleItemSelect(safePageId, '')} + onMouseEnter={() => setHoveredItemId(pageId)} + onMouseLeave={() => setHoveredItemId(null)} + id={`sidebar-item-${pageId}`} + data-testid={`custom-page-${pageId}`} + > +
+ + + + +
+
{page.name}
+
+
+ ); + })} +
+ )} + + + {collection && 'items' in collection && filteredItems && filteredItems.length > 0 && ( + <> + {filteredItems.map((item: OpenCollectionItem) => renderItem(item))} + + )} + + {collection && 'endpoints' in collection && filteredEndpoints.length > 0 && ( + <> + {filteredEndpoints.map((endpoint: ApiEndpoint) => { + const endpointId = endpoint.id || `endpoint-${endpoint.path}`; + const safeEndpointId = generateSafeId(endpointId); + const fullPathId = endpoint.path ? `${endpoint.path.replace(/^\//, '')}/${endpointId}` : endpointId; + const isActive = activeItemId === safeEndpointId; + const isHovered = hoveredItemId === fullPathId; + + return ( + + ); + })} + + )} +
+
+ ); +}; + +export default Sidebar; \ No newline at end of file diff --git a/packages/oc-core/src/ui/SinglePageRenderer.tsx b/packages/oc-core/src/ui/SinglePageRenderer.tsx new file mode 100644 index 00000000..822eea9c --- /dev/null +++ b/packages/oc-core/src/ui/SinglePageRenderer.tsx @@ -0,0 +1,164 @@ +import React from 'react'; +import { OpenCollectionItem, OpenCollectionCollection, CustomPage, HttpRequest } from '../types'; +import ItemComponent from './ItemComponent'; +import Overview from './Overview'; +import CustomPageRenderer from './CustomPageRenderer'; +import PageNavigation from './PageNavigation'; +import { RequestRunner } from './request-runner'; + +interface SinglePageRendererProps { + currentPageItem: OpenCollectionItem | CustomPage | null; + currentPageId: string | null; + pageType: 'item' | 'custom' | 'overview'; + theme: 'light' | 'dark' | 'auto'; + md: any; + collection: OpenCollectionCollection | null; + customPageContents: Record; + // Navigation props + currentPageIndex: number; + totalPages: number; + canGoNext: boolean; + canGoPrevious: boolean; + goToNext: () => void; + goToPrevious: () => void; + // Runner mode props + isRunnerMode?: boolean; + proxyUrl?: string; +} + +const SinglePageRenderer: React.FC = ({ + currentPageItem, + currentPageId, + pageType, + theme, + md, + collection, + customPageContents, + currentPageIndex, + totalPages, + canGoNext, + canGoPrevious, + goToNext, + goToPrevious, + isRunnerMode, + proxyUrl +}) => { + if (!currentPageItem || !currentPageId) { + return ( +
+
+ + + +

No content available

+

Please select an item from the sidebar.

+
+
+ ); + } + + // Render overview + if (pageType === 'overview' && collection) { + return ( +
+
+ +
+ +
+ ); + } + + // Render custom page + if (pageType === 'custom') { + const customPage = currentPageItem as CustomPage; + const content = customPageContents[customPage.name] || ''; + + return ( +
+
+
+

+ + + + {customPage.name} +

+ {}} // No-op since we don't need section refs in page mode + /> +
+
+ +
+ ); + } + + // Render collection item + if (pageType === 'item') { + const item = currentPageItem as OpenCollectionItem; + + // Render in runner mode if enabled and it's an HTTP request + if (isRunnerMode && item.type === 'http' && collection) { + return ( +
+ +
+ ); + } + + return ( +
+
+ {}} // No-op since we don't need section refs in page mode + theme={theme} + md={md} + parentPath="" + collection={collection || undefined} + /> +
+ +
+ ); + } + + return null; +}; + +export default SinglePageRenderer; \ No newline at end of file diff --git a/packages/oc-core/src/ui/TableHelpers.tsx b/packages/oc-core/src/ui/TableHelpers.tsx new file mode 100644 index 00000000..d86217c3 --- /dev/null +++ b/packages/oc-core/src/ui/TableHelpers.tsx @@ -0,0 +1,221 @@ +import React from 'react'; +import DataTable from './DataTable'; + +interface HeaderItem { + name: string; + value: string; + enabled?: boolean; + description?: string; + uid?: string; +} + +interface VarItem { + name: string; + value: string; + enabled?: boolean; + local?: boolean; + uid?: string; +} + +interface AssertionItem { + name: string; + value: string; + enabled?: boolean; + uid?: string; +} + +interface FormItem { + name: string; + value: string; + enabled?: boolean; +} + +export const HeadersTable: React.FC<{ headers: HeaderItem[] }> = ({ headers }) => { + const columns = [ + { key: 'name', label: 'Name', width: '25%' }, + { key: 'value', label: 'Value', width: '40%' }, + { key: 'description', label: 'Description', width: '25%' }, + { + key: 'enabled', + label: 'Status', + width: '10%', + render: (enabled: boolean) => ( + + {enabled !== false ? 'Enabled' : 'Disabled'} + + ) + } + ]; + + return ( + + + + + } + columns={columns} + data={headers} + emptyMessage="No headers defined" + /> + ); +}; + +export const VarsTable: React.FC<{ vars: VarItem[] }> = ({ vars }) => { + const columns = [ + { key: 'name', label: 'Name', width: '40%' }, + { key: 'value', label: 'Value', width: '45%' }, + { + key: 'enabled', + label: 'Status', + width: '15%', + render: (enabled: boolean) => ( + + {enabled !== false ? 'Enabled' : 'Disabled'} + + ) + } + ]; + + return ( + + + + + + + + } + columns={columns} + data={vars} + emptyMessage="No variables defined" + /> + ); +}; + +export const AssertionsTable: React.FC<{ assertions: AssertionItem[] }> = ({ assertions }) => { + const columns = [ + { key: 'name', label: 'Name', width: '40%' }, + { key: 'value', label: 'Value', width: '45%' }, + { + key: 'enabled', + label: 'Status', + width: '15%', + render: (enabled: boolean) => ( + + {enabled !== false ? 'Enabled' : 'Disabled'} + + ) + } + ]; + + return ( + + + + + } + columns={columns} + data={assertions} + emptyMessage="No assertions defined" + /> + ); +}; + +export const MultipartFormTable: React.FC<{ formData: FormItem[] }> = ({ formData }) => { + const columns = [ + { key: 'name', label: 'Name', width: '40%' }, + { key: 'value', label: 'Value', width: '45%' }, + { + key: 'enabled', + label: 'Status', + width: '15%', + render: (enabled: boolean) => ( + + {enabled !== false ? 'Enabled' : 'Disabled'} + + ) + } + ]; + + return ( + + ); +}; + +export const FormUrlEncodedTable: React.FC<{ formData: FormItem[] }> = ({ formData }) => { + const columns = [ + { key: 'name', label: 'Name', width: '40%' }, + { key: 'value', label: 'Value', width: '45%' }, + { + key: 'enabled', + label: 'Status', + width: '15%', + render: (enabled: boolean) => ( + + {enabled !== false ? 'Enabled' : 'Disabled'} + + ) + } + ]; + + return ( + + ); +}; \ No newline at end of file diff --git a/packages/oc-core/src/ui/index.ts b/packages/oc-core/src/ui/index.ts new file mode 100644 index 00000000..b62a850a --- /dev/null +++ b/packages/oc-core/src/ui/index.ts @@ -0,0 +1,9 @@ +export { default as Card } from './Card'; +export { default as DataTable } from './DataTable'; +export { default as CodeBlock } from './CodeBlock'; +export { default as BodyCard } from './BodyCard'; +export { default as PageNavigation } from './PageNavigation'; +export { default as SinglePageRenderer } from './SinglePageRenderer'; +export { default as CodeEditor } from './CodeEditor'; +export * from './TableHelpers'; +export * from './request-runner'; \ No newline at end of file diff --git a/packages/oc-core/src/ui/request-runner/QueryBar.tsx b/packages/oc-core/src/ui/request-runner/QueryBar.tsx new file mode 100644 index 00000000..57f12dd0 --- /dev/null +++ b/packages/oc-core/src/ui/request-runner/QueryBar.tsx @@ -0,0 +1,90 @@ +import React, { useState, useEffect } from 'react'; +import { HttpRequest } from '../../types'; + +interface QueryBarProps { + item: HttpRequest; + onSendRequest: () => void; + isLoading: boolean; + onItemChange: (item: HttpRequest) => void; +} + +const QueryBar: React.FC = ({ item, onSendRequest, isLoading, onItemChange }) => { + const [url, setUrl] = useState(item.url || ''); + const [method, setMethod] = useState(item.method || 'GET'); + + useEffect(() => { + setUrl(item.url || ''); + setMethod(item.method || 'GET'); + }, [item]); + + const handleUrlChange = (newUrl: string) => { + setUrl(newUrl); + onItemChange({ ...item, url: newUrl }); + }; + + const handleMethodChange = (newMethod: string) => { + setMethod(newMethod); + onItemChange({ ...item, method: newMethod }); + }; + + const methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']; + + return ( +
+ + +
+ handleUrlChange(e.target.value)} + placeholder="Enter request URL" + className="w-full px-3 py-2 text-sm rounded border focus:outline-none focus:ring-2 focus:ring-blue-500" + style={{ + backgroundColor: 'var(--bg-secondary)', + borderColor: 'var(--border-color)', + color: 'var(--text-primary)' + }} + /> +
+ + +
+ ); +}; + +export default QueryBar; \ No newline at end of file diff --git a/packages/oc-core/src/ui/request-runner/RequestHeader.tsx b/packages/oc-core/src/ui/request-runner/RequestHeader.tsx new file mode 100644 index 00000000..f3831562 --- /dev/null +++ b/packages/oc-core/src/ui/request-runner/RequestHeader.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { HttpRequest, OpenCollectionCollection } from '../../types'; + +interface RequestHeaderProps { + item: HttpRequest; + collection: OpenCollectionCollection; + selectedEnvironment: string; + onEnvironmentChange: (environment: string) => void; +} + +const RequestHeader: React.FC = ({ + item, + collection, + selectedEnvironment, + onEnvironmentChange +}) => { + return ( +
+
+

+ {item.name || 'Untitled Request'} +

+ + + {item.method || 'GET'} + +
+ +
+ {collection.environments && collection.environments.length > 0 && ( +
+ + Environment: + + +
+ )} +
+
+ ); +}; + +export default RequestHeader; \ No newline at end of file diff --git a/packages/oc-core/src/ui/request-runner/RequestPane.tsx b/packages/oc-core/src/ui/request-runner/RequestPane.tsx new file mode 100644 index 00000000..eabc814b --- /dev/null +++ b/packages/oc-core/src/ui/request-runner/RequestPane.tsx @@ -0,0 +1,682 @@ +import React, { useState, useEffect } from 'react'; +import { HttpRequest } from '../../types'; +import CodeEditor from '../CodeEditor'; + +interface RequestPaneProps { + item: HttpRequest; + onItemChange: (item: HttpRequest) => void; +} + +const RequestPane: React.FC = ({ item, onItemChange }) => { + const [activeTab, setActiveTab] = useState<'params' | 'headers' | 'body' | 'auth' | 'scripts' | 'tests'>('params'); + + const tabs = [ + { id: 'params', label: 'Params', count: item.params?.length || 0 }, + { id: 'headers', label: 'Headers', count: item.headers?.length || 0 }, + { id: 'body', label: 'Body' }, + { id: 'auth', label: 'Auth' }, + { id: 'scripts', label: 'Scripts' }, + { id: 'tests', label: 'Tests' } + ] as const; + + const handleParamChange = (index: number, field: 'name' | 'value' | 'enabled', value: any) => { + const updatedParams = [...(item.params || [])]; + updatedParams[index] = { ...updatedParams[index], [field]: value }; + onItemChange({ ...item, params: updatedParams }); + }; + + const handleHeaderChange = (index: number, field: 'name' | 'value' | 'disabled', value: any) => { + const updatedHeaders = [...(item.headers || [])]; + updatedHeaders[index] = { ...updatedHeaders[index], [field]: value }; + onItemChange({ ...item, headers: updatedHeaders }); + }; + + const addParam = () => { + const newParams = [...(item.params || []), { name: '', value: '', enabled: true, type: 'query' as const }]; + onItemChange({ ...item, params: newParams }); + }; + + const addHeader = () => { + const newHeaders = [...(item.headers || []), { name: '', value: '', disabled: false }]; + onItemChange({ ...item, headers: newHeaders }); + }; + + const removeParam = (index: number) => { + const updatedParams = (item.params || []).filter((_, i) => i !== index); + onItemChange({ ...item, params: updatedParams }); + }; + + const removeHeader = (index: number) => { + const updatedHeaders = (item.headers || []).filter((_, i) => i !== index); + onItemChange({ ...item, headers: updatedHeaders }); + }; + + const renderTabContent = () => { + switch (activeTab) { + case 'params': + return ( +
+
+
+ + Query Parameters + + +
+ + {(item.params || []).length === 0 ? ( +
+ No parameters defined. Click "Add Parameter" to add one. +
+ ) : ( + (item.params || []).map((param, index) => ( +
+ handleParamChange(index, 'enabled', e.target.checked)} + className="w-4 h-4" + /> + handleParamChange(index, 'name', e.target.value)} + placeholder="Key" + className="flex-1 px-2 py-1 text-sm border rounded" + style={{ + backgroundColor: 'var(--bg-primary)', + borderColor: 'var(--border-color)', + color: 'var(--text-primary)' + }} + /> + handleParamChange(index, 'value', e.target.value)} + placeholder="Value" + className="flex-1 px-2 py-1 text-sm border rounded" + style={{ + backgroundColor: 'var(--bg-primary)', + borderColor: 'var(--border-color)', + color: 'var(--text-primary)' + }} + /> + + {param.type || 'query'} + + +
+ )) + )} +
+
+ ); + + case 'headers': + return ( +
+
+
+ + Headers + + +
+ + {(item.headers || []).length === 0 ? ( +
+ No headers defined. Click "Add Header" to add one. +
+ ) : ( + (item.headers || []).map((header, index) => ( +
+ handleHeaderChange(index, 'disabled', !e.target.checked)} + className="w-4 h-4" + /> + handleHeaderChange(index, 'name', e.target.value)} + placeholder="Header" + className="flex-1 px-2 py-1 text-sm border rounded" + style={{ + backgroundColor: 'var(--bg-primary)', + borderColor: 'var(--border-color)', + color: 'var(--text-primary)' + }} + /> + handleHeaderChange(index, 'value', e.target.value)} + placeholder="Value" + className="flex-1 px-2 py-1 text-sm border rounded" + style={{ + backgroundColor: 'var(--bg-primary)', + borderColor: 'var(--border-color)', + color: 'var(--text-primary)' + }} + /> + +
+ )) + )} +
+
+ ); + + case 'body': + return ( +
+
+
+
+ + Body Type: + + +
+
+ + {!item.body ? ( +
+ No body content. Select a body type to add content. +
+ ) : 'data' in item.body && typeof item.body.data === 'string' ? ( + { + if ('data' in item.body!) { + onItemChange({ + ...item, + body: { ...item.body, data: value } + }); + } + }} + language={item.body.type === 'json' ? 'json' : item.body.type === 'xml' ? 'xml' : 'text'} + height="300px" + /> + ) : Array.isArray(item.body) ? ( +
+
+ + Form Data + + +
+ + {(item.body as any[]).length === 0 ? ( +
+ No form fields defined. Click "Add Field" to add one. +
+ ) : ( + (item.body as any[]).map((field: any, index: number) => ( +
+ { + const updatedBody = [...(item.body as any[])]; + updatedBody[index] = { ...updatedBody[index], enabled: e.target.checked }; + onItemChange({ ...item, body: updatedBody }); + }} + className="w-4 h-4" + /> + { + const updatedBody = [...(item.body as any[])]; + updatedBody[index] = { ...updatedBody[index], name: e.target.value }; + onItemChange({ ...item, body: updatedBody }); + }} + placeholder="Key" + className="flex-1 px-2 py-1 text-sm border rounded" + style={{ + backgroundColor: 'var(--bg-primary)', + borderColor: 'var(--border-color)', + color: 'var(--text-primary)' + }} + /> + { + const updatedBody = [...(item.body as any[])]; + updatedBody[index] = { ...updatedBody[index], value: e.target.value }; + onItemChange({ ...item, body: updatedBody }); + }} + placeholder="Value" + className="flex-1 px-2 py-1 text-sm border rounded" + style={{ + backgroundColor: 'var(--bg-primary)', + borderColor: 'var(--border-color)', + color: 'var(--text-primary)' + }} + /> + +
+ )) + )} +
+ ) : ( +
+ Unsupported body type +
+ )} +
+
+ ); + + case 'auth': + return ( +
+
+
+ + Authentication + + +
+ + {!item.auth ? ( +
+ No authentication configured. Select an auth type to configure. +
+ ) : ( +
+
+ + Type: {item.auth.type} + +
+ + {item.auth.type === 'basic' && ( + <> +
+ + { + onItemChange({ + ...item, + auth: { ...item.auth!, username: e.target.value } + }); + }} + className="flex-1 px-2 py-1 text-sm border rounded" + style={{ + backgroundColor: 'var(--bg-secondary)', + borderColor: 'var(--border-color)', + color: 'var(--text-primary)' + }} + placeholder="Enter username" + /> +
+
+ + { + onItemChange({ + ...item, + auth: { ...item.auth!, password: e.target.value } + }); + }} + className="flex-1 px-2 py-1 text-sm border rounded" + style={{ + backgroundColor: 'var(--bg-secondary)', + borderColor: 'var(--border-color)', + color: 'var(--text-primary)' + }} + placeholder="Enter password" + /> +
+ + )} + + {item.auth.type === 'bearer' && ( +
+ + { + onItemChange({ + ...item, + auth: { ...item.auth!, token: e.target.value } + }); + }} + className="flex-1 px-2 py-1 text-sm border rounded" + style={{ + backgroundColor: 'var(--bg-secondary)', + borderColor: 'var(--border-color)', + color: 'var(--text-primary)' + }} + placeholder="Enter bearer token" + /> +
+ )} + + {item.auth.type === 'apikey' && ( + <> +
+ + { + onItemChange({ + ...item, + auth: { ...item.auth!, key: e.target.value } + }); + }} + className="flex-1 px-2 py-1 text-sm border rounded" + style={{ + backgroundColor: 'var(--bg-secondary)', + borderColor: 'var(--border-color)', + color: 'var(--text-primary)' + }} + placeholder="Enter API key name" + /> +
+
+ + { + onItemChange({ + ...item, + auth: { ...item.auth!, value: e.target.value } + }); + }} + className="flex-1 px-2 py-1 text-sm border rounded" + style={{ + backgroundColor: 'var(--bg-secondary)', + borderColor: 'var(--border-color)', + color: 'var(--text-primary)' + }} + placeholder="Enter API key value" + /> +
+
+ + +
+ + )} + + {!['basic', 'bearer', 'apikey'].includes(item.auth.type) && ( +
+ Configuration for {item.auth.type} auth is not yet implemented +
+ )} +
+ )} +
+
+ ); + + case 'scripts': + return ( +
+
+
+

+ Pre-request Script +

+ { + const scripts = item.scripts || {}; + onItemChange({ + ...item, + scripts: { ...scripts, preRequest: value } + }); + }} + language="javascript" + height="150px" + /> +
+ +
+

+ Post-response Script +

+ { + const scripts = item.scripts || {}; + onItemChange({ + ...item, + scripts: { ...scripts, postResponse: value } + }); + }} + language="javascript" + height="150px" + /> +
+ + +
+
+ ); + + case 'tests': + return ( +
+
+

+ Tests +

+ { + const scripts = item.scripts || {}; + onItemChange({ + ...item, + scripts: { ...scripts, tests: value } + }); + }} + language="javascript" + height="400px" + /> +
+
+ ); + + default: + return null; + } + }; + + return ( +
+
+ {tabs.map((tab) => ( + + ))} +
+ +
+ {renderTabContent()} +
+
+ ); +}; + +export default RequestPane; \ No newline at end of file diff --git a/packages/oc-core/src/ui/request-runner/RequestRunner.tsx b/packages/oc-core/src/ui/request-runner/RequestRunner.tsx new file mode 100644 index 00000000..e5c9bab8 --- /dev/null +++ b/packages/oc-core/src/ui/request-runner/RequestRunner.tsx @@ -0,0 +1,121 @@ +import React, { useState, useEffect } from 'react'; +import { HttpRequest, OpenCollectionCollection } from '../../types'; +import { createRequestRunner } from '../../runner'; +import RequestHeader from './RequestHeader'; +import QueryBar from './QueryBar'; +import RequestPane from './RequestPane'; +import ResponsePane from './ResponsePane'; + +interface RequestRunnerProps { + item: HttpRequest; + collection: OpenCollectionCollection; + proxyUrl?: string; +} + +const RequestRunner: React.FC = ({ item, collection, proxyUrl }) => { + const [editableItem, setEditableItem] = useState(item); + const [selectedEnvironment, setSelectedEnvironment] = useState(''); + const [response, setResponse] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [requestPaneWidth, setRequestPaneWidth] = useState(50); + const [isDragging, setIsDragging] = useState(false); + + useEffect(() => { + setEditableItem(item); + setResponse(null); + }, [item]); + + const handleSendRequest = async () => { + setIsLoading(true); + try { + const environment = collection.environments?.find(env => env.name === selectedEnvironment); + const runner = createRequestRunner(proxyUrl); + + const result = await runner.runRequest({ + item: editableItem, + collection, + environment, + runtimeVariables: {} + }); + setResponse(result); + } catch (error) { + setResponse({ + error: error instanceof Error ? error.message : 'Unknown error occurred' + }); + } finally { + setIsLoading(false); + } + }; + + const handleMouseDown = (e: React.MouseEvent) => { + setIsDragging(true); + e.preventDefault(); + }; + + const handleMouseMove = (e: MouseEvent) => { + if (!isDragging) return; + const container = document.querySelector('.request-runner-container'); + if (!container) return; + + const rect = container.getBoundingClientRect(); + const newWidth = ((e.clientX - rect.left) / rect.width) * 100; + + if (newWidth >= 20 && newWidth <= 80) { + setRequestPaneWidth(newWidth); + } + }; + + const handleMouseUp = () => { + setIsDragging(false); + }; + + React.useEffect(() => { + if (isDragging) { + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + } + }, [isDragging]); + + return ( +
+ + + + +
+
+ +
+ +
+ +
+ +
+
+
+ ); +}; + +export default RequestRunner; \ No newline at end of file diff --git a/packages/oc-core/src/ui/request-runner/ResponsePane.tsx b/packages/oc-core/src/ui/request-runner/ResponsePane.tsx new file mode 100644 index 00000000..453e56be --- /dev/null +++ b/packages/oc-core/src/ui/request-runner/ResponsePane.tsx @@ -0,0 +1,201 @@ +import React, { useState } from 'react'; +import CodeEditor from '../CodeEditor'; + +interface ResponsePaneProps { + response: any; + isLoading: boolean; +} + +const ResponsePane: React.FC = ({ response, isLoading }) => { + const [activeTab, setActiveTab] = useState<'body' | 'headers'>('body'); + + const tabs = [ + { id: 'body', label: 'Body' }, + { id: 'headers', label: 'Headers' } + ] as const; + + const formatJson = (data: any) => { + try { + return JSON.stringify(data, null, 2); + } catch { + return String(data); + } + }; + + const getStatusColor = (status?: number) => { + if (!status) return '#6b7280'; + if (status >= 200 && status < 300) return '#10b981'; + if (status >= 300 && status < 400) return '#f59e0b'; + if (status >= 400 && status < 500) return '#ef4444'; + if (status >= 500) return '#dc2626'; + return '#6b7280'; + }; + + const renderTabContent = () => { + if (isLoading) { + return ( +
+
+
+ Sending request... +
+
+ ); + } + + if (!response) { + return ( +
+
+
+ + + +
+

Click Send to make a request

+
+
+ ); + } + + if (response.error) { + return ( +
+
+

Request Failed

+

{response.error}

+
+
+ ); + } + + switch (activeTab) { + case 'body': + const responseText = typeof response.data === 'string' ? response.data : formatJson(response.data); + const contentType = response.headers?.['content-type'] || response.headers?.['Content-Type'] || ''; + let language = 'text'; + + if (contentType.includes('application/json') || contentType.includes('text/json')) { + language = 'json'; + } else if (contentType.includes('text/html')) { + language = 'html'; + } else if (contentType.includes('text/xml') || contentType.includes('application/xml')) { + language = 'xml'; + } else if (contentType.includes('text/css')) { + language = 'css'; + } else if (contentType.includes('text/javascript') || contentType.includes('application/javascript')) { + language = 'javascript'; + } + + return ( +
+
+ {}} // Read-only + language={language} + height="100%" + readOnly={true} + /> +
+
+ ); + + case 'headers': + return ( +
+
+ {response.headers ? ( + Object.entries(response.headers).map(([key, value]) => ( +
+ + {key}: + + + {String(value)} + +
+ )) + ) : ( +
+ No response headers +
+ )} +
+
+ ); + + + + default: + return null; + } + }; + + return ( +
+
+
+
+ {tabs.map((tab) => ( + + ))} +
+
+ + {response && !response.error && ( +
+
+ Status: + + {response.status} {response.statusText} + +
+ + {response.duration && ( +
+ Time: + + {response.duration}ms + +
+ )} + + {response.size && ( +
+ Size: + + {(response.size / 1024).toFixed(2)} KB + +
+ )} +
+ )} +
+ +
+ {renderTabContent()} +
+
+ ); +}; + +export default ResponsePane; \ No newline at end of file diff --git a/packages/oc-core/src/ui/request-runner/index.ts b/packages/oc-core/src/ui/request-runner/index.ts new file mode 100644 index 00000000..1c4a5489 --- /dev/null +++ b/packages/oc-core/src/ui/request-runner/index.ts @@ -0,0 +1,5 @@ +export { default as RequestRunner } from './RequestRunner'; +export { default as RequestHeader } from './RequestHeader'; +export { default as QueryBar } from './QueryBar'; +export { default as RequestPane } from './RequestPane'; +export { default as ResponsePane } from './ResponsePane'; \ No newline at end of file diff --git a/packages/oc-core/src/utils/fileUtils.ts b/packages/oc-core/src/utils/fileUtils.ts new file mode 100644 index 00000000..295800c2 --- /dev/null +++ b/packages/oc-core/src/utils/fileUtils.ts @@ -0,0 +1,93 @@ +import { isYamlFile } from './yamlUtils'; +import { parseYaml } from './yamlUtils'; + +const loadOpenCollectionData = async (source: string | File): Promise => { + let content: string; + + if (source instanceof File) { + content = await source.text(); + } else if (typeof source === 'string') { + if (source.startsWith('http://') || source.startsWith('https://')) { + const response = await fetch(source); + if (!response.ok) { + throw new Error(`Failed to fetch collection: ${response.statusText}`); + } + content = await response.text(); + } else { + content = source; + } + } else { + throw new Error('Invalid source type for collection'); + } + + try { + return parseYaml(content); + } catch (yamlError) { + try { + return JSON.parse(content); + } catch (jsonError) { + throw new Error('Failed to parse collection as YAML or JSON'); + } + } +}; + +export const loadCollectionFromUrl = async (source: string | File): Promise => { + try { + if (source instanceof File) { + const fileName = source.name.toLowerCase(); + + if (isYamlFile(fileName)) { + return await loadOpenCollectionData(source); + } else { + const content = await source.text(); + return JSON.parse(content); + } + } + + if (typeof source === 'string') { + if (source.startsWith('file://') || source.startsWith('/')) { + const filePath = source.replace('file://', ''); + + const response = await fetch(`http://localhost:3001/api/read-collection?path=${encodeURIComponent(filePath)}`); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || `Failed to load collection: ${response.statusText}`); + } + + return await response.json(); + } else if (source.startsWith('http://') || source.startsWith('https://')) { + const response = await fetch(source); + + if (!response.ok) { + throw new Error(`Failed to load collection: ${response.statusText}`); + } + + const contentType = response.headers.get('content-type') || ''; + const isYamlUrl = isYamlFile(source); + const isYamlContentType = contentType.includes('yaml') || contentType.includes('yml'); + + if (isYamlUrl || isYamlContentType) { + const yamlText = await response.text(); + return await loadOpenCollectionData(yamlText); + } else { + return await response.json(); + } + } else { + const response = await fetch(`http://localhost:3001/api/read-collection?path=${encodeURIComponent(source)}`); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || `Failed to load collection: ${response.statusText}`); + } + + return await response.json(); + } + } + + throw new Error('Invalid source type for collection loading'); + } catch (error) { + console.error('Error loading collection:', error); + throw error; + } +}; \ No newline at end of file diff --git a/packages/oc-core/src/utils/itemUtils.ts b/packages/oc-core/src/utils/itemUtils.ts new file mode 100644 index 00000000..bc838ef7 --- /dev/null +++ b/packages/oc-core/src/utils/itemUtils.ts @@ -0,0 +1,45 @@ +/** + * Utility functions for handling OpenCollection items + */ + +/** + * Generate a safe HTML ID from an item name or ID + * @param input The input string to convert to a safe ID + * @returns A safe HTML ID string + */ +export const generateSafeId = (input: string): string => { + if (!input) return 'unnamed-item'; + + return input + .toLowerCase() + .replace(/[^a-z0-9\-_]/g, '-') + .replace(/-+/g, '-') + .replace(/^-|-$/g, ''); +}; + +/** + * Get the effective ID for an item (prefers id, falls back to uid, then name) + * @param item The Bruno item + * @returns The effective ID string + */ +export const getItemId = (item: any): string => { + return item.id || item.uid || item.name || 'unnamed-item'; +}; + +/** + * Generate a section ID for use in HTML elements + * @param item The Bruno item + * @param parentPath Optional parent path for nested items + * @returns A safe section ID + */ +export const generateSectionId = (item: any, parentPath?: string): string => { + const itemId = getItemId(item); + const safeItemId = generateSafeId(itemId); + + if (parentPath) { + const safeParentPath = generateSafeId(parentPath); + return `${safeParentPath}-${safeItemId}`; + } + + return safeItemId; +}; \ No newline at end of file diff --git a/packages/oc-core/src/utils/yamlUtils.ts b/packages/oc-core/src/utils/yamlUtils.ts new file mode 100644 index 00000000..f710d4d3 --- /dev/null +++ b/packages/oc-core/src/utils/yamlUtils.ts @@ -0,0 +1,66 @@ +import { load } from 'js-yaml'; + +/** + * Utility functions for handling YAML collections + */ + +/** + * Check if a string represents a YAML file path or URL + * @param input The file path or URL to check + * @returns True if the input appears to be a YAML file + */ +export const isYamlFile = (input: string): boolean => { + const lowerInput = input.toLowerCase(); + return lowerInput.endsWith('.yml') || lowerInput.endsWith('.yaml'); +}; + +/** + * Parse YAML content into a JavaScript object + * @param yamlContent The YAML content as a string + * @returns Parsed JavaScript object + */ +export const parseYaml = (yamlContent: string): any => { + try { + return load(yamlContent); + } catch (error) { + throw new Error(`Failed to parse YAML: ${error instanceof Error ? error.message : 'Unknown error'}`); + } +}; + +/** + * Load collection data from either JSON or YAML format + * @param content The file content as a string + * @param filePath Optional file path to help determine format + * @returns Parsed collection data + */ +export const parseCollectionContent = (content: string, filePath?: string): any => { + if (filePath && isYamlFile(filePath)) { + return parseYaml(content); + } + + const trimmedContent = content.trim(); + + if (trimmedContent.startsWith('{') || trimmedContent.startsWith('[')) { + try { + return JSON.parse(content); + } catch (error) { + throw new Error(`Failed to parse JSON: ${error instanceof Error ? error.message : 'Unknown error'}`); + } + } + + return parseYaml(content); +}; + +/** + * Validate that the parsed content matches the expected collection structure + * @param data The parsed data to validate + * @returns True if the data appears to be a valid collection + */ +export const isValidCollection = (data: any): boolean => { + if (!data || typeof data !== 'object') { + return false; + } + + // Basic validation - should have a name and items array + return typeof data.name === 'string' && Array.isArray(data.items); +}; \ No newline at end of file diff --git a/packages/oc-core/src/vite-env.d.ts b/packages/oc-core/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/packages/oc-core/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/oc-core/standalone.d.ts b/packages/oc-core/standalone.d.ts new file mode 100644 index 00000000..bae650f5 --- /dev/null +++ b/packages/oc-core/standalone.d.ts @@ -0,0 +1,15 @@ +declare global { + interface Window { + OpenCollectionPlayground: { + render: (container: HTMLElement, props) => void; + unmount: (container: HTMLElement) => void; + }; + } +} + +export const OpenCollectionPlayground: { + render: (container: HTMLElement, props) => void; + unmount: (container: HTMLElement) => void; +}; + +export default OpenCollectionPlayground; \ No newline at end of file diff --git a/packages/oc-core/tsconfig.json b/packages/oc-core/tsconfig.json new file mode 100644 index 00000000..97d38557 --- /dev/null +++ b/packages/oc-core/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["DOM", "DOM.Iterable", "ES6"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": false, + "emitDeclarationOnly": false, + "jsx": "react-jsx", + "declaration": true, + "outDir": "dist", + "module": "ESNext" + }, + "include": [ + "src" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/packages/oc-core/vite.config.standalone.ts b/packages/oc-core/vite.config.standalone.ts new file mode 100644 index 00000000..416dd5ac --- /dev/null +++ b/packages/oc-core/vite.config.standalone.ts @@ -0,0 +1,39 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { resolve } from 'path'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + define: { + 'process.env.NODE_ENV': '"production"' + }, + build: { + lib: { + entry: resolve(__dirname, 'src/standalone.ts'), + name: 'OpenCollectionPlayground', + fileName: (format) => `playground.${format === 'es' ? 'esm' : format}.js`, + formats: ['umd', 'es'] + }, + rollupOptions: { + output: { + // Bundle everything into a single file + inlineDynamicImports: true, + manualChunks: undefined, + globals: {}, + exports: 'named' + } + }, + outDir: 'dist-standalone', + minify: 'terser', + terserOptions: { + compress: { + drop_console: true, + drop_debugger: true + } + } + }, + css: { + postcss: './postcss.config.cjs' + } +}); \ No newline at end of file diff --git a/packages/oc-core/vite.config.ts b/packages/oc-core/vite.config.ts new file mode 100644 index 00000000..98d26bc3 --- /dev/null +++ b/packages/oc-core/vite.config.ts @@ -0,0 +1,37 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import { resolve } from 'path' +import tailwindcss from '@tailwindcss/vite' + + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react(), tailwindcss()], + build: { + lib: { + entry: { + index: resolve(__dirname, 'src/index.ts'), + express: resolve(__dirname, 'src/express.ts'), + server: resolve(__dirname, 'src/server.ts') + }, + name: 'OpenCollectionPlayground', + fileName: (format, entryName) => { + if (entryName === 'index') { + return `oc-playground.${format}.js`; + } + return `${entryName}.${format === 'es' ? 'js' : 'cjs'}`; + }, + }, + rollupOptions: { + // externalize deps that shouldn't be bundled + external: ['react', 'react-dom', 'express'], + output: { + globals: { + react: 'React', + 'react-dom': 'ReactDOM', + express: 'express', + }, + }, + }, + }, +}) diff --git a/packages/oc-proxy/package-lock.json b/packages/oc-proxy/package-lock.json new file mode 100644 index 00000000..d418aa95 --- /dev/null +++ b/packages/oc-proxy/package-lock.json @@ -0,0 +1,967 @@ +{ + "name": "@opencollection/proxy", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@opencollection/proxy", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "axios": "^1.6.0", + "cors": "^2.8.5", + "express": "^4.18.2" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/packages/oc-proxy/package.json b/packages/oc-proxy/package.json new file mode 100644 index 00000000..38521bb3 --- /dev/null +++ b/packages/oc-proxy/package.json @@ -0,0 +1,23 @@ +{ + "name": "@opencollection/proxy", + "version": "1.0.0", + "description": "CORS proxy server for OpenCollection requests", + "main": "src/index.js", + "scripts": { + "start": "node src/index.js", + "dev": "node src/index.js" + }, + "keywords": [ + "opencollection", + "cors", + "proxy", + "http" + ], + "author": "OpenCollection", + "license": "MIT", + "dependencies": { + "express": "^4.18.2", + "cors": "^2.8.5", + "axios": "^1.6.0" + } +} \ No newline at end of file diff --git a/packages/oc-proxy/src/index.js b/packages/oc-proxy/src/index.js new file mode 100644 index 00000000..0c14b355 --- /dev/null +++ b/packages/oc-proxy/src/index.js @@ -0,0 +1,127 @@ +const express = require('express'); +const cors = require('cors'); +const axios = require('axios'); + +const app = express(); +const PORT = process.env.PORT || 3001; + +app.use(cors()); +app.use(express.json({ limit: '50mb' })); +app.use(express.urlencoded({ extended: true, limit: '50mb' })); + +app.post('/proxy', async (req, res) => { + try { + const { url, method, headers, body, timeout } = req.body; + + if (!url) { + return res.status(400).json({ error: 'URL is required' }); + } + + const startTime = Date.now(); + + const axiosConfig = { + method: method || 'GET', + url, + headers: headers || {}, + timeout: timeout || 30000, + validateStatus: () => true, + maxRedirects: 5 + }; + + if (body && ['POST', 'PUT', 'PATCH', 'DELETE'].includes((method || '').toUpperCase())) { + if (typeof body === 'string') { + try { + JSON.parse(body); + if (!axiosConfig.headers['content-type'] && !axiosConfig.headers['Content-Type']) { + axiosConfig.headers['content-type'] = 'application/json'; + } + } catch { + } + axiosConfig.data = body; + } else if (body instanceof FormData || body.constructor.name === 'FormData') { + axiosConfig.data = body; + } else { + // Object body, convert to JSON + axiosConfig.data = JSON.stringify(body); + if (!axiosConfig.headers['content-type'] && !axiosConfig.headers['Content-Type']) { + axiosConfig.headers['content-type'] = 'application/json'; + } + } + } + + const response = await axios(axiosConfig); + const endTime = Date.now(); + + let responseSize = 0; + try { + if (typeof response.data === 'string') { + responseSize = response.data.length; + } else if (response.data) { + responseSize = JSON.stringify(response.data).length; + } + } catch { + responseSize = 0; + } + + const responseData = { + status: response.status, + statusText: response.statusText, + headers: response.headers, + data: response.data, + duration: endTime - startTime, + size: responseSize, + url: response.config.url + }; + res.json(responseData); + } catch (error) { + const endTime = Date.now(); + + if (error.response) { + let errorSize = 0; + try { + if (typeof error.response.data === 'string') { + errorSize = error.response.data.length; + } else if (error.response.data) { + errorSize = JSON.stringify(error.response.data).length; + } + } catch { + errorSize = 0; + } + + res.json({ + status: error.response.status, + statusText: error.response.statusText, + headers: error.response.headers, + data: error.response.data, + duration: endTime - startTime, + size: errorSize, + url: error.config?.url + }); + } else { + res.status(500).json({ + error: error.message || 'Request failed', + duration: endTime - startTime + }); + } + } +}); + +app.get('/health', (req, res) => { + res.json({ status: 'OK', message: 'OpenCollection Proxy is running' }); +}); + +app.get('/', (req, res) => { + res.json({ + name: '@opencollection/proxy', + version: '1.0.0', + description: 'CORS proxy server for OpenCollection requests', + endpoints: { + 'POST /proxy': 'Forward HTTP requests and bypass CORS', + 'GET /health': 'Health check endpoint' + } + }); +}); + +app.listen(PORT, () => { + console.log(`OpenCollection Proxy running on http://localhost:${PORT}`); +}); \ No newline at end of file From 8e132d4134e47b60a50aadd99053e2bf04256707 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Fri, 19 Sep 2025 05:36:05 +0530 Subject: [PATCH 004/161] UX revamp --- .nvmrc | 1 + examples/vanilla-html/index.html | 148 +- package-lock.json | 1486 +++++++++++------ packages/oc-core/index.html | 3 + packages/oc-core/package.json | 4 +- .../oc-core/src/components/Method/Method.tsx | 20 + .../src/components/Method/StyledWrapper.ts | 77 + .../src/core/OpenCollectionPlayground.tsx | 82 +- packages/oc-core/src/hooks/index.ts | 1 - .../oc-core/src/hooks/usePageNavigation.ts | 173 -- packages/oc-core/src/index.ts | 1 - .../oc-core/src/layouts/ContentLayout.tsx | 2 +- .../oc-core/src/layouts/DesktopLayout.tsx | 147 +- packages/oc-core/src/standalone.ts | 23 + packages/oc-core/src/styles/index.css | 274 +-- packages/oc-core/src/ui/ItemComponent.tsx | 6 +- packages/oc-core/src/ui/MobileLayout.tsx | 36 +- packages/oc-core/src/ui/PageNavigation.tsx | 124 -- packages/oc-core/src/ui/Sidebar.tsx | 146 +- .../oc-core/src/ui/SinglePageRenderer.tsx | 67 +- packages/oc-core/src/ui/Tabs/StyledWrapper.ts | 69 + packages/oc-core/src/ui/Tabs/Tabs.tsx | 84 + packages/oc-core/src/ui/index.ts | 1 - .../src/ui/request-runner/QueryBar.tsx | 96 +- .../src/ui/request-runner/RequestHeader.tsx | 69 +- .../src/ui/request-runner/RequestPane.tsx | 93 +- .../src/ui/request-runner/RequestRunner.tsx | 27 +- .../src/ui/request-runner/ResponsePane.tsx | 283 ++-- packages/oc-core/src/utils/itemUtils.ts | 26 + packages/oc-core/vite.config.standalone.ts | 17 +- packages/oc-core/vite.config.ts | 7 +- 31 files changed, 1981 insertions(+), 1612 deletions(-) create mode 100644 .nvmrc create mode 100644 packages/oc-core/src/components/Method/Method.tsx create mode 100644 packages/oc-core/src/components/Method/StyledWrapper.ts delete mode 100644 packages/oc-core/src/hooks/usePageNavigation.ts delete mode 100644 packages/oc-core/src/ui/PageNavigation.tsx create mode 100644 packages/oc-core/src/ui/Tabs/StyledWrapper.ts create mode 100644 packages/oc-core/src/ui/Tabs/Tabs.tsx diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..05f04686 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v22.11.0 \ No newline at end of file diff --git a/examples/vanilla-html/index.html b/examples/vanilla-html/index.html index ae53b620..39ebe681 100644 --- a/examples/vanilla-html/index.html +++ b/examples/vanilla-html/index.html @@ -19,8 +19,8 @@
- - + + diff --git a/package-lock.json b/package-lock.json index e5bd1f01..6606796b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,130 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/compat-data": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", @@ -35,6 +159,93 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@codemirror/autocomplete": { "version": "6.18.7", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.7.tgz", @@ -116,6 +327,192 @@ "w3c-keyname": "^2.2.4" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache/node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/@emotion/css": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.13.5.tgz", + "integrity": "sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==", + "license": "MIT", + "dependencies": { + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.10", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", @@ -532,24 +929,64 @@ "node": ">=18" } }, - "node_modules/@lezer/common": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", - "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==", - "license": "MIT" - }, - "node_modules/@lezer/highlight": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", - "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", - "license": "MIT", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dependencies": { - "@lezer/common": "^1.0.0" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@lezer/lr": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lezer/common": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", + "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", + "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", "license": "MIT", "dependencies": { @@ -620,7 +1057,7 @@ "node": ">= 8" } }, - "node_modules/@opencollection/playground": { + "node_modules/@opencollection/core": { "resolved": "packages/oc-core", "link": true }, @@ -922,6 +1359,12 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, "node_modules/@types/trusted-types": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-1.0.6.tgz", @@ -964,12 +1407,61 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/babel-plugin-macros/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.6.tgz", + "integrity": "sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==", + "dev": true, + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -992,6 +1484,39 @@ "node": ">=8" } }, + "node_modules/browserslist": { + "version": "4.26.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", + "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "baseline-browser-mapping": "^2.8.3", + "caniuse-lite": "^1.0.30001741", + "electron-to-chromium": "^1.5.218", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1001,6 +1526,26 @@ "node": ">=6" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001743", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz", + "integrity": "sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, "node_modules/cli": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", @@ -1043,6 +1588,12 @@ "date-now": "^0.1.4" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -1081,11 +1632,33 @@ "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", "license": "MIT" }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, "node_modules/date-now": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", "integrity": "sha512-AsElvov3LoNB7tf5k37H2jYSB+ZZPMT5sG2QjJCcdlV5chIv6htBUBUui2IKRjgtKAKtCBN7Zbwa+MtwLjSeNw==" }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/diff": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", @@ -1149,6 +1722,12 @@ "domelementtype": "1" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.222", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.222.tgz", + "integrity": "sha512-gA7psSwSwQRE60CEoLz6JBCQPIxNeuzB2nL8vE03GK/OHxlvykbLyeiumQy1iH5C2f3YbRAZpGCMT12a/9ih9w==", + "dev": true + }, "node_modules/entities": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", @@ -1214,6 +1793,36 @@ "@esbuild/win32-x64": "0.25.10" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -1281,6 +1890,28 @@ "node": ">=8" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1301,6 +1932,23 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -1346,6 +1994,27 @@ "node": "*" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/htmlparser2": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", @@ -1398,6 +2067,20 @@ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "license": "MIT" }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1452,6 +2135,17 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jshint": { "version": "2.13.6", "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.13.6.tgz", @@ -1494,12 +2188,39 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -1518,6 +2239,15 @@ "loose-envify": "cli.js" } }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -1561,6 +2291,11 @@ "@types/trusted-types": "^1.0.6" } }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -1579,6 +2314,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-releases": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", + "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "dev": true + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1588,23 +2329,53 @@ "wrappy": "1" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "license": "MIT", + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "dependencies": { - "callsites": "^3.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "license": "MIT", + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -1618,6 +2389,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1627,6 +2407,19 @@ "node": ">=0.10.0" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1718,6 +2511,13 @@ "react": "^18.3.1" } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT", + "peer": true + }, "node_modules/readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", @@ -1739,6 +2539,25 @@ "node": ">=0.10.0" } }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -1830,6 +2649,15 @@ "loose-envify": "^1.1.0" } }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1857,6 +2685,17 @@ "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", "license": "MIT" }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -1914,6 +2753,42 @@ "node": ">=8.0" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/vite": { "version": "6.3.6", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz", @@ -2029,11 +2904,45 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "packages/oc-core": { - "name": "@opencollection/playground", + "name": "@opencollection/core", "version": "1.0.0", "license": "MIT", "dependencies": { + "@emotion/css": "^11.13.5", + "@emotion/styled": "^11.14.1", "@monaco-editor/react": "^4.7.0", "@prantlf/jsonlint": "^16.0.0", "@tailwindcss/vite": "^4.0.9", @@ -2095,159 +3004,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/oc-core/node_modules/@babel/compat-data": { - "version": "7.28.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "packages/oc-core/node_modules/@babel/core": { - "version": "7.28.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.4", - "@babel/types": "^7.28.4", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "packages/oc-core/node_modules/@babel/generator": { - "version": "7.28.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "packages/oc-core/node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "packages/oc-core/node_modules/@babel/helper-globals": { - "version": "7.28.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "packages/oc-core/node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "packages/oc-core/node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "packages/oc-core/node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "packages/oc-core/node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "packages/oc-core/node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "packages/oc-core/node_modules/@babel/helpers": { - "version": "7.28.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "packages/oc-core/node_modules/@babel/parser": { - "version": "7.28.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.4" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, "packages/oc-core/node_modules/@babel/plugin-transform-react-jsx-self": { "version": "7.27.1", "dev": true, @@ -2276,48 +3032,6 @@ "@babel/core": "^7.0.0-0" } }, - "packages/oc-core/node_modules/@babel/template": { - "version": "7.27.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "packages/oc-core/node_modules/@babel/traverse": { - "version": "7.28.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "packages/oc-core/node_modules/@babel/types": { - "version": "7.28.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "packages/oc-core/node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", "dev": true, @@ -2559,48 +3273,13 @@ "node": ">=18.0.0" } }, - "packages/oc-core/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "packages/oc-core/node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "packages/oc-core/node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "packages/oc-core/node_modules/@jridgewell/source-map": { "version": "0.3.11", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "packages/oc-core/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "license": "MIT" - }, - "packages/oc-core/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "packages/oc-core/node_modules/@rolldown/pluginutils": { @@ -3262,70 +3941,11 @@ "url": "https://github.com/sponsors/wooorm" } }, - "packages/oc-core/node_modules/baseline-browser-mapping": { - "version": "2.8.4", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "packages/oc-core/node_modules/browserslist": { - "version": "4.26.0", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.8.2", - "caniuse-lite": "^1.0.30001741", - "electron-to-chromium": "^1.5.218", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, "packages/oc-core/node_modules/buffer-from": { "version": "1.1.2", "dev": true, "license": "MIT" }, - "packages/oc-core/node_modules/caniuse-lite": { - "version": "1.0.30001741", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, "packages/oc-core/node_modules/ccount": { "version": "2.0.1", "license": "MIT", @@ -3517,11 +4137,6 @@ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, - "packages/oc-core/node_modules/convert-source-map": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, "packages/oc-core/node_modules/cross-spawn": { "version": "7.0.6", "dev": true, @@ -3546,25 +4161,6 @@ "node": ">=4" } }, - "packages/oc-core/node_modules/csstype": { - "version": "3.1.3", - "license": "MIT" - }, - "packages/oc-core/node_modules/debug": { - "version": "4.4.3", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "packages/oc-core/node_modules/decode-named-character-reference": { "version": "1.2.0", "license": "MIT", @@ -3611,11 +4207,6 @@ "dev": true, "license": "MIT" }, - "packages/oc-core/node_modules/electron-to-chromium": { - "version": "1.5.218", - "dev": true, - "license": "ISC" - }, "packages/oc-core/node_modules/emoji-regex": { "version": "9.2.2", "dev": true, @@ -3642,25 +4233,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "packages/oc-core/node_modules/escalade": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "packages/oc-core/node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "packages/oc-core/node_modules/eslint": { "version": "9.35.0", "dev": true, @@ -3839,14 +4411,6 @@ "url": "https://opencollective.com/unified" } }, - "packages/oc-core/node_modules/esutils": { - "version": "2.0.3", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "packages/oc-core/node_modules/extend": { "version": "3.0.2", "license": "MIT" @@ -3898,21 +4462,6 @@ "node": ">=16.0.0" } }, - "packages/oc-core/node_modules/find-up": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "packages/oc-core/node_modules/flat-cache": { "version": "4.0.1", "dev": true, @@ -3957,14 +4506,6 @@ "url": "https://github.com/sponsors/rawify" } }, - "packages/oc-core/node_modules/gensync": { - "version": "1.0.0-beta.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "packages/oc-core/node_modules/get-caller-file": { "version": "2.0.5", "dev": true, @@ -4192,17 +4733,6 @@ "jiti": "lib/jiti-cli.mjs" } }, - "packages/oc-core/node_modules/jsesc": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "packages/oc-core/node_modules/json-buffer": { "version": "3.0.1", "dev": true, @@ -4213,17 +4743,6 @@ "dev": true, "license": "MIT" }, - "packages/oc-core/node_modules/json5": { - "version": "2.2.3", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "packages/oc-core/node_modules/keyv": { "version": "4.5.4", "dev": true, @@ -4295,20 +4814,6 @@ "uc.micro": "^2.0.0" } }, - "packages/oc-core/node_modules/locate-path": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "packages/oc-core/node_modules/lodash.castarray": { "version": "4.4.0", "dev": true, @@ -4332,14 +4837,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "packages/oc-core/node_modules/lru-cache": { - "version": "5.1.1", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, "packages/oc-core/node_modules/magic-string": { "version": "0.30.19", "license": "MIT", @@ -5172,20 +5669,11 @@ "url": "https://github.com/sponsors/isaacs" } }, - "packages/oc-core/node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, "packages/oc-core/node_modules/natural-compare": { "version": "1.4.0", "dev": true, "license": "MIT" }, - "packages/oc-core/node_modules/node-releases": { - "version": "2.0.21", - "dev": true, - "license": "MIT" - }, "packages/oc-core/node_modules/normalize-range": { "version": "0.1.2", "dev": true, @@ -5210,34 +5698,6 @@ "node": ">= 0.8.0" } }, - "packages/oc-core/node_modules/p-limit": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "packages/oc-core/node_modules/p-locate": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "packages/oc-core/node_modules/package-json-from-dist": { "version": "1.0.1", "dev": true, @@ -5264,14 +5724,6 @@ "version": "2.0.11", "license": "MIT" }, - "packages/oc-core/node_modules/path-exists": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "packages/oc-core/node_modules/path-key": { "version": "3.1.1", "dev": true, @@ -5524,14 +5976,6 @@ "tslib": "^2.1.0" } }, - "packages/oc-core/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "packages/oc-core/node_modules/set-cookie-parser": { "version": "2.7.1", "license": "MIT" @@ -5829,11 +6273,6 @@ "typescript": ">=4.8.4" } }, - "packages/oc-core/node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "license": "0BSD" - }, "packages/oc-core/node_modules/type-check": { "version": "0.4.0", "dev": true, @@ -5963,35 +6402,6 @@ "url": "https://opencollective.com/unified" } }, - "packages/oc-core/node_modules/update-browserslist-db": { - "version": "1.1.3", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "packages/oc-core/node_modules/uri-js": { "version": "4.4.1", "dev": true, @@ -6140,11 +6550,6 @@ "node": ">=10" } }, - "packages/oc-core/node_modules/yallist": { - "version": "3.1.1", - "dev": true, - "license": "ISC" - }, "packages/oc-core/node_modules/yargs": { "version": "17.7.2", "dev": true, @@ -6207,17 +6612,6 @@ "node": ">=8" } }, - "packages/oc-core/node_modules/yocto-queue": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "packages/oc-core/node_modules/zwitch": { "version": "2.0.4", "license": "MIT", diff --git a/packages/oc-core/index.html b/packages/oc-core/index.html index ce4b2ef5..c748ff79 100644 --- a/packages/oc-core/index.html +++ b/packages/oc-core/index.html @@ -4,6 +4,9 @@ + + + OpenCollection Playground - Interactive API Documentation diff --git a/packages/oc-core/package.json b/packages/oc-core/package.json index bfdb30e7..c7dfae6b 100644 --- a/packages/oc-core/package.json +++ b/packages/oc-core/package.json @@ -1,5 +1,5 @@ { - "name": "@opencollection/playground", + "name": "@opencollection/core", "version": "1.0.0", "description": "Interactive API documentation component for OpenCollection - supports both React components and standalone JavaScript", "type": "module", @@ -47,6 +47,8 @@ "clean:standalone": "rimraf dist-standalone" }, "dependencies": { + "@emotion/css": "^11.13.5", + "@emotion/styled": "^11.14.1", "@monaco-editor/react": "^4.7.0", "@prantlf/jsonlint": "^16.0.0", "@tailwindcss/vite": "^4.0.9", diff --git a/packages/oc-core/src/components/Method/Method.tsx b/packages/oc-core/src/components/Method/Method.tsx new file mode 100644 index 00000000..f555638d --- /dev/null +++ b/packages/oc-core/src/components/Method/Method.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { StyledWrapper } from './StyledWrapper'; + +interface MethodProps { + method: string; + className?: string; +} + +export const Method: React.FC = ({ method, className = '' }) => { + const normalizedMethod = method?.toLowerCase() || 'get'; + const displayMethod = method?.toUpperCase() === 'DELETE' ? 'DEL' : method?.toUpperCase() || 'GET'; + + return ( + + {displayMethod} + + ); +}; + +export default Method; diff --git a/packages/oc-core/src/components/Method/StyledWrapper.ts b/packages/oc-core/src/components/Method/StyledWrapper.ts new file mode 100644 index 00000000..91be2176 --- /dev/null +++ b/packages/oc-core/src/components/Method/StyledWrapper.ts @@ -0,0 +1,77 @@ +import styled from '@emotion/styled'; + +export const StyledWrapper = styled.div` + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.1rem 0.4rem; + font-size: 10px; + font-weight: 600; + border-radius: 4px; + text-transform: uppercase; + font-family: var(--font-sans); + min-width: 42px; + margin-right: 8px; + + &.get { + background-color: #e6f0ff; + color: #0057b7; + } + + &.post { + background-color: #e6f9e6; + color: #00824d; + } + + &.put { + background-color: #fff0e6; + color: #b74600; + } + + &.delete { + background-color: #ffe6e6; + color: #b70000; + } + + &.patch { + background-color: #f9e6f9; + color: #6b0082; + } + + &.options, &.head { + background-color: #f0f0f0; + color: #4a4a4a; + } + + .dark & { + &.get { + background-color: #1a3a5c; + color: #0077ff; + } + + &.post { + background-color: #1a4d1a; + color: #00a060; + } + + &.put { + background-color: #4d2a00; + color: #ff6b00; + } + + &.delete { + background-color: #4d0000; + color: #ff0000; + } + + &.patch { + background-color: #3d003d; + color: #9500b3; + } + + &.options, &.head { + background-color: #2a2a2a; + color: #888888; + } + } +`; \ No newline at end of file diff --git a/packages/oc-core/src/core/OpenCollectionPlayground.tsx b/packages/oc-core/src/core/OpenCollectionPlayground.tsx index 8134a3bd..ae153fd7 100644 --- a/packages/oc-core/src/core/OpenCollectionPlayground.tsx +++ b/packages/oc-core/src/core/OpenCollectionPlayground.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React, { useRef, useState, useEffect } from 'react'; import { useCollectionData, useCustomPages, @@ -7,7 +7,6 @@ import { useTheme, useItemFiltering, useSearchModal, - usePageNavigation, useRunnerMode } from '../hooks'; import { OpenCollectionPlaygroundProps } from '../types'; @@ -39,25 +38,54 @@ const OpenCollectionPlayground: React.FC = ({ shouldShowOverview } = useItemFiltering(collectionData, validCustomPages, onlyShow); - const { - currentPageId, - currentPageItem, - currentPageIndex, - totalPages, - canGoNext, - canGoPrevious, - goToNext, - goToPrevious, - goToPage, - allPageItems - } = usePageNavigation( - collectionData, - filteredCustomPages, - filteredCollectionItems, - shouldShowOverview, - isMobile, - setMobileView - ); + // Page selection state + const [currentPageId, setCurrentPageId] = useState('overview'); + const [currentPageItem, setCurrentPageItem] = useState(null); + + // Handle item selection + const handleSelectItem = (id: string, path?: string) => { + console.log('Selecting item:', id, 'path:', path); + setCurrentPageId(id); + + if (id === 'overview') { + setCurrentPageItem(null); + return; + } + + // Check if it's a custom page + const customPage = filteredCustomPages.find(page => page.name === id || page.name === id.replace(/-/g, ' ')); + if (customPage) { + setCurrentPageItem(customPage); + return; + } + + // Find the item in the collection + const findItem = (items: any[]): any => { + for (const item of items) { + const itemId = item.id || item.uid || item.name || 'unnamed-item'; + if (itemId === id || itemId.toLowerCase().replace(/[^a-z0-9\-_]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') === id) { + return item; + } + if (item.type === 'folder' && item.items) { + const found = findItem(item.items); + if (found) return found; + } + } + return null; + }; + + if (collectionData && 'items' in collectionData && collectionData.items) { + const item = findItem(collectionData.items); + setCurrentPageItem(item); + } + }; + + // Set initial page to overview when collection loads + useEffect(() => { + if (collectionData && currentPageId === null) { + setCurrentPageId('overview'); + } + }, [collectionData, currentPageId]); const { isSearchOpen, @@ -67,7 +95,7 @@ const OpenCollectionPlayground: React.FC = ({ searchResults, searchInputRef, handleSearchResultSelect: handleSearchResultSelectHook - } = useSearchModal(collectionData, validCustomPages, goToPage); + } = useSearchModal(collectionData, validCustomPages, handleSelectItem); const { isRunnerMode, toggleRunnerMode } = useRunnerMode(); @@ -89,15 +117,7 @@ const OpenCollectionPlayground: React.FC = ({ theme, currentPageId, currentPageItem, - currentPageIndex, - totalPages, - canGoNext, - canGoPrevious, - goToNext, - goToPrevious, - goToPage, - allPageItems, - onSelectItem: goToPage, + onSelectItem: handleSelectItem, filteredCustomPages, onlyShow, containerRef, diff --git a/packages/oc-core/src/hooks/index.ts b/packages/oc-core/src/hooks/index.ts index 44a0a285..dca1589d 100644 --- a/packages/oc-core/src/hooks/index.ts +++ b/packages/oc-core/src/hooks/index.ts @@ -5,5 +5,4 @@ export { useTheme } from './useTheme'; export { useResponsive } from './useResponsive'; export { useItemFiltering } from './useItemFiltering'; export { useMarkdownRenderer } from './useMarkdownRenderer'; -export { usePageNavigation } from './usePageNavigation'; export { useRunnerMode } from './useRunnerMode'; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/usePageNavigation.ts b/packages/oc-core/src/hooks/usePageNavigation.ts deleted file mode 100644 index 20510a2e..00000000 --- a/packages/oc-core/src/hooks/usePageNavigation.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { useState, useEffect, useCallback, useMemo } from 'react'; -import { OpenCollectionCollection, OpenCollectionItem, CustomPage } from '../types'; -import { getItemId, generateSafeId } from '../utils/itemUtils'; - -interface UsePageNavigationReturn { - currentPageId: string | null; - currentPageItem: OpenCollectionItem | CustomPage | null; - currentPageIndex: number; - totalPages: number; - canGoNext: boolean; - canGoPrevious: boolean; - goToNext: () => void; - goToPrevious: () => void; - goToPage: (id: string) => void; - allPageItems: Array<{id: string; item: OpenCollectionItem | CustomPage; type: 'item' | 'custom' | 'overview'}>; -} - -export const usePageNavigation = ( - collectionData: OpenCollectionCollection | null, - filteredCustomPages: CustomPage[], - filteredCollectionItems: OpenCollectionItem[], - shouldShowOverview: boolean, - isMobile: boolean, - setMobileView: (view: 'sidebar' | 'content') => void -): UsePageNavigationReturn => { - const [currentPageId, setCurrentPageId] = useState(null); - - // Memoize all available pages - const allPageItems = useMemo(() => { - const pages: Array<{id: string; item: OpenCollectionItem | CustomPage; type: 'item' | 'custom' | 'overview'}> = []; - - // Add overview if it should be shown - if (shouldShowOverview && collectionData) { - pages.push({ - id: 'overview', - item: { name: 'Overview', type: 'overview' } as any, - type: 'overview' - }); - } - - // Add custom pages - filteredCustomPages.forEach(page => { - pages.push({ - id: page.name, - item: page, - type: 'custom' - }); - }); - - // Add collection items (flatten nested items in folders) - const flattenItems = (items: OpenCollectionItem[], parentPath: string = ''): OpenCollectionItem[] => { - const result: OpenCollectionItem[] = []; - items.forEach(item => { - // Always include the item itself (folders are also pages now) - result.push(item); - - // If it's a folder with nested items, recursively flatten them - if (item.type === 'folder' && (item as any).items) { - const nestedItems = flattenItems((item as any).items, parentPath ? `${parentPath}/${item.name || 'folder'}` : item.name || 'folder'); - result.push(...nestedItems); - } - }); - return result; - }; - - const flatItems = flattenItems(filteredCollectionItems); - flatItems.forEach(item => { - const itemId = getItemId(item); - const safeId = generateSafeId(itemId); - pages.push({ - id: safeId, - item, - type: 'item' - }); - }); - - return pages; - }, [collectionData, filteredCustomPages, filteredCollectionItems, shouldShowOverview]); - - // Get current page index and item - const currentPageIndex = useMemo(() => { - if (!currentPageId) return -1; - return allPageItems.findIndex(page => page.id === currentPageId); - }, [currentPageId, allPageItems]); - - const currentPageItem = useMemo(() => { - if (currentPageIndex === -1) return null; - return allPageItems[currentPageIndex]?.item || null; - }, [currentPageIndex, allPageItems]); - - // Navigation state - const canGoNext = currentPageIndex < allPageItems.length - 1 && currentPageIndex !== -1; - const canGoPrevious = currentPageIndex > 0; - const totalPages = allPageItems.length; - - // Update URL parameters - const updateUrlParams = useCallback((pageId: string) => { - if (typeof window !== 'undefined') { - const url = new URL(window.location.href); - url.searchParams.set('page', pageId); - window.history.replaceState({}, '', url.toString()); - } - }, []); - - // Read initial page from URL parameters - useEffect(() => { - if (typeof window !== 'undefined' && allPageItems.length > 0) { - const url = new URL(window.location.href); - const pageParam = url.searchParams.get('page'); - - if (pageParam && allPageItems.some(page => page.id === pageParam)) { - setCurrentPageId(pageParam); - } else if (!currentPageId) { - // Set first page as default - const firstPageId = allPageItems[0]?.id; - if (firstPageId) { - setCurrentPageId(firstPageId); - updateUrlParams(firstPageId); - } - } - } - }, [allPageItems, currentPageId, updateUrlParams]); - - // Navigation functions - const goToNext = useCallback(() => { - if (canGoNext) { - const nextPageId = allPageItems[currentPageIndex + 1]?.id; - if (nextPageId) { - setCurrentPageId(nextPageId); - updateUrlParams(nextPageId); - if (isMobile) { - setMobileView('content'); - } - } - } - }, [canGoNext, currentPageIndex, allPageItems, updateUrlParams, isMobile, setMobileView]); - - const goToPrevious = useCallback(() => { - if (canGoPrevious) { - const prevPageId = allPageItems[currentPageIndex - 1]?.id; - if (prevPageId) { - setCurrentPageId(prevPageId); - updateUrlParams(prevPageId); - if (isMobile) { - setMobileView('content'); - } - } - } - }, [canGoPrevious, currentPageIndex, allPageItems, updateUrlParams, isMobile, setMobileView]); - - const goToPage = useCallback((id: string) => { - if (allPageItems.some(page => page.id === id)) { - setCurrentPageId(id); - updateUrlParams(id); - if (isMobile) { - setMobileView('content'); - } - } - }, [allPageItems, updateUrlParams, isMobile, setMobileView]); - - return { - currentPageId, - currentPageItem, - currentPageIndex, - totalPages, - canGoNext, - canGoPrevious, - goToNext, - goToPrevious, - goToPage, - allPageItems - }; -}; \ No newline at end of file diff --git a/packages/oc-core/src/index.ts b/packages/oc-core/src/index.ts index 7190e8a8..227b85b1 100644 --- a/packages/oc-core/src/index.ts +++ b/packages/oc-core/src/index.ts @@ -13,7 +13,6 @@ export { export * from './types'; export * from './hooks'; export { - PageNavigation, SinglePageRenderer, CodeEditor, RequestRunner, diff --git a/packages/oc-core/src/layouts/ContentLayout.tsx b/packages/oc-core/src/layouts/ContentLayout.tsx index 646708de..9ef7a065 100644 --- a/packages/oc-core/src/layouts/ContentLayout.tsx +++ b/packages/oc-core/src/layouts/ContentLayout.tsx @@ -21,7 +21,7 @@ const ContentLayout = ({ theme: 'light' | 'dark' | 'auto'; }) => { return ( -
+

{title}

{badge && ( diff --git a/packages/oc-core/src/layouts/DesktopLayout.tsx b/packages/oc-core/src/layouts/DesktopLayout.tsx index 7ef30164..b51cc800 100644 --- a/packages/oc-core/src/layouts/DesktopLayout.tsx +++ b/packages/oc-core/src/layouts/DesktopLayout.tsx @@ -11,14 +11,6 @@ interface DesktopLayoutProps { theme: 'light' | 'dark' | 'auto'; currentPageId: string | null; currentPageItem: OpenCollectionItem | CustomPage | null; - currentPageIndex: number; - totalPages: number; - canGoNext: boolean; - canGoPrevious: boolean; - goToNext: () => void; - goToPrevious: () => void; - goToPage: (id: string) => void; - allPageItems: Array<{id: string; item: OpenCollectionItem | CustomPage; type: 'item' | 'custom' | 'overview'}>; onSelectItem: (id: string, path?: string) => void; filteredCustomPages: CustomPage[]; onlyShow?: string[]; @@ -47,14 +39,6 @@ const DesktopLayout: React.FC = ({ theme, currentPageId, currentPageItem, - currentPageIndex, - totalPages, - canGoNext, - canGoPrevious, - goToNext, - goToPrevious, - goToPage, - allPageItems, onSelectItem, filteredCustomPages, onlyShow, @@ -76,10 +60,7 @@ const DesktopLayout: React.FC = ({ proxyUrl }) => { return ( -
+
= ({ handleSearchResultSelect={handleSearchResultSelect} /> - - {!hideSidebar && ( -
-
- {logo ? ( -
{logo}
- ) : ( - - - - - - - - )} -

- {collectionData?.name} -

-
-
- )} - -
- -
- -
- -
- {/* {collectionData?.version && ( - - v{collectionData.version} - - )} */} - - - Open in Bruno - -
-
- - {!hideSidebar && (
= ({ collection={collectionData} activeItemId={currentPageId} onSelectItem={onSelectItem} + logo={logo} theme={theme} customPages={filteredCustomPages} onlyShow={onlyShow} + onSearchClick={() => setIsSearchOpen(true)} + isRunnerMode={isRunnerMode} + onToggleRunnerMode={toggleRunnerMode} />
)} -
-
+
p.id === currentPageId)?.type || 'item'} + pageType={currentPageId === 'overview' ? 'overview' : (currentPageItem && 'name' in currentPageItem && !('type' in currentPageItem)) ? 'custom' : 'item'} theme={theme} md={md} collection={collectionData} customPageContents={customPageContents} - currentPageIndex={currentPageIndex} - totalPages={totalPages} - canGoNext={canGoNext} - canGoPrevious={canGoPrevious} - goToNext={goToNext} - goToPrevious={goToPrevious} isRunnerMode={isRunnerMode} proxyUrl={proxyUrl} /> @@ -242,4 +117,4 @@ const DesktopLayout: React.FC = ({ ); }; -export default DesktopLayout; \ No newline at end of file +export default DesktopLayout; \ No newline at end of file diff --git a/packages/oc-core/src/standalone.ts b/packages/oc-core/src/standalone.ts index 16080960..5f3d634e 100644 --- a/packages/oc-core/src/standalone.ts +++ b/packages/oc-core/src/standalone.ts @@ -28,11 +28,34 @@ export class OpenCollectionPlaygroundRenderer { this.init(); } + private injectInterFont() { + // Only inject if not already present + if (!document.querySelector('link[href*="fonts.googleapis.com/css2?family=Inter"]')) { + const links = [ + { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, + { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossOrigin: 'anonymous' }, + { + rel: 'stylesheet', + href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap' + } + ]; + + links.forEach(linkProps => { + const link = document.createElement('link'); + Object.entries(linkProps).forEach(([key, value]) => { + link.setAttribute(key, value); + }); + document.head.appendChild(link); + }); + } + } + private init() { if (!this.options.target) { throw new Error('Target element is required'); } + this.injectInterFont(); this.root = createRoot(this.options.target); this.render(); } diff --git a/packages/oc-core/src/styles/index.css b/packages/oc-core/src/styles/index.css index f4204913..132660a9 100644 --- a/packages/oc-core/src/styles/index.css +++ b/packages/oc-core/src/styles/index.css @@ -2,7 +2,7 @@ :root { /* Core theme colors */ - --primary-color: #546de5; + --primary-color: #d97706; --secondary-color: #0ea5e9; /* Background colors */ @@ -24,8 +24,8 @@ --code-text-dark: rgb(204, 204, 204); /* Sidebar colors */ - --sidebar-bg-light: #F3F3F3; - --sidebar-bg-dark: #252526; + --sidebar-bg-light: #FAFAFA; + --sidebar-bg-dark: #1a1a1a; --sidebar-color-light: rgb(52, 52, 52); --sidebar-color-dark: #ccc; @@ -80,8 +80,11 @@ --prose-hr-color: rgba(0, 0, 0, 0.1); /* Layout dimensions */ - --sidebar-width: 280px; + --sidebar-width: 260px; + --sidebar-width-compact: 200px; --header-height: 60px; + --item-padding-compact: 4px; + --item-height-compact: 28px; /* Z-index layers */ --z-header: 10; @@ -94,7 +97,7 @@ /* Fonts */ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif; + --font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif; /* Current theme variables (default to light) */ --background-color: var(--background-light); @@ -169,7 +172,7 @@ width: 100%; height: 100vh !important; overflow: hidden; - font-family: -apple-system, BlinkMacSystemFont, "San Francisco", "Segoe UI", Roboto, "Helvetica Neue", sans-serif; + font-family: Inter, -apple-system, BlinkMacSystemFont, "San Francisco", "Segoe UI", Roboto, "Helvetica Neue", sans-serif; color: var(--text-primary); background-color: var(--background-color); position: relative; @@ -277,27 +280,26 @@ width: var(--sidebar-width); min-width: var(--sidebar-width); max-width: var(--sidebar-width); - height: calc(100vh - var(--header-height)) !important; overflow-y: auto; background-color: var(--sidebar-bg-light); color: var(--sidebar-color-light); + border-right: 1px solid rgba(0, 0, 0, 0.08); + padding: 16px 0; } .dark .playground-sidebar { background-color: var(--sidebar-bg-dark); color: var(--sidebar-color-dark); - border-right: 1px solid var(--border-color); + border-right: 1px solid rgba(255, 255, 255, 0.08); } .playground-content { flex: 1; overflow-y: auto; - height: calc(100vh - var(--header-height)) !important; } .playground-console { overflow: hidden; - height: calc(100vh - var(--header-height)) !important; backdrop-filter: saturate(180%) blur(20px); -webkit-backdrop-filter: saturate(180%) blur(20px); background-color: var(--background-color); @@ -349,113 +351,117 @@ display: inline-flex; align-items: center; justify-content: center; - padding: 0.25rem 0.5rem; - font-size: 0.75rem; + padding: 0.2rem 0.4rem; + font-size: 0.65rem; font-weight: 600; - border-radius: 0.375rem; + border-radius: 4px; text-transform: uppercase; - letter-spacing: 0.05em; - font-family: var(--font-mono); + letter-spacing: 0.03em; + font-family: var(--font-sans); + min-width: 42px; } .method-badge.get { - background-color: var(--method-get-bg); - color: white; + background-color: rgba(0, 112, 243, 0.09); + color: #0057b7; +} + +.dark .method-badge.get { + background-color: rgba(0, 112, 243, 0.15); + color: #0077ff; } .method-badge.post { - background-color: var(--method-post-bg); - color: white; + background-color: rgba(0, 155, 0, 0.09); + color: #00824d; +} + +.dark .method-badge.post { + background-color: rgba(0, 155, 0, 0.15); + color: #00a060; } .method-badge.put { - background-color: var(--method-put-bg); - color: white; + background-color: rgba(243, 112, 0, 0.09); + color: #b74600; +} + +.dark .method-badge.put { + background-color: rgba(243, 112, 0, 0.15); + color: #ff6b00; } .method-badge.delete { - background-color: var(--method-delete-bg); - color: white; + background-color: rgba(243, 0, 0, 0.09); + color: #b70000; +} + +.dark .method-badge.delete { + background-color: rgba(243, 0, 0, 0.15); + color: #ff0000; } .method-badge.patch { - background-color: var(--method-patch-bg); - color: white; + background-color: rgba(155, 0, 155, 0.09); + color: #6b0082; +} + +.dark .method-badge.patch { + background-color: rgba(155, 0, 155, 0.15); + color: #9500b3; } .method-badge.options, .method-badge.head { - background-color: var(--method-options-bg); - color: white; + background-color: rgba(100, 100, 100, 0.09); + color: #4a4a4a; } -/* Dark mode method badges */ -.dark .method-badge.get { - background-color: var(--apple-blue-900); - color: var(--apple-blue-100); +.dark .method-badge.options, +.dark .method-badge.head { + background-color: rgba(100, 100, 100, 0.15); + color: #888888; } -.dark .method-badge.post { - background-color: var(--apple-green-900); - color: var(--apple-green-100); +/* Sidebar scrollbar styling */ +.sidebar ::-webkit-scrollbar { + width: 6px; } -.dark .method-badge.put { - background-color: var(--apple-orange-900); - color: var(--apple-orange-100); +.sidebar ::-webkit-scrollbar-track { + background: transparent; } -.dark .method-badge.delete { - background-color: var(--apple-red-900); - color: var(--apple-red-100); +.sidebar ::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.2); + border-radius: 10px; } -.dark .method-badge.patch { - background-color: var(--apple-purple-900); - color: var(--apple-purple-100); +.dark .sidebar ::-webkit-scrollbar-thumb { + background-color: rgba(255, 255, 255, 0.2); } -.dark .method-badge.options, -.dark .method-badge.head { - background-color: var(--apple-gray-900); - color: var(--apple-gray-100); +/* Sidebar Logo styling */ +.sidebar .logo { + padding: 0 10px 16px 10px; + border-bottom: 1px solid rgba(0, 0, 0, 0.06); + margin-bottom: 8px; } -/* Tab styling */ -.tabs { - display: flex; - border-bottom: 1px solid var(--border-color); - position: relative; +.dark .sidebar .logo { + border-bottom: 1px solid rgba(255, 255, 255, 0.06); } -.tab { - padding: 0.75rem 1rem; - font-size: 0.875rem; - font-weight: 500; - color: var(--text-secondary); - position: relative; - transition: color 0.2s ease; -} -.tab:hover { - color: var(--text-primary); +/* Overview and Custom Page icons */ +.sidebar-item svg { + transition: opacity 0.15s ease; } -.tab.active { - color: var(--primary-color); - font-weight: 600; +.sidebar-item:hover svg { + opacity: 1; } -.tab.active::after { - content: ''; - position: absolute; - bottom: -1px; - left: 0; - width: 100%; - height: 2px; - background-color: var(--primary-color); - border-radius: 1px 1px 0 0; -} /* Code block styling */ .code-block { @@ -507,7 +513,6 @@ .api-table th { text-align: left; padding: 0.75rem 1rem; - font-weight: 500; background-color: var(--table-header-bg); border-bottom: 1px solid var(--border-color); color: var(--text-primary); @@ -837,55 +842,132 @@ tr.dark-row:nth-child(even) { /* Sidebar item styles */ .sidebar-item { cursor: pointer; - transition: all 0.08s ease-out; - border-radius: 0.375rem; - margin-bottom: 2px; + transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1); + border-radius: 0; + margin-bottom: 1px; position: relative; overflow: hidden; - will-change: background-color, color; + padding: 5px 16px; + display: flex; + align-items: center; + font-size: 13px; + letter-spacing: -0.01em; + color: rgba(0, 0, 0, 0.6); + font-weight: 450; +} + +/* Compact sidebar styles */ +.sidebar.compact { + min-width: var(--sidebar-width-compact); + max-width: var(--sidebar-width-compact); +} + +.sidebar.compact .sidebar-item { + padding: 4px 8px; + font-size: 12px; + margin-bottom: 1px; +} + +.sidebar.compact .method-badge { + padding: 2px 4px; + min-width: 36px; + font-size: 10px; +} + +.sidebar.compact .logo { + padding: 8px; +} + +.sidebar.compact .sidebar-items { + padding: 0 4px; +} + +.sidebar.compact .sidebar-item svg { + width: 14px; + height: 14px; } .sidebar-item:hover, .sidebar-item.hovered { - background-color: rgba(0, 0, 0, 0.05); + background-color: rgba(0, 0, 0, 0.04); + color: rgba(0, 0, 0, 0.8); +} + +.dark .sidebar-item { + color: rgba(255, 255, 255, 0.6); } .dark .sidebar-item:hover, .dark .sidebar-item.hovered { - background-color: rgba(255, 255, 255, 0.07); + background-color: rgba(255, 255, 255, 0.06); + color: rgba(255, 255, 255, 0.9); } .sidebar-item.active { - background-color: rgba(84, 109, 229, 0.1); - color: var(--primary-color); + background-color: rgba(0, 0, 0, 0.08); + color: rgba(0, 0, 0, 0.9); font-weight: 500; - box-shadow: inset 3px 0 0 var(--primary-color); } .dark .sidebar-item.active { - background-color: rgba(84, 109, 229, 0.25); - color: #ffffff; - border-left: 3px solid var(--primary-color); + background-color: rgba(255, 255, 255, 0.1); + color: #fff; + font-weight: 500; } .sidebar-item.active:hover, .sidebar-item.active.hovered { - background-color: rgba(84, 109, 229, 0.3); + background-color: rgba(0, 0, 0, 0.1); +} + +.dark .sidebar-item.active:hover, .dark .sidebar-item.active.hovered { + background-color: rgba(255, 255, 255, 0.12); } .sidebar-item.folder { - font-weight: 500; + font-weight: 600; + color: rgba(0, 0, 0, 0.4); + background-color: transparent; + margin-top: 16px; + margin-bottom: 4px; + letter-spacing: 0.05em; + font-size: 11px; + text-transform: uppercase; + padding: 4px 16px; +} + +.dark .sidebar-item.folder { + color: rgba(255, 255, 255, 0.4); + background-color: transparent; +} + +.sidebar-item.folder:hover { + background-color: transparent; + color: rgba(0, 0, 0, 0.5); +} + +.dark .sidebar-item.folder:hover { + background-color: transparent; + color: rgba(255, 255, 255, 0.5); } .sidebar-item .method-badge { - font-size: 0.7rem; - padding: 0.15rem 0.5rem; - border-radius: 0.25rem; + font-size: 10px; + padding: 3px 6px; + border-radius: 4px; font-weight: 600; - min-width: 45px; + min-width: 42px; text-align: center; - transition: all 0.08s ease-out; + letter-spacing: 0.03em; + transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1); + text-transform: uppercase; + margin-right: 8px; + opacity: 0.85; +} + +.dark .sidebar-item .method-badge { + opacity: 0.9; } .sidebar-items { - padding: 0 0.5rem; + padding: 0; } /* Fix for click events */ diff --git a/packages/oc-core/src/ui/ItemComponent.tsx b/packages/oc-core/src/ui/ItemComponent.tsx index db44008d..c860bfa9 100644 --- a/packages/oc-core/src/ui/ItemComponent.tsx +++ b/packages/oc-core/src/ui/ItemComponent.tsx @@ -6,7 +6,7 @@ import 'prismjs/components/prism-graphql'; import 'prismjs/components/prism-json'; import 'prismjs/components/prism-xml-doc'; import { OpenCollectionItem, OpenCollectionCollection, HttpRequest, Script, Folder } from '../types'; -import { generateSectionId, getItemId, generateSafeId } from '../utils/itemUtils'; +import { generateSectionId, getItemId, generateSafeId, sortItemsWithFoldersFirst } from '../utils/itemUtils'; import { HeadersTable, VarsTable, @@ -695,7 +695,7 @@ const ItemComponent = memo(({
- {folderItem.items.map((nestedItem, index) => { + {sortItemsWithFoldersFirst(folderItem.items).map((nestedItem, index) => { const nestedItemId = getItemId(nestedItem); const safeId = generateSafeId(nestedItemId); @@ -952,7 +952,7 @@ const ItemComponent = memo(({ }; return ( -
+

{endpoint.name}

diff --git a/packages/oc-core/src/ui/MobileLayout.tsx b/packages/oc-core/src/ui/MobileLayout.tsx index 37d35ca5..b122f14c 100644 --- a/packages/oc-core/src/ui/MobileLayout.tsx +++ b/packages/oc-core/src/ui/MobileLayout.tsx @@ -10,14 +10,6 @@ interface MobileLayoutProps { theme: 'light' | 'dark' | 'auto'; currentPageId: string | null; currentPageItem: OpenCollectionItem | CustomPage | null; - currentPageIndex: number; - totalPages: number; - canGoNext: boolean; - canGoPrevious: boolean; - goToNext: () => void; - goToPrevious: () => void; - goToPage: (id: string) => void; - allPageItems: Array<{id: string; item: OpenCollectionItem | CustomPage; type: 'item' | 'custom' | 'overview'}>; onSelectItem: (id: string, path?: string) => void; filteredCustomPages: CustomPage[]; onlyShow?: string[]; @@ -36,14 +28,6 @@ const MobileLayout: React.FC = ({ theme, currentPageId, currentPageItem, - currentPageIndex, - totalPages, - canGoNext, - canGoPrevious, - goToNext, - goToPrevious, - goToPage, - allPageItems, onSelectItem, filteredCustomPages, onlyShow, @@ -58,21 +42,15 @@ const MobileLayout: React.FC = ({ if (hideSidebar) { return (
-
+
p.id === currentPageId)?.type || 'item'} + pageType={currentPageId === 'overview' ? 'overview' : (currentPageItem && 'name' in currentPageItem && !('type' in currentPageItem)) ? 'custom' : 'item'} theme={theme} md={md} collection={collectionData} customPageContents={customPageContents} - currentPageIndex={currentPageIndex} - totalPages={totalPages} - canGoNext={canGoNext} - canGoPrevious={canGoPrevious} - goToNext={goToNext} - goToPrevious={goToPrevious} />
@@ -120,21 +98,15 @@ const MobileLayout: React.FC = ({ className={`h-full overflow-y-auto ${mobileView === 'content' ? 'block' : 'hidden'}`} ref={containerRef} > -
+
p.id === currentPageId)?.type || 'item'} + pageType={currentPageId === 'overview' ? 'overview' : (currentPageItem && 'name' in currentPageItem && !('type' in currentPageItem)) ? 'custom' : 'item'} theme={theme} md={md} collection={collectionData} customPageContents={customPageContents} - currentPageIndex={currentPageIndex} - totalPages={totalPages} - canGoNext={canGoNext} - canGoPrevious={canGoPrevious} - goToNext={goToNext} - goToPrevious={goToPrevious} />
diff --git a/packages/oc-core/src/ui/PageNavigation.tsx b/packages/oc-core/src/ui/PageNavigation.tsx deleted file mode 100644 index 2fe613db..00000000 --- a/packages/oc-core/src/ui/PageNavigation.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import React, { useEffect } from 'react'; - -interface PageNavigationProps { - currentPageIndex: number; - totalPages: number; - canGoNext: boolean; - canGoPrevious: boolean; - goToNext: () => void; - goToPrevious: () => void; - currentPageName?: string; -} - -const PageNavigation: React.FC = ({ - currentPageIndex, - totalPages, - canGoNext, - canGoPrevious, - goToNext, - goToPrevious, - currentPageName -}) => { - // Keyboard navigation - useEffect(() => { - const handleKeyPress = (event: KeyboardEvent) => { - if (event.key === 'ArrowLeft' && canGoPrevious) { - goToPrevious(); - } else if (event.key === 'ArrowRight' && canGoNext) { - goToNext(); - } - }; - - document.addEventListener('keydown', handleKeyPress); - return () => document.removeEventListener('keydown', handleKeyPress); - }, [canGoNext, canGoPrevious, goToNext, goToPrevious]); - if (totalPages <= 1) { - return null; - } - - return ( -
-
- - -
-
-
- Page {currentPageIndex + 1} of {totalPages} -
- {currentPageName && ( - - {currentPageName} - - )} -
- - -
-
- ); -}; - -export default PageNavigation; \ No newline at end of file diff --git a/packages/oc-core/src/ui/Sidebar.tsx b/packages/oc-core/src/ui/Sidebar.tsx index 4de9ca31..375d1b7d 100644 --- a/packages/oc-core/src/ui/Sidebar.tsx +++ b/packages/oc-core/src/ui/Sidebar.tsx @@ -1,6 +1,7 @@ import React, { useState, useMemo, useEffect, useCallback } from 'react'; import { OpenCollectionCollection, OpenCollectionItem, HttpRequest, CustomPage, Folder } from '../types'; -import { getItemId, generateSafeId } from '../utils/itemUtils'; +import Method from '../components/Method/Method'; +import { getItemId, generateSafeId, sortItemsWithFoldersFirst } from '../utils/itemUtils'; interface ApiEndpoint { id: string; @@ -25,6 +26,10 @@ export interface SidebarProps { theme?: 'light' | 'dark' | 'auto'; customPages?: CustomPage[]; onlyShow?: string[]; + isCompact?: boolean; + onSearchClick?: () => void; + isRunnerMode?: boolean; + onToggleRunnerMode?: () => void; } const Sidebar: React.FC = ({ @@ -35,7 +40,11 @@ const Sidebar: React.FC = ({ className = '', theme = 'light', customPages = [], - onlyShow = [] + onlyShow = [], + isCompact = false, + onSearchClick, + isRunnerMode = false, + onToggleRunnerMode }) => { const [expandedFolders, setExpandedFolders] = useState>({}); const [hoveredItemId, setHoveredItemId] = useState(null); @@ -57,7 +66,7 @@ const Sidebar: React.FC = ({ items.forEach((item) => { if (item.type === 'folder') { const itemId = getItemId(item); - initExpandedFolders[itemId] = true; + initExpandedFolders[itemId] = false; } }); } @@ -121,7 +130,8 @@ const Sidebar: React.FC = ({ items = filterItemsByOnlyShow(items as OpenCollectionItem[]); } - return items; + // Sort items with folders first + return sortItemsWithFoldersFirst(items as OpenCollectionItem[]); }, [normalizedItems, onlyShow, collection, shouldShowItem]); const filteredEndpoints = useMemo(() => { @@ -150,12 +160,12 @@ const Sidebar: React.FC = ({ const renderFolderIcon = (isExpanded: boolean) => ( = ({
isFolder ? toggleFolder(itemId) : handleItemSelect(safeItemId, itemPath)} onMouseEnter={() => setHoveredItemId(fullPathId)} @@ -225,11 +234,10 @@ const Sidebar: React.FC = ({ {renderFolderIcon(isExpanded)}
) : ( -
- {item.type === "http" ? (item as HttpRequest).method || 'GET' : 'GET'} -
+ )} @@ -259,7 +267,7 @@ const Sidebar: React.FC = ({ /> - {((item as Folder).items || []).map((child: OpenCollectionItem) => renderItem(child, level + 1, itemPath))} + {sortItemsWithFoldersFirst((item as Folder).items || []).map((child: OpenCollectionItem) => renderItem(child, level + 1, itemPath))}
)}
@@ -267,14 +275,17 @@ const Sidebar: React.FC = ({ }; return ( -
- {logo && ( -
- {logo} +
+ {/* Collection name at top */} +
+
+

+ {collection?.name || 'API Collection'} +

- )} +
-
+
{collection && (!onlyShow.length || shouldShowItem('overview')) && (
@@ -295,7 +306,7 @@ const Sidebar: React.FC = ({ id="sidebar-item-overview" data-testid="overview" > -
+
= ({ fill="none" xmlns="http://www.w3.org/2000/svg" > - - + + +
Overview
@@ -351,7 +352,7 @@ const Sidebar: React.FC = ({ id={`sidebar-item-${pageId}`} data-testid={`custom-page-${pageId}`} > -
+
= ({ xmlns="http://www.w3.org/2000/svg" > - = ({ id={`sidebar-item-${fullPathId}`} data-item-id={fullPathId} > - - {endpoint.method} - + {endpoint.path} ); @@ -419,6 +414,59 @@ const Sidebar: React.FC = ({ )}
+ + {/* Bottom buttons */} +
+ {/* Search button */} + {onSearchClick && ( + + )} + + {/* Runner mode and Open in Bruno buttons */} +
+ {onToggleRunnerMode && ( + + )} + + Open in Bruno + +
+
); }; diff --git a/packages/oc-core/src/ui/SinglePageRenderer.tsx b/packages/oc-core/src/ui/SinglePageRenderer.tsx index 822eea9c..85bd6e0c 100644 --- a/packages/oc-core/src/ui/SinglePageRenderer.tsx +++ b/packages/oc-core/src/ui/SinglePageRenderer.tsx @@ -3,7 +3,6 @@ import { OpenCollectionItem, OpenCollectionCollection, CustomPage, HttpRequest } import ItemComponent from './ItemComponent'; import Overview from './Overview'; import CustomPageRenderer from './CustomPageRenderer'; -import PageNavigation from './PageNavigation'; import { RequestRunner } from './request-runner'; interface SinglePageRendererProps { @@ -14,13 +13,6 @@ interface SinglePageRendererProps { md: any; collection: OpenCollectionCollection | null; customPageContents: Record; - // Navigation props - currentPageIndex: number; - totalPages: number; - canGoNext: boolean; - canGoPrevious: boolean; - goToNext: () => void; - goToPrevious: () => void; // Runner mode props isRunnerMode?: boolean; proxyUrl?: string; @@ -34,16 +26,10 @@ const SinglePageRenderer: React.FC = ({ md, collection, customPageContents, - currentPageIndex, - totalPages, - canGoNext, - canGoPrevious, - goToNext, - goToPrevious, isRunnerMode, proxyUrl }) => { - if (!currentPageItem || !currentPageId) { + if (!currentPageId) { return (
@@ -60,31 +46,19 @@ const SinglePageRenderer: React.FC = ({ // Render overview if (pageType === 'overview' && collection) { return ( -
-
- -
- +
+
); } // Render custom page - if (pageType === 'custom') { + if (pageType === 'custom' && currentPageItem) { const customPage = currentPageItem as CustomPage; const content = customPageContents[customPage.name] || ''; return ( -
-
+

@@ -101,29 +75,19 @@ const SinglePageRenderer: React.FC = ({ registerSectionRef={() => {}} // No-op since we don't need section refs in page mode />

-
-
); } // Render collection item - if (pageType === 'item') { + if (pageType === 'item' && currentPageItem) { const item = currentPageItem as OpenCollectionItem; // Render in runner mode if enabled and it's an HTTP request - if (isRunnerMode && item.type === 'http' && collection) { + if (isRunnerMode && item.type === 'http' && collection) { return (
- = ({ } return ( -
-
+
= ({ parentPath="" collection={collection || undefined} /> -
-
); } @@ -161,4 +114,4 @@ const SinglePageRenderer: React.FC = ({ return null; }; -export default SinglePageRenderer; \ No newline at end of file +export default SinglePageRenderer; \ No newline at end of file diff --git a/packages/oc-core/src/ui/Tabs/StyledWrapper.ts b/packages/oc-core/src/ui/Tabs/StyledWrapper.ts new file mode 100644 index 00000000..ea2d0ae4 --- /dev/null +++ b/packages/oc-core/src/ui/Tabs/StyledWrapper.ts @@ -0,0 +1,69 @@ +import styled from '@emotion/styled'; + +export const StyledWrapper = styled.div` + display: flex; + flex-direction: column; + height: 100%; + + .tabs-header { + display: flex; + justify-content: space-between; + align-items: center; + flex-shrink: 0; + } + + .tabs { + display: flex; + flex-shrink: 0; + + .tab { + border: none; + border-bottom: solid 2px transparent; + margin-right: 1rem; + color: var(--text-secondary); + cursor: pointer; + font-size: 14px; + transition: all 0.2s ease; + position: relative; + background: none; + padding-bottom: 6px; + + &:focus, + &:active, + &:focus-within, + &:focus-visible, + &:target { + outline: none !important; + box-shadow: none !important; + } + + &:hover:not(.active) { + color: var(--text-primary); + } + + &.active { + color: var(--primary-color); + border-bottom-color: var(--primary-color); + } + + .content-indicator { + margin-left: 2px; + font-size: 0.7em; + color: var(--text-tertiary); + font-weight: normal; + } + } + } + + .tabs-right { + display: flex; + align-items: center; + gap: 1rem; + } + + .tab-content { + flex: 1; + overflow: auto; + min-height: 0; + } +`; \ No newline at end of file diff --git a/packages/oc-core/src/ui/Tabs/Tabs.tsx b/packages/oc-core/src/ui/Tabs/Tabs.tsx new file mode 100644 index 00000000..3783aa29 --- /dev/null +++ b/packages/oc-core/src/ui/Tabs/Tabs.tsx @@ -0,0 +1,84 @@ +import React, { useState, ReactNode } from 'react'; +import { StyledWrapper } from './StyledWrapper'; + +export interface Tab { + id: string; + label: string; + contentIndicator?: string | number; + content: ReactNode; + disabled?: boolean; +} + +interface TabsProps { + tabs: Tab[]; + defaultActiveTab?: string; + activeTab?: string; + onTabChange?: (tabId: string) => void; + className?: string; + rightElement?: ReactNode; +} + +const Tabs: React.FC = ({ + tabs, + defaultActiveTab, + activeTab: controlledActiveTab, + onTabChange, + className = '', + rightElement +}) => { + const [internalActiveTab, setInternalActiveTab] = useState( + defaultActiveTab || (tabs.length > 0 ? tabs[0].id : '') + ); + + // Use controlled active tab if provided, otherwise use internal state + const activeTab = controlledActiveTab !== undefined ? controlledActiveTab : internalActiveTab; + + const handleTabClick = (tabId: string) => { + const tab = tabs.find(t => t.id === tabId); + if (tab && !tab.disabled) { + if (controlledActiveTab === undefined) { + setInternalActiveTab(tabId); + } + if (onTabChange) { + onTabChange(tabId); + } + } + }; + + const activeTabData = tabs.find(tab => tab.id === activeTab); + + return ( + +
+
+ {tabs.map((tab) => ( + + ))} +
+ {rightElement && ( +
+ {rightElement} +
+ )} +
+ {activeTabData && ( +
+ {activeTabData.content} +
+ )} +
+ ); +}; + +export default Tabs; \ No newline at end of file diff --git a/packages/oc-core/src/ui/index.ts b/packages/oc-core/src/ui/index.ts index b62a850a..b65d6f24 100644 --- a/packages/oc-core/src/ui/index.ts +++ b/packages/oc-core/src/ui/index.ts @@ -2,7 +2,6 @@ export { default as Card } from './Card'; export { default as DataTable } from './DataTable'; export { default as CodeBlock } from './CodeBlock'; export { default as BodyCard } from './BodyCard'; -export { default as PageNavigation } from './PageNavigation'; export { default as SinglePageRenderer } from './SinglePageRenderer'; export { default as CodeEditor } from './CodeEditor'; export * from './TableHelpers'; diff --git a/packages/oc-core/src/ui/request-runner/QueryBar.tsx b/packages/oc-core/src/ui/request-runner/QueryBar.tsx index 57f12dd0..0b0376ce 100644 --- a/packages/oc-core/src/ui/request-runner/QueryBar.tsx +++ b/packages/oc-core/src/ui/request-runner/QueryBar.tsx @@ -29,62 +29,88 @@ const QueryBar: React.FC = ({ item, onSendRequest, isLoading, onI const methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']; + const getMethodColor = (method: string) => { + const colors: { [key: string]: string } = { + 'GET': '#10b981', + 'POST': '#f59e0b', + 'PUT': '#3b82f6', + 'PATCH': '#8b5cf6', + 'DELETE': '#ef4444', + 'HEAD': '#6b7280', + 'OPTIONS': '#6b7280' + }; + return colors[method] || '#6b7280'; + }; + return (
- - -
- handleUrlChange(e.target.value)} - placeholder="Enter request URL" - className="w-full px-3 py-2 text-sm rounded border focus:outline-none focus:ring-2 focus:ring-blue-500" +
+ +
+ + + +
+ handleUrlChange(e.target.value)} + placeholder="Enter request URL" + className="flex-1 px-3 text-sm focus:outline-none focus:ring-1 focus:ring-inset focus:ring-blue-500" + style={{ + backgroundColor: 'transparent', + color: 'var(--text-primary)', + borderRadius: 0 + }} + onKeyPress={(e) => { + if (e.key === 'Enter' && url.trim() && !isLoading) { + onSendRequest(); + } + }} + /> +
); }; -export default QueryBar; \ No newline at end of file +export default QueryBar; \ No newline at end of file diff --git a/packages/oc-core/src/ui/request-runner/RequestHeader.tsx b/packages/oc-core/src/ui/request-runner/RequestHeader.tsx index f3831562..4cacf951 100644 --- a/packages/oc-core/src/ui/request-runner/RequestHeader.tsx +++ b/packages/oc-core/src/ui/request-runner/RequestHeader.tsx @@ -16,62 +16,43 @@ const RequestHeader: React.FC = ({ }) => { return (
-
+

{item.name || 'Untitled Request'}

- - - {item.method || 'GET'} -
-
+
{collection.environments && collection.environments.length > 0 && ( -
- - Environment: - - -
+ )}
diff --git a/packages/oc-core/src/ui/request-runner/RequestPane.tsx b/packages/oc-core/src/ui/request-runner/RequestPane.tsx index eabc814b..a6c4f87c 100644 --- a/packages/oc-core/src/ui/request-runner/RequestPane.tsx +++ b/packages/oc-core/src/ui/request-runner/RequestPane.tsx @@ -1,6 +1,7 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import { HttpRequest } from '../../types'; import CodeEditor from '../CodeEditor'; +import Tabs from '../Tabs/Tabs'; interface RequestPaneProps { item: HttpRequest; @@ -8,16 +9,7 @@ interface RequestPaneProps { } const RequestPane: React.FC = ({ item, onItemChange }) => { - const [activeTab, setActiveTab] = useState<'params' | 'headers' | 'body' | 'auth' | 'scripts' | 'tests'>('params'); - - const tabs = [ - { id: 'params', label: 'Params', count: item.params?.length || 0 }, - { id: 'headers', label: 'Headers', count: item.headers?.length || 0 }, - { id: 'body', label: 'Body' }, - { id: 'auth', label: 'Auth' }, - { id: 'scripts', label: 'Scripts' }, - { id: 'tests', label: 'Tests' } - ] as const; + const [activeTab, setActiveTab] = useState('params'); const handleParamChange = (index: number, field: 'name' | 'value' | 'enabled', value: any) => { const updatedParams = [...(item.params || [])]; @@ -51,11 +43,8 @@ const RequestPane: React.FC = ({ item, onItemChange }) => { onItemChange({ ...item, headers: updatedHeaders }); }; - const renderTabContent = () => { - switch (activeTab) { - case 'params': - return ( -
+ const renderParams = () => ( +
@@ -129,10 +118,9 @@ const RequestPane: React.FC = ({ item, onItemChange }) => { )}
- ); + ); - case 'headers': - return ( + const renderHeaders = () => (
@@ -204,10 +192,9 @@ const RequestPane: React.FC = ({ item, onItemChange }) => { )}
- ); + ); - case 'body': - return ( + const renderBody = () => (
@@ -366,10 +353,9 @@ const RequestPane: React.FC = ({ item, onItemChange }) => { )}
- ); + ); - case 'auth': - return ( + const renderAuth = () => (
@@ -572,10 +558,9 @@ const RequestPane: React.FC = ({ item, onItemChange }) => { )}
- ); + ); - case 'scripts': - return ( + const renderScripts = () => (
@@ -617,10 +602,9 @@ const RequestPane: React.FC = ({ item, onItemChange }) => {
- ); + ); - case 'tests': - return ( + const renderTests = () => (

@@ -640,41 +624,24 @@ const RequestPane: React.FC = ({ item, onItemChange }) => { />

- ); + ); - default: - return null; - } - }; + const tabs = [ + { id: 'params', label: 'Params', contentIndicator: item.params?.length || undefined, content: renderParams() }, + { id: 'headers', label: 'Headers', contentIndicator: item.headers?.length || undefined, content: renderHeaders() }, + { id: 'body', label: 'Body', content: renderBody() }, + { id: 'auth', label: 'Auth', content: renderAuth() }, + { id: 'scripts', label: 'Scripts', content: renderScripts() }, + { id: 'tests', label: 'Tests', content: renderTests() } + ]; return ( -
-
- {tabs.map((tab) => ( - - ))} -
- -
- {renderTabContent()} -
+
+
); }; diff --git a/packages/oc-core/src/ui/request-runner/RequestRunner.tsx b/packages/oc-core/src/ui/request-runner/RequestRunner.tsx index e5c9bab8..89d0e229 100644 --- a/packages/oc-core/src/ui/request-runner/RequestRunner.tsx +++ b/packages/oc-core/src/ui/request-runner/RequestRunner.tsx @@ -82,7 +82,7 @@ const RequestRunner: React.FC = ({ item, collection, proxyUr return (
- = ({ item, collection, proxyUr onItemChange={setEditableItem} /> -
+
+ onMouseEnter={(e) => { + e.currentTarget.style.backgroundColor = 'rgba(0, 0, 0, 0.2)'; + }} + onMouseLeave={(e) => { + e.currentTarget.style.backgroundColor = 'var(--border-color)'; + }} + > +
diff --git a/packages/oc-core/src/ui/request-runner/ResponsePane.tsx b/packages/oc-core/src/ui/request-runner/ResponsePane.tsx index 453e56be..a51b62f7 100644 --- a/packages/oc-core/src/ui/request-runner/ResponsePane.tsx +++ b/packages/oc-core/src/ui/request-runner/ResponsePane.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import CodeEditor from '../CodeEditor'; +import Tabs from '../Tabs/Tabs'; interface ResponsePaneProps { response: any; @@ -7,12 +8,7 @@ interface ResponsePaneProps { } const ResponsePane: React.FC = ({ response, isLoading }) => { - const [activeTab, setActiveTab] = useState<'body' | 'headers'>('body'); - - const tabs = [ - { id: 'body', label: 'Body' }, - { id: 'headers', label: 'Headers' } - ] as const; + const [activeTab, setActiveTab] = useState('response'); const formatJson = (data: any) => { try { @@ -31,169 +27,148 @@ const ResponsePane: React.FC = ({ response, isLoading }) => { return '#6b7280'; }; - const renderTabContent = () => { - if (isLoading) { - return ( -
-
-
- Sending request... -
-
- ); + const renderResponseBody = () => { + const responseText = typeof response.data === 'string' ? response.data : formatJson(response.data); + const contentType = response.headers?.['content-type'] || response.headers?.['Content-Type'] || ''; + let language = 'text'; + + if (contentType.includes('application/json') || contentType.includes('text/json')) { + language = 'json'; + } else if (contentType.includes('text/html')) { + language = 'html'; + } else if (contentType.includes('text/xml') || contentType.includes('application/xml')) { + language = 'xml'; + } else if (contentType.includes('text/css')) { + language = 'css'; + } else if (contentType.includes('text/javascript') || contentType.includes('application/javascript')) { + language = 'javascript'; } + + return ( +
+ {}} // Read-only + language={language} + height="100%" + readOnly={true} + /> +
+ ); + }; - if (!response) { - return ( -
-
-
- - - + const renderHeaders = () => ( +
+
+ {response.headers ? ( + Object.entries(response.headers).map(([key, value]) => ( +
+ + {key}: + + + {String(value)} +
-

Click Send to make a request

+ )) + ) : ( +
+ No response headers
+ )} +
+
+ ); + + // Handle loading, empty, and error states + if (isLoading) { + return ( +
+
+
+ Sending request...
- ); - } +
+ ); + } + + if (!response) { + return ( +
+
+
+ + + +
+

Click Send to make a request

+
+
+ ); + } - if (response.error) { - return ( + if (response.error) { + return ( +

Request Failed

{response.error}

- ); - } - - switch (activeTab) { - case 'body': - const responseText = typeof response.data === 'string' ? response.data : formatJson(response.data); - const contentType = response.headers?.['content-type'] || response.headers?.['Content-Type'] || ''; - let language = 'text'; - - if (contentType.includes('application/json') || contentType.includes('text/json')) { - language = 'json'; - } else if (contentType.includes('text/html')) { - language = 'html'; - } else if (contentType.includes('text/xml') || contentType.includes('application/xml')) { - language = 'xml'; - } else if (contentType.includes('text/css')) { - language = 'css'; - } else if (contentType.includes('text/javascript') || contentType.includes('application/javascript')) { - language = 'javascript'; - } - - return ( -
-
- {}} // Read-only - language={language} - height="100%" - readOnly={true} - /> -
-
- ); - - case 'headers': - return ( -
-
- {response.headers ? ( - Object.entries(response.headers).map(([key, value]) => ( -
- - {key}: - - - {String(value)} - -
- )) - ) : ( -
- No response headers -
- )} -
-
- ); - - - - default: - return null; - } - }; - - return ( -
-
-
-
- {tabs.map((tab) => ( - - ))} -
-
- - {response && !response.error && ( -
-
- Status: - - {response.status} {response.statusText} - -
- - {response.duration && ( -
- Time: - - {response.duration}ms - -
- )} - - {response.size && ( -
- Size: - - {(response.size / 1024).toFixed(2)} KB - -
- )} -
- )}
+ ); + } -
- {renderTabContent()} + const tabs = [ + { id: 'response', label: 'Response', content: renderResponseBody() }, + { id: 'headers', label: 'Headers', content: renderHeaders() } + ]; + + const statusInfo = ( +
+
+ Status: + + {response.status} {response.statusText} +
+ + {response.duration && ( +
+ Time: + + {response.duration}ms + +
+ )} + + {response.size && ( +
+ Size: + + {(response.size / 1024).toFixed(2)} KB + +
+ )} +
+ ); + + return ( +
+
); }; diff --git a/packages/oc-core/src/utils/itemUtils.ts b/packages/oc-core/src/utils/itemUtils.ts index bc838ef7..edd7567a 100644 --- a/packages/oc-core/src/utils/itemUtils.ts +++ b/packages/oc-core/src/utils/itemUtils.ts @@ -2,6 +2,8 @@ * Utility functions for handling OpenCollection items */ +import { OpenCollectionItem } from '../types'; + /** * Generate a safe HTML ID from an item name or ID * @param input The input string to convert to a safe ID @@ -23,6 +25,7 @@ export const generateSafeId = (input: string): string => { * @returns The effective ID string */ export const getItemId = (item: any): string => { + if (!item) return 'unnamed-item'; return item.id || item.uid || item.name || 'unnamed-item'; }; @@ -42,4 +45,27 @@ export const generateSectionId = (item: any, parentPath?: string): string => { } return safeItemId; +}; + +/** + * Sort OpenCollection items with folders first, then other items + * @param items Array of OpenCollection items to sort + * @returns Sorted array with folders first + */ +export const sortItemsWithFoldersFirst = (items: OpenCollectionItem[]): OpenCollectionItem[] => { + if (!items || !Array.isArray(items)) { + return []; + } + + return [...items].filter(item => item != null).sort((a, b) => { + // Folders come first + if (a.type === 'folder' && b.type !== 'folder') return -1; + if (a.type !== 'folder' && b.type === 'folder') return 1; + + // Within the same type, sort alphabetically by name + const nameA = getItemId(a).toLowerCase(); + const nameB = getItemId(b).toLowerCase(); + + return nameA.localeCompare(nameB); + }); }; \ No newline at end of file diff --git a/packages/oc-core/vite.config.standalone.ts b/packages/oc-core/vite.config.standalone.ts index 416dd5ac..fc661076 100644 --- a/packages/oc-core/vite.config.standalone.ts +++ b/packages/oc-core/vite.config.standalone.ts @@ -1,10 +1,15 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import { resolve } from 'path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = fileURLToPath(new URL('.', import.meta.url)); // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [ + react() + ], define: { 'process.env.NODE_ENV': '"production"' }, @@ -15,13 +20,21 @@ export default defineConfig({ fileName: (format) => `playground.${format === 'es' ? 'esm' : format}.js`, formats: ['umd', 'es'] }, + cssCodeSplit: false, rollupOptions: { output: { // Bundle everything into a single file inlineDynamicImports: true, manualChunks: undefined, globals: {}, - exports: 'named' + exports: 'named', + // Ensure CSS is extracted properly + assetFileNames: (assetInfo) => { + if (assetInfo.name && assetInfo.name.endsWith('.css')) { + return 'core.css'; + } + return assetInfo.name || 'asset'; + } } }, outDir: 'dist-standalone', diff --git a/packages/oc-core/vite.config.ts b/packages/oc-core/vite.config.ts index 98d26bc3..67c7660f 100644 --- a/packages/oc-core/vite.config.ts +++ b/packages/oc-core/vite.config.ts @@ -1,12 +1,17 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import { resolve } from 'path' +import { fileURLToPath } from 'node:url' import tailwindcss from '@tailwindcss/vite' +const __dirname = fileURLToPath(new URL('.', import.meta.url)) // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react(), tailwindcss()], + plugins: [ + react(), + tailwindcss() + ], build: { lib: { entry: { From 7e9af3ea5d72d19bb95dead5fbff0973f4c8efe6 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Fri, 19 Sep 2025 07:13:16 +0530 Subject: [PATCH 005/161] Spec site --- packages/oc-spec/.gitignore | 3 + packages/oc-spec/src/schema.json | 786 +++++++++++++++++++++++++++++++ 2 files changed, 789 insertions(+) create mode 100644 packages/oc-spec/.gitignore create mode 100644 packages/oc-spec/src/schema.json diff --git a/packages/oc-spec/.gitignore b/packages/oc-spec/.gitignore new file mode 100644 index 00000000..6acc570d --- /dev/null +++ b/packages/oc-spec/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +.DS_Store \ No newline at end of file diff --git a/packages/oc-spec/src/schema.json b/packages/oc-spec/src/schema.json new file mode 100644 index 00000000..3d0a8983 --- /dev/null +++ b/packages/oc-spec/src/schema.json @@ -0,0 +1,786 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.opencollection.com/json/draft-07/opencollection/v1.0.0", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the collection" + }, + "description": { + "$ref": "#/$defs/Description" + }, + "environments": { + "type": "array", + "description": "Array of environments", + "items": { + "$ref": "#/$defs/Environment" + } + }, + "items": { + "type": "array", + "description": "Array of items in the collection", + "items": { + "$ref": "#/$defs/Item" + } + }, + "base": { + "type": "object", + "description": "Base configuration for the collection", + "properties": { + "headers": { + "type": "array", + "description": "Array of request headers", + "items": { + "$ref": "#/$defs/RequestHeader" + } + }, + "auth": { + "$ref": "#/$defs/Auth" + }, + "variables": { + "type": "array", + "description": "Array of variables", + "items": { + "$ref": "#/$defs/Variable" + } + }, + "scripts": { + "$ref": "#/$defs/Scripts" + } + }, + "additionalProperties": false + }, + "docs": { + "type": "string", + "description": "The documentation" + } + }, + "$defs": { + "Description": { + "description": "The description", + "oneOf": [ + { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "The content of the description" + }, + "type": { + "type": "string", + "description": "The MIME type of the content" + } + }, + "required": ["content", "type"], + "additionalProperties": false + }, + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "RequestHeader": { + "type": "object", + "description": "A request header with name, value, description, and disabled state", + "properties": { + "name": { + "type": "string", + "description": "The header name" + }, + "value": { + "type": "string", + "description": "The header value" + }, + "description": { + "$ref": "#/$defs/Description" + }, + "disabled": { + "type": "boolean", + "description": "Whether the header is disabled" + } + }, + "required": ["name", "value"], + "additionalProperties": false + }, + "RequestParam": { + "type": "object", + "description": "A request parameter with name, value, description, type, and enabled state", + "properties": { + "name": { + "type": "string", + "description": "The parameter name" + }, + "value": { + "type": "string", + "description": "The parameter value" + }, + "description": { + "$ref": "#/$defs/Description" + }, + "type": { + "type": "string", + "enum": ["query", "path"], + "description": "The type of parameter" + }, + "enabled": { + "type": "boolean", + "description": "Whether the parameter is enabled" + } + }, + "required": ["name", "value", "type"], + "additionalProperties": false + }, + "RequestBody": { + "oneOf": [ + { + "$ref": "#/$defs/RawBody" + }, + { + "$ref": "#/$defs/FormUrlEncodedBody" + }, + { + "$ref": "#/$defs/MultipartFormBody" + }, + { + "$ref": "#/$defs/FileBody" + }, + { + "type": "null" + } + ] + }, + "RawBody": { + "type": "object", + "description": "Raw request body with type and data", + "properties": { + "type": { + "type": "string", + "enum": ["json", "text", "xml", "sparql"], + "description": "The type of raw body content" + }, + "data": { + "type": "string", + "description": "The raw body data" + } + }, + "required": ["type", "data"], + "additionalProperties": false + }, + "FormUrlEncodedBody": { + "type": "array", + "description": "Form URL encoded body as array of key-value pairs", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The form field name" + }, + "value": { + "type": "string", + "description": "The form field value" + }, + "description": { + "$ref": "#/$defs/Description" + }, + "enabled": { + "type": "boolean", + "description": "Whether the form field is enabled" + } + }, + "required": ["name", "value"], + "additionalProperties": false + } + }, + "MultipartFormBody": { + "type": "array", + "description": "Multipart form body as array of form parts", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The form part name" + }, + "type": { + "type": "string", + "enum": ["text", "file"], + "description": "The type of form part" + }, + "value": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "description": "The form part value" + }, + "description": { + "$ref": "#/$defs/Description" + }, + "enabled": { + "type": "boolean", + "description": "Whether the form part is enabled" + } + }, + "required": ["name", "type", "value"], + "additionalProperties": false + } + }, + "FileBody": { + "type": "array", + "description": "File body as array of file objects", + "items": { + "type": "object", + "properties": { + "filePath": { + "type": "string", + "description": "Path to the file" + }, + "contentType": { + "type": "string", + "description": "MIME type of the file" + }, + "selected": { + "type": "boolean", + "description": "Whether the file is selected" + } + }, + "required": ["filePath", "contentType", "selected"], + "additionalProperties": false + } + }, + "Assertion": { + "type": "object", + "description": "An assertion for response validation", + "properties": { + "expression": { + "type": "string", + "description": "The expression to evaluate" + }, + "operator": { + "type": "string", + "description": "The comparison operator" + }, + "value": { + "type": "string", + "description": "The expected value" + }, + "enabled": { + "type": "boolean", + "description": "Whether the assertion is enabled" + }, + "description": { + "$ref": "#/$defs/Description" + } + }, + "required": ["expression", "operator", "value"], + "additionalProperties": false + }, + "Auth": { + "description": "Authentication", + "oneOf": [ + { + "$ref": "#/$defs/AuthAwsV4" + }, + { + "$ref": "#/$defs/AuthBasic" + }, + { + "$ref": "#/$defs/AuthWsse" + }, + { + "$ref": "#/$defs/AuthBearer" + }, + { + "$ref": "#/$defs/AuthDigest" + }, + { + "$ref": "#/$defs/AuthNTLM" + }, + { + "$ref": "#/$defs/AuthApiKey" + } + ] + }, + "AuthAwsV4": { + "type": "object", + "description": "AWS V4 authentication", + "properties": { + "type": { + "type": "string", + "const": "awsv4" + }, + "accessKeyId": { + "type": "string", + "description": "AWS access key ID" + }, + "secretAccessKey": { + "type": "string", + "description": "AWS secret access key" + }, + "sessionToken": { + "type": "string", + "description": "AWS session token" + }, + "service": { + "type": "string", + "description": "AWS service name" + }, + "region": { + "type": "string", + "description": "AWS region" + }, + "profileName": { + "type": "string", + "description": "AWS profile name" + } + }, + "required": ["type"], + "additionalProperties": false + }, + "AuthBasic": { + "type": "object", + "description": "Basic authentication", + "properties": { + "type": { + "type": "string", + "const": "basic" + }, + "username": { + "type": "string", + "description": "Username for basic auth" + }, + "password": { + "type": "string", + "description": "Password for basic auth" + } + }, + "required": ["type"], + "additionalProperties": false + }, + "AuthWsse": { + "type": "object", + "description": "WSSE authentication", + "properties": { + "type": { + "type": "string", + "const": "wsse" + }, + "username": { + "type": "string", + "description": "Username for WSSE auth" + }, + "password": { + "type": "string", + "description": "Password for WSSE auth" + } + }, + "required": ["type"], + "additionalProperties": false + }, + "AuthBearer": { + "type": "object", + "description": "Bearer token authentication", + "properties": { + "type": { + "type": "string", + "const": "bearer" + }, + "token": { + "type": "string", + "description": "Bearer token" + } + }, + "required": ["type"], + "additionalProperties": false + }, + "AuthDigest": { + "type": "object", + "description": "Digest authentication", + "properties": { + "type": { + "type": "string", + "const": "digest" + }, + "username": { + "type": "string", + "description": "Username for digest auth" + }, + "password": { + "type": "string", + "description": "Password for digest auth" + } + }, + "required": ["type"], + "additionalProperties": false + }, + "AuthNTLM": { + "type": "object", + "description": "NTLM authentication", + "properties": { + "type": { + "type": "string", + "const": "ntlm" + }, + "username": { + "type": "string", + "description": "Username for NTLM auth" + }, + "password": { + "type": "string", + "description": "Password for NTLM auth" + }, + "domain": { + "type": "string", + "description": "Domain for NTLM auth" + } + }, + "required": ["type"], + "additionalProperties": false + }, + "AuthApiKey": { + "type": "object", + "description": "API Key authentication", + "properties": { + "type": { + "type": "string", + "const": "apikey" + }, + "key": { + "type": "string", + "description": "API key name" + }, + "value": { + "type": "string", + "description": "API key value" + }, + "placement": { + "type": "string", + "enum": ["header", "query"], + "description": "Where to place the API key" + } + }, + "required": ["type"], + "additionalProperties": false + }, + "Item": { + "oneOf": [ + { + "$ref": "#/$defs/HttpRequest" + }, + { + "$ref": "#/$defs/GraphQLRequest" + }, + { + "$ref": "#/$defs/GrpcRequest" + }, + { + "$ref": "#/$defs/Folder" + }, + { + "$ref": "#/$defs/Script" + } + ] + }, + "HttpRequest": { + "type": "object", + "description": "HTTP request configuration", + "properties": { + "name": { + "type": "string", + "description": "The name of the request" + }, + "type": { + "type": "string", + "const": "http", + "description": "The type of request" + }, + "url": { + "type": "string", + "description": "The URL of the request" + }, + "method": { + "type": "string", + "description": "HTTP method" + }, + "params": { + "type": "array", + "description": "Array of request parameters", + "items": { + "$ref": "#/$defs/RequestParam" + } + }, + "headers": { + "type": "array", + "description": "Array of request headers", + "items": { + "$ref": "#/$defs/RequestHeader" + } + }, + "body": { + "$ref": "#/$defs/RequestBody" + }, + "auth": { + "$ref": "#/$defs/Auth" + }, + "scripts": { + "$ref": "#/$defs/Scripts" + }, + "variables": { + "type": "array", + "description": "Array of variables", + "items": { + "$ref": "#/$defs/Variable" + } + }, + "assertions": { + "type": "array", + "description": "Array of assertions for response validation", + "items": { + "$ref": "#/$defs/Assertion" + } + }, + "docs": { + "type": "string", + "description": "Documentation for this request" + } + }, + "required": ["type"], + "additionalProperties": false + }, + "GraphQLRequest": { + }, + "GrpcRequest": { + }, + "Folder": { + "type": "object", + "description": "A folder for organizing collection items", + "properties": { + "type": { + "type": "string", + "const": "folder" + }, + "Item": { + "oneOf": [ + { + "$ref": "#/$defs/HttpRequest" + }, + { + "$ref": "#/$defs/GraphQLRequest" + }, + { + "$ref": "#/$defs/GrpcRequest" + }, + { + "$ref": "#/$defs/Folder" + }, + { + "$ref": "#/$defs/Script" + } + ] + }, + "headers": { + "type": "array", + "description": "Array of request headers", + "items": { + "$ref": "#/$defs/RequestHeader" + } + }, + "auth": { + "$ref": "#/$defs/Auth" + }, + "variables": { + "type": "array", + "description": "Array of variables", + "items": { + "$ref": "#/$defs/Variable" + } + }, + "scripts": { + "$ref": "#/$defs/Scripts" + }, + "docs": { + "type": "string", + "description": "Documentation for this folder" + } + }, + "additionalProperties": false + }, + "Script": { + "type": "object", + "description": "Javscript module or shared collection scripts", + "properties": { + "type": { + "type": "string", + "const": "script" + }, + "script": { + "type": "string", + "description": "The script" + } + }, + "additionalProperties": false + }, + "Environment": { + "type": "object", + "description": "An environment configuration", + "properties": { + "name": { + "type": "string", + "description": "The name of the environment" + }, + "description": { + "$ref": "#/$defs/Description" + }, + "variables": { + "type": "array", + "description": "Array of environment variables", + "items": { + "$ref": "#/$defs/Variable" + } + } + }, + "required": ["name"], + "additionalProperties": false + }, + "Variable": { + "type": "object", + "description": "A variable with name, value, description, and state flags", + "properties": { + "name": { + "type": "string", + "description": "The variable name" + }, + "value": { + "description": "The variable value", + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/ValueSet" + }, + { + "type": "null" + } + ] + }, + "description": { + "$ref": "#/$defs/Description" + }, + "disabled": { + "type": "boolean", + "description": "Whether the variable is disabled" + }, + "transient": { + "type": "boolean", + "description": "Whether the variable value should be persisted or not" + }, + "default": { + "description": "The default value when transient is true", + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/Value" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Value": { + "type": "object", + "description": "A value with data and type information", + "properties": { + "data": { + "type": "string", + "description": "The string representation of the value" + }, + "type": { + "type": "string", + "description": "The type of the value", + "enum": ["string", "number", "boolean", "null", "object"] + } + }, + "additionalProperties": false + }, + "ValueSet": { + "type": "object", + "description": "A value with data, type and variants", + "properties": { + "data": { + "type": "string", + "description": "The string representation of the value" + }, + "type": { + "type": "string", + "description": "The type of the value", + "enum": ["string", "number", "boolean", "null", "object"] + }, + "variants": { + "type": "array", + "description": "Array of variants for this value", + "items": { + "$ref": "#/$defs/Variant" + } + } + }, + "additionalProperties": false + }, + "Variant": { + "type": "object", + "description": "A variant with data and description", + "properties": { + "data": { + "type": "string", + "description": "The data for this variant" + }, + "description": { + "type": "string", + "description": "The description for this variant" + } + }, + "required": ["data"], + "additionalProperties": false + }, + "Scripts": { + "type": "object", + "description": "Scripts for collection execution lifecycle", + "properties": { + "preRequest": { + "type": "string", + "description": "Script to execute before making requests" + }, + "postResponse": { + "type": "string", + "description": "Script to execute after receiving responses" + }, + "tests": { + "type": "string", + "description": "Test scripts to validate responses" + }, + "hooks": { + "type": "string", + "description": "Scripts that can be injected into specific stages of the execution process." + } + }, + "additionalProperties": false + } + } +} \ No newline at end of file From bb5b921fc1795a26b71fbf60bc75b0543d008e11 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Fri, 19 Sep 2025 17:43:38 +0530 Subject: [PATCH 006/161] UX Revamp --- examples/vanilla-html/index.html | 1 - .../Playground/QueryBar}/QueryBar.tsx | 38 +- .../Playground/QueryBar/StyledWrapper.ts | 31 + .../oc-core/src/layouts/DesktopLayout.tsx | 3 +- packages/oc-core/src/styles/index.css | 346 +++- .../oc-core/src/ui/ExpandableFormTable.tsx | 1 - packages/oc-core/src/ui/ItemComponent.tsx | 1518 +++-------------- packages/oc-core/src/ui/MinimalComponents.tsx | 231 +++ packages/oc-core/src/ui/MobileLayout.tsx | 4 +- packages/oc-core/src/ui/Tabs/StyledWrapper.ts | 6 +- .../src/ui/request-runner/RequestHeader.tsx | 9 +- .../src/ui/request-runner/RequestRunner.tsx | 4 +- .../oc-core/src/ui/request-runner/index.ts | 1 - 13 files changed, 849 insertions(+), 1344 deletions(-) rename packages/oc-core/src/{ui/request-runner => components/Playground/QueryBar}/QueryBar.tsx (59%) create mode 100644 packages/oc-core/src/components/Playground/QueryBar/StyledWrapper.ts create mode 100644 packages/oc-core/src/ui/MinimalComponents.tsx diff --git a/examples/vanilla-html/index.html b/examples/vanilla-html/index.html index 39ebe681..fe7b8bc9 100644 --- a/examples/vanilla-html/index.html +++ b/examples/vanilla-html/index.html @@ -8,7 +8,6 @@ body { margin: 0; padding: 0; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } #playground-container { width: 100vw; diff --git a/packages/oc-core/src/ui/request-runner/QueryBar.tsx b/packages/oc-core/src/components/Playground/QueryBar/QueryBar.tsx similarity index 59% rename from packages/oc-core/src/ui/request-runner/QueryBar.tsx rename to packages/oc-core/src/components/Playground/QueryBar/QueryBar.tsx index 0b0376ce..0a4d16a3 100644 --- a/packages/oc-core/src/ui/request-runner/QueryBar.tsx +++ b/packages/oc-core/src/components/Playground/QueryBar/QueryBar.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from 'react'; -import { HttpRequest } from '../../types'; +import { HttpRequest } from '../../../types'; +import { StyledWrapper } from './StyledWrapper'; interface QueryBarProps { item: HttpRequest; @@ -43,11 +44,9 @@ const QueryBar: React.FC = ({ item, onSendRequest, isLoading, onI }; return ( -
@@ -55,14 +54,7 @@ const QueryBar: React.FC = ({ item, onSendRequest, isLoading, onI -
- - - -
= ({ item, onSendRequest, isLoading, onI value={url} onChange={(e) => handleUrlChange(e.target.value)} placeholder="Enter request URL" - className="flex-1 px-3 text-sm focus:outline-none focus:ring-1 focus:ring-inset focus:ring-blue-500" - style={{ - backgroundColor: 'transparent', - color: 'var(--text-primary)', - borderRadius: 0 - }} + className="flex-1 px-3 text-sm" onKeyPress={(e) => { if (e.key === 'Enter' && url.trim() && !isLoading) { onSendRequest(); @@ -98,18 +80,14 @@ const QueryBar: React.FC = ({ item, onSendRequest, isLoading, onI -
+ ); }; diff --git a/packages/oc-core/src/components/Playground/QueryBar/StyledWrapper.ts b/packages/oc-core/src/components/Playground/QueryBar/StyledWrapper.ts new file mode 100644 index 00000000..a69bfb8d --- /dev/null +++ b/packages/oc-core/src/components/Playground/QueryBar/StyledWrapper.ts @@ -0,0 +1,31 @@ +import styled from '@emotion/styled'; + +export const StyledWrapper = styled.div` + background-color: rgb(243, 243, 243); + border-top-left-radius: var(--oc-radius); + border-bottom-left-radius: var(--oc-radius); + border-top-right-radius: var(--oc-radius); + border-bottom-right-radius: var(--oc-radius); + + select { + background-color: transparent; + outline: none; + } + + input { + outline: none; + background-color: transparent; + color: var(--oc-text-primary); + border-radius: 0; + } + + button.send { + background-color: var(--primary-color); + border-top-right-radius: var(--oc-radius); + border-bottom-right-radius: var(--oc-radius); + border-top-left-radius: 0; + border-bottom-left-radius: 0; + font-size: 13px; + text-transform: uppercase; + } +`; \ No newline at end of file diff --git a/packages/oc-core/src/layouts/DesktopLayout.tsx b/packages/oc-core/src/layouts/DesktopLayout.tsx index b51cc800..2c5a6840 100644 --- a/packages/oc-core/src/layouts/DesktopLayout.tsx +++ b/packages/oc-core/src/layouts/DesktopLayout.tsx @@ -76,7 +76,6 @@ const DesktopLayout: React.FC = ({ className="playground-sidebar h-full overflow-hidden flex" style={{ width: 'var(--sidebar-width)', - borderRight: '1px solid var(--border-color)', transition: 'width 0.3s ease' }} > @@ -99,7 +98,7 @@ const DesktopLayout: React.FC = ({ className="playground-content h-full overflow-y-auto flex-1" ref={containerRef} > -
+
= ({ {items.map((item, index) => ( = { + 'GET': '#16a34a', + 'POST': '#2563eb', + 'PUT': '#f97316', + 'PATCH': '#8b5cf6', + 'DELETE': '#dc2626', + 'HEAD': '#6b7280', + 'OPTIONS': '#6b7280' +}; -// This is a memoized component to render individual items const ItemComponent = memo(({ item, registerSectionRef, theme, md, - parentPath = '', - collection + parentPath = '' }: { item: OpenCollectionItem; registerSectionRef: (id: string, ref: HTMLDivElement | null) => void; theme: 'light' | 'dark' | 'auto'; md: any; parentPath?: string; - collection?: OpenCollectionCollection; }) => { - const [activeCodeTab, setActiveCodeTab] = useState<'curl' | 'fetch' | 'http'>('curl'); - const [codeCopied, setCodeCopied] = useState(false); - const codeRef = useRef(null); - - // Script-related hooks (moved from renderScripts to fix hook rendering error) - const [activeScriptTab, setActiveScriptTab] = useState<'req' | 'res'>('req'); - const scriptCodeRef = useRef(null); - const itemId = getItemId(item); const sectionId = generateSectionId(item, parentPath); - const trackingId = parentPath ? `${parentPath}/${itemId}` : itemId; - const sectionRefCallback = useCallback((element: HTMLDivElement | null) => { if (element) { - console.log('Registering section ref for:', sectionId, 'with element ID:', element.id); registerSectionRef(sectionId, element); } }, [sectionId, registerSectionRef]); - // Effect for script syntax highlighting - useEffect(() => { - if (scriptCodeRef.current) { - Prism.highlightAllUnder(scriptCodeRef.current); - } - }, [activeScriptTab]); - - const renderHeadersTable = (headers: Array<{ name: string; value: string; enabled?: boolean; description?: string; uid?: string }>) => { - if (!headers || !headers.length) { - return null; - } - return ; - }; - - const renderVarsTable = (vars: Array<{ name: string; value: string; enabled?: boolean; local?: boolean; uid?: string }>) => { - if (!vars || !vars.length) { - return null; - } - return ; - }; - - const renderScripts = (script: Record) => { - if (!script || Object.keys(script).length === 0) { - return null; - } - - const scriptMap = { - req: script.req || script.pre || '', - res: script.res || script.post || '' - }; - - if (!scriptMap.req && !scriptMap.res) { - return null; - } + if (item.type === 'folder') { + const folderItem = item as Folder; return ( -
-
- -
-
- -
- - - - - - +
+
+
+
+ + + + Folder
-
- - -
- {activeScriptTab === 'req' && scriptMap.req && ( -
-
-                  {scriptMap.req}
-                
-
- )} - - {activeScriptTab === 'res' && scriptMap.res && ( -
-
-                  {scriptMap.res}
-                
-
- )} - - {activeScriptTab === 'req' && !scriptMap.req && ( -
- No pre-request script available -
- )} - - {activeScriptTab === 'res' && !scriptMap.res && ( -
- No post-response script available -
- )} +

{folderItem.name || 'Untitled Folder'}

-
- ); - }; - - const formatCurlHeaders = (headers: Record = {}) => { - return Object.entries(headers) - .map(([key, value]) => `-H "${key}: ${value}"`) - .join(" \\\n "); - }; - - const formatQueryParams = (queryParams: Record = {}) => { - const params = Object.entries(queryParams) - .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) - .join("&"); - return params ? `?${params}` : ""; - }; - - const formatRequestBody = (requestBody: any, contentType: string = "application/json") => { - if (!requestBody) return ""; - - if (typeof requestBody === "string") { - if (contentType === "application/json") { - try { - const parsed = JSON.parse(requestBody); - return JSON.stringify(parsed, null, 2); - } catch { - return requestBody; - } - } else if (contentType === "application/xml") { - return requestBody; - } else { - return requestBody; - } - } - - return JSON.stringify(requestBody, null, 2); - }; - - const getNormalizedEndpoint = () => { - if (item.type !== 'http') return null; - - const httpItem = item as HttpRequest; - const headers: Record = {}; - if (httpItem.headers) { - httpItem.headers.forEach((header: any) => { - if (header.disabled !== true) { - headers[header.name] = header.value; - } - }); - } - - const queryParams: Record = {}; - if (httpItem.params) { - httpItem.params.forEach((param: any) => { - if (param.enabled !== false) { - queryParams[param.name] = param.value; - } - }); - } - let requestBody = null; - let contentType = "application/json"; - - if (httpItem.body) { - if (typeof httpItem.body === 'object' && 'data' in httpItem.body) { - const rawBody = httpItem.body as any; - if (rawBody.type === 'json') { - try { - requestBody = JSON.parse(rawBody.data); - contentType = "application/json"; - } catch { - requestBody = rawBody.data; - } - } else if (rawBody.type === 'text') { - requestBody = rawBody.data; - contentType = "text/plain"; - } else if (rawBody.type === 'xml') { - requestBody = rawBody.data; - contentType = "application/xml"; - } - } - } - - const itemName = httpItem.name || httpItem.type; - return { - id: itemName, - method: httpItem.method || 'GET', - path: httpItem.url || '', - description: httpItem.docs || '', - headers: headers, - queryParams: queryParams, - requestBody: requestBody, - contentType: contentType - }; - }; - - const generateCodeSample = () => { - const endpoint = getNormalizedEndpoint(); - if (!endpoint) return ''; - - const { method, path, headers, queryParams, requestBody, contentType } = endpoint; - const url = `${path}${formatQueryParams(queryParams)}`; - - const headersWithContentType = { ...headers }; - if (requestBody && !Object.keys(headers).some(key => key.toLowerCase() === 'content-type')) { - headersWithContentType['Content-Type'] = contentType; - } - - switch (activeCodeTab) { - case 'curl': - let curlCommand = `curl -X ${method} "${url}"`; - if (Object.keys(headersWithContentType).length) { - curlCommand += ` \\\n ${formatCurlHeaders(headersWithContentType)}`; - } - if (requestBody) { - const bodyStr = formatRequestBody(requestBody, contentType); - curlCommand += ` \\\n -d '${bodyStr.replace(/'/g, "\\'")}'`; - } - return curlCommand; - - case 'fetch': - let fetchCode = `const options = {\n method: "${method}"`; - if (Object.keys(headersWithContentType).length) { - fetchCode += `,\n headers: {\n ${Object.entries(headersWithContentType) - .map(([key, value]) => `"${key}": "${value}"`) - .join(",\n ")}\n }`; - } - if (requestBody) { - if (contentType === "application/json") { - fetchCode += `,\n body: JSON.stringify(${formatRequestBody(requestBody, contentType)})`; - } else { - fetchCode += `,\n body: \`${formatRequestBody(requestBody, contentType).replace(/`/g, "\\`")}\``; - } - } - - let responseHandling = ""; - if (contentType === "application/json") { - responseHandling = ".then(response => response.json())\n .then(json => console.log(json))"; - } else if (contentType === "application/xml") { - responseHandling = ".then(response => response.text())\n .then(text => console.log(text))"; - } else { - responseHandling = ".then(response => response.text())\n .then(text => console.log(text))"; - } - - fetchCode += `\n};\n\nfetch("${url}", options)\n ${responseHandling}\n .catch(err => console.error('error', err));`; - return fetchCode; - - case 'http': - let httpRequest = `${method} ${url} HTTP/1.1\n`; - if (Object.keys(headersWithContentType).length) { - httpRequest += Object.entries(headersWithContentType) - .map(([key, value]) => `${key}: ${value}`) - .join("\n"); - } - if (requestBody) { - httpRequest += `\n\n${formatRequestBody(requestBody, contentType)}`; - } - return httpRequest; - - default: - return ''; - } - }; - - const handleCopyCode = () => { - const code = generateCodeSample(); - navigator.clipboard.writeText(code); - setCodeCopied(true); - setTimeout(() => setCodeCopied(false), 2000); - }; - - const renderAssertionsTable = (assertions: Array<{ name: string; value: string; enabled?: boolean; uid?: string }>) => { - if (!assertions || !assertions.length) { - return null; - } - return ; - }; - - const renderCodeSamples = () => { - if (item.type !== 'http') return null; - - const code = generateCodeSample(); - const codeLanguage = activeCodeTab === 'curl' ? 'bash' : activeCodeTab === 'fetch' ? 'javascript' : 'http'; - - const languageOptions = [ - { - id: 'curl', label: 'cURL', icon: ( - - - - ) - }, - { - id: 'fetch', label: 'Fetch API', icon: ( - - - - - - ) - }, - { - id: 'http', label: 'HTTP', icon: ( - - - - - ) - } - ]; - - const selectedLanguage = languageOptions.find(lang => lang.id === activeCodeTab) || languageOptions[0]; - - const [dropdownOpen, setDropdownOpen] = useState(false); - - const toggleDropdown = () => { - setDropdownOpen(!dropdownOpen); - }; - - useEffect(() => { - const handleClickOutside = () => { - setDropdownOpen(false); - }; - - if (dropdownOpen) { - document.addEventListener('click', handleClickOutside); - } + {folderItem.docs && ( +
+
+
+ )} + +
+ {folderItem.headers && folderItem.headers.length > 0 && ( + } + ]} + /> + )} - return () => { - document.removeEventListener('click', handleClickOutside); - }; - }, [dropdownOpen]); + {folderItem.variables && folderItem.variables.length > 0 && ( + ({ + name: v.name, + value: v.value || v.default || '', + enabled: !v.disabled + }))} + title="Variables" + columns={[ + { key: 'name', label: 'Name', width: '40%' }, + { key: 'value', label: 'Value', width: '40%' }, + { key: 'enabled', label: '', width: '20%', render: (val) => } + ]} + /> + )} - return ( -
-
- -
e.stopPropagation()}> - + {folderItem.scripts && (folderItem.scripts.preRequest || folderItem.scripts.postResponse) && ( + + )} - {dropdownOpen && ( -
- {languageOptions.map(option => ( - - ))} -
- )} -
-
- -
-
-          {code}
-        
-
- ); - }; - - // Highlight code when it changes or tab changes - useEffect(() => { - if (codeRef.current) { - Prism.highlightAllUnder(codeRef.current); - } - }, [activeCodeTab, item]); - - if (item.type === 'folder') { - const folderItem = item as Folder; - - return ( -
-
-

{folderItem.name || 'Untitled Folder'}

-
- - - - - FOLDER - -
- - {folderItem.docs && ( -
-
-
- )} -
- -
- {!folderItem.docs && ( -
- This folder contains configuration items. OpenCollection folders are containers for organizing requests and other items. -
- )} - - {/* Folder configuration details */} -
- {folderItem.headers && folderItem.headers.length > 0 && ( -
-

Folder Headers

- {renderHeadersTable(folderItem.headers)} -
- )} - - {folderItem.variables && folderItem.variables.length > 0 && ( -
-

Folder Variables

- {renderVarsTable(folderItem.variables.map(v => ({ - name: v.name, - value: v.value || v.default || '', - enabled: !v.disabled, - local: v.transient - })))} -
- )} - - {folderItem.auth && Object.keys(folderItem.auth).length > 0 && ( -
-

Folder Authentication

-
- Type: {folderItem.auth.type} -
-
- )} - - {folderItem.scripts && Object.keys(folderItem.scripts).length > 0 && ( -
-

Folder Scripts

- {renderScripts({ - req: folderItem.scripts.preRequest || '', - res: folderItem.scripts.postResponse || '' - })} -
- )} - - {/* Show nested items if they exist */} - {folderItem.items && folderItem.items.length > 0 && ( -
-
- - - - - - Folder Contents ({folderItem.items.length}) -
- -
- {sortItemsWithFoldersFirst(folderItem.items).map((nestedItem, index) => { - const nestedItemId = getItemId(nestedItem); - const safeId = generateSafeId(nestedItemId); - - const getMethodColor = (method: string) => { - const colors: Record = { - 'GET': '#10b981', - 'POST': '#3b82f6', - 'PUT': '#f59e0b', - 'PATCH': '#8b5cf6', - 'DELETE': '#ef4444', - 'HEAD': '#6b7280', - 'OPTIONS': '#6b7280' - }; - return colors[method?.toUpperCase()] || '#6b7280'; - }; - - return ( -
{ - // Navigate to the nested item page - if (typeof window !== 'undefined') { - const url = new URL(window.location.href); - url.searchParams.set('page', safeId); - window.history.pushState({}, '', url.toString()); - window.location.reload(); // Simple way to trigger navigation - } - }} - > - {/* Card Header */} -
-
- {nestedItem.type === 'http' && ( -
- - {(nestedItem as any).method || 'GET'} - - - - - -
- )} - - {nestedItem.type === 'folder' && ( -
-
- -
- - FOLDER - -
- )} - - {nestedItem.type === 'script' && ( -
-
- -
- - SCRIPT - -
- )} -
- - {/* Navigate Arrow */} - - - -
- - {/* Card Content */} -
-

- {nestedItem.name || nestedItemId} -

- - {nestedItem.type === 'http' && (nestedItem as any).url && ( -

- {(nestedItem as any).url} -

- )} - - {nestedItem.type === 'http' && (nestedItem as any).docs && ( -

- {(nestedItem as any).docs} -

- )} - - {nestedItem.type === 'folder' && ( -

- Contains {(nestedItem as any).items?.length || 0} items -

- )} - - {nestedItem.type === 'script' && ( -

- JavaScript execution script -

- )} -
- - {/* Hover Effect Overlay */} -
+
+

{nestedItem.name || nestedItemId}

+ {nestedItem.type === 'http' && (nestedItem as any).url && ( +

{(nestedItem as any).url}

+ )}
- ); - })} -
- -
-
- - - - - - Click on any card to navigate to that item's dedicated page - -
-
+ + + +
+ ); + })}
- )} -
+
+ )}
); @@ -864,77 +163,32 @@ const ItemComponent = memo(({ const scriptItem = item as Script; return ( -
-
-

{scriptItem.name || 'Untitled Script'}

-
- - - - - - - +
+
+
+
+ + + - SCRIPT - + Script +
+

{scriptItem.name || 'Untitled Script'}

-
- {scriptItem.script && ( -
-
-
- Script Content -
-
-                  {scriptItem.script}
-                
-
-
- )} - - {!scriptItem.script && ( -
- No script content available. -
- )} -
+ {scriptItem.script && ( + + )}
); } else if (item.type === 'http') { const httpItem = item as HttpRequest; + const endpoint = { id: itemId, name: httpItem.name || 'Untitled', @@ -951,412 +205,136 @@ const ItemComponent = memo(({ script: httpItem.scripts || {} }; - return ( -
+ const generateCurlCommand = () => { + const headers = endpoint.headers?.filter((h: any) => h.enabled !== false) + .map((h: any) => `-H "${h.name}: ${h.value}"`).join(" \\\n ") || ''; + + let bodyData = ''; + if (endpoint.body && typeof endpoint.body === 'object' && 'data' in endpoint.body) { + const rawBody = endpoint.body as any; + if (rawBody.type === 'json' && rawBody.data) { + bodyData = ` \\\n -d '${rawBody.data}'`; + } + } + + return `curl -X ${endpoint.method} "${endpoint.url}"${headers ? ` \\\n ${headers}` : ''}${bodyData}`; + }; -
-

{endpoint.name}

-
- - {endpoint.method} - - {endpoint.url} -
- {endpoint.description && ( -
-
+ return ( +
+
+
+

{endpoint.name}

+
+ + {endpoint.method} + + {endpoint.url} +
- )} +
-
- -
- - {false && endpoint.auth && ( -
-

Authentication

-
- {(endpoint.auth as any)?.mode === 'basic' && ( -
-
- - - - - Basic Authentication -
-
- {(endpoint.auth as any)?.username && ( -
- Username: - {(endpoint.auth as any)?.username} -
- )} - {(endpoint.auth as any)?.password && ( -
- Password: - •••••••• -
- )} -
-
- )} - {(endpoint.auth as any)?.mode === 'bearer' && ( -
-
- - - - - Bearer Token Authentication -
-
- {(endpoint.auth as any)?.bearer.token && ( -
- Token: - {(endpoint.auth as any)?.token || 'token here'} -
- )} -
-
- )} - {(endpoint.auth as any)?.mode === 'oauth2' && ( -
-
- - - - - - - - OAuth 2.0 -
-
- {(endpoint.auth as any)?.grantType && ( -
- Grant Type: - {(endpoint.auth as any)?.grantType} -
- )} - {(endpoint.auth as any)?.accessTokenUrl && ( -
- Access Token URL: - {(endpoint.auth as any)?.accessTokenUrl} -
- )} - {(endpoint.auth as any)?.clientId && ( -
- Client ID: - {(endpoint.auth as any)?.clientId} -
- )} - {(endpoint.auth as any)?.scope && ( -
- Scope: - {(endpoint.auth as any)?.scope} -
- )} -
-
- )} -
-
- )} - + {endpoint.description && ( +
+
+
+ )} +
+
{endpoint.params && endpoint.params.length > 0 && ( -
-

Params

-
- - - - - - - - - - {endpoint.params.map((param: any, index: number) => ( - - - - - - ))} - -
NameValueStatus
{param.name} - {param.value} - - - {param.enabled !== false ? 'Enabled' : 'Disabled'} - -
-
-
+ } + ]} + /> )} + {endpoint.headers && endpoint.headers.length > 0 && ( + } + ]} + /> + )} - {endpoint.headers && endpoint.headers.length > 0 && renderHeadersTable(endpoint.headers)} - - - {endpoint.body && ( -
-
-
- - Body -
- - {typeof endpoint.body === 'object' && 'type' in endpoint.body && 'data' in endpoint.body && ( - - {(endpoint.body as any).type === 'json' ? - JSON.stringify(JSON.parse((endpoint.body as any).data || '{}'), null, 2) : - (endpoint.body as any).data - } - - )} - - - {Array.isArray(endpoint.body) && endpoint.body.length > 0 && - endpoint.body[0] && 'name' in endpoint.body[0] && 'value' in endpoint.body[0] && - !('type' in endpoint.body[0]) && ( - - )} - - - {Array.isArray(endpoint.body) && endpoint.body.length > 0 && - endpoint.body[0] && 'type' in endpoint.body[0] && ( - - )} - - - {Array.isArray(endpoint.body) && endpoint.body.length > 0 && - endpoint.body[0] && 'filePath' in endpoint.body[0] && ( -
-

Files:

- {(endpoint.body as any).map((file: any, index: number) => ( -
- {file.filePath} ({file.contentType}) -
- ))} -
- )} - -
-

- {typeof endpoint.body === 'object' && 'type' in endpoint.body ? - (endpoint.body as any).type : - Array.isArray(endpoint.body) ? 'form-data' : 'body' - } -

-
-
+ {endpoint.body && typeof endpoint.body === 'object' && 'data' in endpoint.body && ( +
+

Body

+
)} - - {endpoint.vars && Array.isArray(endpoint.vars) && renderVarsTable(endpoint.vars)} - - - {endpoint.assertions && endpoint.assertions.length > 0 && renderAssertionsTable(endpoint.assertions.map((assertion: any) => ({ - name: assertion.expression || 'assertion', - value: assertion.value || '', - enabled: assertion.enabled !== false, - uid: assertion.expression - })))} - - - {endpoint.tests && ( - )} - - - {endpoint.script && Object.keys(endpoint.script).length > 0 && renderScripts({ - req: (endpoint.script as any).preRequest || '', - res: (endpoint.script as any).postResponse || '', - test: (endpoint.script as any).tests || '', - hooks: (endpoint.script as any).hooks || '' - })}
+
+
+

Example Request

+ { + if (activeTab === 'curl') { + return ; + } else if (activeTab === 'javascript') { + const jsCode = `const response = await fetch("${endpoint.url}", { + method: "${endpoint.method}", + headers: { +${endpoint.headers?.filter((h: any) => h.enabled !== false).map((h: any) => ` "${h.name}": "${h.value}"`).join(',\n')} + }${endpoint.body && typeof endpoint.body === 'object' && 'data' in endpoint.body ? `, + body: JSON.stringify(${(endpoint.body as any).data || '{}'})` : ''} +}); -
-
- {renderCodeSamples()} +const data = await response.json();`; + return ; + } else { + const pyCode = `import requests + +response = requests.${endpoint.method.toLowerCase()}( + "${endpoint.url}", + headers={ +${endpoint.headers?.filter((h: any) => h.enabled !== false).map((h: any) => ` "${h.name}": "${h.value}"`).join(',\n')} + }${endpoint.body && typeof endpoint.body === 'object' && 'data' in endpoint.body ? `, + json=${(endpoint.body as any).data || '{}'}` : ''} +) + +data = response.json()`; + return ; + } + }} + />
@@ -1364,14 +342,11 @@ const ItemComponent = memo(({ ); } - // Default case for unsupported item types return ( -
-
-

{(item as any).name || 'Untitled Item'}

-
- Unsupported item type: {(item as any).type} -
+
+
+

{(item as any).name || 'Untitled Item'}

+

Unsupported item type: {(item as any).type}

); @@ -1388,9 +363,8 @@ const ItemComponent = memo(({ return ( prevProps.theme === nextProps.theme && - prevProps.parentPath === nextProps.parentPath && - prevProps.collection === nextProps.collection + prevProps.parentPath === nextProps.parentPath ); }); -export default ItemComponent; \ No newline at end of file +export default ItemComponent; \ No newline at end of file diff --git a/packages/oc-core/src/ui/MinimalComponents.tsx b/packages/oc-core/src/ui/MinimalComponents.tsx new file mode 100644 index 00000000..eb648738 --- /dev/null +++ b/packages/oc-core/src/ui/MinimalComponents.tsx @@ -0,0 +1,231 @@ +import React, { useState, useEffect, useRef } from 'react'; +import Prism from 'prismjs'; +import 'prismjs/components/prism-javascript'; +import 'prismjs/components/prism-bash'; +import 'prismjs/components/prism-python'; +import 'prismjs/components/prism-json'; +import 'prismjs/components/prism-xml-doc'; + +interface Column { + key: string; + label: string; + width?: string; + render?: (value: any, row: any) => React.ReactNode; +} + +interface MinimalDataTableProps { + data: any[]; + title?: string; + columns: Column[]; + compact?: boolean; +} + +export const MinimalDataTable: React.FC = ({ + data, + title, + columns, + compact = true +}) => { + if (!data || data.length === 0) return null; + + return ( +
+ {title &&

{title}

} +
+ + + + {columns.map(col => ( + + ))} + + + + {data.map((row, idx) => ( + + {columns.map(col => ( + + ))} + + ))} + +
+ {col.label} +
+ {col.render ? col.render(row[col.key], row) : ( + {row[col.key] || '-'} + )} +
+
+
+ ); +}; + +interface CompactCodeViewProps { + code?: string; + language?: string; + title?: string; + tabs?: Array<{ id: string; label: string; content: string }>; + copyButton?: boolean; +} + +export const CompactCodeView: React.FC = ({ + code, + language = 'text', + title, + tabs, + copyButton = true +}) => { + const [activeTab, setActiveTab] = useState(tabs?.[0]?.id || ''); + const [copied, setCopied] = useState(false); + const codeRef = useRef(null); + + useEffect(() => { + if (codeRef.current) { + Prism.highlightAllUnder(codeRef.current); + } + }, [activeTab, code, language]); + + const handleCopy = () => { + const textToCopy = tabs ? tabs.find(t => t.id === activeTab)?.content : code; + if (textToCopy) { + navigator.clipboard.writeText(textToCopy); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + }; + + const displayCode = tabs ? tabs.find(t => t.id === activeTab)?.content : code; + + return ( +
+ {(title || tabs) && ( +
+ {title && !tabs &&

{title}

} + {tabs && ( +
+ {tabs.map(tab => ( + + ))} +
+ )} + {copyButton && ( + + )} +
+ )} +
+
+          
+            {displayCode || ''}
+          
+        
+
+
+ ); +}; + +interface StatusBadgeProps { + status: 'active' | 'inactive' | 'warning' | 'error'; + text?: string; +} + +export const StatusBadge: React.FC = ({ status, text }) => { + const statusConfig = { + active: { color: '#16a34a', label: 'Active' }, + inactive: { color: '#6b7280', label: 'Inactive' }, + warning: { color: '#f97316', label: 'Warning' }, + error: { color: '#dc2626', label: 'Error' } + }; + + const config = statusConfig[status]; + + return ( + + {text || config.label} + + ); +}; + +interface IconButtonProps { + icon: React.ReactNode; + onClick?: () => void; + tooltip?: string; + size?: 'small' | 'medium' | 'large'; +} + +export const IconButton: React.FC = ({ + icon, + onClick, + tooltip, + size = 'medium' +}) => { + return ( + + ); +}; + +interface TabGroupProps { + tabs: Array<{ id: string; label: string }>; + defaultTab?: string; + renderContent: (activeTab: string) => React.ReactNode; +} + +export const TabGroup: React.FC = ({ + tabs, + defaultTab, + renderContent +}) => { + const [activeTab, setActiveTab] = useState(defaultTab || tabs[0]?.id || ''); + + return ( +
+
+ {tabs.map(tab => ( + + ))} +
+
+ {renderContent(activeTab)} +
+
+ ); +}; \ No newline at end of file diff --git a/packages/oc-core/src/ui/MobileLayout.tsx b/packages/oc-core/src/ui/MobileLayout.tsx index b122f14c..165b1f05 100644 --- a/packages/oc-core/src/ui/MobileLayout.tsx +++ b/packages/oc-core/src/ui/MobileLayout.tsx @@ -42,7 +42,7 @@ const MobileLayout: React.FC = ({ if (hideSidebar) { return (
-
+
= ({ className={`h-full overflow-y-auto ${mobileView === 'content' ? 'block' : 'hidden'}`} ref={containerRef} > -
+
= ({ }) => { return (
= ({

{item.name || 'Untitled Request'} diff --git a/packages/oc-core/src/ui/request-runner/RequestRunner.tsx b/packages/oc-core/src/ui/request-runner/RequestRunner.tsx index 89d0e229..5cea3ffc 100644 --- a/packages/oc-core/src/ui/request-runner/RequestRunner.tsx +++ b/packages/oc-core/src/ui/request-runner/RequestRunner.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { HttpRequest, OpenCollectionCollection } from '../../types'; import { createRequestRunner } from '../../runner'; import RequestHeader from './RequestHeader'; -import QueryBar from './QueryBar'; +import QueryBar from '../../components/Playground/QueryBar/QueryBar'; import RequestPane from './RequestPane'; import ResponsePane from './ResponsePane'; @@ -96,7 +96,7 @@ const RequestRunner: React.FC = ({ item, collection, proxyUr onItemChange={setEditableItem} /> -
+
Date: Sat, 20 Sep 2025 14:03:10 +0530 Subject: [PATCH 007/161] UX Revamp --- examples/standalone-html/bruno.json | 647 ++++++++++++++ examples/standalone-html/index.html | 206 +++++ examples/standalone-html/index2.html | 254 ++++++ examples/standalone-html/oc.yaml | 230 +++++ examples/standalone-html/schema.json | 786 ++++++++++++++++++ examples/vanilla-html/index.html | 281 ------- packages/oc-core/src/App.tsx | 5 +- .../src/assets/opencollection-logo.svg | 7 + .../oc-core/src/core/FileCollectionLoader.tsx | 4 +- ...ctionPlayground.tsx => OpenCollection.tsx} | 31 +- .../oc-core/src/hooks/useCollectionData.ts | 47 +- .../oc-core/src/hooks/useItemFiltering.ts | 8 +- packages/oc-core/src/index.ts | 4 +- .../oc-core/src/layouts/DesktopLayout.tsx | 8 +- packages/oc-core/src/server.ts | 6 +- packages/oc-core/src/standalone.ts | 23 +- packages/oc-core/src/styles/index.css | 4 +- packages/oc-core/src/types/index.ts | 2 +- packages/oc-core/src/ui/ItemComponent.tsx | 8 +- packages/oc-core/src/ui/MobileLayout.tsx | 7 +- packages/oc-core/src/ui/Overview.tsx | 204 ----- packages/oc-core/src/ui/Sidebar.tsx | 97 +-- .../oc-core/src/ui/SinglePageRenderer.tsx | 15 +- .../src/ui/request-runner/RequestHeader.tsx | 24 +- .../src/ui/request-runner/RequestRunner.tsx | 4 +- 25 files changed, 2242 insertions(+), 670 deletions(-) create mode 100644 examples/standalone-html/bruno.json create mode 100644 examples/standalone-html/index.html create mode 100644 examples/standalone-html/index2.html create mode 100644 examples/standalone-html/oc.yaml create mode 100644 examples/standalone-html/schema.json delete mode 100644 examples/vanilla-html/index.html create mode 100644 packages/oc-core/src/assets/opencollection-logo.svg rename packages/oc-core/src/core/{OpenCollectionPlayground.tsx => OpenCollection.tsx} (83%) delete mode 100644 packages/oc-core/src/ui/Overview.tsx diff --git a/examples/standalone-html/bruno.json b/examples/standalone-html/bruno.json new file mode 100644 index 00000000..50a80eba --- /dev/null +++ b/examples/standalone-html/bruno.json @@ -0,0 +1,647 @@ +{ + "name": "E-commerce API", + "version": "1", + "items": [ + { + "type": "folder", + "name": "Cart", + "filename": "Cart", + "seq": 4, + "root": { + "docs": "Shopping cart endpoints", + "meta": { + "name": "Cart", + "seq": 4 + } + }, + "items": [ + { + "type": "http", + "name": "Add Item to Cart", + "filename": "Add Item to Cart.bru", + "seq": 2, + "settings": {}, + "tags": [], + "request": { + "url": "{{baseUrl}}/cart/add/item/:itemId", + "method": "POST", + "headers": [ + { + "name": "Content-Type", + "value": "application/json", + "enabled": true + }, + { + "name": "Authorization", + "value": "Bearer {{accessToken}}", + "enabled": true + } + ], + "params": [ + { + "name": "itemId", + "value": "cd3ad475-0784-4a0d-9fe4-a9a17e5391d9", + "type": "path", + "enabled": true + } + ], + "body": { + "mode": "json", + "json": "{ \n \"quantity\": 2\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "Add a product to the user's shopping cart", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "Clear Cart", + "filename": "Clear Cart.bru", + "seq": 5, + "settings": {}, + "tags": [], + "request": { + "url": "{{baseUrl}}/cart", + "method": "DELETE", + "headers": [ + { + "name": "Authorization", + "value": "Bearer {{accessToken}}", + "enabled": true + } + ], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "Remove all items from the user's cart", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "Get User Cart", + "filename": "Get User Cart.bru", + "seq": 1, + "settings": {}, + "tags": [], + "request": { + "url": "{{baseUrl}}/cart", + "method": "GET", + "headers": [ + { + "name": "Authorization", + "value": "Bearer {{accessToken}}", + "enabled": true + } + ], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "Retrieve the current user's shopping cart", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "Remove Item from Cart", + "filename": "Remove Item from Cart.bru", + "seq": 4, + "settings": {}, + "tags": [], + "request": { + "url": "{{baseUrl}}/cart/item/:itemId", + "method": "DELETE", + "headers": [ + { + "name": "Authorization", + "value": "Bearer {{accessToken}}", + "enabled": true + } + ], + "params": [ + { + "name": "itemId", + "value": "cd3ad475-0784-4a0d-9fe4-a9a17e5391d9", + "type": "path", + "enabled": true + } + ], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "Remove a specific item from the cart", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "Update Cart Item", + "filename": "Update Cart Item.bru", + "seq": 3, + "settings": {}, + "tags": [], + "request": { + "url": "{{baseUrl}}/cart/item/:itemId", + "method": "PUT", + "headers": [ + { + "name": "Content-Type", + "value": "application/json", + "enabled": true + }, + { + "name": "Authorization", + "value": "Bearer {{accessToken}}", + "enabled": true + } + ], + "params": [ + { + "name": "itemId", + "value": "cd3ad475-0784-4a0d-9fe4-a9a17e5391d9", + "type": "path", + "enabled": true + } + ], + "body": { + "mode": "json", + "json": "{\n \"quantity\": 5\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "Update the quantity of an item in the cart", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "Authentication", + "filename": "Authentication", + "seq": 2, + "root": { + "docs": "User authentication endpoints", + "meta": { + "name": "Authentication", + "seq": 2 + } + }, + "items": [ + { + "type": "http", + "name": "Register User", + "filename": "Register User.bru", + "seq": 1, + "settings": {}, + "tags": [], + "request": { + "url": "{{baseUrl}}/auth/register", + "method": "POST", + "headers": [ + { + "name": "Content-Type", + "value": "application/json", + "enabled": true + } + ], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"name\": \"Anoop\",\n \"email\": \"test@example.com\",\n \"password\": \"password123\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "Register a new user account", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "Whoami", + "filename": "Whoami.bru", + "seq": 3, + "settings": {}, + "tags": [], + "request": { + "url": "{{baseUrl}}/auth/whoami", + "method": "GET", + "headers": [ + { + "name": "Authorization", + "value": "Bearer {{accessToken}}", + "enabled": true + } + ], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "Retrieve authenticated user's profile information", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "Login", + "filename": "Login.bru", + "seq": 2, + "settings": {}, + "tags": [], + "request": { + "url": "{{baseUrl}}/auth/login", + "method": "POST", + "headers": [], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"email\": \"test@example.com\",\n \"password\": \"password123\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": { + "res": "bru.setEnvVar(\"accessToken\", res.body.token)" + }, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "Authenticate a user and receive a token", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "Orders", + "filename": "Orders", + "seq": 5, + "root": { + "docs": "Order management endpoints", + "meta": { + "name": "Orders", + "seq": 5 + } + }, + "items": [ + { + "type": "http", + "name": "Create Order", + "filename": "Create Order.bru", + "seq": 1, + "settings": {}, + "tags": [], + "request": { + "url": "{{baseUrl}}/orders", + "method": "POST", + "headers": [ + { + "name": "Content-Type", + "value": "application/json", + "enabled": true + }, + { + "name": "Authorization", + "value": "Bearer {{accessToken}}", + "enabled": true + } + ], + "params": [], + "body": { + "mode": "json", + "json": "{\n \"shippingAddress\": \"Mars\"\n}", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "// Check if the request was successful\nif (res.getStatus() === 200 || res.getStatus() === 201) {\n // Parse the response body\n var jsonData = res.getBody();\n \n // Check if the order has an ID\n if (jsonData && jsonData.id) {\n // Store the order ID in the environment\n bru.setEnvVar('order_id', jsonData.id);\n console.log('Order ID saved to environment: ' + jsonData.id);\n }\n}", + "docs": "Create a new order using the items in the user's cart", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "Get User Orders", + "filename": "Get User Orders.bru", + "seq": 2, + "settings": {}, + "tags": [], + "request": { + "url": "{{baseUrl}}/orders", + "method": "GET", + "headers": [ + { + "name": "Authorization", + "value": "Bearer {{accessToken}}", + "enabled": true + } + ], + "params": [], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "Retrieve a list of all orders placed by the current user", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "Get Order by ID", + "filename": "Get Order by ID.bru", + "seq": 3, + "settings": {}, + "tags": [], + "request": { + "url": "{{baseUrl}}/orders/:orderId", + "method": "GET", + "headers": [ + { + "name": "Authorization", + "value": "Bearer {{accessToken}}", + "enabled": true + } + ], + "params": [ + { + "name": "orderId", + "value": "9c4d1e4f-64dd-465a-b52a-5471caab828b", + "type": "path", + "enabled": true + } + ], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "Retrieve detailed information about a specific order", + "auth": { + "mode": "none" + } + } + } + ] + }, + { + "type": "folder", + "name": "Products", + "filename": "Products", + "seq": 3, + "root": { + "docs": "Product-related endpoints. The server automatically seeds 5 sample products on startup if no products exist.", + "meta": { + "name": "Products", + "seq": 3 + } + }, + "items": [ + { + "type": "http", + "name": "Get Products by Category", + "filename": "Get Products by Category.bru", + "seq": 2, + "settings": {}, + "tags": [], + "request": { + "url": "{{baseUrl}}/products?category=Electronics", + "method": "GET", + "headers": [], + "params": [ + { + "name": "category", + "value": "Electronics", + "type": "query", + "enabled": true + } + ], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "Retrieve products filtered by category. Seeded categories include: Electronics, Sports, and Home.", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "Get Product by ID", + "filename": "Get Product by ID.bru", + "seq": 3, + "settings": {}, + "tags": [], + "request": { + "url": "{{baseUrl}}/products/:productId", + "method": "GET", + "headers": [], + "params": [ + { + "name": "productId", + "value": "cd3ad475-0784-4a0d-9fe4-a9a17e5391d9", + "type": "path", + "enabled": true + } + ], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "Retrieve detailed information about a specific product", + "auth": { + "mode": "none" + } + } + }, + { + "type": "http", + "name": "Get All Products", + "filename": "Get All Products.bru", + "seq": 1, + "settings": {}, + "tags": [], + "request": { + "url": "{{baseUrl}}/products", + "method": "GET", + "headers": [], + "params": [ + { + "name": "category", + "value": "electronics", + "type": "query", + "enabled": false + } + ], + "body": { + "mode": "none", + "formUrlEncoded": [], + "multipartForm": [], + "file": [] + }, + "script": {}, + "vars": {}, + "assertions": [], + "tests": "", + "docs": "Retrieve a list of all products, optionally filtered by category. Default seeded categories include: Electronics, Sports, and Home.", + "auth": { + "mode": "none" + } + } + } + ] + } + ], + "activeEnvironmentUid": "RcGeNRbYHRYrqqtGIxs7V", + "environments": [ + { + "variables": [ + { + "name": "baseUrl", + "value": "http://localhost:3001/api", + "enabled": true, + "secret": false, + "type": "text" + }, + { + "name": "userToken", + "value": "ZjFhZDJhMDItNmQ3Yi00OTg2LWFhZjYtODJkZGE1M2UyYTJh", + "secret": false, + "enabled": true, + "type": "text" + }, + { + "name": "accessToken", + "value": "ZjFhZDJhMDItNmQ3Yi00OTg2LWFhZjYtODJkZGE1M2UyYTJh", + "secret": false, + "enabled": true, + "type": "text" + }, + { + "name": "order_id", + "value": "9c4d1e4f-64dd-465a-b52a-5471caab828b", + "secret": false, + "enabled": true, + "type": "text" + } + ], + "name": "dev" + } + ], + "root": { + "request": { + "auth": { + "mode": "none" + } + }, + "docs": "Collection for testing the E-commerce API endpoints. The server automatically seeds 5 sample products (Smartphone X, Running Shoes, Coffee Maker, Laptop Pro, Wireless Earbuds) on startup if no products exist in the database.\n\n[\"Fetch](https://fetch.usebruno.com?url=git%40bitbucket.org%3Ausebruno%2Fdemo-api-collection.git \"target=_blank rel=noopener noreferrer\")", + "meta": { + "name": "E-commerce API" + } + }, + "brunoConfig": { + "version": "1", + "name": "E-commerce API", + "type": "collection", + "ignore": [ + "node_modules", + ".git" + ], + "size": 0.005391120910644531, + "filesCount": 23 + } +} \ No newline at end of file diff --git a/examples/standalone-html/index.html b/examples/standalone-html/index.html new file mode 100644 index 00000000..c1f0ef01 --- /dev/null +++ b/examples/standalone-html/index.html @@ -0,0 +1,206 @@ + + + + + + OpenCollection - Standalone HTML Example + + + + + + + +
+ + + + \ No newline at end of file diff --git a/examples/standalone-html/index2.html b/examples/standalone-html/index2.html new file mode 100644 index 00000000..fede4a13 --- /dev/null +++ b/examples/standalone-html/index2.html @@ -0,0 +1,254 @@ + + + + + + OpenCollection - Standalone HTML Example + + + + + + + +
+ + + + \ No newline at end of file diff --git a/examples/standalone-html/oc.yaml b/examples/standalone-html/oc.yaml new file mode 100644 index 00000000..82e6de60 --- /dev/null +++ b/examples/standalone-html/oc.yaml @@ -0,0 +1,230 @@ +name: "E-commerce API" +docs: | + Collection for testing the E-commerce API endpoints. The server automatically seeds 5 sample products (Smartphone X, Running Shoes, Coffee Maker, Laptop Pro, Wireless Earbuds) on startup if no products exist in the database. + + [Fetch in Bruno](https://fetch.usebruno.com?url=git%40bitbucket.org%3Ausebruno%2Fdemo-api-collection.git "target=_blank rel=noopener noreferrer") + +environments: + - name: "dev" + variables: + - name: "baseUrl" + value: "http://localhost:3001/api" + +items: + # Cart folder + - type: "folder" + name: "Cart" + docs: "Shopping cart endpoints" + items: + - type: "http" + name: "Get User Cart" + url: "{{baseUrl}}/cart" + method: "GET" + headers: + - name: "Authorization" + value: "Bearer {{accessToken}}" + docs: "Retrieve the current user's shopping cart" + + - type: "http" + name: "Add Item to Cart" + url: "{{baseUrl}}/cart/add/item/:itemId" + method: "POST" + headers: + - name: "Content-Type" + value: "application/json" + - name: "Authorization" + value: "Bearer {{accessToken}}" + params: + - name: "itemId" + value: "cd3ad475-0784-4a0d-9fe4-a9a17e5391d9" + type: "path" + body: + type: "json" + data: | + { + "quantity": 2 + } + docs: "Add a product to the user's shopping cart" + + - type: "http" + name: "Update Cart Item" + url: "{{baseUrl}}/cart/item/:itemId" + method: "PUT" + headers: + - name: "Content-Type" + value: "application/json" + - name: "Authorization" + value: "Bearer {{accessToken}}" + params: + - name: "itemId" + value: "cd3ad475-0784-4a0d-9fe4-a9a17e5391d9" + type: "path" + body: + type: "json" + data: | + { + "quantity": 5 + } + docs: "Update the quantity of an item in the cart" + + - type: "http" + name: "Remove Item from Cart" + url: "{{baseUrl}}/cart/item/:itemId" + method: "DELETE" + headers: + - name: "Authorization" + value: "Bearer {{accessToken}}" + params: + - name: "itemId" + value: "cd3ad475-0784-4a0d-9fe4-a9a17e5391d9" + type: "path" + docs: "Remove a specific item from the cart" + + - type: "http" + name: "Clear Cart" + url: "{{baseUrl}}/cart" + method: "DELETE" + headers: + - name: "Authorization" + value: "Bearer {{accessToken}}" + docs: "Remove all items from the user's cart" + + # Authentication folder + - type: "folder" + name: "Authentication" + docs: "User authentication endpoints" + items: + - type: "http" + name: "Register User" + url: "{{baseUrl}}/auth/register" + method: "POST" + headers: + - name: "Content-Type" + value: "application/json" + body: + type: "json" + data: | + { + "name": "Anoop", + "email": "test@example.com", + "password": "password123" + } + docs: "Register a new user account" + + - type: "http" + name: "Login" + url: "{{baseUrl}}/auth/login" + method: "POST" + body: + type: "json" + data: | + { + "email": "test@example.com", + "password": "password123" + } + scripts: + postResponse: 'bru.setEnvVar("accessToken", res.body.token)' + docs: "Authenticate a user and receive a token" + + - type: "http" + name: "Whoami" + url: "{{baseUrl}}/auth/whoami" + method: "GET" + headers: + - name: "Authorization" + value: "Bearer {{accessToken}}" + docs: "Retrieve authenticated user's profile information" + + # Products folder + - type: "folder" + name: "Products" + docs: "Product-related endpoints. The server automatically seeds 5 sample products on startup if no products exist." + items: + - type: "http" + name: "Get All Products" + url: "{{baseUrl}}/products" + method: "GET" + params: + - name: "category" + value: "electronics" + type: "query" + enabled: false + docs: "Retrieve a list of all products, optionally filtered by category. Default seeded categories include: Electronics, Sports, and Home." + + - type: "http" + name: "Get Products by Category" + url: "{{baseUrl}}/products?category=Electronics" + method: "GET" + params: + - name: "category" + value: "Electronics" + type: "query" + docs: "Retrieve products filtered by category. Seeded categories include: Electronics, Sports, and Home." + + - type: "http" + name: "Get Product by ID" + url: "{{baseUrl}}/products/:productId" + method: "GET" + params: + - name: "productId" + value: "cd3ad475-0784-4a0d-9fe4-a9a17e5391d9" + type: "path" + docs: "Retrieve detailed information about a specific product" + + # Orders folder + - type: "folder" + name: "Orders" + docs: "Order management endpoints" + items: + - type: "http" + name: "Create Order" + url: "{{baseUrl}}/orders" + method: "POST" + headers: + - name: "Content-Type" + value: "application/json" + - name: "Authorization" + value: "Bearer {{accessToken}}" + body: + type: "json" + data: | + { + "shippingAddress": "Mars" + } + scripts: + tests: | + // Check if the request was successful + if (res.getStatus() === 200 || res.getStatus() === 201) { + // Parse the response body + var jsonData = res.getBody(); + + // Check if the order has an ID + if (jsonData && jsonData.id) { + // Store the order ID in the environment + bru.setEnvVar('order_id', jsonData.id); + console.log('Order ID saved to environment: ' + jsonData.id); + } + } + docs: "Create a new order using the items in the user's cart" + + - type: "http" + name: "Get User Orders" + url: "{{baseUrl}}/orders" + method: "GET" + headers: + - name: "Authorization" + value: "Bearer {{accessToken}}" + docs: "Retrieve a list of all orders placed by the current user" + + - type: "http" + name: "Get Order by ID" + url: "{{baseUrl}}/orders/:orderId" + method: "GET" + headers: + - name: "Authorization" + value: "Bearer {{accessToken}}" + params: + - name: "orderId" + value: "9c4d1e4f-64dd-465a-b52a-5471caab828b" + type: "path" + docs: "Retrieve detailed information about a specific order" diff --git a/examples/standalone-html/schema.json b/examples/standalone-html/schema.json new file mode 100644 index 00000000..3d0a8983 --- /dev/null +++ b/examples/standalone-html/schema.json @@ -0,0 +1,786 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.opencollection.com/json/draft-07/opencollection/v1.0.0", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the collection" + }, + "description": { + "$ref": "#/$defs/Description" + }, + "environments": { + "type": "array", + "description": "Array of environments", + "items": { + "$ref": "#/$defs/Environment" + } + }, + "items": { + "type": "array", + "description": "Array of items in the collection", + "items": { + "$ref": "#/$defs/Item" + } + }, + "base": { + "type": "object", + "description": "Base configuration for the collection", + "properties": { + "headers": { + "type": "array", + "description": "Array of request headers", + "items": { + "$ref": "#/$defs/RequestHeader" + } + }, + "auth": { + "$ref": "#/$defs/Auth" + }, + "variables": { + "type": "array", + "description": "Array of variables", + "items": { + "$ref": "#/$defs/Variable" + } + }, + "scripts": { + "$ref": "#/$defs/Scripts" + } + }, + "additionalProperties": false + }, + "docs": { + "type": "string", + "description": "The documentation" + } + }, + "$defs": { + "Description": { + "description": "The description", + "oneOf": [ + { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "The content of the description" + }, + "type": { + "type": "string", + "description": "The MIME type of the content" + } + }, + "required": ["content", "type"], + "additionalProperties": false + }, + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "RequestHeader": { + "type": "object", + "description": "A request header with name, value, description, and disabled state", + "properties": { + "name": { + "type": "string", + "description": "The header name" + }, + "value": { + "type": "string", + "description": "The header value" + }, + "description": { + "$ref": "#/$defs/Description" + }, + "disabled": { + "type": "boolean", + "description": "Whether the header is disabled" + } + }, + "required": ["name", "value"], + "additionalProperties": false + }, + "RequestParam": { + "type": "object", + "description": "A request parameter with name, value, description, type, and enabled state", + "properties": { + "name": { + "type": "string", + "description": "The parameter name" + }, + "value": { + "type": "string", + "description": "The parameter value" + }, + "description": { + "$ref": "#/$defs/Description" + }, + "type": { + "type": "string", + "enum": ["query", "path"], + "description": "The type of parameter" + }, + "enabled": { + "type": "boolean", + "description": "Whether the parameter is enabled" + } + }, + "required": ["name", "value", "type"], + "additionalProperties": false + }, + "RequestBody": { + "oneOf": [ + { + "$ref": "#/$defs/RawBody" + }, + { + "$ref": "#/$defs/FormUrlEncodedBody" + }, + { + "$ref": "#/$defs/MultipartFormBody" + }, + { + "$ref": "#/$defs/FileBody" + }, + { + "type": "null" + } + ] + }, + "RawBody": { + "type": "object", + "description": "Raw request body with type and data", + "properties": { + "type": { + "type": "string", + "enum": ["json", "text", "xml", "sparql"], + "description": "The type of raw body content" + }, + "data": { + "type": "string", + "description": "The raw body data" + } + }, + "required": ["type", "data"], + "additionalProperties": false + }, + "FormUrlEncodedBody": { + "type": "array", + "description": "Form URL encoded body as array of key-value pairs", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The form field name" + }, + "value": { + "type": "string", + "description": "The form field value" + }, + "description": { + "$ref": "#/$defs/Description" + }, + "enabled": { + "type": "boolean", + "description": "Whether the form field is enabled" + } + }, + "required": ["name", "value"], + "additionalProperties": false + } + }, + "MultipartFormBody": { + "type": "array", + "description": "Multipart form body as array of form parts", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The form part name" + }, + "type": { + "type": "string", + "enum": ["text", "file"], + "description": "The type of form part" + }, + "value": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "description": "The form part value" + }, + "description": { + "$ref": "#/$defs/Description" + }, + "enabled": { + "type": "boolean", + "description": "Whether the form part is enabled" + } + }, + "required": ["name", "type", "value"], + "additionalProperties": false + } + }, + "FileBody": { + "type": "array", + "description": "File body as array of file objects", + "items": { + "type": "object", + "properties": { + "filePath": { + "type": "string", + "description": "Path to the file" + }, + "contentType": { + "type": "string", + "description": "MIME type of the file" + }, + "selected": { + "type": "boolean", + "description": "Whether the file is selected" + } + }, + "required": ["filePath", "contentType", "selected"], + "additionalProperties": false + } + }, + "Assertion": { + "type": "object", + "description": "An assertion for response validation", + "properties": { + "expression": { + "type": "string", + "description": "The expression to evaluate" + }, + "operator": { + "type": "string", + "description": "The comparison operator" + }, + "value": { + "type": "string", + "description": "The expected value" + }, + "enabled": { + "type": "boolean", + "description": "Whether the assertion is enabled" + }, + "description": { + "$ref": "#/$defs/Description" + } + }, + "required": ["expression", "operator", "value"], + "additionalProperties": false + }, + "Auth": { + "description": "Authentication", + "oneOf": [ + { + "$ref": "#/$defs/AuthAwsV4" + }, + { + "$ref": "#/$defs/AuthBasic" + }, + { + "$ref": "#/$defs/AuthWsse" + }, + { + "$ref": "#/$defs/AuthBearer" + }, + { + "$ref": "#/$defs/AuthDigest" + }, + { + "$ref": "#/$defs/AuthNTLM" + }, + { + "$ref": "#/$defs/AuthApiKey" + } + ] + }, + "AuthAwsV4": { + "type": "object", + "description": "AWS V4 authentication", + "properties": { + "type": { + "type": "string", + "const": "awsv4" + }, + "accessKeyId": { + "type": "string", + "description": "AWS access key ID" + }, + "secretAccessKey": { + "type": "string", + "description": "AWS secret access key" + }, + "sessionToken": { + "type": "string", + "description": "AWS session token" + }, + "service": { + "type": "string", + "description": "AWS service name" + }, + "region": { + "type": "string", + "description": "AWS region" + }, + "profileName": { + "type": "string", + "description": "AWS profile name" + } + }, + "required": ["type"], + "additionalProperties": false + }, + "AuthBasic": { + "type": "object", + "description": "Basic authentication", + "properties": { + "type": { + "type": "string", + "const": "basic" + }, + "username": { + "type": "string", + "description": "Username for basic auth" + }, + "password": { + "type": "string", + "description": "Password for basic auth" + } + }, + "required": ["type"], + "additionalProperties": false + }, + "AuthWsse": { + "type": "object", + "description": "WSSE authentication", + "properties": { + "type": { + "type": "string", + "const": "wsse" + }, + "username": { + "type": "string", + "description": "Username for WSSE auth" + }, + "password": { + "type": "string", + "description": "Password for WSSE auth" + } + }, + "required": ["type"], + "additionalProperties": false + }, + "AuthBearer": { + "type": "object", + "description": "Bearer token authentication", + "properties": { + "type": { + "type": "string", + "const": "bearer" + }, + "token": { + "type": "string", + "description": "Bearer token" + } + }, + "required": ["type"], + "additionalProperties": false + }, + "AuthDigest": { + "type": "object", + "description": "Digest authentication", + "properties": { + "type": { + "type": "string", + "const": "digest" + }, + "username": { + "type": "string", + "description": "Username for digest auth" + }, + "password": { + "type": "string", + "description": "Password for digest auth" + } + }, + "required": ["type"], + "additionalProperties": false + }, + "AuthNTLM": { + "type": "object", + "description": "NTLM authentication", + "properties": { + "type": { + "type": "string", + "const": "ntlm" + }, + "username": { + "type": "string", + "description": "Username for NTLM auth" + }, + "password": { + "type": "string", + "description": "Password for NTLM auth" + }, + "domain": { + "type": "string", + "description": "Domain for NTLM auth" + } + }, + "required": ["type"], + "additionalProperties": false + }, + "AuthApiKey": { + "type": "object", + "description": "API Key authentication", + "properties": { + "type": { + "type": "string", + "const": "apikey" + }, + "key": { + "type": "string", + "description": "API key name" + }, + "value": { + "type": "string", + "description": "API key value" + }, + "placement": { + "type": "string", + "enum": ["header", "query"], + "description": "Where to place the API key" + } + }, + "required": ["type"], + "additionalProperties": false + }, + "Item": { + "oneOf": [ + { + "$ref": "#/$defs/HttpRequest" + }, + { + "$ref": "#/$defs/GraphQLRequest" + }, + { + "$ref": "#/$defs/GrpcRequest" + }, + { + "$ref": "#/$defs/Folder" + }, + { + "$ref": "#/$defs/Script" + } + ] + }, + "HttpRequest": { + "type": "object", + "description": "HTTP request configuration", + "properties": { + "name": { + "type": "string", + "description": "The name of the request" + }, + "type": { + "type": "string", + "const": "http", + "description": "The type of request" + }, + "url": { + "type": "string", + "description": "The URL of the request" + }, + "method": { + "type": "string", + "description": "HTTP method" + }, + "params": { + "type": "array", + "description": "Array of request parameters", + "items": { + "$ref": "#/$defs/RequestParam" + } + }, + "headers": { + "type": "array", + "description": "Array of request headers", + "items": { + "$ref": "#/$defs/RequestHeader" + } + }, + "body": { + "$ref": "#/$defs/RequestBody" + }, + "auth": { + "$ref": "#/$defs/Auth" + }, + "scripts": { + "$ref": "#/$defs/Scripts" + }, + "variables": { + "type": "array", + "description": "Array of variables", + "items": { + "$ref": "#/$defs/Variable" + } + }, + "assertions": { + "type": "array", + "description": "Array of assertions for response validation", + "items": { + "$ref": "#/$defs/Assertion" + } + }, + "docs": { + "type": "string", + "description": "Documentation for this request" + } + }, + "required": ["type"], + "additionalProperties": false + }, + "GraphQLRequest": { + }, + "GrpcRequest": { + }, + "Folder": { + "type": "object", + "description": "A folder for organizing collection items", + "properties": { + "type": { + "type": "string", + "const": "folder" + }, + "Item": { + "oneOf": [ + { + "$ref": "#/$defs/HttpRequest" + }, + { + "$ref": "#/$defs/GraphQLRequest" + }, + { + "$ref": "#/$defs/GrpcRequest" + }, + { + "$ref": "#/$defs/Folder" + }, + { + "$ref": "#/$defs/Script" + } + ] + }, + "headers": { + "type": "array", + "description": "Array of request headers", + "items": { + "$ref": "#/$defs/RequestHeader" + } + }, + "auth": { + "$ref": "#/$defs/Auth" + }, + "variables": { + "type": "array", + "description": "Array of variables", + "items": { + "$ref": "#/$defs/Variable" + } + }, + "scripts": { + "$ref": "#/$defs/Scripts" + }, + "docs": { + "type": "string", + "description": "Documentation for this folder" + } + }, + "additionalProperties": false + }, + "Script": { + "type": "object", + "description": "Javscript module or shared collection scripts", + "properties": { + "type": { + "type": "string", + "const": "script" + }, + "script": { + "type": "string", + "description": "The script" + } + }, + "additionalProperties": false + }, + "Environment": { + "type": "object", + "description": "An environment configuration", + "properties": { + "name": { + "type": "string", + "description": "The name of the environment" + }, + "description": { + "$ref": "#/$defs/Description" + }, + "variables": { + "type": "array", + "description": "Array of environment variables", + "items": { + "$ref": "#/$defs/Variable" + } + } + }, + "required": ["name"], + "additionalProperties": false + }, + "Variable": { + "type": "object", + "description": "A variable with name, value, description, and state flags", + "properties": { + "name": { + "type": "string", + "description": "The variable name" + }, + "value": { + "description": "The variable value", + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/ValueSet" + }, + { + "type": "null" + } + ] + }, + "description": { + "$ref": "#/$defs/Description" + }, + "disabled": { + "type": "boolean", + "description": "Whether the variable is disabled" + }, + "transient": { + "type": "boolean", + "description": "Whether the variable value should be persisted or not" + }, + "default": { + "description": "The default value when transient is true", + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/Value" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Value": { + "type": "object", + "description": "A value with data and type information", + "properties": { + "data": { + "type": "string", + "description": "The string representation of the value" + }, + "type": { + "type": "string", + "description": "The type of the value", + "enum": ["string", "number", "boolean", "null", "object"] + } + }, + "additionalProperties": false + }, + "ValueSet": { + "type": "object", + "description": "A value with data, type and variants", + "properties": { + "data": { + "type": "string", + "description": "The string representation of the value" + }, + "type": { + "type": "string", + "description": "The type of the value", + "enum": ["string", "number", "boolean", "null", "object"] + }, + "variants": { + "type": "array", + "description": "Array of variants for this value", + "items": { + "$ref": "#/$defs/Variant" + } + } + }, + "additionalProperties": false + }, + "Variant": { + "type": "object", + "description": "A variant with data and description", + "properties": { + "data": { + "type": "string", + "description": "The data for this variant" + }, + "description": { + "type": "string", + "description": "The description for this variant" + } + }, + "required": ["data"], + "additionalProperties": false + }, + "Scripts": { + "type": "object", + "description": "Scripts for collection execution lifecycle", + "properties": { + "preRequest": { + "type": "string", + "description": "Script to execute before making requests" + }, + "postResponse": { + "type": "string", + "description": "Script to execute after receiving responses" + }, + "tests": { + "type": "string", + "description": "Test scripts to validate responses" + }, + "hooks": { + "type": "string", + "description": "Scripts that can be injected into specific stages of the execution process." + } + }, + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/examples/vanilla-html/index.html b/examples/vanilla-html/index.html deleted file mode 100644 index fe7b8bc9..00000000 --- a/examples/vanilla-html/index.html +++ /dev/null @@ -1,281 +0,0 @@ - - - - - - OpenCollection Playground - Vanilla HTML Example - - - -
- - - - - - - \ No newline at end of file diff --git a/packages/oc-core/src/App.tsx b/packages/oc-core/src/App.tsx index 971d5467..3d495aee 100644 --- a/packages/oc-core/src/App.tsx +++ b/packages/oc-core/src/App.tsx @@ -1,4 +1,4 @@ -import OpenCollectionPlayground from './core/OpenCollectionPlayground'; +import OpenCollection from './core/OpenCollection'; import './styles/App.css'; import { test } from './test'; @@ -7,10 +7,9 @@ function App() { return (
-
); diff --git a/packages/oc-core/src/assets/opencollection-logo.svg b/packages/oc-core/src/assets/opencollection-logo.svg new file mode 100644 index 00000000..62f1dc76 --- /dev/null +++ b/packages/oc-core/src/assets/opencollection-logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/oc-core/src/core/FileCollectionLoader.tsx b/packages/oc-core/src/core/FileCollectionLoader.tsx index 8b3cfbc9..5f3d946a 100644 --- a/packages/oc-core/src/core/FileCollectionLoader.tsx +++ b/packages/oc-core/src/core/FileCollectionLoader.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import OpenCollectionPlayground from './OpenCollectionPlayground'; +import OpenCollection from './OpenCollection'; const FileCollectionLoader: React.FC = () => { const [selectedFile, setSelectedFile] = useState(null); @@ -53,7 +53,7 @@ const FileCollectionLoader: React.FC = () => {
{selectedFile ? ( - diff --git a/packages/oc-core/src/core/OpenCollectionPlayground.tsx b/packages/oc-core/src/core/OpenCollection.tsx similarity index 83% rename from packages/oc-core/src/core/OpenCollectionPlayground.tsx rename to packages/oc-core/src/core/OpenCollection.tsx index ae153fd7..d7070c5c 100644 --- a/packages/oc-core/src/core/OpenCollectionPlayground.tsx +++ b/packages/oc-core/src/core/OpenCollection.tsx @@ -9,17 +9,16 @@ import { useSearchModal, useRunnerMode } from '../hooks'; -import { OpenCollectionPlaygroundProps } from '../types'; +import { OpenCollectionProps } from '../types'; import MobileLayout from '../ui/MobileLayout'; import DesktopLayout from '../layouts/DesktopLayout'; -const OpenCollectionPlayground: React.FC = ({ +const OpenCollection: React.FC = ({ collection, theme = 'light', logo, customPages, hideSidebar = false, - hideHeader = false, onlyShow, proxyUrl, }) => { @@ -34,12 +33,11 @@ const OpenCollectionPlayground: React.FC = ({ const { filteredCollectionItems, - filteredCustomPages, - shouldShowOverview + filteredCustomPages } = useItemFiltering(collectionData, validCustomPages, onlyShow); // Page selection state - const [currentPageId, setCurrentPageId] = useState('overview'); + const [currentPageId, setCurrentPageId] = useState(null); const [currentPageItem, setCurrentPageItem] = useState(null); // Handle item selection @@ -47,10 +45,6 @@ const OpenCollectionPlayground: React.FC = ({ console.log('Selecting item:', id, 'path:', path); setCurrentPageId(id); - if (id === 'overview') { - setCurrentPageItem(null); - return; - } // Check if it's a custom page const customPage = filteredCustomPages.find(page => page.name === id || page.name === id.replace(/-/g, ' ')); @@ -80,10 +74,20 @@ const OpenCollectionPlayground: React.FC = ({ } }; - // Set initial page to overview when collection loads + // Set initial page to first root-level request when collection loads useEffect(() => { if (collectionData && currentPageId === null) { - setCurrentPageId('overview'); + // Find the first root-level request + const firstRequest = collectionData.items?.find(item => item.type === 'http'); + if (firstRequest && firstRequest.name) { + handleSelectItem(firstRequest.name); + } else if (collectionData.items && collectionData.items.length > 0) { + // If no request found, select the first item + const firstItem = collectionData.items[0]; + if (firstItem.name) { + handleSelectItem(firstItem.name); + } + } } }, [collectionData, currentPageId]); @@ -121,7 +125,6 @@ const OpenCollectionPlayground: React.FC = ({ filteredCustomPages, onlyShow, containerRef, - shouldShowOverview, filteredCollectionItems, md, customPageContents @@ -159,4 +162,4 @@ const OpenCollectionPlayground: React.FC = ({ ); }; -export default OpenCollectionPlayground; \ No newline at end of file +export default OpenCollection; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/useCollectionData.ts b/packages/oc-core/src/hooks/useCollectionData.ts index ec58c4a9..88b63144 100644 --- a/packages/oc-core/src/hooks/useCollectionData.ts +++ b/packages/oc-core/src/hooks/useCollectionData.ts @@ -63,28 +63,35 @@ export const useCollectionData = (collection: OpenCollectionCollection | string setCollectionData(data as OpenCollectionCollection); } else if (typeof collection === 'string') { - // Handle URLs - const response = await fetch(collection); - if (!response.ok) { - throw new Error(`Failed to load API collection: ${response.statusText}`); - } - - // Check if the URL or content suggests YAML format - const contentType = response.headers.get('content-type') || ''; - const isYamlUrl = isYamlFile(collection); - const isYamlContentType = contentType.includes('yaml') || contentType.includes('yml'); - - let data; - if (isYamlUrl || isYamlContentType) { - // Handle YAML content - const yamlText = await response.text(); - data = await loadOpenCollectionData(yamlText); + // Check if it's a URL or raw content + if (collection.startsWith('http://') || collection.startsWith('https://')) { + // Handle URLs + const response = await fetch(collection); + if (!response.ok) { + throw new Error(`Failed to load API collection: ${response.statusText}`); + } + + // Check if the URL or content suggests YAML format + const contentType = response.headers.get('content-type') || ''; + const isYamlUrl = isYamlFile(collection); + const isYamlContentType = contentType.includes('yaml') || contentType.includes('yml'); + + let data; + if (isYamlUrl || isYamlContentType) { + // Handle YAML content + const yamlText = await response.text(); + data = await loadOpenCollectionData(yamlText); + } else { + // Default to JSON + data = await response.json(); + } + + setCollectionData(data as OpenCollectionCollection); } else { - // Default to JSON - data = await response.json(); + // Handle raw string content (JSON or YAML) + const data = await loadOpenCollectionData(collection); + setCollectionData(data as OpenCollectionCollection); } - - setCollectionData(data as OpenCollectionCollection); } else { // Handle collection objects setCollectionData(collection as OpenCollectionCollection); diff --git a/packages/oc-core/src/hooks/useItemFiltering.ts b/packages/oc-core/src/hooks/useItemFiltering.ts index 9357a29f..a10368f7 100644 --- a/packages/oc-core/src/hooks/useItemFiltering.ts +++ b/packages/oc-core/src/hooks/useItemFiltering.ts @@ -5,7 +5,6 @@ interface UseItemFilteringReturn { shouldShowItem: (itemId: string) => boolean; filteredCollectionItems: OpenCollectionItem[]; filteredCustomPages: CustomPage[]; - shouldShowOverview: boolean; } export const useItemFiltering = ( @@ -47,14 +46,9 @@ export const useItemFiltering = ( return validCustomPages.filter(page => shouldShowItem(page.name)); }, [validCustomPages, onlyShow, shouldShowItem]); - const shouldShowOverview = useMemo(() => { - return shouldShowItem('overview'); - }, [shouldShowItem]); - return { shouldShowItem, filteredCollectionItems, - filteredCustomPages, - shouldShowOverview + filteredCustomPages }; }; \ No newline at end of file diff --git a/packages/oc-core/src/index.ts b/packages/oc-core/src/index.ts index 227b85b1..326dd574 100644 --- a/packages/oc-core/src/index.ts +++ b/packages/oc-core/src/index.ts @@ -1,11 +1,11 @@ -import OpenCollectionPlayground from './core/OpenCollectionPlayground'; +import OpenCollection from './core/OpenCollection'; import FileCollectionLoader from './core/FileCollectionLoader'; import Sidebar from './ui/Sidebar'; import './styles/index.css'; export { - OpenCollectionPlayground, + OpenCollection, FileCollectionLoader, Sidebar, }; diff --git a/packages/oc-core/src/layouts/DesktopLayout.tsx b/packages/oc-core/src/layouts/DesktopLayout.tsx index 2c5a6840..d943257e 100644 --- a/packages/oc-core/src/layouts/DesktopLayout.tsx +++ b/packages/oc-core/src/layouts/DesktopLayout.tsx @@ -22,7 +22,6 @@ interface DesktopLayoutProps { searchInputRef: React.RefObject; handleSearchResultSelect: (result: {id: string; path?: string; type: string}) => void; containerRef: React.RefObject; - shouldShowOverview: boolean; filteredCollectionItems: any[]; md: any; customPageContents: Record; @@ -50,7 +49,6 @@ const DesktopLayout: React.FC = ({ searchInputRef, handleSearchResultSelect, containerRef, - shouldShowOverview, filteredCollectionItems, md, customPageContents, @@ -87,9 +85,6 @@ const DesktopLayout: React.FC = ({ theme={theme} customPages={filteredCustomPages} onlyShow={onlyShow} - onSearchClick={() => setIsSearchOpen(true)} - isRunnerMode={isRunnerMode} - onToggleRunnerMode={toggleRunnerMode} />
)} @@ -102,12 +97,13 @@ const DesktopLayout: React.FC = ({
diff --git a/packages/oc-core/src/server.ts b/packages/oc-core/src/server.ts index 90236949..4d9a258c 100644 --- a/packages/oc-core/src/server.ts +++ b/packages/oc-core/src/server.ts @@ -90,10 +90,10 @@ export function renderPlayground(options: ServerRenderOptions): string { - - - -`; -} - -// For CommonJS compatibility -export default renderPlayground; \ No newline at end of file diff --git a/packages/oc-core/src/styles/index.css b/packages/oc-core/src/styles/index.css index d7cf0c93..f734a115 100644 --- a/packages/oc-core/src/styles/index.css +++ b/packages/oc-core/src/styles/index.css @@ -1129,92 +1129,6 @@ tr.dark-row:nth-child(even) { max-width: 100%; } -/* ContentLayout styles */ -.content-layout { - padding: 1rem; - max-width: 100%; - overflow-x: hidden; -} - -.content-header { - margin-bottom: 1rem; - border-bottom: 1px solid var(--border-color, #e2e8f0); - padding-bottom: 0.75rem; -} - -.content-title { - font-size: 1.5rem; - font-weight: 600; - color: var(--text-primary); -} - -.content-badge-container { - display: flex; - align-items: center; - font-size: 1rem; - margin: 0.5rem 0 0.75rem; - flex-wrap: wrap; -} - -.content-badge { - padding: 0.15rem 0.4rem; - border-radius: 4px; - font-weight: 600; - font-size: 0.75rem; - margin-right: 0.5rem; - text-transform: uppercase; -} - -.content-subtitle { - font-family: monospace; - padding: 0.3rem 0.4rem; - background: var(--code-bg); - border-radius: 4px; - overflow-x: auto; - word-break: break-all; - max-width: 100%; - color: var(--code-text); - font-size: 0.85rem; -} - -.content-description { - margin-top: 0.75rem; - line-height: 1.6; -} - -.content-grid { - display: grid; - grid-template-columns: 2fr 1fr; - gap: 1rem; - margin-top: 0.75rem; -} - -.content-main, -.content-side { - display: flex; - flex-direction: column; - gap: 1rem; - overflow-x: hidden; -} - -.content-side { - width: 350px; - min-width: 350px; - max-width: 350px; -} - -@media (max-width: 1100px) { - .content-grid { - grid-template-columns: 1fr; - } - - .content-side { - width: 100%; - min-width: 100%; - max-width: 100%; - } -} - /* Original styles */ .endpoint-header { margin-bottom: 1rem; diff --git a/packages/oc-core/src/types/index.ts b/packages/oc-core/src/types/index.ts index 96ef8afc..5f16e96d 100644 --- a/packages/oc-core/src/types/index.ts +++ b/packages/oc-core/src/types/index.ts @@ -128,18 +128,10 @@ export interface OpenCollectionCollection { docs?: string; } -export interface CustomPage { - name: string; - content?: string; - contentPath?: string; - consoleView?: React.ReactNode; -} - export interface OpenCollectionProps { collection: OpenCollectionCollection | string | File; theme?: 'light' | 'dark' | 'auto'; logo?: React.ReactNode; - customPages?: CustomPage[]; hideSidebar?: boolean; hideHeader?: boolean; onlyShow?: string[]; diff --git a/packages/oc-core/src/ui/AllEndpointsView.tsx b/packages/oc-core/src/ui/AllEndpointsView.tsx index 8a59f4f3..372a906d 100644 --- a/packages/oc-core/src/ui/AllEndpointsView.tsx +++ b/packages/oc-core/src/ui/AllEndpointsView.tsx @@ -1,14 +1,11 @@ import React, { useMemo } from 'react'; -import { OpenCollectionCollection, OpenCollectionItem, HttpRequest, Folder, CustomPage } from '../types'; +import { OpenCollectionCollection, OpenCollectionItem, HttpRequest, Folder } from '../types'; import Item from '../components/Docs/Item/Item'; -import CustomPageRenderer from './CustomPageRenderer'; import { getItemId, generateSafeId } from '../utils/itemUtils'; interface AllEndpointsViewProps { collection: OpenCollectionCollection | null; filteredCollectionItems: OpenCollectionItem[]; - filteredCustomPages: CustomPage[]; - customPageContents: Record; theme: 'light' | 'dark' | 'auto'; md: any; selectedItemId: string | null; @@ -18,8 +15,6 @@ interface AllEndpointsViewProps { const AllEndpointsView: React.FC = ({ collection, filteredCollectionItems, - filteredCustomPages, - customPageContents, theme, md, selectedItemId, @@ -59,37 +54,6 @@ const AllEndpointsView: React.FC = ({ return (
- {/* Render custom pages first */} - {filteredCustomPages.map((page) => { - const pageId = generateSafeId(page.name); - const content = customPageContents[page.name] || ''; - - return ( -
-
-

- - - - {page.name} -

- -
-
- ); - })} - {/* Render all collection items */} {allItems.map((item, index) => { const itemId = getItemId(item); @@ -126,7 +90,7 @@ const AllEndpointsView: React.FC = ({ ); })} - {allItems.length === 0 && filteredCustomPages.length === 0 && ( + {allItems.length === 0 && (
diff --git a/packages/oc-core/src/ui/CustomPageRenderer.tsx b/packages/oc-core/src/ui/CustomPageRenderer.tsx deleted file mode 100644 index 8dd6907a..00000000 --- a/packages/oc-core/src/ui/CustomPageRenderer.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import { CustomPage } from '../types'; -import ContentLayout from '../layouts/ContentLayout'; - -const CustomPageRenderer = ({ - pageName, - customPage, - content, - theme, - md, - registerSectionRef -}: { - pageName: string; - customPage?: CustomPage; - content: string; - theme: 'light' | 'dark' | 'auto'; - md: any; - registerSectionRef: (id: string, ref: HTMLDivElement | null) => void; -}) => { - const leftColumnContent = ( -
-
-
- ); - - const rightColumnContent = customPage?.consoleView ? ( -
- {customPage.consoleView} -
- ) : ( -
-
-

- Custom Page Information -

-
- Name: - {pageName} -
-
- Content Source: - {customPage?.contentPath ? 'External File' : 'Inline Content'} -
-
-
- ); - - return ( -
registerSectionRef(pageName, ref)}> - -
- ); -}; - -export default CustomPageRenderer; \ No newline at end of file diff --git a/packages/oc-core/src/ui/MobileLayout.tsx b/packages/oc-core/src/ui/MobileLayout.tsx deleted file mode 100644 index 6f0aa80a..00000000 --- a/packages/oc-core/src/ui/MobileLayout.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import React from 'react'; -import { OpenCollectionCollection, CustomPage, OpenCollectionItem } from '../types'; -import Sidebar from '../components/Sidebar/Sidebar'; -import SinglePageRenderer from './SinglePageRenderer'; - -interface MobileLayoutProps { - collectionData: OpenCollectionCollection | null; - hideSidebar: boolean; - theme: 'light' | 'dark' | 'auto'; - currentPageId: string | null; - currentPageItem: OpenCollectionItem | CustomPage | null; - onSelectItem: (id: string, path?: string) => void; - filteredCustomPages: CustomPage[]; - onlyShow?: string[]; - mobileView: 'sidebar' | 'content'; - setMobileView: (view: 'sidebar' | 'content') => void; - containerRef: React.RefObject; - filteredCollectionItems: any[]; - md: any; - customPageContents: Record; -} - -const MobileLayout: React.FC = ({ - collectionData, - hideSidebar, - theme, - currentPageId, - currentPageItem, - onSelectItem, - filteredCustomPages, - onlyShow, - mobileView, - setMobileView, - containerRef, - filteredCollectionItems, - md, - customPageContents -}) => { - if (hideSidebar) { - return ( -
-
- -
-
- ); - } - - return ( -
-
- {!hideSidebar && ( - - )} - -
- - {!hideSidebar && ( -
- -
- )} - -
-
- -
-
-
- ); -}; - -export default MobileLayout; \ No newline at end of file diff --git a/packages/oc-core/src/ui/SinglePageRenderer.tsx b/packages/oc-core/src/ui/SinglePageRenderer.tsx deleted file mode 100644 index c717ad37..00000000 --- a/packages/oc-core/src/ui/SinglePageRenderer.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import React from 'react'; -import { OpenCollectionItem, OpenCollectionCollection, CustomPage, HttpRequest } from '../types'; -import Item from '../components/Docs/Item/Item'; -import CustomPageRenderer from './CustomPageRenderer'; -import { RequestRunner } from './request-runner'; - -interface SinglePageRendererProps { - currentPageItem: OpenCollectionItem | CustomPage | null; - currentPageId: string | null; - pageType: 'item' | 'custom'; - theme: 'light' | 'dark' | 'auto'; - md: any; - collection: OpenCollectionCollection | null; - customPageContents: Record; - // Runner mode props - isRunnerMode?: boolean; - toggleRunnerMode?: () => void; - proxyUrl?: string; -} - -const SinglePageRenderer: React.FC = ({ - currentPageItem, - currentPageId, - pageType, - theme, - md, - collection, - customPageContents, - isRunnerMode, - toggleRunnerMode, - proxyUrl -}) => { - if (!currentPageId) { - return ( -
-
- - - -

No content available

-

Please select an item from the sidebar.

-
-
- ); - } - - - // Render custom page - if (pageType === 'custom' && currentPageItem) { - const customPage = currentPageItem as CustomPage; - const content = customPageContents[customPage.name] || ''; - - return ( -
-
-

- - - - {customPage.name} -

- {}} // No-op since we don't need section refs in page mode - /> -
-
- ); - } - - // Render collection item - if (pageType === 'item' && currentPageItem) { - const item = currentPageItem as OpenCollectionItem; - - // Render in runner mode if enabled and it's an HTTP request - if (isRunnerMode && item.type === 'http' && collection) { - return ( -
- -
- ); - } - - return ( -
- {}} // No-op since we don't need section refs in page mode - theme={theme} - md={md} - parentPath="" - collection={collection || undefined} - toggleRunnerMode={toggleRunnerMode} - /> -
- ); - } - - return null; -}; - -export default SinglePageRenderer; \ No newline at end of file diff --git a/packages/oc-core/src/ui/index.ts b/packages/oc-core/src/ui/index.ts index b65d6f24..bbba8587 100644 --- a/packages/oc-core/src/ui/index.ts +++ b/packages/oc-core/src/ui/index.ts @@ -2,7 +2,6 @@ export { default as Card } from './Card'; export { default as DataTable } from './DataTable'; export { default as CodeBlock } from './CodeBlock'; export { default as BodyCard } from './BodyCard'; -export { default as SinglePageRenderer } from './SinglePageRenderer'; export { default as CodeEditor } from './CodeEditor'; export * from './TableHelpers'; export * from './request-runner'; \ No newline at end of file diff --git a/packages/oc-proxy/package-lock.json b/packages/oc-proxy/package-lock.json deleted file mode 100644 index d418aa95..00000000 --- a/packages/oc-proxy/package-lock.json +++ /dev/null @@ -1,967 +0,0 @@ -{ - "name": "@opencollection/proxy", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@opencollection/proxy", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "axios": "^1.6.0", - "cors": "^2.8.5", - "express": "^4.18.2" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", - "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - } - } -} diff --git a/packages/oc-proxy/package.json b/packages/oc-proxy/package.json deleted file mode 100644 index 38521bb3..00000000 --- a/packages/oc-proxy/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "@opencollection/proxy", - "version": "1.0.0", - "description": "CORS proxy server for OpenCollection requests", - "main": "src/index.js", - "scripts": { - "start": "node src/index.js", - "dev": "node src/index.js" - }, - "keywords": [ - "opencollection", - "cors", - "proxy", - "http" - ], - "author": "OpenCollection", - "license": "MIT", - "dependencies": { - "express": "^4.18.2", - "cors": "^2.8.5", - "axios": "^1.6.0" - } -} \ No newline at end of file diff --git a/packages/oc-proxy/src/index.js b/packages/oc-proxy/src/index.js deleted file mode 100644 index 6a5600a8..00000000 --- a/packages/oc-proxy/src/index.js +++ /dev/null @@ -1,127 +0,0 @@ -const express = require('express'); -const cors = require('cors'); -const axios = require('axios'); - -const app = express(); -const PORT = process.env.PORT || 3001; - -app.use(cors()); -app.use(express.json({ limit: '50mb' })); -app.use(express.urlencoded({ extended: true, limit: '50mb' })); - -app.post('/proxy', async (req, res) => { - try { - const { url, method, headers, body, timeout } = req.body; - - if (!url) { - return res.status(400).json({ error: 'URL is required' }); - } - - const startTime = Date.now(); - - const axiosConfig = { - method: method || 'GET', - url, - headers: headers || {}, - timeout: timeout || 30000, - validateStatus: () => true, - maxRedirects: 5 - }; - - if (body && ['POST', 'PUT', 'PATCH', 'DELETE'].includes((method || '').toUpperCase())) { - if (typeof body === 'string') { - try { - JSON.parse(body); - if (!axiosConfig.headers['content-type'] && !axiosConfig.headers['Content-Type']) { - axiosConfig.headers['content-type'] = 'application/json'; - } - } catch { - } - axiosConfig.data = body; - } else if (body instanceof FormData || body.constructor.name === 'FormData') { - axiosConfig.data = body; - } else { - // Object body, convert to JSON - axiosConfig.data = JSON.stringify(body); - if (!axiosConfig.headers['content-type'] && !axiosConfig.headers['Content-Type']) { - axiosConfig.headers['content-type'] = 'application/json'; - } - } - } - - const response = await axios(axiosConfig); - const endTime = Date.now(); - - let responseSize = 0; - try { - if (typeof response.data === 'string') { - responseSize = response.data.length; - } else if (response.data) { - responseSize = JSON.stringify(response.data).length; - } - } catch { - responseSize = 0; - } - - const responseData = { - status: response.status, - statusText: response.statusText, - headers: response.headers, - data: response.data, - duration: endTime - startTime, - size: responseSize, - url: response.config.url - }; - res.json(responseData); - } catch (error) { - const endTime = Date.now(); - - if (error.response) { - let errorSize = 0; - try { - if (typeof error.response.data === 'string') { - errorSize = error.response.data.length; - } else if (error.response.data) { - errorSize = JSON.stringify(error.response.data).length; - } - } catch { - errorSize = 0; - } - - res.json({ - status: error.response.status, - statusText: error.response.statusText, - headers: error.response.headers, - data: error.response.data, - duration: endTime - startTime, - size: errorSize, - url: error.config?.url - }); - } else { - res.status(500).json({ - error: error.message || 'Request failed', - // duration: endTime - startTime - }); - } - } -}); - -app.get('/health', (req, res) => { - res.json({ status: 'OK', message: 'OpenCollection Proxy is running' }); -}); - -app.get('/', (req, res) => { - res.json({ - name: '@opencollection/proxy', - version: '1.0.0', - description: 'CORS proxy server for OpenCollection requests', - endpoints: { - 'POST /proxy': 'Forward HTTP requests and bypass CORS', - 'GET /health': 'Health check endpoint' - } - }); -}); - -app.listen(PORT, () => { - console.log(`OpenCollection Proxy running on http://localhost:${PORT}`); -}); \ No newline at end of file From 211e1fb52726c9a911635a6de1ffc5bf2b2fafb7 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sun, 16 Nov 2025 22:26:46 +0530 Subject: [PATCH 031/161] feat: add oc-types package with TypeScript types for OpenCollection schema --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index eb831972..d1d7ebeb 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "packages/oc-schema", "packages/oc-core", "packages/oc-express", - "packages/oc-schema-explorer" + "packages/oc-schema-explorer", + "packages/oc-types" ], "homepage": "https://uwww.opencollection.com" } From 0ed96351b56ee30fc37ab2797b15224859c80d06 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sun, 16 Nov 2025 22:54:59 +0530 Subject: [PATCH 032/161] feat: integrate @opencollection/types into oc-core, updating imports and re-exporting types --- package-lock.json | 30 +++- packages/oc-core/package.json | 1 + .../oc-core/src/components/Docs/Item/Item.tsx | 2 +- .../PlaygroudDrawer/PlaygroundDrawer.tsx | 3 +- .../Playground/QueryBar/QueryBar.tsx | 2 +- .../src/components/Sidebar/Sidebar.tsx | 4 +- packages/oc-core/src/core/OpenCollection.tsx | 2 +- .../oc-core/src/hooks/useCollectionData.ts | 2 +- packages/oc-core/src/hooks/useCustomPages.ts | 2 +- .../oc-core/src/hooks/useItemFiltering.ts | 4 +- packages/oc-core/src/hooks/useSearchModal.ts | 4 +- .../oc-core/src/hooks/useSectionTracking.ts | 3 +- packages/oc-core/src/index.ts | 26 +++- .../oc-core/src/layouts/DesktopLayout.tsx | 3 +- .../oc-core/src/runner/RequestExecutor.ts | 2 +- packages/oc-core/src/runner/ScriptRunner.ts | 3 +- .../src/runner/VariableInterpolator.ts | 2 +- packages/oc-core/src/runner/index.ts | 4 +- packages/oc-core/src/standalone.ts | 2 +- packages/oc-core/src/types/component-types.ts | 26 ++++ packages/oc-core/src/types/index.ts | 139 ------------------ packages/oc-core/src/ui/AllEndpointsView.tsx | 3 +- packages/oc-core/src/ui/ContentView.tsx | 2 +- .../src/ui/request-runner/RequestHeader.tsx | 3 +- .../src/ui/request-runner/RequestPane.tsx | 2 +- .../src/ui/request-runner/RequestRunner.tsx | 3 +- packages/oc-core/src/utils/itemUtils.ts | 2 +- 27 files changed, 118 insertions(+), 163 deletions(-) create mode 100644 packages/oc-core/src/types/component-types.ts delete mode 100644 packages/oc-core/src/types/index.ts diff --git a/package-lock.json b/package-lock.json index b665c4ef..18666650 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "packages/oc-schema", "packages/oc-core", "packages/oc-express", - "packages/oc-schema-explorer" + "packages/oc-schema-explorer", + "packages/oc-types" ] }, "node_modules/@alloc/quick-lru": { @@ -1196,6 +1197,10 @@ "resolved": "packages/oc-schema-explorer", "link": true }, + "node_modules/@opencollection/types": { + "resolved": "packages/oc-types", + "link": true + }, "node_modules/@prantlf/jsonlint": { "version": "16.0.0", "resolved": "https://registry.npmjs.org/@prantlf/jsonlint/-/jsonlint-16.0.0.tgz", @@ -4479,6 +4484,20 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "devOptional": true }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -5805,6 +5824,7 @@ "@emotion/css": "^11.13.5", "@emotion/styled": "^11.14.1", "@monaco-editor/react": "^4.7.0", + "@opencollection/types": "0.0.1", "@prantlf/jsonlint": "^16.0.0", "@tailwindcss/vite": "^4.0.9", "@types/js-yaml": "^4.0.9", @@ -9602,6 +9622,14 @@ "optional": true } } + }, + "packages/oc-types": { + "name": "@opencollection/types", + "version": "0.0.1", + "license": "MIT", + "devDependencies": { + "typescript": "^5.0.0" + } } } } diff --git a/packages/oc-core/package.json b/packages/oc-core/package.json index 05f8f2fa..86649eb8 100644 --- a/packages/oc-core/package.json +++ b/packages/oc-core/package.json @@ -54,6 +54,7 @@ "@emotion/css": "^11.13.5", "@emotion/styled": "^11.14.1", "@monaco-editor/react": "^4.7.0", + "@opencollection/types": "0.0.1", "@prantlf/jsonlint": "^16.0.0", "@tailwindcss/vite": "^4.0.9", "@types/js-yaml": "^4.0.9", diff --git a/packages/oc-core/src/components/Docs/Item/Item.tsx b/packages/oc-core/src/components/Docs/Item/Item.tsx index de4147de..c2159500 100644 --- a/packages/oc-core/src/components/Docs/Item/Item.tsx +++ b/packages/oc-core/src/components/Docs/Item/Item.tsx @@ -5,7 +5,7 @@ import 'prismjs/components/prism-graphql'; import 'prismjs/components/prism-json'; import 'prismjs/components/prism-xml-doc'; import 'prismjs/components/prism-python'; -import { OpenCollectionItem, HttpRequest, Script, Folder } from '../../../types'; +import type { Item as OpenCollectionItem, HttpRequest, Script, Folder } from '@opencollection/types'; import { generateSectionId, getItemId } from '../../../utils/itemUtils'; import { MinimalDataTable, diff --git a/packages/oc-core/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx b/packages/oc-core/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx index 8d43a813..b059aa4f 100644 --- a/packages/oc-core/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx +++ b/packages/oc-core/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react'; -import { OpenCollectionCollection, OpenCollectionItem, HttpRequest, Folder } from '../../../types'; +import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; +import type { Item as OpenCollectionItem, HttpRequest, Folder } from '@opencollection/types'; import { RequestRunner } from '../../../ui/request-runner'; import { getItemId, generateSafeId } from '../../../utils/itemUtils'; import Method from '../../Method/Method'; diff --git a/packages/oc-core/src/components/Playground/QueryBar/QueryBar.tsx b/packages/oc-core/src/components/Playground/QueryBar/QueryBar.tsx index 0a4d16a3..5061f22b 100644 --- a/packages/oc-core/src/components/Playground/QueryBar/QueryBar.tsx +++ b/packages/oc-core/src/components/Playground/QueryBar/QueryBar.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { HttpRequest } from '../../../types'; +import type { HttpRequest } from '@opencollection/types/requests/http'; import { StyledWrapper } from './StyledWrapper'; interface QueryBarProps { diff --git a/packages/oc-core/src/components/Sidebar/Sidebar.tsx b/packages/oc-core/src/components/Sidebar/Sidebar.tsx index a98271d0..10399f94 100644 --- a/packages/oc-core/src/components/Sidebar/Sidebar.tsx +++ b/packages/oc-core/src/components/Sidebar/Sidebar.tsx @@ -1,5 +1,7 @@ import React, { useState, useMemo, useEffect, useCallback } from 'react'; -import { OpenCollectionCollection, OpenCollectionItem, HttpRequest, CustomPage, Folder } from '../../types'; +import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; +import type { Item as OpenCollectionItem, HttpRequest, Folder } from '@opencollection/types'; +import type { CustomPage } from '../../types/component-types'; import Method from '../Method/Method'; import { getItemId, generateSafeId } from '../../utils/itemUtils'; import OpenCollectionLogo from '../../assets/opencollection-logo.svg'; diff --git a/packages/oc-core/src/core/OpenCollection.tsx b/packages/oc-core/src/core/OpenCollection.tsx index 7cf2363f..f8e7c97b 100644 --- a/packages/oc-core/src/core/OpenCollection.tsx +++ b/packages/oc-core/src/core/OpenCollection.tsx @@ -9,7 +9,7 @@ import { useSearchModal, useRunnerMode } from '../hooks'; -import { OpenCollectionProps } from '../types'; +import type { OpenCollectionProps } from '../types/component-types'; import DesktopLayout from '../layouts/DesktopLayout'; const OpenCollection: React.FC = ({ diff --git a/packages/oc-core/src/hooks/useCollectionData.ts b/packages/oc-core/src/hooks/useCollectionData.ts index 88b63144..3cbb39d7 100644 --- a/packages/oc-core/src/hooks/useCollectionData.ts +++ b/packages/oc-core/src/hooks/useCollectionData.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { isYamlFile, parseYaml } from '../utils/yamlUtils'; -import { OpenCollectionCollection } from '../types'; +import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; const loadOpenCollectionData = async (source: string | File): Promise => { let content: string; diff --git a/packages/oc-core/src/hooks/useCustomPages.ts b/packages/oc-core/src/hooks/useCustomPages.ts index c83e366b..40d145c8 100644 --- a/packages/oc-core/src/hooks/useCustomPages.ts +++ b/packages/oc-core/src/hooks/useCustomPages.ts @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { CustomPage } from '../types'; +import type { CustomPage } from '../types/component-types'; interface UseCustomPagesReturn { validCustomPages: CustomPage[]; diff --git a/packages/oc-core/src/hooks/useItemFiltering.ts b/packages/oc-core/src/hooks/useItemFiltering.ts index a10368f7..7a4dbf5c 100644 --- a/packages/oc-core/src/hooks/useItemFiltering.ts +++ b/packages/oc-core/src/hooks/useItemFiltering.ts @@ -1,5 +1,7 @@ import { useMemo, useCallback } from 'react'; -import { OpenCollectionCollection, OpenCollectionItem, HttpRequest, CustomPage } from '../types'; +import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; +import type { Item as OpenCollectionItem, HttpRequest } from '@opencollection/types'; +import type { CustomPage } from '../types/component-types'; interface UseItemFilteringReturn { shouldShowItem: (itemId: string) => boolean; diff --git a/packages/oc-core/src/hooks/useSearchModal.ts b/packages/oc-core/src/hooks/useSearchModal.ts index 697b0f71..69ea44dd 100644 --- a/packages/oc-core/src/hooks/useSearchModal.ts +++ b/packages/oc-core/src/hooks/useSearchModal.ts @@ -1,5 +1,7 @@ import { useState, useEffect, useRef } from 'react'; -import { OpenCollectionCollection, OpenCollectionItem, HttpRequest, CustomPage } from '../types'; +import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; +import type { Item as OpenCollectionItem, HttpRequest } from '@opencollection/types'; +import type { CustomPage } from '../types/component-types'; import { getItemId, generateSafeId } from '../utils/itemUtils'; interface SearchResult { diff --git a/packages/oc-core/src/hooks/useSectionTracking.ts b/packages/oc-core/src/hooks/useSectionTracking.ts index 8b459990..84a1db12 100644 --- a/packages/oc-core/src/hooks/useSectionTracking.ts +++ b/packages/oc-core/src/hooks/useSectionTracking.ts @@ -1,5 +1,6 @@ import { useState, useEffect, useRef, useCallback } from 'react'; -import { OpenCollectionCollection, OpenCollectionItem, HttpRequest } from '../types'; +import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; +import type { Item as OpenCollectionItem, HttpRequest } from '@opencollection/types'; import { getItemId, generateSafeId } from '../utils/itemUtils'; interface UseSectionTrackingReturn { diff --git a/packages/oc-core/src/index.ts b/packages/oc-core/src/index.ts index 4e818e05..d9e50b38 100644 --- a/packages/oc-core/src/index.ts +++ b/packages/oc-core/src/index.ts @@ -8,7 +8,31 @@ export { FileCollectionLoader, }; -export * from './types'; +// Re-export types from @opencollection/types +export type { + OpenCollection as OpenCollectionCollection, + Item as OpenCollectionItem, + HttpRequest, + Folder, + Script, + Environment, + RequestDefaults as BaseConfiguration, + Auth, + Scripts, + Variable, + Assertion, + HttpHeader as RequestHeader, + HttpRequestParam as RequestParam, + RawBody, + FormUrlEncodedBody, + MultipartFormBody, + FileBody, + HttpRequestBody as RequestBody, +} from '@opencollection/types'; + +// Re-export component-specific types +export type { OpenCollectionProps, CustomPage } from './types/component-types'; + export * from './hooks'; export { CodeEditor, diff --git a/packages/oc-core/src/layouts/DesktopLayout.tsx b/packages/oc-core/src/layouts/DesktopLayout.tsx index 9397c9e7..c4a37209 100644 --- a/packages/oc-core/src/layouts/DesktopLayout.tsx +++ b/packages/oc-core/src/layouts/DesktopLayout.tsx @@ -1,5 +1,6 @@ import React, { useState, useMemo } from 'react'; -import { OpenCollectionCollection, OpenCollectionItem, HttpRequest } from '../types'; +import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; +import type { Item as OpenCollectionItem, HttpRequest } from '@opencollection/types'; import SearchModal from '../ui/SearchModal'; import Sidebar from '../components/Sidebar/Sidebar'; import AllEndpointsView from '../ui/AllEndpointsView'; diff --git a/packages/oc-core/src/runner/RequestExecutor.ts b/packages/oc-core/src/runner/RequestExecutor.ts index cc8e140f..fb1047a1 100644 --- a/packages/oc-core/src/runner/RequestExecutor.ts +++ b/packages/oc-core/src/runner/RequestExecutor.ts @@ -1,4 +1,4 @@ -import { HttpRequest } from '../types'; +import type { HttpRequest } from '@opencollection/types/requests/http'; import { RunRequestResponse } from './index'; export class RequestExecutor { diff --git a/packages/oc-core/src/runner/ScriptRunner.ts b/packages/oc-core/src/runner/ScriptRunner.ts index 33c97998..3b3f1b6e 100644 --- a/packages/oc-core/src/runner/ScriptRunner.ts +++ b/packages/oc-core/src/runner/ScriptRunner.ts @@ -1,4 +1,5 @@ -import { HttpRequest, OpenCollectionCollection } from '../types'; +import type { HttpRequest } from '@opencollection/types/requests/http'; +import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; import { RunRequestResponse } from './index'; interface ScriptContext { diff --git a/packages/oc-core/src/runner/VariableInterpolator.ts b/packages/oc-core/src/runner/VariableInterpolator.ts index 766de843..da0290db 100644 --- a/packages/oc-core/src/runner/VariableInterpolator.ts +++ b/packages/oc-core/src/runner/VariableInterpolator.ts @@ -1,4 +1,4 @@ -import { HttpRequest } from '../types'; +import type { HttpRequest } from '@opencollection/types/requests/http'; export class VariableInterpolator { interpolateRequest(request: HttpRequest, variables: Record): HttpRequest { diff --git a/packages/oc-core/src/runner/index.ts b/packages/oc-core/src/runner/index.ts index 060e8b18..1ed9a7f6 100644 --- a/packages/oc-core/src/runner/index.ts +++ b/packages/oc-core/src/runner/index.ts @@ -1,4 +1,6 @@ -import { HttpRequest, OpenCollectionCollection, Environment } from '../types'; +import type { HttpRequest } from '@opencollection/types/requests/http'; +import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; +import type { Environment } from '@opencollection/types/config/environments'; import { RequestExecutor } from './RequestExecutor'; import { VariableInterpolator } from './VariableInterpolator'; import ScriptRuntime from '../scripting/runtime/script-runtime'; diff --git a/packages/oc-core/src/standalone.ts b/packages/oc-core/src/standalone.ts index 036002f1..f5b24810 100644 --- a/packages/oc-core/src/standalone.ts +++ b/packages/oc-core/src/standalone.ts @@ -2,7 +2,7 @@ import React from 'react'; import { createRoot, Root } from 'react-dom/client'; import './styles/index.css'; import OpenCollection from './core/OpenCollection'; -import { OpenCollectionCollection } from './types'; +import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; import { parseCollectionContent } from './utils/yamlUtils'; export interface OpenCollectionOptions { diff --git a/packages/oc-core/src/types/component-types.ts b/packages/oc-core/src/types/component-types.ts new file mode 100644 index 00000000..c24ee384 --- /dev/null +++ b/packages/oc-core/src/types/component-types.ts @@ -0,0 +1,26 @@ +import React from 'react'; +import type { OpenCollection } from '@opencollection/types'; + +/** + * Custom page configuration (oc-core specific, not part of OpenCollection schema) + */ +export interface CustomPage { + name: string; + content?: string; + contentPath?: string; +} + +/** + * OpenCollection React component props + */ +export interface OpenCollectionProps { + collection: OpenCollection | string | File; + theme?: 'light' | 'dark' | 'auto'; + logo?: React.ReactNode; + hideSidebar?: boolean; + hideHeader?: boolean; + onlyShow?: string[]; + proxyUrl?: string; + customPages?: CustomPage[]; +} + diff --git a/packages/oc-core/src/types/index.ts b/packages/oc-core/src/types/index.ts deleted file mode 100644 index 5f16e96d..00000000 --- a/packages/oc-core/src/types/index.ts +++ /dev/null @@ -1,139 +0,0 @@ -import React from 'react'; - -export interface RequestHeader { - name: string; - value: string; - description?: string; - disabled?: boolean; -} - -export interface RequestParam { - name: string; - value: string; - description?: string; - type: 'query' | 'path'; - enabled?: boolean; -} - -export interface RawBody { - type: 'json' | 'text' | 'xml' | 'sparql'; - data: string; -} - -export interface FormUrlEncodedBody extends Array<{ - name: string; - value: string; - description?: string; - enabled?: boolean; -}> {} - -export interface MultipartFormBody extends Array<{ - name: string; - type: 'text' | 'file'; - value: string | string[]; - description?: string; - enabled?: boolean; -}> {} - -export interface FileBody extends Array<{ - filePath: string; - contentType: string; - selected: boolean; -}> {} - -export type RequestBody = RawBody | FormUrlEncodedBody | MultipartFormBody | FileBody | null; - -export interface Auth { - type: 'awsv4' | 'basic' | 'wsse' | 'bearer' | 'digest' | 'ntlm' | 'apikey'; - [key: string]: any; -} - -export interface Scripts { - preRequest?: string; - postResponse?: string; - tests?: string; - hooks?: string; -} - -export interface Variable { - name: string; - value?: string | null; - description?: string; - disabled?: boolean; - transient?: boolean; - default?: string | null; -} - -export interface Assertion { - expression: string; - operator: string; - value: string; - enabled?: boolean; - description?: string; -} - -export interface HttpRequest { - type: 'http'; - name?: string; - url?: string; - method?: string; - params?: RequestParam[]; - headers?: RequestHeader[]; - body?: RequestBody; - auth?: Auth; - scripts?: Scripts; - variables?: Variable[]; - assertions?: Assertion[]; - docs?: string; -} - -export interface Folder { - type: 'folder'; - name?: string; - items?: OpenCollectionItem[]; - headers?: RequestHeader[]; - auth?: Auth; - variables?: Variable[]; - scripts?: Scripts; - docs?: string; -} - -export interface Script { - type: 'script'; - name?: string; - script?: string; -} - -export type OpenCollectionItem = HttpRequest | Folder | Script; - -export interface Environment { - name: string; - description?: string; - variables?: Variable[]; -} - -export interface BaseConfiguration { - headers?: RequestHeader[]; - auth?: Auth; - variables?: Variable[]; - scripts?: Scripts; -} - -export interface OpenCollectionCollection { - name?: string; - description?: string; - environments?: Environment[]; - items?: OpenCollectionItem[]; - base?: BaseConfiguration; - docs?: string; -} - -export interface OpenCollectionProps { - collection: OpenCollectionCollection | string | File; - theme?: 'light' | 'dark' | 'auto'; - logo?: React.ReactNode; - hideSidebar?: boolean; - hideHeader?: boolean; - onlyShow?: string[]; - proxyUrl?: string; -} \ No newline at end of file diff --git a/packages/oc-core/src/ui/AllEndpointsView.tsx b/packages/oc-core/src/ui/AllEndpointsView.tsx index 372a906d..558d3922 100644 --- a/packages/oc-core/src/ui/AllEndpointsView.tsx +++ b/packages/oc-core/src/ui/AllEndpointsView.tsx @@ -1,5 +1,6 @@ import React, { useMemo } from 'react'; -import { OpenCollectionCollection, OpenCollectionItem, HttpRequest, Folder } from '../types'; +import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; +import type { Item as OpenCollectionItem, HttpRequest, Folder } from '@opencollection/types'; import Item from '../components/Docs/Item/Item'; import { getItemId, generateSafeId } from '../utils/itemUtils'; diff --git a/packages/oc-core/src/ui/ContentView.tsx b/packages/oc-core/src/ui/ContentView.tsx index b267b60a..63a357d7 100644 --- a/packages/oc-core/src/ui/ContentView.tsx +++ b/packages/oc-core/src/ui/ContentView.tsx @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import MarkdownIt from 'markdown-it'; import '../styles/index.css'; -import { OpenCollectionItem, HttpRequest } from '../types'; +import type { Item as OpenCollectionItem, HttpRequest } from '@opencollection/types'; interface ApiEndpoint { id: string; diff --git a/packages/oc-core/src/ui/request-runner/RequestHeader.tsx b/packages/oc-core/src/ui/request-runner/RequestHeader.tsx index f830dbf6..6470634a 100644 --- a/packages/oc-core/src/ui/request-runner/RequestHeader.tsx +++ b/packages/oc-core/src/ui/request-runner/RequestHeader.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { HttpRequest, OpenCollectionCollection } from '../../types'; +import type { HttpRequest } from '@opencollection/types/requests/http'; +import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; interface RequestHeaderProps { item: HttpRequest; diff --git a/packages/oc-core/src/ui/request-runner/RequestPane.tsx b/packages/oc-core/src/ui/request-runner/RequestPane.tsx index a6c4f87c..78e97067 100644 --- a/packages/oc-core/src/ui/request-runner/RequestPane.tsx +++ b/packages/oc-core/src/ui/request-runner/RequestPane.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { HttpRequest } from '../../types'; +import type { HttpRequest } from '@opencollection/types/requests/http'; import CodeEditor from '../CodeEditor'; import Tabs from '../Tabs/Tabs'; diff --git a/packages/oc-core/src/ui/request-runner/RequestRunner.tsx b/packages/oc-core/src/ui/request-runner/RequestRunner.tsx index 4c0fe75a..9ce022c5 100644 --- a/packages/oc-core/src/ui/request-runner/RequestRunner.tsx +++ b/packages/oc-core/src/ui/request-runner/RequestRunner.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect, useMemo } from 'react'; -import { HttpRequest, OpenCollectionCollection } from '../../types'; +import type { HttpRequest } from '@opencollection/types/requests/http'; +import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; import { requestRunner, createRequestRunner } from '../../runner'; import RequestHeader from './RequestHeader'; import QueryBar from '../../components/Playground/QueryBar/QueryBar'; diff --git a/packages/oc-core/src/utils/itemUtils.ts b/packages/oc-core/src/utils/itemUtils.ts index edd7567a..91435271 100644 --- a/packages/oc-core/src/utils/itemUtils.ts +++ b/packages/oc-core/src/utils/itemUtils.ts @@ -2,7 +2,7 @@ * Utility functions for handling OpenCollection items */ -import { OpenCollectionItem } from '../types'; +import type { Item as OpenCollectionItem } from '@opencollection/types/collection/item'; /** * Generate a safe HTML ID from an item name or ID From fa33053d7fa71a4068c3cabcbe16828f2b1c189a Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 17 Nov 2025 00:26:18 +0530 Subject: [PATCH 033/161] removed code no longer needed --- examples/standalone-html/oc.yaml | 230 --- examples/standalone-html/schema.json | 786 -------- packages/oc-core/index.html | 17 - packages/oc-core/src/App.tsx | 18 - .../oc-core/src/components/Docs/Item/Item.tsx | 17 +- .../OpenCollection/OpenCollection.tsx} | 193 +- .../PlaygroudDrawer/PlaygroundDrawer.tsx | 16 +- .../src/components/Sidebar/Sidebar.tsx | 127 +- .../oc-core/src/core/FileCollectionLoader.tsx | 76 - packages/oc-core/src/core/OpenCollection.tsx | 160 -- packages/oc-core/src/hooks/index.ts | 4 - packages/oc-core/src/hooks/useCustomPages.ts | 63 - .../oc-core/src/hooks/useItemFiltering.ts | 56 - .../oc-core/src/hooks/useMarkdownRenderer.ts | 100 +- packages/oc-core/src/hooks/useResponsive.ts | 43 - packages/oc-core/src/hooks/useSearchModal.ts | 171 -- .../oc-core/src/hooks/useSectionTracking.ts | 425 ----- packages/oc-core/src/index.ts | 49 - packages/oc-core/src/main.tsx | 10 - .../oc-core/src/runner/RequestExecutor.ts | 47 +- packages/oc-core/src/runner/index.ts | 6 +- packages/oc-core/src/standalone.ts | 30 +- packages/oc-core/src/test.ts | 1662 ----------------- packages/oc-core/src/types/component-types.ts | 13 - packages/oc-core/src/ui/AllEndpointsView.tsx | 18 +- packages/oc-core/src/ui/BodyCard.tsx | 71 - packages/oc-core/src/ui/Card.tsx | 127 -- packages/oc-core/src/ui/CodeBlock.tsx | 177 -- .../oc-core/src/ui/ExpandableFormTable.tsx | 169 -- packages/oc-core/src/ui/SearchModal.tsx | 136 -- packages/oc-core/src/ui/index.ts | 3 - .../src/ui/request-runner/RequestRunner.tsx | 22 +- packages/oc-core/tsconfig.json | 2 +- packages/oc-core/vite.config.ts | 58 - 34 files changed, 236 insertions(+), 4866 deletions(-) delete mode 100644 examples/standalone-html/oc.yaml delete mode 100644 examples/standalone-html/schema.json delete mode 100644 packages/oc-core/index.html delete mode 100644 packages/oc-core/src/App.tsx rename packages/oc-core/src/{layouts/DesktopLayout.tsx => components/OpenCollection/OpenCollection.tsx} (53%) delete mode 100644 packages/oc-core/src/core/FileCollectionLoader.tsx delete mode 100644 packages/oc-core/src/core/OpenCollection.tsx delete mode 100644 packages/oc-core/src/hooks/useCustomPages.ts delete mode 100644 packages/oc-core/src/hooks/useItemFiltering.ts delete mode 100644 packages/oc-core/src/hooks/useResponsive.ts delete mode 100644 packages/oc-core/src/hooks/useSearchModal.ts delete mode 100644 packages/oc-core/src/hooks/useSectionTracking.ts delete mode 100644 packages/oc-core/src/index.ts delete mode 100644 packages/oc-core/src/main.tsx delete mode 100644 packages/oc-core/src/test.ts delete mode 100644 packages/oc-core/src/ui/BodyCard.tsx delete mode 100644 packages/oc-core/src/ui/Card.tsx delete mode 100644 packages/oc-core/src/ui/CodeBlock.tsx delete mode 100644 packages/oc-core/src/ui/ExpandableFormTable.tsx delete mode 100644 packages/oc-core/src/ui/SearchModal.tsx delete mode 100644 packages/oc-core/vite.config.ts diff --git a/examples/standalone-html/oc.yaml b/examples/standalone-html/oc.yaml deleted file mode 100644 index 82e6de60..00000000 --- a/examples/standalone-html/oc.yaml +++ /dev/null @@ -1,230 +0,0 @@ -name: "E-commerce API" -docs: | - Collection for testing the E-commerce API endpoints. The server automatically seeds 5 sample products (Smartphone X, Running Shoes, Coffee Maker, Laptop Pro, Wireless Earbuds) on startup if no products exist in the database. - - [Fetch in Bruno](https://fetch.usebruno.com?url=git%40bitbucket.org%3Ausebruno%2Fdemo-api-collection.git "target=_blank rel=noopener noreferrer") - -environments: - - name: "dev" - variables: - - name: "baseUrl" - value: "http://localhost:3001/api" - -items: - # Cart folder - - type: "folder" - name: "Cart" - docs: "Shopping cart endpoints" - items: - - type: "http" - name: "Get User Cart" - url: "{{baseUrl}}/cart" - method: "GET" - headers: - - name: "Authorization" - value: "Bearer {{accessToken}}" - docs: "Retrieve the current user's shopping cart" - - - type: "http" - name: "Add Item to Cart" - url: "{{baseUrl}}/cart/add/item/:itemId" - method: "POST" - headers: - - name: "Content-Type" - value: "application/json" - - name: "Authorization" - value: "Bearer {{accessToken}}" - params: - - name: "itemId" - value: "cd3ad475-0784-4a0d-9fe4-a9a17e5391d9" - type: "path" - body: - type: "json" - data: | - { - "quantity": 2 - } - docs: "Add a product to the user's shopping cart" - - - type: "http" - name: "Update Cart Item" - url: "{{baseUrl}}/cart/item/:itemId" - method: "PUT" - headers: - - name: "Content-Type" - value: "application/json" - - name: "Authorization" - value: "Bearer {{accessToken}}" - params: - - name: "itemId" - value: "cd3ad475-0784-4a0d-9fe4-a9a17e5391d9" - type: "path" - body: - type: "json" - data: | - { - "quantity": 5 - } - docs: "Update the quantity of an item in the cart" - - - type: "http" - name: "Remove Item from Cart" - url: "{{baseUrl}}/cart/item/:itemId" - method: "DELETE" - headers: - - name: "Authorization" - value: "Bearer {{accessToken}}" - params: - - name: "itemId" - value: "cd3ad475-0784-4a0d-9fe4-a9a17e5391d9" - type: "path" - docs: "Remove a specific item from the cart" - - - type: "http" - name: "Clear Cart" - url: "{{baseUrl}}/cart" - method: "DELETE" - headers: - - name: "Authorization" - value: "Bearer {{accessToken}}" - docs: "Remove all items from the user's cart" - - # Authentication folder - - type: "folder" - name: "Authentication" - docs: "User authentication endpoints" - items: - - type: "http" - name: "Register User" - url: "{{baseUrl}}/auth/register" - method: "POST" - headers: - - name: "Content-Type" - value: "application/json" - body: - type: "json" - data: | - { - "name": "Anoop", - "email": "test@example.com", - "password": "password123" - } - docs: "Register a new user account" - - - type: "http" - name: "Login" - url: "{{baseUrl}}/auth/login" - method: "POST" - body: - type: "json" - data: | - { - "email": "test@example.com", - "password": "password123" - } - scripts: - postResponse: 'bru.setEnvVar("accessToken", res.body.token)' - docs: "Authenticate a user and receive a token" - - - type: "http" - name: "Whoami" - url: "{{baseUrl}}/auth/whoami" - method: "GET" - headers: - - name: "Authorization" - value: "Bearer {{accessToken}}" - docs: "Retrieve authenticated user's profile information" - - # Products folder - - type: "folder" - name: "Products" - docs: "Product-related endpoints. The server automatically seeds 5 sample products on startup if no products exist." - items: - - type: "http" - name: "Get All Products" - url: "{{baseUrl}}/products" - method: "GET" - params: - - name: "category" - value: "electronics" - type: "query" - enabled: false - docs: "Retrieve a list of all products, optionally filtered by category. Default seeded categories include: Electronics, Sports, and Home." - - - type: "http" - name: "Get Products by Category" - url: "{{baseUrl}}/products?category=Electronics" - method: "GET" - params: - - name: "category" - value: "Electronics" - type: "query" - docs: "Retrieve products filtered by category. Seeded categories include: Electronics, Sports, and Home." - - - type: "http" - name: "Get Product by ID" - url: "{{baseUrl}}/products/:productId" - method: "GET" - params: - - name: "productId" - value: "cd3ad475-0784-4a0d-9fe4-a9a17e5391d9" - type: "path" - docs: "Retrieve detailed information about a specific product" - - # Orders folder - - type: "folder" - name: "Orders" - docs: "Order management endpoints" - items: - - type: "http" - name: "Create Order" - url: "{{baseUrl}}/orders" - method: "POST" - headers: - - name: "Content-Type" - value: "application/json" - - name: "Authorization" - value: "Bearer {{accessToken}}" - body: - type: "json" - data: | - { - "shippingAddress": "Mars" - } - scripts: - tests: | - // Check if the request was successful - if (res.getStatus() === 200 || res.getStatus() === 201) { - // Parse the response body - var jsonData = res.getBody(); - - // Check if the order has an ID - if (jsonData && jsonData.id) { - // Store the order ID in the environment - bru.setEnvVar('order_id', jsonData.id); - console.log('Order ID saved to environment: ' + jsonData.id); - } - } - docs: "Create a new order using the items in the user's cart" - - - type: "http" - name: "Get User Orders" - url: "{{baseUrl}}/orders" - method: "GET" - headers: - - name: "Authorization" - value: "Bearer {{accessToken}}" - docs: "Retrieve a list of all orders placed by the current user" - - - type: "http" - name: "Get Order by ID" - url: "{{baseUrl}}/orders/:orderId" - method: "GET" - headers: - - name: "Authorization" - value: "Bearer {{accessToken}}" - params: - - name: "orderId" - value: "9c4d1e4f-64dd-465a-b52a-5471caab828b" - type: "path" - docs: "Retrieve detailed information about a specific order" diff --git a/examples/standalone-html/schema.json b/examples/standalone-html/schema.json deleted file mode 100644 index 3d0a8983..00000000 --- a/examples/standalone-html/schema.json +++ /dev/null @@ -1,786 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schema.opencollection.com/json/draft-07/opencollection/v1.0.0", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the collection" - }, - "description": { - "$ref": "#/$defs/Description" - }, - "environments": { - "type": "array", - "description": "Array of environments", - "items": { - "$ref": "#/$defs/Environment" - } - }, - "items": { - "type": "array", - "description": "Array of items in the collection", - "items": { - "$ref": "#/$defs/Item" - } - }, - "base": { - "type": "object", - "description": "Base configuration for the collection", - "properties": { - "headers": { - "type": "array", - "description": "Array of request headers", - "items": { - "$ref": "#/$defs/RequestHeader" - } - }, - "auth": { - "$ref": "#/$defs/Auth" - }, - "variables": { - "type": "array", - "description": "Array of variables", - "items": { - "$ref": "#/$defs/Variable" - } - }, - "scripts": { - "$ref": "#/$defs/Scripts" - } - }, - "additionalProperties": false - }, - "docs": { - "type": "string", - "description": "The documentation" - } - }, - "$defs": { - "Description": { - "description": "The description", - "oneOf": [ - { - "type": "object", - "properties": { - "content": { - "type": "string", - "description": "The content of the description" - }, - "type": { - "type": "string", - "description": "The MIME type of the content" - } - }, - "required": ["content", "type"], - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "RequestHeader": { - "type": "object", - "description": "A request header with name, value, description, and disabled state", - "properties": { - "name": { - "type": "string", - "description": "The header name" - }, - "value": { - "type": "string", - "description": "The header value" - }, - "description": { - "$ref": "#/$defs/Description" - }, - "disabled": { - "type": "boolean", - "description": "Whether the header is disabled" - } - }, - "required": ["name", "value"], - "additionalProperties": false - }, - "RequestParam": { - "type": "object", - "description": "A request parameter with name, value, description, type, and enabled state", - "properties": { - "name": { - "type": "string", - "description": "The parameter name" - }, - "value": { - "type": "string", - "description": "The parameter value" - }, - "description": { - "$ref": "#/$defs/Description" - }, - "type": { - "type": "string", - "enum": ["query", "path"], - "description": "The type of parameter" - }, - "enabled": { - "type": "boolean", - "description": "Whether the parameter is enabled" - } - }, - "required": ["name", "value", "type"], - "additionalProperties": false - }, - "RequestBody": { - "oneOf": [ - { - "$ref": "#/$defs/RawBody" - }, - { - "$ref": "#/$defs/FormUrlEncodedBody" - }, - { - "$ref": "#/$defs/MultipartFormBody" - }, - { - "$ref": "#/$defs/FileBody" - }, - { - "type": "null" - } - ] - }, - "RawBody": { - "type": "object", - "description": "Raw request body with type and data", - "properties": { - "type": { - "type": "string", - "enum": ["json", "text", "xml", "sparql"], - "description": "The type of raw body content" - }, - "data": { - "type": "string", - "description": "The raw body data" - } - }, - "required": ["type", "data"], - "additionalProperties": false - }, - "FormUrlEncodedBody": { - "type": "array", - "description": "Form URL encoded body as array of key-value pairs", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The form field name" - }, - "value": { - "type": "string", - "description": "The form field value" - }, - "description": { - "$ref": "#/$defs/Description" - }, - "enabled": { - "type": "boolean", - "description": "Whether the form field is enabled" - } - }, - "required": ["name", "value"], - "additionalProperties": false - } - }, - "MultipartFormBody": { - "type": "array", - "description": "Multipart form body as array of form parts", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The form part name" - }, - "type": { - "type": "string", - "enum": ["text", "file"], - "description": "The type of form part" - }, - "value": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ], - "description": "The form part value" - }, - "description": { - "$ref": "#/$defs/Description" - }, - "enabled": { - "type": "boolean", - "description": "Whether the form part is enabled" - } - }, - "required": ["name", "type", "value"], - "additionalProperties": false - } - }, - "FileBody": { - "type": "array", - "description": "File body as array of file objects", - "items": { - "type": "object", - "properties": { - "filePath": { - "type": "string", - "description": "Path to the file" - }, - "contentType": { - "type": "string", - "description": "MIME type of the file" - }, - "selected": { - "type": "boolean", - "description": "Whether the file is selected" - } - }, - "required": ["filePath", "contentType", "selected"], - "additionalProperties": false - } - }, - "Assertion": { - "type": "object", - "description": "An assertion for response validation", - "properties": { - "expression": { - "type": "string", - "description": "The expression to evaluate" - }, - "operator": { - "type": "string", - "description": "The comparison operator" - }, - "value": { - "type": "string", - "description": "The expected value" - }, - "enabled": { - "type": "boolean", - "description": "Whether the assertion is enabled" - }, - "description": { - "$ref": "#/$defs/Description" - } - }, - "required": ["expression", "operator", "value"], - "additionalProperties": false - }, - "Auth": { - "description": "Authentication", - "oneOf": [ - { - "$ref": "#/$defs/AuthAwsV4" - }, - { - "$ref": "#/$defs/AuthBasic" - }, - { - "$ref": "#/$defs/AuthWsse" - }, - { - "$ref": "#/$defs/AuthBearer" - }, - { - "$ref": "#/$defs/AuthDigest" - }, - { - "$ref": "#/$defs/AuthNTLM" - }, - { - "$ref": "#/$defs/AuthApiKey" - } - ] - }, - "AuthAwsV4": { - "type": "object", - "description": "AWS V4 authentication", - "properties": { - "type": { - "type": "string", - "const": "awsv4" - }, - "accessKeyId": { - "type": "string", - "description": "AWS access key ID" - }, - "secretAccessKey": { - "type": "string", - "description": "AWS secret access key" - }, - "sessionToken": { - "type": "string", - "description": "AWS session token" - }, - "service": { - "type": "string", - "description": "AWS service name" - }, - "region": { - "type": "string", - "description": "AWS region" - }, - "profileName": { - "type": "string", - "description": "AWS profile name" - } - }, - "required": ["type"], - "additionalProperties": false - }, - "AuthBasic": { - "type": "object", - "description": "Basic authentication", - "properties": { - "type": { - "type": "string", - "const": "basic" - }, - "username": { - "type": "string", - "description": "Username for basic auth" - }, - "password": { - "type": "string", - "description": "Password for basic auth" - } - }, - "required": ["type"], - "additionalProperties": false - }, - "AuthWsse": { - "type": "object", - "description": "WSSE authentication", - "properties": { - "type": { - "type": "string", - "const": "wsse" - }, - "username": { - "type": "string", - "description": "Username for WSSE auth" - }, - "password": { - "type": "string", - "description": "Password for WSSE auth" - } - }, - "required": ["type"], - "additionalProperties": false - }, - "AuthBearer": { - "type": "object", - "description": "Bearer token authentication", - "properties": { - "type": { - "type": "string", - "const": "bearer" - }, - "token": { - "type": "string", - "description": "Bearer token" - } - }, - "required": ["type"], - "additionalProperties": false - }, - "AuthDigest": { - "type": "object", - "description": "Digest authentication", - "properties": { - "type": { - "type": "string", - "const": "digest" - }, - "username": { - "type": "string", - "description": "Username for digest auth" - }, - "password": { - "type": "string", - "description": "Password for digest auth" - } - }, - "required": ["type"], - "additionalProperties": false - }, - "AuthNTLM": { - "type": "object", - "description": "NTLM authentication", - "properties": { - "type": { - "type": "string", - "const": "ntlm" - }, - "username": { - "type": "string", - "description": "Username for NTLM auth" - }, - "password": { - "type": "string", - "description": "Password for NTLM auth" - }, - "domain": { - "type": "string", - "description": "Domain for NTLM auth" - } - }, - "required": ["type"], - "additionalProperties": false - }, - "AuthApiKey": { - "type": "object", - "description": "API Key authentication", - "properties": { - "type": { - "type": "string", - "const": "apikey" - }, - "key": { - "type": "string", - "description": "API key name" - }, - "value": { - "type": "string", - "description": "API key value" - }, - "placement": { - "type": "string", - "enum": ["header", "query"], - "description": "Where to place the API key" - } - }, - "required": ["type"], - "additionalProperties": false - }, - "Item": { - "oneOf": [ - { - "$ref": "#/$defs/HttpRequest" - }, - { - "$ref": "#/$defs/GraphQLRequest" - }, - { - "$ref": "#/$defs/GrpcRequest" - }, - { - "$ref": "#/$defs/Folder" - }, - { - "$ref": "#/$defs/Script" - } - ] - }, - "HttpRequest": { - "type": "object", - "description": "HTTP request configuration", - "properties": { - "name": { - "type": "string", - "description": "The name of the request" - }, - "type": { - "type": "string", - "const": "http", - "description": "The type of request" - }, - "url": { - "type": "string", - "description": "The URL of the request" - }, - "method": { - "type": "string", - "description": "HTTP method" - }, - "params": { - "type": "array", - "description": "Array of request parameters", - "items": { - "$ref": "#/$defs/RequestParam" - } - }, - "headers": { - "type": "array", - "description": "Array of request headers", - "items": { - "$ref": "#/$defs/RequestHeader" - } - }, - "body": { - "$ref": "#/$defs/RequestBody" - }, - "auth": { - "$ref": "#/$defs/Auth" - }, - "scripts": { - "$ref": "#/$defs/Scripts" - }, - "variables": { - "type": "array", - "description": "Array of variables", - "items": { - "$ref": "#/$defs/Variable" - } - }, - "assertions": { - "type": "array", - "description": "Array of assertions for response validation", - "items": { - "$ref": "#/$defs/Assertion" - } - }, - "docs": { - "type": "string", - "description": "Documentation for this request" - } - }, - "required": ["type"], - "additionalProperties": false - }, - "GraphQLRequest": { - }, - "GrpcRequest": { - }, - "Folder": { - "type": "object", - "description": "A folder for organizing collection items", - "properties": { - "type": { - "type": "string", - "const": "folder" - }, - "Item": { - "oneOf": [ - { - "$ref": "#/$defs/HttpRequest" - }, - { - "$ref": "#/$defs/GraphQLRequest" - }, - { - "$ref": "#/$defs/GrpcRequest" - }, - { - "$ref": "#/$defs/Folder" - }, - { - "$ref": "#/$defs/Script" - } - ] - }, - "headers": { - "type": "array", - "description": "Array of request headers", - "items": { - "$ref": "#/$defs/RequestHeader" - } - }, - "auth": { - "$ref": "#/$defs/Auth" - }, - "variables": { - "type": "array", - "description": "Array of variables", - "items": { - "$ref": "#/$defs/Variable" - } - }, - "scripts": { - "$ref": "#/$defs/Scripts" - }, - "docs": { - "type": "string", - "description": "Documentation for this folder" - } - }, - "additionalProperties": false - }, - "Script": { - "type": "object", - "description": "Javscript module or shared collection scripts", - "properties": { - "type": { - "type": "string", - "const": "script" - }, - "script": { - "type": "string", - "description": "The script" - } - }, - "additionalProperties": false - }, - "Environment": { - "type": "object", - "description": "An environment configuration", - "properties": { - "name": { - "type": "string", - "description": "The name of the environment" - }, - "description": { - "$ref": "#/$defs/Description" - }, - "variables": { - "type": "array", - "description": "Array of environment variables", - "items": { - "$ref": "#/$defs/Variable" - } - } - }, - "required": ["name"], - "additionalProperties": false - }, - "Variable": { - "type": "object", - "description": "A variable with name, value, description, and state flags", - "properties": { - "name": { - "type": "string", - "description": "The variable name" - }, - "value": { - "description": "The variable value", - "oneOf": [ - { - "type": "string" - }, - { - "$ref": "#/$defs/ValueSet" - }, - { - "type": "null" - } - ] - }, - "description": { - "$ref": "#/$defs/Description" - }, - "disabled": { - "type": "boolean", - "description": "Whether the variable is disabled" - }, - "transient": { - "type": "boolean", - "description": "Whether the variable value should be persisted or not" - }, - "default": { - "description": "The default value when transient is true", - "oneOf": [ - { - "type": "string" - }, - { - "$ref": "#/$defs/Value" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - }, - "Value": { - "type": "object", - "description": "A value with data and type information", - "properties": { - "data": { - "type": "string", - "description": "The string representation of the value" - }, - "type": { - "type": "string", - "description": "The type of the value", - "enum": ["string", "number", "boolean", "null", "object"] - } - }, - "additionalProperties": false - }, - "ValueSet": { - "type": "object", - "description": "A value with data, type and variants", - "properties": { - "data": { - "type": "string", - "description": "The string representation of the value" - }, - "type": { - "type": "string", - "description": "The type of the value", - "enum": ["string", "number", "boolean", "null", "object"] - }, - "variants": { - "type": "array", - "description": "Array of variants for this value", - "items": { - "$ref": "#/$defs/Variant" - } - } - }, - "additionalProperties": false - }, - "Variant": { - "type": "object", - "description": "A variant with data and description", - "properties": { - "data": { - "type": "string", - "description": "The data for this variant" - }, - "description": { - "type": "string", - "description": "The description for this variant" - } - }, - "required": ["data"], - "additionalProperties": false - }, - "Scripts": { - "type": "object", - "description": "Scripts for collection execution lifecycle", - "properties": { - "preRequest": { - "type": "string", - "description": "Script to execute before making requests" - }, - "postResponse": { - "type": "string", - "description": "Script to execute after receiving responses" - }, - "tests": { - "type": "string", - "description": "Test scripts to validate responses" - }, - "hooks": { - "type": "string", - "description": "Scripts that can be injected into specific stages of the execution process." - } - }, - "additionalProperties": false - } - } -} \ No newline at end of file diff --git a/packages/oc-core/index.html b/packages/oc-core/index.html deleted file mode 100644 index c748ff79..00000000 --- a/packages/oc-core/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - OpenCollection Playground - Interactive API Documentation - - - -
- - - diff --git a/packages/oc-core/src/App.tsx b/packages/oc-core/src/App.tsx deleted file mode 100644 index 3d495aee..00000000 --- a/packages/oc-core/src/App.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import OpenCollection from './core/OpenCollection'; -import './styles/App.css'; -import { test } from './test'; - -function App() { - const theme = 'dark'; - - return ( -
- -
- ); -} - -export default App; diff --git a/packages/oc-core/src/components/Docs/Item/Item.tsx b/packages/oc-core/src/components/Docs/Item/Item.tsx index c2159500..ef6ce1ab 100644 --- a/packages/oc-core/src/components/Docs/Item/Item.tsx +++ b/packages/oc-core/src/components/Docs/Item/Item.tsx @@ -5,7 +5,8 @@ import 'prismjs/components/prism-graphql'; import 'prismjs/components/prism-json'; import 'prismjs/components/prism-xml-doc'; import 'prismjs/components/prism-python'; -import type { Item as OpenCollectionItem, HttpRequest, Script, Folder } from '@opencollection/types'; +import type { HttpRequest } from '@opencollection/types/requests/http'; +import type { Variable } from '@opencollection/types/common/variables'; import { generateSectionId, getItemId } from '../../../utils/itemUtils'; import { MinimalDataTable, @@ -15,6 +16,7 @@ import { import { CodeSnippets } from '../CodeSnippets/CodeSnippets'; import { StyledWrapper } from './StyledWrapper'; import { Scripts } from './Scripts/Scripts'; +import { useMarkdownRenderer } from '../../../hooks'; const methodColors: Record = { GET: '#10b981', @@ -30,21 +32,20 @@ const Item = memo(({ item, registerSectionRef, theme, - md, parentPath = '', collection, toggleRunnerMode, onTryClick }: { - item: OpenCollectionItem; + item: any; registerSectionRef: (id: string, ref: HTMLDivElement | null) => void; theme: 'light' | 'dark' | 'auto'; - md: any; parentPath?: string; collection?: any; toggleRunnerMode?: () => void; onTryClick?: () => void; }) => { + const md = useMarkdownRenderer(); const itemId = getItemId(item); const sectionId = generateSectionId(item, parentPath); @@ -58,7 +59,7 @@ const Item = memo(({ }, [sectionId, registerSectionRef]); if (item.type === 'folder') { - const folderItem = item as Folder; + const folderItem = item as any; return ( -
+
)} @@ -100,7 +101,7 @@ const Item = memo(({ {folderItem.variables && folderItem.variables.length > 0 && ( ({ + data={folderItem.variables.map((v: Variable) => ({ name: v.name, value: v.value || v.default || '', enabled: !v.disabled @@ -124,7 +125,7 @@ const Item = memo(({ } if (item.type === 'script') { - const scriptItem = item as Script; + const scriptItem = item as any; return ( void; - onlyShow?: string[]; - isSearchOpen: boolean; - setIsSearchOpen: (open: boolean) => void; - searchQuery: string; - setSearchQuery: (query: string) => void; - searchResults: Array<{id: string; name: string; path?: string; type: string}>; - searchInputRef: React.RefObject; - handleSearchResultSelect: (result: {id: string; path?: string; type: string}) => void; containerRef: React.RefObject; filteredCollectionItems: any[]; - md: any; - customPageContents: Record; children?: React.ReactNode; isRunnerMode?: boolean; toggleRunnerMode?: () => void; - proxyUrl?: string; } const DesktopLayout: React.FC = ({ collectionData, - hideSidebar, logo, theme, currentPageId, onSelectItem, - onlyShow, - isSearchOpen, - setIsSearchOpen, - searchQuery, - setSearchQuery, - searchResults, - searchInputRef, - handleSearchResultSelect, containerRef, - filteredCollectionItems, - md, - proxyUrl + filteredCollectionItems }) => { const [selectedItemId, setSelectedItemId] = useState(currentPageId); const [playgroundItem, setPlaygroundItem] = useState(null); @@ -86,7 +69,7 @@ const DesktopLayout: React.FC = ({ }, [selectedItemId, collectionData]); // Update playground item when selection changes - React.useEffect(() => { + useEffect(() => { if (selectedItem && selectedItem.type === 'http') { setPlaygroundItem(selectedItem); // If drawer is open and we select a new HTTP item, update it @@ -141,7 +124,7 @@ const DesktopLayout: React.FC = ({ }; // Sync with external currentPageId changes - React.useEffect(() => { + useEffect(() => { if (currentPageId) { setSelectedItemId(currentPageId); } @@ -149,36 +132,23 @@ const DesktopLayout: React.FC = ({ return (
- - {/* Left Pane: Sidebar */} - {!hideSidebar && ( -
- handleItemSelect(id, false)} - logo={logo} - theme={theme} - onlyShow={onlyShow} - /> -
- )} +
+ handleItemSelect(id, false)} + logo={logo} + theme={theme} + /> +
{/* Middle Pane: All Endpoints View */}
= ({ > handleItemSelect(id, openPlayground || false)} /> @@ -202,11 +171,103 @@ const DesktopLayout: React.FC = ({ collection={collectionData} selectedItem={playgroundItem} onSelectItem={handlePlaygroundItemSelect} - proxyUrl={proxyUrl} theme={theme} />
); }; -export default DesktopLayout; \ No newline at end of file +const OpenCollection: React.FC = ({ + collection, + theme = 'light', + logo, +}) => { + const containerRef = useRef(null); + + const { collectionData, isLoading, error } = useCollectionData(collection); + useTheme(theme); + + const filteredCollectionItems = collectionData?.items || []; + + // Page selection state + const [currentPageId, setCurrentPageId] = useState(null); + const [currentPageItem, setCurrentPageItem] = useState(null); + + // Handle item selection + const handleSelectItem = (id: string, path?: string) => { + console.log('Selecting item:', id, 'path:', path); + setCurrentPageId(id); + + // Find the item in the collection + const findItem = (items: any[]): any => { + for (const item of items) { + const itemId = item.id || item.uid || item.name || 'unnamed-item'; + if (itemId === id || itemId.toLowerCase().replace(/[^a-z0-9\-_]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') === id) { + return item; + } + if (item.type === 'folder' && item.items) { + const found = findItem(item.items); + if (found) return found; + } + } + return null; + }; + + if (collectionData && 'items' in collectionData && collectionData.items) { + const item = findItem(collectionData.items); + setCurrentPageItem(item); + } + }; + + // Set initial page to first root-level request when collection loads + useEffect(() => { + if (collectionData && currentPageId === null) { + const items = collectionData.items as any[] | undefined; + + const firstRequest = items?.find((item) => item.type === 'http'); + if (firstRequest?.name) { + handleSelectItem(firstRequest.name); + } else if (items && items.length > 0) { + const firstItem = items[0]; + if (firstItem && 'name' in firstItem && firstItem.name) { + handleSelectItem(firstItem.name); + } + } + } + }, [collectionData, currentPageId]); + + const { isRunnerMode, toggleRunnerMode } = useRunnerMode(); + + if (isLoading) { + return
Loading...
; + } + + if (error) { + return
Error: {error}
; + } + + const commonProps = { + collectionData, + theme, + currentPageId, + currentPageItem, + onSelectItem: handleSelectItem, + containerRef, + filteredCollectionItems + }; + + const desktopProps = { + ...commonProps, + logo, + isRunnerMode, + toggleRunnerMode + }; + + return ( +
+ +
+ ); +}; + +export default OpenCollection; \ No newline at end of file diff --git a/packages/oc-core/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx b/packages/oc-core/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx index b059aa4f..f295db84 100644 --- a/packages/oc-core/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx +++ b/packages/oc-core/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react'; import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; -import type { Item as OpenCollectionItem, HttpRequest, Folder } from '@opencollection/types'; +import type { Item as OpenCollectionItem, Folder } from '@opencollection/types/collection/item'; +import type { HttpRequest } from '@opencollection/types/requests/http'; import { RequestRunner } from '../../../ui/request-runner'; import { getItemId, generateSafeId } from '../../../utils/itemUtils'; import Method from '../../Method/Method'; @@ -12,7 +13,6 @@ interface PlaygroundDrawerProps { collection: OpenCollectionCollection | null; selectedItem: HttpRequest | null; onSelectItem: (item: HttpRequest) => void; - proxyUrl?: string; theme?: 'light' | 'dark' | 'auto'; } @@ -27,7 +27,6 @@ const PlaygroundDrawer: React.FC = ({ collection, selectedItem, onSelectItem, - proxyUrl, theme = 'light' }) => { const [height, setHeight] = useState(() => isOpen ? getDefaultHeight() : 0); @@ -46,7 +45,7 @@ const PlaygroundDrawer: React.FC = ({ const traverseItems = (items: OpenCollectionItem[]) => { items.forEach((item) => { - if (item.type === 'folder') { + if ((item as any).type === 'folder') { const itemId = getItemId(item); initExpandedFolders[itemId] = false; if ('items' in item && item.items) { @@ -90,7 +89,7 @@ const PlaygroundDrawer: React.FC = ({ const renderItem = (item: OpenCollectionItem, level = 0) => { const itemId = getItemId(item); const safeItemId = generateSafeId(itemId); - const isFolder = item.type === 'folder'; + const isFolder = (item as any).type === 'folder'; const isExpanded = expandedFolders[itemId] || false; const isSelected = !isFolder && selectedItem && getItemId(selectedItem) === itemId; @@ -138,14 +137,14 @@ const PlaygroundDrawer: React.FC = ({
- {isExpanded && folder.items && ( + {isExpanded && folder.items && (
- {folder.items.map((child) => renderItem(child, level + 1))} + {folder.items.map((child: OpenCollectionItem) => renderItem(child, level + 1))}
)}
); - } else if (item.type === 'http') { + } else if ((item as any).type === 'http') { const httpItem = item as HttpRequest; return (
= ({ ) : ( diff --git a/packages/oc-core/src/components/Sidebar/Sidebar.tsx b/packages/oc-core/src/components/Sidebar/Sidebar.tsx index 10399f94..526cc434 100644 --- a/packages/oc-core/src/components/Sidebar/Sidebar.tsx +++ b/packages/oc-core/src/components/Sidebar/Sidebar.tsx @@ -1,7 +1,7 @@ import React, { useState, useMemo, useEffect, useCallback } from 'react'; import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; -import type { Item as OpenCollectionItem, HttpRequest, Folder } from '@opencollection/types'; -import type { CustomPage } from '../../types/component-types'; +import type { Item as OpenCollectionItem, Folder } from '@opencollection/types/collection/item'; +import type { HttpRequest } from '@opencollection/types/requests/http'; import Method from '../Method/Method'; import { getItemId, generateSafeId } from '../../utils/itemUtils'; import OpenCollectionLogo from '../../assets/opencollection-logo.svg'; @@ -28,8 +28,6 @@ export interface SidebarProps { logo?: React.ReactNode; className?: string; theme?: 'light' | 'dark' | 'auto'; - customPages?: CustomPage[]; - onlyShow?: string[]; isCompact?: boolean; } @@ -40,20 +38,11 @@ const Sidebar: React.FC = ({ logo, className = '', theme = 'light', - customPages = [], - onlyShow = [], isCompact = false }) => { const [expandedFolders, setExpandedFolders] = useState>({}); const [hoveredItemId, setHoveredItemId] = useState(null); - const shouldShowItem = useCallback((itemId: string): boolean => { - if (!onlyShow.length) { - return true; - } - return onlyShow.includes(itemId); - }, [onlyShow]); - useEffect(() => { if (!collection) return; @@ -61,7 +50,7 @@ const Sidebar: React.FC = ({ if ('items' in collection) { const items = collection.items || []; - items.forEach((item) => { + items.forEach((item: any) => { if (item.type === 'folder') { const itemId = getItemId(item); initExpandedFolders[itemId] = false; @@ -92,7 +81,7 @@ const Sidebar: React.FC = ({ onSelectItem(id, parentPath); }; - const normalizedItems = useMemo(() => { + const normalizedItems = useMemo(() => { if (!collection) return []; if ('endpoints' in collection) { @@ -103,58 +92,23 @@ const Sidebar: React.FC = ({ url: endpoint.path })); } else if ('items' in collection) { - return collection.items; + return collection.items as any[]; } return []; }, [collection]); const filteredItems = useMemo(() => { - let items = normalizedItems; - - if (onlyShow.length && collection !== null && 'items' in collection) { - const filterItemsByOnlyShow = (items: OpenCollectionItem[]): OpenCollectionItem[] => { - return items.filter(item => { - const itemName = item.type === 'http' ? (item as HttpRequest).name || item.type : item.type; - if (shouldShowItem(itemName)) { - return true; - } - - - return false; - }); - }; - - items = filterItemsByOnlyShow(items as OpenCollectionItem[]); - } - - // Return items in their original order - return items as OpenCollectionItem[]; - }, [normalizedItems, onlyShow, collection, shouldShowItem]); + return normalizedItems as OpenCollectionItem[]; + }, [normalizedItems]); const filteredEndpoints = useMemo(() => { if (!collection || !('endpoints' in collection) || !collection.endpoints) { return []; } - let endpoints = collection.endpoints; - if (onlyShow.length) { - endpoints = endpoints.filter((endpoint: ApiEndpoint) => - shouldShowItem(endpoint.id) || shouldShowItem(endpoint.path) - ); - } - - return endpoints; - }, [collection, onlyShow, shouldShowItem]); - - const filteredCustomPages = useMemo(() => { - let pages = customPages; - if (onlyShow.length) { - pages = pages.filter(page => shouldShowItem(page.name)); - } - - return pages - }, [customPages, onlyShow, shouldShowItem]); + return collection.endpoints; + }, [collection]); const renderFolderIcon = (isExpanded: boolean) => ( = ({ ); - const renderItem = (item: OpenCollectionItem, level = 0, parentPath = '') => { + const renderItem = (item: any, level = 0, parentPath = '') => { const itemId = getItemId(item); const itemName = itemId; const itemPath = parentPath ? `${parentPath}/${itemName}` : itemName; @@ -191,11 +145,6 @@ const Sidebar: React.FC = ({ const isHovered = hoveredItemId === fullPathId; const isExpanded = expandedFolders[itemId] || false; - // Don't render if this item should be filtered out - if (!shouldShowItem(itemId)) { - return null; - } - return (
= ({
- - {filteredCustomPages.length > 0 && ( -
- {filteredCustomPages.map((page) => { - const pageId = page.name; - const safePageId = generateSafeId(pageId); - const isActive = activeItemId === safePageId; - const isHovered = hoveredItemId === pageId; - - return ( -
- handleItemSelect(safePageId, '')} - onMouseEnter={() => setHoveredItemId(pageId)} - onMouseLeave={() => setHoveredItemId(null)} - id={`sidebar-item-${pageId}`} - data-testid={`custom-page-${pageId}`} - > -
- - - -
-
{page.name}
-
-
- ); - })} -
- )} - - {collection && 'items' in collection && filteredItems && filteredItems.length > 0 && ( <> - {filteredItems.map((item: OpenCollectionItem) => renderItem(item))} + {filteredItems.map((item) => renderItem(item))} )} diff --git a/packages/oc-core/src/core/FileCollectionLoader.tsx b/packages/oc-core/src/core/FileCollectionLoader.tsx deleted file mode 100644 index 5f3d946a..00000000 --- a/packages/oc-core/src/core/FileCollectionLoader.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import React, { useState } from 'react'; -import OpenCollection from './OpenCollection'; - -const FileCollectionLoader: React.FC = () => { - const [selectedFile, setSelectedFile] = useState(null); - const [theme] = useState<'light' | 'dark' | 'auto'>('light'); - - const handleFileChange = (event: React.ChangeEvent) => { - const file = event.target.files?.[0]; - if (file) { - setSelectedFile(file); - } - }; - - return ( -
- -
-
-

- Collection Loader -

-
- - - {selectedFile && ( - - Selected: {selectedFile.name} ({selectedFile.type || 'unknown type'}) - - )} -
-

- Supports JSON collections and YAML collections following the OpenCollection schema. -

-
-
- - -
- {selectedFile ? ( - - ) : ( -
-
- - - -

No collection loaded

-

Select a JSON or YAML collection file to get started.

-
-
- )} -
-
- ); -}; - -export default FileCollectionLoader; \ No newline at end of file diff --git a/packages/oc-core/src/core/OpenCollection.tsx b/packages/oc-core/src/core/OpenCollection.tsx deleted file mode 100644 index f8e7c97b..00000000 --- a/packages/oc-core/src/core/OpenCollection.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import React, { useRef, useState, useEffect } from 'react'; -import { - useCollectionData, - useCustomPages, - useMarkdownRenderer, - useResponsive, - useTheme, - useItemFiltering, - useSearchModal, - useRunnerMode -} from '../hooks'; -import type { OpenCollectionProps } from '../types/component-types'; -import DesktopLayout from '../layouts/DesktopLayout'; - -const OpenCollection: React.FC = ({ - collection, - theme = 'light', - logo, - customPages, - hideSidebar = false, - onlyShow, - proxyUrl, -}) => { - const containerRef = useRef(null); - - const { collectionData, isLoading, error } = useCollectionData(collection); - const { validCustomPages, customPageContents } = useCustomPages(customPages); - const md = useMarkdownRenderer(); - const { isMobile, mobileView, setMobileView } = useResponsive(hideSidebar); - - useTheme(theme); - - const { - filteredCollectionItems, - filteredCustomPages - } = useItemFiltering(collectionData, validCustomPages, onlyShow); - - // Page selection state - const [currentPageId, setCurrentPageId] = useState(null); - const [currentPageItem, setCurrentPageItem] = useState(null); - - // Handle item selection - const handleSelectItem = (id: string, path?: string) => { - console.log('Selecting item:', id, 'path:', path); - setCurrentPageId(id); - - - // Check if it's a custom page - const customPage = filteredCustomPages.find(page => page.name === id || page.name === id.replace(/-/g, ' ')); - if (customPage) { - setCurrentPageItem(customPage); - return; - } - - // Find the item in the collection - const findItem = (items: any[]): any => { - for (const item of items) { - const itemId = item.id || item.uid || item.name || 'unnamed-item'; - if (itemId === id || itemId.toLowerCase().replace(/[^a-z0-9\-_]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') === id) { - return item; - } - if (item.type === 'folder' && item.items) { - const found = findItem(item.items); - if (found) return found; - } - } - return null; - }; - - if (collectionData && 'items' in collectionData && collectionData.items) { - const item = findItem(collectionData.items); - setCurrentPageItem(item); - } - }; - - // Set initial page to first root-level request when collection loads - useEffect(() => { - if (collectionData && currentPageId === null) { - // Find the first root-level request - const firstRequest = collectionData.items?.find(item => item.type === 'http'); - if (firstRequest && firstRequest.name) { - handleSelectItem(firstRequest.name); - } else if (collectionData.items && collectionData.items.length > 0) { - // If no request found, select the first item - const firstItem = collectionData.items[0]; - if (firstItem.name) { - handleSelectItem(firstItem.name); - } - } - } - }, [collectionData, currentPageId]); - - const { - isSearchOpen, - setIsSearchOpen, - searchQuery, - setSearchQuery, - searchResults, - searchInputRef, - handleSearchResultSelect: handleSearchResultSelectHook - } = useSearchModal(collectionData, validCustomPages, handleSelectItem); - - const { isRunnerMode, toggleRunnerMode } = useRunnerMode(); - - const handleSearchResultSelect = (result: {id: string; path?: string; type: string}) => { - handleSearchResultSelectHook({ ...result, name: result.id }); - }; - - if (isLoading) { - return
Loading...
; - } - - if (error) { - return
Error: {error}
; - } - - const commonProps = { - collectionData, - hideSidebar, - theme, - currentPageId, - currentPageItem, - onSelectItem: handleSelectItem, - filteredCustomPages, - onlyShow, - containerRef, - filteredCollectionItems, - md, - customPageContents - }; - - const desktopProps = { - ...commonProps, - logo, - isSearchOpen, - setIsSearchOpen, - searchQuery, - setSearchQuery, - searchResults, - searchInputRef, - handleSearchResultSelect, - isRunnerMode, - toggleRunnerMode, - proxyUrl - }; - - const mobileProps = { - ...commonProps, - mobileView, - setMobileView - }; - - return ( -
- -
- ); -}; - -export default OpenCollection; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/index.ts b/packages/oc-core/src/hooks/index.ts index dca1589d..4b73d492 100644 --- a/packages/oc-core/src/hooks/index.ts +++ b/packages/oc-core/src/hooks/index.ts @@ -1,8 +1,4 @@ export { useCollectionData } from './useCollectionData'; -export { useCustomPages } from './useCustomPages'; -export { useSearchModal } from './useSearchModal'; export { useTheme } from './useTheme'; -export { useResponsive } from './useResponsive'; -export { useItemFiltering } from './useItemFiltering'; export { useMarkdownRenderer } from './useMarkdownRenderer'; export { useRunnerMode } from './useRunnerMode'; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/useCustomPages.ts b/packages/oc-core/src/hooks/useCustomPages.ts deleted file mode 100644 index 40d145c8..00000000 --- a/packages/oc-core/src/hooks/useCustomPages.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { useState, useEffect } from 'react'; -import type { CustomPage } from '../types/component-types'; - -interface UseCustomPagesReturn { - validCustomPages: CustomPage[]; - customPageContents: Record; -} - -export const useCustomPages = (customPages?: CustomPage[]): UseCustomPagesReturn => { - const [validCustomPages, setValidCustomPages] = useState([]); - const [customPageContents, setCustomPageContents] = useState>({}); - - useEffect(() => { - if (!customPages || customPages.length === 0) { - setValidCustomPages([]); - return; - } - - const valid = customPages.filter(page => { - if (!page.name) { - console.error('Custom page missing required "name" property'); - return false; - } - - if ((!page.content && !page.contentPath) || (page.content && page.contentPath)) { - console.error(`Custom page "${page.name}" must have either content or contentPath, but not both`); - return false; - } - - return true; - }); - - setValidCustomPages(valid); - - const loadPageContents = async () => { - const contents: Record = {}; - - for (const page of valid) { - if (page.content) { - contents[page.name] = page.content; - } else if (page.contentPath) { - try { - const response = await fetch(page.contentPath); - if (!response.ok) { - throw new Error(`Failed to load content from ${page.contentPath}`); - } - contents[page.name] = await response.text(); - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - console.error(`Error loading content for custom page "${page.name}":`, error); - contents[page.name] = `Error loading content: ${errorMessage}`; - } - } - } - - setCustomPageContents(contents); - }; - - loadPageContents(); - }, [customPages]); - - return { validCustomPages, customPageContents }; -}; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/useItemFiltering.ts b/packages/oc-core/src/hooks/useItemFiltering.ts deleted file mode 100644 index 7a4dbf5c..00000000 --- a/packages/oc-core/src/hooks/useItemFiltering.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { useMemo, useCallback } from 'react'; -import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; -import type { Item as OpenCollectionItem, HttpRequest } from '@opencollection/types'; -import type { CustomPage } from '../types/component-types'; - -interface UseItemFilteringReturn { - shouldShowItem: (itemId: string) => boolean; - filteredCollectionItems: OpenCollectionItem[]; - filteredCustomPages: CustomPage[]; -} - -export const useItemFiltering = ( - collectionData: OpenCollectionCollection | null, - validCustomPages: CustomPage[], - onlyShow?: string[] -): UseItemFilteringReturn => { - const shouldShowItem = useCallback((itemId: string): boolean => { - if (!onlyShow || onlyShow.length === 0) { - return true; - } - return onlyShow.includes(itemId); - }, [onlyShow]); - - const filteredCollectionItems = useMemo(() => { - if (!collectionData?.items) { - return []; - } - - if (!onlyShow || onlyShow.length === 0) { - return collectionData.items; - } - - const filterItems = (items: OpenCollectionItem[]): OpenCollectionItem[] => { - return items.filter(item => { - const itemId = item.type === 'http' ? (item as HttpRequest).name || '' : item.type; - return shouldShowItem(itemId); - }); - }; - - return filterItems(collectionData.items); - }, [collectionData, onlyShow, shouldShowItem]); - - const filteredCustomPages = useMemo(() => { - if (!validCustomPages || !onlyShow || onlyShow.length === 0) { - return validCustomPages; - } - - return validCustomPages.filter(page => shouldShowItem(page.name)); - }, [validCustomPages, onlyShow, shouldShowItem]); - - return { - shouldShowItem, - filteredCollectionItems, - filteredCustomPages - }; -}; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/useMarkdownRenderer.ts b/packages/oc-core/src/hooks/useMarkdownRenderer.ts index ba233d14..c7d48e9d 100644 --- a/packages/oc-core/src/hooks/useMarkdownRenderer.ts +++ b/packages/oc-core/src/hooks/useMarkdownRenderer.ts @@ -2,54 +2,66 @@ import { useMemo } from 'react'; import MarkdownIt from 'markdown-it'; import Prism from 'prismjs'; -export const useMarkdownRenderer = () => { - const md = useMemo(() => { - const markdownIt = new MarkdownIt({ - html: true, - linkify: true, - typographer: true, - breaks: true, - highlight: function (str, lang) { - if (lang && Prism.languages[lang]) { - try { - return Prism.highlight(str, Prism.languages[lang], lang); - } catch (__) { } +let cachedRenderer: MarkdownIt | null = null; + +const createRenderer = (): MarkdownIt => { + const markdownIt = new MarkdownIt({ + html: true, + linkify: true, + typographer: true, + breaks: true, + highlight: (str, lang) => { + if (lang && Prism.languages[lang]) { + try { + return Prism.highlight(str, Prism.languages[lang], lang); + } catch (error) { + // Ignore highlighting errors and fall back to default behaviour } - return ''; - }, - }); - - const defaultRender = - markdownIt.renderer.rules.heading_open || - function (tokens, idx, options, env, self) { - return self.renderToken(tokens, idx, options); - }; - - markdownIt.renderer.rules.heading_open = function ( - tokens, - idx, - options, - env, - self - ) { - const token = tokens[idx]; - const level = token.tag.substr(1); - - if (level === '1') { - token.attrJoin('class', 'text-2xl font-bold mt-6 mb-3 heading-1'); - } else if (level === '2') { - token.attrJoin('class', 'text-xl font-semibold mt-5 mb-2 heading-2'); - } else if (level === '3') { - token.attrJoin('class', 'text-lg font-semibold mt-4 mb-2 heading-3'); - } else { - token.attrJoin('class', 'font-semibold mt-3 mb-1 heading-4'); } + return ''; + }, + }); - return defaultRender(tokens, idx, options, env, self); + const defaultRender = + markdownIt.renderer.rules.heading_open || + function (tokens, idx, options, env, self) { + return self.renderToken(tokens, idx, options); }; - return markdownIt; + markdownIt.renderer.rules.heading_open = function ( + tokens, + idx, + options, + env, + self + ) { + const token = tokens[idx]; + const level = token.tag.substr(1); + + if (level === '1') { + token.attrJoin('class', 'text-2xl font-bold mt-6 mb-3 heading-1'); + } else if (level === '2') { + token.attrJoin('class', 'text-xl font-semibold mt-5 mb-2 heading-2'); + } else if (level === '3') { + token.attrJoin('class', 'text-lg font-semibold mt-4 mb-2 heading-3'); + } else { + token.attrJoin('class', 'font-semibold mt-3 mb-1 heading-4'); + } + + return defaultRender(tokens, idx, options, env, self); + }; + + return markdownIt; +}; + +export const useMarkdownRenderer = () => { + const renderer = useMemo(() => { + if (!cachedRenderer) { + cachedRenderer = createRenderer(); + } + + return cachedRenderer; }, []); - return md; -}; \ No newline at end of file + return renderer; +}; diff --git a/packages/oc-core/src/hooks/useResponsive.ts b/packages/oc-core/src/hooks/useResponsive.ts deleted file mode 100644 index 8595f176..00000000 --- a/packages/oc-core/src/hooks/useResponsive.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { useState, useEffect } from 'react'; - -interface UseResponsiveReturn { - isMobile: boolean; - mobileView: 'sidebar' | 'content'; - setMobileView: (view: 'sidebar' | 'content') => void; - availableTabs: Array<'sidebar' | 'content'>; -} - -export const useResponsive = (hideSidebar: boolean = false): UseResponsiveReturn => { - const [isMobile, setIsMobile] = useState(false); - const [mobileView, setMobileView] = useState<'sidebar' | 'content'>('sidebar'); - - const availableTabs: Array<'sidebar' | 'content'> = []; - if (!hideSidebar) availableTabs.push('sidebar'); - availableTabs.push('content'); - - useEffect(() => { - const handleResize = () => { - setIsMobile(window.innerWidth < 768); - }; - - handleResize(); - window.addEventListener('resize', handleResize); - - return () => { - window.removeEventListener('resize', handleResize); - }; - }, []); - - useEffect(() => { - if (!availableTabs.includes(mobileView)) { - setMobileView(availableTabs.includes('content') ? 'content' : availableTabs[0]); - } - }, [hideSidebar, mobileView, availableTabs]); - - return { - isMobile, - mobileView, - setMobileView, - availableTabs - }; -}; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/useSearchModal.ts b/packages/oc-core/src/hooks/useSearchModal.ts deleted file mode 100644 index 69ea44dd..00000000 --- a/packages/oc-core/src/hooks/useSearchModal.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { useState, useEffect, useRef } from 'react'; -import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; -import type { Item as OpenCollectionItem, HttpRequest } from '@opencollection/types'; -import type { CustomPage } from '../types/component-types'; -import { getItemId, generateSafeId } from '../utils/itemUtils'; - -interface SearchResult { - id: string; - name: string; - path?: string; - type: string; -} - -interface UseSearchModalReturn { - isSearchOpen: boolean; - setIsSearchOpen: (open: boolean) => void; - searchQuery: string; - setSearchQuery: (query: string) => void; - searchResults: SearchResult[]; - selectedResultIndex: number; - searchInputRef: React.RefObject; - handleSearchResultSelect: (result: SearchResult) => void; -} - -export const useSearchModal = ( - collectionData: OpenCollectionCollection | null, - validCustomPages: CustomPage[], - onSelectItem: (id: string, path?: string) => void -): UseSearchModalReturn => { - const [isSearchOpen, setIsSearchOpen] = useState(false); - const [searchQuery, setSearchQuery] = useState(''); - const [searchResults, setSearchResults] = useState([]); - const [selectedResultIndex, setSelectedResultIndex] = useState(0); - const searchInputRef = useRef(null); - - const performSearch = (query: string) => { - if (!query.trim() || !collectionData) { - setSearchResults([]); - return; - } - - const results: SearchResult[] = []; - const addedIds = new Set(); // Track added items to avoid duplicates - - const searchInItems = (items: OpenCollectionItem[], currentPath: string = '') => { - items.forEach(item => { - const itemId = getItemId(item); - const itemName = itemId; - const itemPath = currentPath ? `${currentPath}/${itemName}` : itemName; - - const lowerQuery = query.toLowerCase(); - let matchFound = false; - - // Search in item name - if (itemName.toLowerCase().includes(lowerQuery)) { - matchFound = true; - } - - // Search in URL for HTTP requests - if (item.type === 'http' && (item as any).url && (item as any).url.toLowerCase().includes(lowerQuery)) { - matchFound = true; - } - - // Search in method for HTTP requests - if (item.type === 'http' && (item as any).method && (item as any).method.toLowerCase().includes(lowerQuery)) { - matchFound = true; - } - - // Search in documentation - if ((item as any).docs && (item as any).docs.toLowerCase().includes(lowerQuery)) { - matchFound = true; - } - - // Add to results if match found and not already added - const safeId = generateSafeId(itemId); - if (matchFound && !addedIds.has(safeId)) { - addedIds.add(safeId); - - // Create a descriptive name based on item type - let displayName = itemName; - if (item.type === 'http') { - const method = (item as any).method || 'GET'; - const url = (item as any).url || ''; - displayName = `${method} ${itemName}`; - if (url && !itemName.includes(url)) { - displayName += ` - ${url}`; - } - } else if (item.type === 'folder') { - const itemCount = (item as any).items?.length || 0; - displayName = `${itemName} (${itemCount} items)`; - } else if (item.type === 'script') { - displayName = `📜 ${itemName}`; - } - - results.push({ - id: safeId, - name: displayName, - path: itemPath, - type: item.type - }); - } - - // Recursively search in folder items - if (item.type === 'folder' && (item as any).items && (item as any).items.length > 0) { - searchInItems((item as any).items, itemPath); - } - }); - }; - - if (collectionData.items) { - searchInItems(collectionData.items); - } - - validCustomPages.forEach(page => { - if (page.name.toLowerCase().includes(query.toLowerCase()) && !addedIds.has(page.name)) { - addedIds.add(page.name); - results.push({ - id: page.name, - name: `📄 ${page.name}`, - type: 'page' - }); - } - }); - - setSearchResults(results); - }; - - const handleSearchResultSelect = (result: SearchResult) => { - setIsSearchOpen(false); - setSearchQuery(''); - onSelectItem(result.id, result.path); - }; - - useEffect(() => { - performSearch(searchQuery); - }, [searchQuery, collectionData, validCustomPages]); - - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - if ((e.metaKey || e.ctrlKey) && e.key === 'k') { - e.preventDefault(); - setIsSearchOpen(true); - setTimeout(() => { - if (searchInputRef.current) { - searchInputRef.current.focus(); - } - }, 10); - } - - if (e.key === 'Escape' && isSearchOpen) { - setIsSearchOpen(false); - } - }; - - window.addEventListener('keydown', handleKeyDown); - return () => { - window.removeEventListener('keydown', handleKeyDown); - }; - }, [isSearchOpen]); - - return { - isSearchOpen, - setIsSearchOpen, - searchQuery, - setSearchQuery, - searchResults, - selectedResultIndex, - searchInputRef, - handleSearchResultSelect - }; -}; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/useSectionTracking.ts b/packages/oc-core/src/hooks/useSectionTracking.ts deleted file mode 100644 index 84a1db12..00000000 --- a/packages/oc-core/src/hooks/useSectionTracking.ts +++ /dev/null @@ -1,425 +0,0 @@ -import { useState, useEffect, useRef, useCallback } from 'react'; -import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; -import type { Item as OpenCollectionItem, HttpRequest } from '@opencollection/types'; -import { getItemId, generateSafeId } from '../utils/itemUtils'; - -interface UseSectionTrackingReturn { - activeItemId: string | null; - activeItem: OpenCollectionItem | null; - sectionRefs: React.MutableRefObject>; - registerSectionRef: (id: string, ref: HTMLDivElement | null) => void; - handleSelectItem: (id: string, path?: string) => void; -} - -export const useSectionTracking = ( - collectionData: OpenCollectionCollection | null, - containerRef: React.RefObject, - isMobile: boolean, - setMobileView: (view: 'sidebar' | 'content') => void -): UseSectionTrackingReturn => { - const [activeItemId, setActiveItemId] = useState(null); - const [activeItem, setActiveItem] = useState(null); - const sectionRefs = useRef>({}); - - const findItemById = useCallback(( - items: OpenCollectionItem[], - id: string - ): OpenCollectionItem | null => { - for (const item of items) { - const itemId = getItemId(item); - if (itemId === id) { - return item; - } - } - return null; - }, []); - - const registerSectionRef = useCallback((id: string, ref: HTMLDivElement | null) => { - if (ref) { - sectionRefs.current[id] = ref; - } - }, []); - - const handleSelectItem = useCallback((id: string, path?: string) => { - console.log('handleSelectItem called with id:', id, 'path:', path); - - if (isMobile) { - setMobileView('content'); - } - - setActiveItemId(id); - - console.log('Available section refs:', Object.keys(sectionRefs.current)); - - if (path) { - const fullPathId = path.replace(/^\//, ''); - console.log('Trying with fullPathId:', fullPathId); - - if (sectionRefs.current[fullPathId]) { - console.log('Found section ref match for fullPathId:', fullPathId); - sectionRefs.current[fullPathId].scrollIntoView({ behavior: 'smooth' }); - return; - } - } - - if (sectionRefs.current[id]) { - console.log('Found exact section ref match:', id); - sectionRefs.current[id].scrollIntoView({ behavior: 'smooth' }); - return; - } - - if (id.includes('/')) { - console.log('Trying to find section for path:', id); - - const normalizedId = id.replace(/\/+/g, '/').replace(/^\/|\/$/g, ''); - const allSectionIds = Object.keys(sectionRefs.current); - - const variations = [ - normalizedId, - normalizedId.replace(/\//g, '//'), - normalizedId.split('/').pop() || '', - normalizedId.split('/').slice(-2).join('/') - ]; - - console.log('Trying path variations:', variations); - - for (const variant of variations) { - if (sectionRefs.current[variant]) { - console.log('Found section ref match with variant:', variant); - sectionRefs.current[variant].scrollIntoView({ behavior: 'smooth' }); - return; - } - } - - const pathParts = normalizedId.split('/'); - const lastPart = pathParts[pathParts.length - 1]; - - const matchingLastPart = allSectionIds.filter(sectionId => - sectionId.endsWith(`/${lastPart}`) || sectionId === lastPart - ); - - if (matchingLastPart.length === 1) { - console.log('Found single match by last part:', matchingLastPart[0]); - sectionRefs.current[matchingLastPart[0]].scrollIntoView({ behavior: 'smooth' }); - return; - } - - if (matchingLastPart.length > 1 || matchingLastPart.length === 0) { - console.log('Multiple or no matches by last part, trying path similarity scoring'); - - const scoredMatches = allSectionIds.map(sectionId => { - if (!sectionId.includes(lastPart)) { - return { id: sectionId, score: 0 }; - } - - const sectionParts = sectionId.split('/'); - let score = 0; - let consecutiveMatches = 0; - - for (let i = 1; i <= Math.min(pathParts.length, sectionParts.length); i++) { - if (pathParts[pathParts.length - i] === sectionParts[sectionParts.length - i]) { - score += 10; - consecutiveMatches++; - score += consecutiveMatches * 5; - } else { - consecutiveMatches = 0; - } - } - - if (sectionId.includes(lastPart)) { - score += 20; - } - - const lengthDiff = Math.abs(sectionId.length - normalizedId.length); - score -= lengthDiff * 0.5; - - return { id: sectionId, score }; - }) - .filter(match => match.score > 0) - .sort((a, b) => b.score - a.score); - - console.log('Scored matches:', scoredMatches); - - if (scoredMatches.length > 0) { - const bestMatch = scoredMatches[0].id; - console.log('Best match by scoring:', bestMatch, 'with score:', scoredMatches[0].score); - sectionRefs.current[bestMatch].scrollIntoView({ behavior: 'smooth' }); - return; - } - } - } - - const baseId = id.includes('/') ? id.split('/').pop() : id; - if (baseId && sectionRefs.current[baseId]) { - console.log('Falling back to base ID:', baseId); - sectionRefs.current[baseId].scrollIntoView({ behavior: 'smooth' }); - } else { - console.log('No matching section found for:', id); - } - }, [isMobile, setMobileView]); - - useEffect(() => { - if (collectionData && !activeItemId) { - const findFirstApiItem = (items: OpenCollectionItem[]): string | null => { - for (const item of items) { - if (item.type === 'http') { - const itemId = getItemId(item); - return generateSafeId(itemId); - } - } - return null; - }; - - const initialItemId = findFirstApiItem(collectionData.items || []); - - if (initialItemId) { - setActiveItemId(initialItemId); - } - } - }, [collectionData, activeItemId]); - - useEffect(() => { - if (!activeItemId || !collectionData || !collectionData.items) return; - - const item = findItemById(collectionData.items, activeItemId); - setActiveItem(item); - }, [activeItemId, collectionData, findItemById]); - - useEffect(() => { - if (!collectionData || !containerRef.current) return; - - console.log("Setting up robust section visibility tracking"); - - const observedSections = new Map(); - let observers: IntersectionObserver[] = []; - let isInitialized = false; - let initializationAttempts = 0; - let initTimer: ReturnType | null = null; - let updateTimer: ReturnType | null = null; - let mutationObserver: MutationObserver | null = null; - - const clearObservers = () => { - observers.forEach(observer => observer.disconnect()); - observers = []; - if (mutationObserver) { - mutationObserver.disconnect(); - mutationObserver = null; - } - }; - - const updateActiveSection = () => { - const visibleSections = Array.from(observedSections.entries()) - .filter(([id, entry]) => { - if (!entry.isIntersecting) return false; - - if (id.includes('/') && collectionData && 'items' in collectionData && collectionData.items) { - const item = findItemById(collectionData.items, id); - if (item && item.type === 'folder') { - return false; - } - } - - return true; - }) - .map(([id, entry]) => { - const rect = entry.boundingClientRect; - const elementCenter = rect.top + (rect.height / 2); - - const containerRect = containerRef.current?.getBoundingClientRect(); - const containerCenter = containerRect - ? containerRect.top + (containerRect.height / 2) - : window.innerHeight / 2; - - const distanceFromCenter = Math.abs(elementCenter - containerCenter); - - const visibleHeight = entry.intersectionRect.height; - const visiblePercent = visibleHeight / rect.height; - - const score = distanceFromCenter * 0.8 - (visiblePercent * 0.2); - - return { id, entry, score, distanceFromCenter, visiblePercent }; - }) - .sort((a, b) => a.score - b.score); - - if (visibleSections.length > 0) { - const bestSection = visibleSections[0]; - - if (bestSection.id !== activeItemId) { - console.log(`Changing active item to ${bestSection.id} (distance: ${bestSection.distanceFromCenter.toFixed(2)}px, visible: ${(bestSection.visiblePercent * 100).toFixed(1)}%)`); - console.log('All visible sections:', visibleSections.map(s => ({ id: s.id, score: s.score, distance: s.distanceFromCenter, visible: s.visiblePercent }))); - - setActiveItemId(bestSection.id); - } - } else { - console.log('No visible sections found'); - } - }; - - const setupObservers = () => { - clearObservers(); - - const sections = Object.entries(sectionRefs.current); - - if (sections.length === 0) { - console.log("No sections found yet, will retry"); - return false; - } - - console.log(`Setting up observers for ${sections.length} sections`); - - const centerObserver = new IntersectionObserver( - (entries) => { - entries.forEach(entry => { - const id = entry.target.id.startsWith('section-') - ? entry.target.id.replace(/^section-/, '') - : entry.target.id; - - if (id) { - observedSections.set(id, entry); - } - }); - - if (isInitialized) { - if (updateTimer) clearTimeout(updateTimer); - updateTimer = setTimeout(updateActiveSection, 50); - } - }, - { - root: containerRef.current, - rootMargin: '-40% 0px -40% 0px', - threshold: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] - } - ); - - const visibilityObserver = new IntersectionObserver( - (entries) => { - entries.forEach(entry => { - const id = entry.target.id.startsWith('section-') - ? entry.target.id.replace(/^section-/, '') - : entry.target.id; - - if (id) { - observedSections.set(id, entry); - } - }); - - if (isInitialized) { - if (updateTimer) clearTimeout(updateTimer); - updateTimer = setTimeout(updateActiveSection, 50); - } - }, - { - root: containerRef.current, - rootMargin: '0px', - threshold: [0, 0.1, 0.5, 1.0] - } - ); - - observers.push(centerObserver, visibilityObserver); - - sections.forEach(([id, element]) => { - if (!element || !element.isConnected) return; - - centerObserver.observe(element); - visibilityObserver.observe(element); - }); - - mutationObserver = new MutationObserver((mutations) => { - let needsReinit = false; - - mutations.forEach(mutation => { - if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { - mutation.addedNodes.forEach(node => { - if (node.nodeType === 1) { - const element = node as Element; - if (element.id && element.id.startsWith('section-')) { - needsReinit = true; - } else if (element.querySelector('[id^="section-"]')) { - needsReinit = true; - } - } - }); - } - }); - - if (needsReinit) { - console.log("DOM changed, reinitializing observers"); - setupObservers(); - } - }); - - if (containerRef.current) { - mutationObserver.observe(containerRef.current, { - childList: true, - subtree: true - }); - } - - return sections.length > 0; - }; - - const initializeTracking = () => { - initializationAttempts++; - - console.log(`Initializing section tracking (attempt ${initializationAttempts})`); - - const success = setupObservers(); - - if (success) { - console.log("Successfully set up section tracking"); - - setTimeout(() => { - isInitialized = true; - - updateActiveSection(); - - if (!activeItemId && observedSections.size > 0) { - const firstSection = Array.from(observedSections.keys())[0]; - setActiveItemId(firstSection); - } - }, 300); - - return true; - } else if (initializationAttempts < 10) { - const delay = Math.min(5000, Math.pow(1.5, initializationAttempts) * 100); - console.log(`Retrying in ${delay}ms`); - - initTimer = setTimeout(initializeTracking, delay); - return false; - } else { - console.error("Failed to initialize section tracking after multiple attempts"); - return false; - } - }; - - const handleScroll = () => { - if (!isInitialized) return; - - if (updateTimer) clearTimeout(updateTimer); - updateTimer = setTimeout(updateActiveSection, 50); - }; - - if (containerRef.current) { - containerRef.current.addEventListener('scroll', handleScroll); - } - - initializeTracking(); - - return () => { - clearObservers(); - if (initTimer) clearTimeout(initTimer); - if (updateTimer) clearTimeout(updateTimer); - if (containerRef.current) { - containerRef.current.removeEventListener('scroll', handleScroll); - } - }; - }, [collectionData, containerRef, activeItemId, findItemById]); - - return { - activeItemId, - activeItem, - sectionRefs, - registerSectionRef, - handleSelectItem - }; -}; \ No newline at end of file diff --git a/packages/oc-core/src/index.ts b/packages/oc-core/src/index.ts deleted file mode 100644 index d9e50b38..00000000 --- a/packages/oc-core/src/index.ts +++ /dev/null @@ -1,49 +0,0 @@ -import OpenCollection from './core/OpenCollection'; -import FileCollectionLoader from './core/FileCollectionLoader'; - -import './styles/index.css'; - -export { - OpenCollection, - FileCollectionLoader, -}; - -// Re-export types from @opencollection/types -export type { - OpenCollection as OpenCollectionCollection, - Item as OpenCollectionItem, - HttpRequest, - Folder, - Script, - Environment, - RequestDefaults as BaseConfiguration, - Auth, - Scripts, - Variable, - Assertion, - HttpHeader as RequestHeader, - HttpRequestParam as RequestParam, - RawBody, - FormUrlEncodedBody, - MultipartFormBody, - FileBody, - HttpRequestBody as RequestBody, -} from '@opencollection/types'; - -// Re-export component-specific types -export type { OpenCollectionProps, CustomPage } from './types/component-types'; - -export * from './hooks'; -export { - CodeEditor, - RequestRunner, - RequestHeader as RequestHeaderComponent, - RequestPane, - ResponsePane -} from './ui'; -export { - requestRunner, - RequestRunner as RequestRunnerClass, - getGlobalVariables, - clearGlobalVariables -} from './runner'; diff --git a/packages/oc-core/src/main.tsx b/packages/oc-core/src/main.tsx deleted file mode 100644 index 77257fa1..00000000 --- a/packages/oc-core/src/main.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './styles/index.css' -import App from './App' - -createRoot(document.getElementById('root')!).render( - - - , -) diff --git a/packages/oc-core/src/runner/RequestExecutor.ts b/packages/oc-core/src/runner/RequestExecutor.ts index fb1047a1..64f8da05 100644 --- a/packages/oc-core/src/runner/RequestExecutor.ts +++ b/packages/oc-core/src/runner/RequestExecutor.ts @@ -2,52 +2,7 @@ import type { HttpRequest } from '@opencollection/types/requests/http'; import { RunRequestResponse } from './index'; export class RequestExecutor { - constructor(private proxyUrl?: string) {} - async executeRequest(request: HttpRequest): Promise { - if (this.proxyUrl) { - return this.executeViaProxy(request); - } - return this.executeDirectly(request); - } - - private async executeViaProxy(request: HttpRequest): Promise { - try { - const proxyPayload = { - url: request.url, - method: request.method || 'GET', - headers: this.buildHeaders(request), - body: await this.buildBody(request), - timeout: 30000 - }; - - const response = await fetch(`${this.proxyUrl}/proxy`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(proxyPayload) - }); - - if (!response.ok) { - throw new Error(`Proxy error: ${response.statusText}`); - } - - const result = await response.json(); - - if (result.error) { - return { error: result.error }; - } - - return result; - } catch (error) { - return { - error: error instanceof Error ? error.message : 'Proxy request failed' - }; - } - } - - private async executeDirectly(request: HttpRequest): Promise { const startTime = Date.now(); try { @@ -88,7 +43,7 @@ export class RequestExecutor { return options; } - private buildHeaders(request: HttpRequest): HeadersInit { + private buildHeaders(request: HttpRequest): HeadersInit { const headers: Record = {}; if (request.headers) { diff --git a/packages/oc-core/src/runner/index.ts b/packages/oc-core/src/runner/index.ts index 1ed9a7f6..bb2d5ff7 100644 --- a/packages/oc-core/src/runner/index.ts +++ b/packages/oc-core/src/runner/index.ts @@ -29,8 +29,8 @@ export class RequestRunner { private interpolator: VariableInterpolator; private scriptRuntime: ScriptRuntime; - constructor(proxyUrl?: string) { - this.executor = new RequestExecutor(proxyUrl); + constructor() { + this.executor = new RequestExecutor(); this.interpolator = new VariableInterpolator(); this.scriptRuntime = new ScriptRuntime(); } @@ -139,7 +139,7 @@ export class RequestRunner { } } -export const createRequestRunner = (proxyUrl?: string) => new RequestRunner(proxyUrl); +export const createRequestRunner = () => new RequestRunner(); export const requestRunner = new RequestRunner(); diff --git a/packages/oc-core/src/standalone.ts b/packages/oc-core/src/standalone.ts index f5b24810..af032288 100644 --- a/packages/oc-core/src/standalone.ts +++ b/packages/oc-core/src/standalone.ts @@ -1,8 +1,8 @@ import React from 'react'; import { createRoot, Root } from 'react-dom/client'; import './styles/index.css'; -import OpenCollection from './core/OpenCollection'; -import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; +import OpenCollection from './components/OpenCollection/OpenCollection'; +import type { OpenCollection as IOpenCollection } from '@opencollection/types'; import { parseCollectionContent } from './utils/yamlUtils'; export interface OpenCollectionOptions { @@ -10,14 +10,6 @@ export interface OpenCollectionOptions { opencollection: any; theme?: 'light' | 'dark' | 'auto'; logo?: string; - customPages?: Array<{ - name: string; - content?: string; - contentPath?: string; - }>; - hideSidebar?: boolean; - hideHeader?: boolean; - onlyShow?: string[]; } export class OpenCollectionRenderer { @@ -61,17 +53,17 @@ export class OpenCollectionRenderer { this.render(); } - private convertCollection(opencollection: any): OpenCollectionCollection { + private convertCollection(opencollection: any): IOpenCollection { if (typeof opencollection === 'string') { try { - return parseCollectionContent(opencollection) as OpenCollectionCollection; + return parseCollectionContent(opencollection) as IOpenCollection; } catch (error) { console.error('Failed to parse collection:', error); throw new Error(`Invalid collection format: ${error instanceof Error ? error.message : 'Unknown error'}`); } } - return opencollection as OpenCollectionCollection; + return opencollection as IOpenCollection; } private createLogoElement(): React.ReactNode { @@ -89,17 +81,11 @@ export class OpenCollectionRenderer { const collection = this.convertCollection(this.options.opencollection); - const playgroundElement = React.createElement(OpenCollection, { + this.root.render(React.createElement(OpenCollection, { collection, theme: this.options.theme || 'light', - logo: this.createLogoElement(), - customPages: this.options.customPages, - hideSidebar: this.options.hideSidebar, - hideHeader: this.options.hideHeader, - onlyShow: this.options.onlyShow - }); - - this.root.render(playgroundElement); + logo: this.createLogoElement() + })); } public updateCollection(opencollection: any) { diff --git a/packages/oc-core/src/test.ts b/packages/oc-core/src/test.ts deleted file mode 100644 index 15a8f938..00000000 --- a/packages/oc-core/src/test.ts +++ /dev/null @@ -1,1662 +0,0 @@ -export const test = { - "name": "bruno-testbench", - - "environments": [ - { - "name": "Local", - "description": null, - "variables": [ - { - "name": "host", - "value": "http://localhost:8080", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "httpfaker", - "value": "https://www.httpfaker.org", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "bearer_auth_token", - "value": "your_secret_token", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "basic_auth_password", - "value": "della", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "env.var1", - "value": "envVar1", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "env-var2", - "value": "envVar2", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "bark", - "value": "{{process.env.PROC_ENV_VAR}}", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "foo", - "value": "bar", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "testSetEnvVar", - "value": "bruno-29653", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "echo-host", - "value": "https://echo.usebruno.com", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "client_id", - "value": "client_id_1", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "client_secret", - "value": "client_secret_1", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "auth_url", - "value": "http://localhost:8080/api/auth/oauth2/authorization_code/authorize", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "callback_url", - "value": "http://localhost:8080/api/auth/oauth2/authorization_code/callback", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "access_token_url", - "value": "http://localhost:8080/api/auth/oauth2/authorization_code/token", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "passwordCredentials_username", - "value": "foo", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "passwordCredentials_password", - "value": "bar", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "github_authorize_url", - "value": "https://github.com/login/oauth/authorize", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "github_access_token_url", - "value": "https://github.com/login/oauth/access_token", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "google_auth_url", - "value": "https://accounts.google.com/o/oauth2/auth", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "google_access_token_url", - "value": "https://accounts.google.com/o/oauth2/token", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "google_scope", - "value": "https://www.googleapis.com/auth/userinfo.email", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "github_client_secret", - "value": "", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "github_client_id", - "value": "", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "google_client_id", - "value": "", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "google_client_secret", - "value": "", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "github_authorization_code", - "value": "", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "passwordCredentials_access_token", - "value": "", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "client_credentials_access_token", - "value": "", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "authorization_code_access_token", - "value": "", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "github_access_token", - "value": "", - "description": null, - "disabled": false, - "transient": null, - "default": null - } - ] - }, - { - "name": "Prod", - "description": null, - "variables": [ - { - "name": "host", - "value": "https://testbench-sanity.usebruno.com", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "httpfaker", - "value": "https://www.httpfaker.org", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "bearer_auth_token", - "value": "your_secret_token", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "basic_auth_password", - "value": "della", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "env.var1", - "value": "envVar1", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "env-var2", - "value": "envVar2", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "bark", - "value": "{{process.env.PROC_ENV_VAR}}", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "foo", - "value": "bar", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "testSetEnvVar", - "value": "bruno-29653", - "description": null, - "disabled": false, - "transient": null, - "default": null - }, - { - "name": "echo-host", - "value": "https://echo.usebruno.com", - "description": null, - "disabled": false, - "transient": null, - "default": null - } - ] - } - ], - "items": [ - { - "name": "ping", - "type": "http", - "url": "{{host}}/ping", - "method": "GET", - "params": [], - "headers": [], - "body": null, - "auth": null, - "scripts": { - "preRequest": "bru.runner.stopExecution();" - }, - "variables": [], - "assertions": [], - "docs": "" - }, - { - "type": "folder", - "name": "graphql", - "headers": [], - "auth": null, - "variables": [], - "scripts": {}, - "docs": null, - "items": [ - { - "name": "spacex", - "type": "graphql", - "url": "https://spacex-production.up.railway.app/", - "method": "POST", - "headers": [], - "params": [], - "body": { - "type": "json", - "data": "{\n \"company\": {\n \"ceo\": \"Elon Musk\"\n }\n}" - }, - "auth": null, - "scripts": {}, - "variables": [], - "assertions": [ - { - "expression": "res.status", - "operator": "eq", - "value": "200", - "enabled": true, - - } - ], - "docs": "" - } - ] - }, - { - "type": "folder", - "name": "echo", - "headers": [], - "auth": null, - "variables": [], - "scripts": {}, - "docs": null, - "items": [ - { - "name": "echo bom json", - "type": "http", - "url": "{{host}}/api/echo/bom-json-test", - "method": "GET", - "params": [], - "headers": [], - "body": null, - "auth": null, - "scripts": {}, - "variables": [], - "assertions": [], - "docs": "" - }, - { - "name": "echo form-url-encoded", - "type": "http", - "url": "{{echo-host}}", - "method": "POST", - "params": [], - "headers": [], - "body": [ - { - "name": "form-data-key", - "value": "{{form-data-key}}", - - "enabled": true - }, - { - "name": "form-data-stringified-object", - "value": "{{form-data-stringified-object}}", - - "enabled": true - } - ], - "auth": null, - "scripts": { - "preRequest": "let obj = JSON.stringify({foo:123});\nbru.setVar('form-data-key', 'form-data-value');\nbru.setVar('form-data-stringified-object', obj);" - }, - "variables": [], - "assertions": [ - { - "expression": "res.body", - "operator": "eq", - "value": "form-data-key=form-data-value&form-data-stringified-object=%7B%22foo%22%3A123%7D", - "enabled": true, - - } - ], - "docs": "" - }, - { - "name": "echo json", - "type": "http", - "url": "{{host}}/api/echo/json", - "method": "POST", - "params": [], - "headers": [ - { - "name": "foo", - "value": "bar", - - "disabled": null - } - ], - "body": { - "type": "json", - "data": "{\n \"hello\": \"bruno\"\n}" - }, - "auth": null, - "scripts": { - "preRequest": "bru.setVar(\"foo\", \"foo-world-2\");", - "tests": "test(\"should return json\", function() {\n const data = res.getBody();\n expect(res.getBody()).to.eql({\n \"hello\": \"bruno\"\n });\n}); " - }, - "variables": [], - "assertions": [ - { - "expression": "res.status", - "operator": "eq", - "value": "200", - "enabled": true, - - } - ], - "docs": "" - }, - { - "name": "echo multipart via scripting", - "type": "http", - "url": "{{echo-host}}", - "method": "POST", - "params": [], - "headers": [], - "body": null, - "auth": null, - "scripts": { - "preRequest": "const FormData = require(\"form-data\");\nconst form = new FormData();\nform.append('form-data-key', 'form-data-value');\nreq.setBody(form);" - }, - "variables": [], - "assertions": [ - { - "expression": "res.body", - "operator": "contains", - "value": "form-data-value", - "enabled": true, - - } - ], - "docs": "" - }, - { - "name": "echo multipart", - "type": "http", - "url": "{{echo-host}}", - "method": "POST", - "params": [], - "headers": [], - "body": [ - { - "name": "foo", - "type": "text", - "value": "{\"bar\":\"baz\"}", - - "enabled": true - }, - { - "name": "form-data-key", - "type": "text", - "value": "{{form-data-key}}", - - "enabled": true - }, - { - "name": "form-data-stringified-object", - "type": "text", - "value": "{{form-data-stringified-object}}", - - "enabled": true - }, - { - "name": "file", - "type": "file", - "value": [ - "bruno.png" - ], - - "enabled": true - } - ], - "auth": null, - "scripts": { - "preRequest": "let obj = JSON.stringify({foo:123});\nbru.setVar('form-data-key', 'form-data-value');\nbru.setVar('form-data-stringified-object', obj);" - }, - "variables": [], - "assertions": [ - { - "expression": "res.body", - "operator": "contains", - "value": "form-data-value", - "enabled": true, - - }, - { - "expression": "res.body", - "operator": "contains", - "value": "{\"foo\":123}", - "enabled": true, - - }, - { - "expression": "res.body", - "operator": "contains", - "value": "Content-Type: application/json--test", - "enabled": true, - - } - ], - "docs": "" - }, - { - "name": "echo numbers", - "type": "http", - "url": "{{echo-host}}", - "method": "POST", - "params": [], - "headers": [], - "body": { - "type": "json", - "data": "{\n \"integer\": 123,\n \"negativeInteger\": -99,\n \"zero\": 0,\n \"float\": 2.718,\n \"negativeFloat\": -1.618,\n \"largeDouble\": 12345.678901234567,\n \"smallDouble\": 9.876e-12,\n \"booleanTrue\": true,\n \"booleanFalse\": false\n}" - }, - "auth": null, - "scripts": {}, - "variables": [], - "assertions": [ - { - "expression": "res.body.integer", - "operator": "eq", - "value": "123", - "enabled": true, - - }, - { - "expression": "res.body.integer", - "operator": "isNumber", - "value": null, - "enabled": true, - - }, - { - "expression": "res.body.negativeInteger", - "operator": "eq", - "value": "-99", - "enabled": true, - - }, - { - "expression": "res.body.negativeInteger", - "operator": "isNumber", - "value": null, - "enabled": true, - - }, - { - "expression": "res.body.zero", - "operator": "eq", - "value": "0", - "enabled": true, - - }, - { - "expression": "res.body.zero", - "operator": "isNumber", - "value": null, - "enabled": true, - - }, - { - "expression": "res.body.float", - "operator": "eq", - "value": "2.718", - "enabled": true, - - }, - { - "expression": "res.body.float", - "operator": "isNumber", - "value": null, - "enabled": true, - - }, - { - "expression": "res.body.negativeFloat", - "operator": "eq", - "value": "-1.618", - "enabled": true, - - }, - { - "expression": "res.body.negativeFloat", - "operator": "isNumber", - "value": null, - "enabled": true, - - }, - { - "expression": "res.body.largeDouble", - "operator": "eq", - "value": "12345.678901234567", - "enabled": true, - - }, - { - "expression": "res.body.largeDouble", - "operator": "isNumber", - "value": null, - "enabled": true, - - }, - { - "expression": "res.body.smallDouble", - "operator": "eq", - "value": "9.876e-12", - "enabled": true, - - }, - { - "expression": "res.body.smallDouble", - "operator": "isNumber", - "value": null, - "enabled": true, - - }, - { - "expression": "res.body.booleanTrue", - "operator": "eq", - "value": "true", - "enabled": true, - - }, - { - "expression": "res.body.booleanFalse", - "operator": "eq", - "value": "false", - "enabled": true, - - } - ], - "docs": "" - }, - { - "name": "echo plaintext", - "type": "http", - "url": "{{host}}/api/echo/text", - "method": "POST", - "params": [], - "headers": [], - "body": { - "type": "text", - "data": "hello" - }, - "auth": null, - "scripts": { - "tests": "test(\"should return plain text\", function() {\n const data = res.getBody();\n expect(res.getBody()).to.eql(\"hello\");\n});\n" - }, - "variables": [], - "assertions": [ - { - "expression": "res.status", - "operator": "eq", - "value": "200", - "enabled": true, - - } - ], - "docs": "" - }, - { - "name": "echo xml parsed(self closing tags)", - "type": "http", - "url": "{{host}}/api/echo/xml-parsed", - "method": "POST", - "params": [], - "headers": [], - "body": { - "type": "xml", - "data": "\n bruno\n \n" - }, - "auth": null, - "scripts": { - "tests": "test(\"should return parsed xml\", function() {\n const data = res.getBody();\n expect(res.getBody()).to.eql({\n \"hello\": {\n \"world\": [\n \"bruno\",\n \"\"\n ]\n }\n });\n});\n" - }, - "variables": [], - "assertions": [ - { - "expression": "res.status", - "operator": "eq", - "value": "200", - "enabled": true, - - } - ], - "docs": "" - }, - { - "name": "echo xml parsed", - "type": "http", - "url": "{{host}}/api/echo/xml-parsed", - "method": "POST", - "params": [], - "headers": [], - "body": { - "type": "xml", - "data": "\n bruno\n" - }, - "auth": null, - "scripts": { - "tests": "test(\"should return parsed xml\", function() {\n const data = res.getBody();\n expect(res.getBody()).to.eql({\n \"hello\": {\n \"world\": [\"bruno\"]\n }\n });\n});\n" - }, - "variables": [], - "assertions": [ - { - "expression": "res.status", - "operator": "eq", - "value": "200", - "enabled": true, - - } - ], - "docs": "" - }, - { - "name": "echo xml raw", - "type": "http", - "url": "{{host}}/api/echo/xml-raw", - "method": "POST", - "params": [], - "headers": [], - "body": { - "type": "xml", - "data": "bruno" - }, - "auth": null, - "scripts": {}, - "variables": [], - "assertions": [], - "docs": "" - }, - { - "name": "test echo any", - "type": "http", - "url": "{{httpfaker}}/api/echo/custom", - "method": "POST", - "params": [], - "headers": [], - "body": { - "type": "json", - "data": "{\n \"headers\": { \"content-type\": \"text/plain\" },\n \"content\": \"hello\"\n}" - }, - "auth": null, - "scripts": {}, - "variables": [], - "assertions": [ - { - "expression": "res.body", - "operator": "eq", - "value": "hello", - "enabled": true, - - } - ], - "docs": "" - }, - { - "name": "test echo-any json", - "type": "http", - "url": "{{httpfaker}}/api/echo/custom", - "method": "POST", - "params": [], - "headers": [], - "body": { - "type": "json", - "data": "{\n \"type\": \"application/json\",\n \"contentJSON\": {\"x\": 42}\n}" - }, - "auth": null, - "scripts": {}, - "variables": [], - "assertions": [ - { - "expression": "res.body.x", - "operator": "eq", - "value": "42", - "enabled": true, - - } - ], - "docs": "" - }, - { - "type": "folder", - "name": "multiline", - "headers": [], - "auth": null, - "variables": [], - "scripts": {}, - "docs": null, - "items": [ - { - "name": "echo binary", - "type": "http", - "url": "{{echo-host}}", - "method": "POST", - "params": [], - "headers": [], - "body": [ - { - "filePath": "bruno.png", - "contentType": "image/png", - "selected": true - } - ], - "auth": null, - "scripts": {}, - "variables": [], - "assertions": [], - "docs": "" - } - ] - } - ] - }, - { - "type": "folder", - "name": "auth", - "headers": [], - "auth": null, - "variables": [], - "scripts": {}, - "docs": null, - "items": [ - { - "type": "folder", - "name": "inherit auth", - "headers": [], - "auth": null, - "variables": [], - "scripts": {}, - "docs": null, - "items": [ - { - "name": "inherit Bearer Auth 200", - "type": "http", - "url": "{{host}}/api/auth/bearer/protected", - "method": "GET", - "params": [], - "headers": [], - "body": null, - "auth": null, - "scripts": { - "postResponse": "bru.setEnvVar(\"foo\", \"bar\");" - }, - "variables": [], - "assertions": [ - { - "expression": "res.status", - "operator": "eq", - "value": "200", - "enabled": true, - - }, - { - "expression": "res.body.message", - "operator": "eq", - "value": "Authentication successful", - "enabled": true, - - } - ], - "docs": "" - } - ] - }, - { - "type": "folder", - "name": "basic", - "headers": [], - "auth": null, - "variables": [], - "scripts": {}, - "docs": null, - "items": [ - { - "type": "folder", - "name": "via auth", - "headers": [], - "auth": null, - "variables": [], - "scripts": {}, - "docs": null, - "items": [ - { - "name": "Basic Auth 200", - "type": "http", - "url": "{{host}}/api/auth/basic/protected", - "method": "POST", - "params": [], - "headers": [], - "body": null, - "auth": { - "type": "basic", - "username": "bruno", - "password": "{{basic_auth_password}}" - }, - "scripts": {}, - "variables": [], - "assertions": [ - { - "expression": "res.status", - "operator": "eq", - "value": "200", - "enabled": true, - - }, - { - "expression": "res.body.message", - "operator": "eq", - "value": "Authentication successful", - "enabled": true, - - } - ], - "docs": "" - }, - { - "name": "Basic Auth 400", - "type": "http", - "url": "{{host}}/api/auth/basic/protected", - "method": "POST", - "params": [], - "headers": [], - "body": null, - "auth": null, - "scripts": {}, - "variables": [], - "assertions": [ - { - "expression": "res.status", - "operator": "eq", - "value": "401", - "enabled": true, - - }, - { - "expression": "res.body", - "operator": "eq", - "value": "Unauthorized", - "enabled": true, - - } - ], - "docs": "" - } - ] - }, - { - "type": "folder", - "name": "via script", - "headers": [], - "auth": null, - "variables": [], - "scripts": {}, - "docs": null, - "items": [ - { - "name": "Basic Auth 401", - "type": "http", - "url": "{{host}}/api/auth/basic/protected", - "method": "POST", - "params": [], - "headers": [], - "body": null, - "auth": null, - "scripts": { - "preRequest": "const username = \"bruno\";\nconst password = \"invalid\";\n\nconst authString = `${username}:${password}`;\nconst encodedAuthString = require('btoa')(authString);\n\nreq.setHeader(\"Authorization\", `Basic ${encodedAuthString}`);" - }, - "variables": [], - "assertions": [ - { - "expression": "res.status", - "operator": "eq", - "value": "401", - "enabled": true, - - }, - { - "expression": "res.body", - "operator": "eq", - "value": "Unauthorized", - "enabled": true, - - } - ], - "docs": "" - }, - { - "name": "Basic Auth 200", - "type": "http", - "url": "{{host}}/api/auth/basic/protected", - "method": "POST", - "params": [], - "headers": [], - "body": null, - "auth": null, - "scripts": { - "preRequest": "const username = \"bruno\";\nconst password = \"della\";\n\nconst authString = `${username}:${password}`;\nconst encodedAuthString = require('btoa')(authString);\n\nreq.setHeader(\"Authorization\", `Basic ${encodedAuthString}`);" - }, - "variables": [], - "assertions": [ - { - "expression": "res.status", - "operator": "eq", - "value": "200", - "enabled": true, - - }, - { - "expression": "res.body.message", - "operator": "eq", - "value": "Authentication successful", - "enabled": true, - - } - ], - "docs": "" - } - ] - } - ] - }, - { - "type": "folder", - "name": "cookie", - "headers": [], - "auth": null, - "variables": [], - "scripts": {}, - "docs": null, - "items": [ - { - "name": "Check", - "type": "http", - "url": "{{host}}/api/auth/cookie/protected", - "method": "GET", - "params": [], - "headers": [], - "body": null, - "auth": null, - "scripts": {}, - "variables": [], - "assertions": [], - "docs": "" - }, - { - "name": "Login", - "type": "http", - "url": "{{host}}/api/auth/cookie/login", - "method": "POST", - "params": [], - "headers": [], - "body": null, - "auth": null, - "scripts": {}, - "variables": [], - "assertions": [], - "docs": "" - } - ] - }, - { - "type": "folder", - "name": "bearer", - "headers": [], - "auth": null, - "variables": [], - "scripts": {}, - "docs": null, - "items": [ - { - "type": "folder", - "name": "via headers", - "headers": [], - "auth": null, - "variables": [], - "scripts": {}, - "docs": null, - "items": [ - { - "name": "Bearer Auth 200", - "type": "http", - "url": "{{host}}/api/auth/bearer/protected", - "method": "GET", - "params": [], - "headers": [ - { - "name": "Authorization", - "value": "Bearer your_secret_token", - - "disabled": null - } - ], - "body": null, - "auth": null, - "scripts": { - "postResponse": "bru.setEnvVar(\"foo\", \"bar\");" - }, - "variables": [ - { - "name": "a-c", - "value": "foo", - - "disabled": false, - "transient": false - } - ], - "assertions": [ - { - "expression": "res.status", - "operator": "eq", - "value": "200", - "enabled": true, - - }, - { - "expression": "res.body.message", - "operator": "eq", - "value": "Authentication successful", - "enabled": true, - - } - ], - "docs": "" - } - ] - }, - { - "type": "folder", - "name": "via auth", - "headers": [], - "auth": null, - "variables": [], - "scripts": {}, - "docs": null, - "items": [ - { - "name": "Bearer Auth 200", - "type": "http", - "url": "{{host}}/api/auth/bearer/protected", - "method": "GET", - "params": [], - "headers": [], - "body": null, - "auth": { - "type": "bearer", - "token": "{{bearer_token}}" - }, - "scripts": {}, - "variables": [], - "assertions": [ - { - "expression": "res.status", - "operator": "eq", - "value": "200", - "enabled": true, - - }, - { - "expression": "res.body.message", - "operator": "eq", - "value": "Authentication successful", - "enabled": true, - - } - ], - "docs": "" - } - ] - } - ] - }, - { - "name": "oauth", - "type": "folder", - "headers": [], - "auth": null, - "variables": [], - "scripts": {}, - "docs": null, - "items": [ - { - "name": "authorization code grant", - "type": "http", - "url": "https://accounts.google.com/o/oauth2/v2/auth", - "method": "GET", - "params": [ - { - "name": "scope", - "value": "https://www.googleapis.com/auth/cloud-platform", - - "type": "query", - "enabled": true - }, - { - "name": "access_type", - "value": "offline", - - "type": "query", - "enabled": true - }, - { - "name": "include_granted_scopes", - "value": "true", - - "type": "query", - "enabled": true - }, - { - "name": "response_type", - "value": "code", - - "type": "query", - "enabled": true - }, - { - "name": "redirect_uri", - "value": "http://localhost:3000", - - "type": "query", - "enabled": true - }, - { - "name": "client_id", - "value": "{{client_id}}", - - "type": "query", - "enabled": true - } - ], - "headers": [], - "body": null, - "auth": null, - "scripts": {}, - "variables": [], - "assertions": [], - "docs": "" - }, - { - "name": "get access token", - "type": "http", - "url": "https://oauth2.googleapis.com/token", - "method": "POST", - "params": [], - "headers": [ - { - "name": "Content-Type", - "value": "application/x-www-form-urlencoded", - - "disabled": null - } - ], - "body": [ - { - "name": "code", - "value": "{{auth_code}}", - - "enabled": true - }, - { - "name": "client_id", - "value": "{{client_id}}", - - "enabled": true - }, - { - "name": "client_secret", - "value": "{{client_secret}}", - - "enabled": true - }, - { - "name": "redirect_uri", - "value": "http://localhost:3000", - - "enabled": true - }, - { - "name": "grant_type", - "value": "authorization_code", - - "enabled": true - } - ], - "auth": null, - "scripts": {}, - "variables": [], - "assertions": [], - "docs": "" - } - ] - }, - { - "type": "folder", - "name": "api key", - "headers": [], - "auth": null, - "variables": [], - "scripts": {}, - "docs": null, - "items": [ - { - "name": "api key in headers", - "type": "http", - "url": "{{host}}/api/auth/api-key", - "method": "GET", - "params": [], - "headers": [], - "body": null, - "auth": { - "type": "apikey", - "key": "x-api-key", - "value": "your_api_key_here", - "placement": "header" - }, - "scripts": {}, - "variables": [], - "assertions": [], - "docs": "" - }, - { - "name": "api key in query", - "type": "http", - "url": "{{host}}/api/auth/api-key", - "method": "GET", - "params": [], - "headers": [], - "body": null, - "auth": { - "type": "apikey", - "key": "x-api-key", - "value": "your_api_key_here", - "placement": "query" - }, - "scripts": {}, - "variables": [], - "assertions": [], - "docs": "" - } - ] - }, - { - "name": "digest", - "type": "folder", - "headers": [], - "auth": null, - "variables": [], - "scripts": {}, - "docs": null, - "items": [ - { - "name": "digest auth 200", - "type": "http", - "url": "{{host}}/api/auth/digest/protected", - "method": "POST", - "params": [], - "headers": [], - "body": null, - "auth": { - "type": "digest", - "username": "bruno", - "password": "della" - }, - "scripts": {}, - "variables": [], - "assertions": [ - { - "expression": "res.status", - "operator": "eq", - "value": "200", - "enabled": true, - - }, - { - "expression": "res.body.message", - "operator": "eq", - "value": "Authentication successful", - "enabled": true, - - } - ], - "docs": "" - } - ] - }, - { - "type": "folder", - "name": "ntlm", - "headers": [], - "auth": null, - "variables": [], - "scripts": {}, - "docs": null, - "items": [ - { - "name": "ntlm auth 200", - "type": "http", - "url": "{{host}}/api/auth/ntlm/protected", - "method": "POST", - "params": [], - "headers": [], - "body": null, - "auth": { - "type": "ntlm", - "username": "bruno", - "password": "della", - "domain": "acme" - }, - "scripts": {}, - "variables": [], - "assertions": [ - { - "expression": "res.status", - "operator": "eq", - "value": "200", - "enabled": true, - - }, - { - "expression": "res.body.message", - "operator": "eq", - "value": "Authentication successful", - "enabled": true, - - } - ], - "docs": "" - } - ] - }, - { - "type": "folder", - "name": "awsv4", - "headers": [], - "auth": null, - "variables": [], - "scripts": {}, - "docs": null, - "items": [ - { - "name": "awsv4 auth 200", - "type": "http", - "url": "{{host}}/api/auth/awsv4/protected", - "method": "GET", - "params": [], - "headers": [], - "body": null, - "auth": { - "type": "awsv4", - "accessKeyId": "AKIAIOSFODNN7EXAMPLE", - "secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "sessionToken": null, - "service": "execute-api", - "region": "us-east-1", - "profileName": null - }, - "scripts": {}, - "variables": [], - "assertions": [ - { - "expression": "res.status", - "operator": "eq", - "value": "200", - "enabled": true, - - }, - { - "expression": "res.body.message", - "operator": "eq", - "value": "Authentication successful", - "enabled": true, - - } - ], - "docs": "" - } - ] - } - ] - } - ], - "base": { - "headers": [], - "auth": null, - "variables": [ - { - "name": "host", - "value": "http://localhost:3001", - - "disabled": null, - "transient": null - }, - { - "name": "echo-host", - "value": "http://localhost:3002", - - "disabled": null, - "transient": null - }, - { - "name": "should-test-collection-scripts", - "value": null, - - "disabled": null, - "transient": true - }, - { - "name": "collection-var-set-by-collection-script", - "value": null, - - "disabled": null, - "transient": true - }, - { - "name": "httpfaker", - "value": "http://localhost:3003", - - "disabled": null, - "transient": null - }, - { - "name": "auth_code", - "value": null, - - "disabled": null, - "transient": true - }, - { - "name": "access_token", - "value": null, - - "disabled": null, - "transient": true - }, - { - "name": "refresh_token", - "value": null, - - "disabled": null, - "transient": true - }, - { - "name": "client_id", - "value": "your_client_id_here", - - "disabled": null, - "transient": null - }, - { - "name": "client_secret", - "value": "your_client_secret_here", - - "disabled": null, - "transient": null - }, - { - "name": "basic_auth_password", - "value": "della", - - "disabled": null, - "transient": null - }, - { - "name": "bearer_token", - "value": "this-is-my-secret-token", - - "disabled": null, - "transient": null - } - ], - "scripts": { - "preRequest": "const varName = 'collection-var-set-by-collection-script';\nbru.setVar(varName, `collection-var-value-set-by-collection-script`);", - "postResponse": "const varName = bru.getVar(\"collection-var-set-by-collection-script\");\nconsole.log(varName);\n", - "tests": "// check if collection scripts work\nconst shouldTestCollectionScripts = bru.getVar('should-test-collection-scripts');\nconst collectionVar = bru.getVar(\"collection-var-set-by-collection-script\");\nif (shouldTestCollectionScripts && collectionVar) {\n test(\"collection level test - should get the var that was set by the collection script\", function() {\n expect(collectionVar).to.equal(\"collection-var-value-set-by-collection-script\");\n }); \n bru.setVar('collection-var-set-by-collection-script', null); \n bru.setVar('should-test-collection-scripts', null);\n}", - "hooks": null - } - }, - "docs": "# bruno-testbench 🐶\n\nThis is a test collection that I am using to test various functionalities around bruno" - } as any \ No newline at end of file diff --git a/packages/oc-core/src/types/component-types.ts b/packages/oc-core/src/types/component-types.ts index c24ee384..41e7bca0 100644 --- a/packages/oc-core/src/types/component-types.ts +++ b/packages/oc-core/src/types/component-types.ts @@ -1,15 +1,6 @@ import React from 'react'; import type { OpenCollection } from '@opencollection/types'; -/** - * Custom page configuration (oc-core specific, not part of OpenCollection schema) - */ -export interface CustomPage { - name: string; - content?: string; - contentPath?: string; -} - /** * OpenCollection React component props */ @@ -17,10 +8,6 @@ export interface OpenCollectionProps { collection: OpenCollection | string | File; theme?: 'light' | 'dark' | 'auto'; logo?: React.ReactNode; - hideSidebar?: boolean; hideHeader?: boolean; - onlyShow?: string[]; - proxyUrl?: string; - customPages?: CustomPage[]; } diff --git a/packages/oc-core/src/ui/AllEndpointsView.tsx b/packages/oc-core/src/ui/AllEndpointsView.tsx index 558d3922..60b745c7 100644 --- a/packages/oc-core/src/ui/AllEndpointsView.tsx +++ b/packages/oc-core/src/ui/AllEndpointsView.tsx @@ -1,29 +1,26 @@ import React, { useMemo } from 'react'; import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; -import type { Item as OpenCollectionItem, HttpRequest, Folder } from '@opencollection/types'; import Item from '../components/Docs/Item/Item'; import { getItemId, generateSafeId } from '../utils/itemUtils'; interface AllEndpointsViewProps { collection: OpenCollectionCollection | null; - filteredCollectionItems: OpenCollectionItem[]; + collectionItems: any[]; theme: 'light' | 'dark' | 'auto'; - md: any; selectedItemId: string | null; onItemSelect?: (id: string, openPlayground?: boolean) => void; } const AllEndpointsView: React.FC = ({ collection, - filteredCollectionItems, + collectionItems, theme, - md, selectedItemId, onItemSelect }) => { // Flatten all items recursively for rendering - const flattenItems = (items: OpenCollectionItem[], parentPath = ''): OpenCollectionItem[] => { - const result: OpenCollectionItem[] = []; + const flattenItems = (items: any[], parentPath = ''): any[] => { + const result: any[] = []; for (const item of items) { const itemId = getItemId(item); @@ -34,7 +31,7 @@ const AllEndpointsView: React.FC = ({ // If it's a folder, recursively add its children if (item.type === 'folder') { - const folder = item as Folder; + const folder = item as any; if (folder.items && folder.items.length > 0) { result.push(...flattenItems(folder.items, itemPath)); } @@ -45,9 +42,9 @@ const AllEndpointsView: React.FC = ({ }; const allItems = useMemo(() => { - const items = flattenItems(filteredCollectionItems); + const items = flattenItems(collectionItems); return items; - }, [filteredCollectionItems]); + }, [collectionItems]); const registerSectionRef = (id: string, ref: HTMLDivElement | null) => { // No-op for now, but can be used for scroll tracking @@ -72,7 +69,6 @@ const AllEndpointsView: React.FC = ({ item={item} registerSectionRef={registerSectionRef} theme={theme} - md={md} parentPath="" collection={collection || undefined} onTryClick={() => { diff --git a/packages/oc-core/src/ui/BodyCard.tsx b/packages/oc-core/src/ui/BodyCard.tsx deleted file mode 100644 index 79e216fd..00000000 --- a/packages/oc-core/src/ui/BodyCard.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React, { useEffect, useRef } from 'react'; -import Prism from 'prismjs'; -import 'prismjs/components/prism-json'; -import 'prismjs/components/prism-xml-doc'; -import 'prismjs/components/prism-graphql'; - -interface BodyCardProps { - children: React.ReactNode; - className?: string; - language?: string; -} - -const BodyCard: React.FC = ({ - children, - className = '', - language = 'text' -}) => { - const codeRef = useRef(null); - - useEffect(() => { - if (codeRef.current) { - Prism.highlightAllUnder(codeRef.current); - } - }, [children, language]); - - return ( -
- -
- -
-
-            
-              {children}
-            
-          
-
-
-
- ); -}; - -export default BodyCard; \ No newline at end of file diff --git a/packages/oc-core/src/ui/Card.tsx b/packages/oc-core/src/ui/Card.tsx deleted file mode 100644 index a4a9c73e..00000000 --- a/packages/oc-core/src/ui/Card.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React from 'react'; - -interface CardProps { - title?: string; - subtitle?: string; - badge?: { - text: string; - variant?: 'primary' | 'secondary' | 'success' | 'warning' | 'error'; - }; - children: React.ReactNode; - className?: string; - headerActions?: React.ReactNode; -} - -const Card: React.FC = ({ - title, - subtitle, - badge, - children, - className = '', - headerActions -}) => { - const getBadgeStyles = (variant: string = 'primary') => { - const baseStyles = { - display: 'inline-flex', - alignItems: 'center', - padding: '4px 8px', - borderRadius: '6px', - fontSize: '12px', - fontWeight: '500', - textTransform: 'uppercase' as const, - letterSpacing: '0.025em' - }; - - const variantStyles = { - primary: { - backgroundColor: 'var(--primary-color, #3b82f6)', - color: 'white' - }, - secondary: { - backgroundColor: 'var(--secondary-color, #6b7280)', - color: 'white' - }, - success: { - backgroundColor: 'var(--success-color, #10b981)', - color: 'white' - }, - warning: { - backgroundColor: 'var(--warning-color, #f59e0b)', - color: 'white' - }, - error: { - backgroundColor: 'var(--error-color, #ef4444)', - color: 'white' - } - }; - - return { ...baseStyles, ...variantStyles[variant as keyof typeof variantStyles] }; - }; - - return ( -
- {(title || subtitle || badge || headerActions) && ( -
-
- {title && ( -

- {title} -

- )} - - {badge && ( - - {badge.text} - - )} - - {subtitle && ( - - {subtitle} - - )} -
- - {headerActions && ( -
- {headerActions} -
- )} -
- )} - -
- {children} -
-
- ); -}; - -export default Card; \ No newline at end of file diff --git a/packages/oc-core/src/ui/CodeBlock.tsx b/packages/oc-core/src/ui/CodeBlock.tsx deleted file mode 100644 index f0e2b342..00000000 --- a/packages/oc-core/src/ui/CodeBlock.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import React from 'react'; - -interface CodeBlockProps { - code: string; - language?: string; - title?: string; - badge?: { - text: string; - variant?: 'primary' | 'secondary' | 'success' | 'warning' | 'error'; - }; - maxHeight?: string; - className?: string; - showLineNumbers?: boolean; -} - -const CodeBlock: React.FC = ({ - code, - language = 'text', - title, - badge, - maxHeight = '400px', - className = '', - showLineNumbers = false -}) => { - const getBadgeStyles = (variant: string = 'primary') => { - const baseStyles = { - display: 'inline-flex', - alignItems: 'center', - padding: '4px 8px', - borderRadius: '6px', - fontSize: '12px', - fontWeight: '500', - textTransform: 'uppercase' as const, - letterSpacing: '0.025em' - }; - - const variantStyles = { - primary: { - backgroundColor: 'var(--primary-color, #3b82f6)', - color: 'white' - }, - secondary: { - backgroundColor: 'var(--secondary-color, #6b7280)', - color: 'white' - }, - success: { - backgroundColor: 'var(--success-color, #10b981)', - color: 'white' - }, - warning: { - backgroundColor: 'var(--warning-color, #f59e0b)', - color: 'white' - }, - error: { - backgroundColor: 'var(--error-color, #ef4444)', - color: 'white' - } - }; - - return { ...baseStyles, ...variantStyles[variant as keyof typeof variantStyles] }; - }; - - const lines = code.split('\n'); - - return ( -
- {(title || badge) && ( -
- {title && ( - {title} - )} - - {badge && ( - - {badge.text} - - )} -
- )} - -
-
-          {showLineNumbers ? (
-            
-              
-                {lines.map((line, index) => (
-                  
-                    
-                    
-                  
-                ))}
-              
-            
- {index + 1} - - - {line || ' '} - -
- ) : ( - - {code} - - )} -
-
-
- ); -}; - -export default CodeBlock; \ No newline at end of file diff --git a/packages/oc-core/src/ui/ExpandableFormTable.tsx b/packages/oc-core/src/ui/ExpandableFormTable.tsx deleted file mode 100644 index b9779a1e..00000000 --- a/packages/oc-core/src/ui/ExpandableFormTable.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import React, { useState } from 'react'; - -interface FormItem { - name: string; - value: string; - enabled?: boolean; - description?: string; - uid?: string; -} - -interface ExpandableFormTableProps { - items: FormItem[]; - title: string; - icon: React.ReactNode; - theme: 'light' | 'dark' | 'auto'; - maxVisibleRows?: number; -} - -const ExpandableFormTable: React.FC = ({ - items, - title, - icon, - theme, - maxVisibleRows = 10 -}) => { - const [isExpanded, setIsExpanded] = useState(false); - const hasLotsOfItems = items.length > maxVisibleRows; - - const toggleExpansion = () => { - setIsExpanded(prev => !prev); - }; - - return ( -
-
- {icon} - {title} -
-
-
- - - - - - - - - - {items.map((item, index) => ( - - - - - - ))} - -
NameValueStatus
{item.name}{item.value} - - {item.enabled !== false ? 'Enabled' : 'Disabled'} - -
-
- - - {hasLotsOfItems && !isExpanded && ( -
-
-
- -
-
- )} - - - {hasLotsOfItems && isExpanded && ( -
- -
- )} -
-
- ); -}; - -export default ExpandableFormTable; \ No newline at end of file diff --git a/packages/oc-core/src/ui/SearchModal.tsx b/packages/oc-core/src/ui/SearchModal.tsx deleted file mode 100644 index aaa8b04c..00000000 --- a/packages/oc-core/src/ui/SearchModal.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import React, { useEffect } from 'react'; - -// Search Modal Component -const SearchModal = ({ - isSearchOpen, - setIsSearchOpen, - searchQuery, - setSearchQuery, - searchResults, - searchInputRef, - handleSearchResultSelect -}: { - isSearchOpen: boolean; - setIsSearchOpen: (open: boolean) => void; - searchQuery: string; - setSearchQuery: (query: string) => void; - searchResults: Array<{id: string; name: string; path?: string; type: string}>; - searchInputRef: React.RefObject; - handleSearchResultSelect: (result: {id: string; path?: string; type: string}) => void; -}) => { - if (!isSearchOpen) return null; - - // Use useEffect to maintain focus on the input when typing - useEffect(() => { - if (isSearchOpen && searchInputRef.current) { - searchInputRef.current.focus(); - } - }, [isSearchOpen, searchQuery, searchInputRef]); - - return ( -
setIsSearchOpen(false)} - > -
e.stopPropagation()} - > - -
-
- -
- setSearchQuery(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter' && searchResults.length > 0) { - handleSearchResultSelect(searchResults[0]); - } - }} - /> -
- ESC -
-
- - -
- {searchResults.length > 0 ? ( -
    - {searchResults.map((result, index) => ( -
  • handleSearchResultSelect(result)} - > - - - {result.type === 'folder' ? ( - - - - ) : result.type === 'page' ? ( - - - - ) : result.type === 'script' ? ( - - - - - - - - ) : ( - - - - )} - - - -
    -
    {result.name}
    - {result.path && result.path !== result.name && ( -
    {result.path}
    - )} -
    -
  • - ))} -
- ) : searchQuery ? ( -
- No results found for "{searchQuery}" -
- ) : ( -
- Type to search for endpoints, folders, scripts, and pages -
- )} -
-
-
- ); -}; - -export default SearchModal; \ No newline at end of file diff --git a/packages/oc-core/src/ui/index.ts b/packages/oc-core/src/ui/index.ts index bbba8587..e39bf1dc 100644 --- a/packages/oc-core/src/ui/index.ts +++ b/packages/oc-core/src/ui/index.ts @@ -1,7 +1,4 @@ -export { default as Card } from './Card'; export { default as DataTable } from './DataTable'; -export { default as CodeBlock } from './CodeBlock'; -export { default as BodyCard } from './BodyCard'; export { default as CodeEditor } from './CodeEditor'; export * from './TableHelpers'; export * from './request-runner'; \ No newline at end of file diff --git a/packages/oc-core/src/ui/request-runner/RequestRunner.tsx b/packages/oc-core/src/ui/request-runner/RequestRunner.tsx index 9ce022c5..44478ef8 100644 --- a/packages/oc-core/src/ui/request-runner/RequestRunner.tsx +++ b/packages/oc-core/src/ui/request-runner/RequestRunner.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useMemo } from 'react'; import type { HttpRequest } from '@opencollection/types/requests/http'; import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; -import { requestRunner, createRequestRunner } from '../../runner'; +import { requestRunner } from '../../runner'; import RequestHeader from './RequestHeader'; import QueryBar from '../../components/Playground/QueryBar/QueryBar'; import RequestPane from './RequestPane'; @@ -10,27 +10,17 @@ import ResponsePane from './ResponsePane'; interface RequestRunnerProps { item: HttpRequest; collection: OpenCollectionCollection; - proxyUrl?: string; toggleRunnerMode?: () => void; } -const globalRunners = new Map(); - -const RequestRunner: React.FC = ({ item, collection, proxyUrl, toggleRunnerMode }) => { +const RequestRunner: React.FC = ({ item, collection, toggleRunnerMode }) => { const [editableItem, setEditableItem] = useState(item); const [selectedEnvironment, setSelectedEnvironment] = useState(''); const [response, setResponse] = useState(null); const [isLoading, setIsLoading] = useState(false); const [requestPaneWidth, setRequestPaneWidth] = useState(50); const [isDragging, setIsDragging] = useState(false); - - const runner = useMemo(() => { - const key = proxyUrl || 'default'; - if (!globalRunners.has(key)) { - globalRunners.set(key, proxyUrl ? createRequestRunner(proxyUrl) : requestRunner); - } - return globalRunners.get(key); - }, [proxyUrl]); + const runner = useMemo(() => requestRunner, []); useEffect(() => { setEditableItem(item); @@ -40,9 +30,9 @@ const RequestRunner: React.FC = ({ item, collection, proxyUr const handleSendRequest = async () => { setIsLoading(true); try { - const environment = collection.environments?.find(env => env.name === selectedEnvironment); - const runner = createRequestRunner(proxyUrl); - + const environment = (collection as any).environments?.find( + (env: any) => env.name === selectedEnvironment + ); const result = await runner.runRequest({ item: editableItem, collection, diff --git a/packages/oc-core/tsconfig.json b/packages/oc-core/tsconfig.json index 97d38557..1e6cc224 100644 --- a/packages/oc-core/tsconfig.json +++ b/packages/oc-core/tsconfig.json @@ -8,7 +8,7 @@ "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "noEmit": false, diff --git a/packages/oc-core/vite.config.ts b/packages/oc-core/vite.config.ts deleted file mode 100644 index 0ab7495f..00000000 --- a/packages/oc-core/vite.config.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import { resolve } from 'path' -import { fileURLToPath } from 'node:url' -import tailwindcss from '@tailwindcss/vite' - -const __dirname = fileURLToPath(new URL('.', import.meta.url)) - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - react(), - tailwindcss() - ], - // Add server configuration for WASM files - server: { - fs: { - allow: ['../..'] - }, - headers: { - 'Cross-Origin-Embedder-Policy': 'require-corp', - 'Cross-Origin-Opener-Policy': 'same-origin', - } - }, - // Configure asset handling for WASM files - assetsInclude: ['**/*.wasm'], - // Add optimizeDeps configuration - optimizeDeps: { - exclude: ['quickjs-emscripten'] - }, - build: { - lib: { - entry: { - index: resolve(__dirname, 'src/index.ts'), - express: resolve(__dirname, 'src/express.ts'), - server: resolve(__dirname, 'src/server.ts') - }, - name: 'OpenCollectionPlayground', - fileName: (format, entryName) => { - if (entryName === 'index') { - return `oc-playground.${format}.js`; - } - return `${entryName}.${format === 'es' ? 'js' : 'cjs'}`; - }, - }, - rollupOptions: { - // externalize deps that shouldn't be bundled - external: ['react', 'react-dom', 'express'], - output: { - globals: { - react: 'React', - 'react-dom': 'ReactDOM', - express: 'express', - }, - }, - }, - }, -}) From 9efa292d95a4fd23f214e0563bcaa45c80b55f0e Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 17 Nov 2025 00:57:05 +0530 Subject: [PATCH 034/161] feat: integrate Redux for state management --- package-lock.json | 100 ++++++++++++++++ packages/oc-core/package.json | 2 + .../OpenCollection/OpenCollection.tsx | 48 +++++++- packages/oc-core/src/hooks/index.ts | 1 - .../oc-core/src/hooks/useCollectionData.ts | 110 ------------------ packages/oc-core/src/store/hooks.ts | 6 + .../oc-core/src/store/opencollectionSlice.ts | 105 +++++++++++++++++ packages/oc-core/src/store/store.ts | 14 +++ packages/oc-core/src/types/component-types.ts | 13 --- 9 files changed, 271 insertions(+), 128 deletions(-) delete mode 100644 packages/oc-core/src/hooks/useCollectionData.ts create mode 100644 packages/oc-core/src/store/hooks.ts create mode 100644 packages/oc-core/src/store/opencollectionSlice.ts create mode 100644 packages/oc-core/src/store/store.ts delete mode 100644 packages/oc-core/src/types/component-types.ts diff --git a/package-lock.json b/package-lock.json index 18666650..01840f37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1322,6 +1322,32 @@ "react-dom": ">=17" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.10.1.tgz", + "integrity": "sha512-/U17EXQ9Do9Yx4DlNGU6eVNfZvFJfYpUtRRdLf19PbPjdWBxNlxGZXywQZ1p1Nz8nMkWplTI7iD/23m07nolDA==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^10.2.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -1602,6 +1628,18 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@tailwindcss/node": { "version": "4.1.13", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.13.tgz", @@ -2203,6 +2241,12 @@ "integrity": "sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==", "license": "MIT" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", @@ -3251,6 +3295,16 @@ "readable-stream": "1.1" } }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -4105,6 +4159,29 @@ "license": "MIT", "peer": true }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -4145,6 +4222,21 @@ "string_decoder": "~0.10.x" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -4154,6 +4246,12 @@ "node": ">=0.10.0" } }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -5826,6 +5924,7 @@ "@monaco-editor/react": "^4.7.0", "@opencollection/types": "0.0.1", "@prantlf/jsonlint": "^16.0.0", + "@reduxjs/toolkit": "^2.10.1", "@tailwindcss/vite": "^4.0.9", "@types/js-yaml": "^4.0.9", "@types/markdown-it": "^14.1.2", @@ -5842,6 +5941,7 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", "react-markdown": "^10.0.0", + "react-redux": "^9.2.0", "react-router-dom": "^7.3.0", "remark-gfm": "^4.0.1", "strip-json-comments": "^3.1.1" diff --git a/packages/oc-core/package.json b/packages/oc-core/package.json index 86649eb8..0e7de65c 100644 --- a/packages/oc-core/package.json +++ b/packages/oc-core/package.json @@ -55,6 +55,7 @@ "@emotion/styled": "^11.14.1", "@monaco-editor/react": "^4.7.0", "@opencollection/types": "0.0.1", + "@reduxjs/toolkit": "^2.10.1", "@prantlf/jsonlint": "^16.0.0", "@tailwindcss/vite": "^4.0.9", "@types/js-yaml": "^4.0.9", @@ -71,6 +72,7 @@ "quickjs-emscripten": "~0.31.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-redux": "^9.2.0", "react-markdown": "^10.0.0", "react-router-dom": "^7.3.0", "remark-gfm": "^4.0.1", diff --git a/packages/oc-core/src/components/OpenCollection/OpenCollection.tsx b/packages/oc-core/src/components/OpenCollection/OpenCollection.tsx index 330eebf7..b17e1413 100644 --- a/packages/oc-core/src/components/OpenCollection/OpenCollection.tsx +++ b/packages/oc-core/src/components/OpenCollection/OpenCollection.tsx @@ -1,17 +1,25 @@ import React, { useRef, useState, useEffect, useMemo } from 'react'; +import { Provider } from 'react-redux'; import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; import type { Item as OpenCollectionItem } from '@opencollection/types/collection/item'; import type { HttpRequest } from '@opencollection/types/requests/http'; import { - useCollectionData, useTheme, useRunnerMode } from '../../hooks'; -import type { OpenCollectionProps } from '../../types/component-types'; +import type { OpenCollection as IOpenCollection } from '@opencollection/types'; import Sidebar from '../Sidebar/Sidebar'; import AllEndpointsView from '../../ui/AllEndpointsView'; import PlaygroundDrawer from '../Playground/PlaygroudDrawer/PlaygroundDrawer'; import { getItemId, generateSafeId } from '../../utils/itemUtils'; +import { useAppDispatch, useAppSelector } from '../../store/hooks'; +import { + loadCollection, + selectCollectionData, + selectCollectionError, + selectCollectionStatus +} from '../../store/opencollectionSlice'; +import { createOpenCollectionStore, type AppStore } from '../../store/store'; interface DesktopLayoutProps { collectionData: OpenCollectionCollection | null; @@ -177,22 +185,40 @@ const DesktopLayout: React.FC = ({ ); }; -const OpenCollection: React.FC = ({ +/** + * OpenCollection React component props + */ +export interface OpenCollectionProps { + collection: IOpenCollection | string | File; + theme?: 'light' | 'dark' | 'auto'; + logo?: React.ReactNode; +} + +const OpenCollectionContent: React.FC = ({ collection, theme = 'light', logo, }) => { + const dispatch = useAppDispatch(); + const collectionData = useAppSelector(selectCollectionData); + const collectionStatus = useAppSelector(selectCollectionStatus); + const error = useAppSelector(selectCollectionError); const containerRef = useRef(null); - const { collectionData, isLoading, error } = useCollectionData(collection); useTheme(theme); + useEffect(() => { + dispatch(loadCollection(collection)); + }, [collection, dispatch]); + const filteredCollectionItems = collectionData?.items || []; // Page selection state const [currentPageId, setCurrentPageId] = useState(null); const [currentPageItem, setCurrentPageItem] = useState(null); + const isLoading = collectionStatus === 'loading' || (collectionStatus === 'idle' && !collectionData); + // Handle item selection const handleSelectItem = (id: string, path?: string) => { console.log('Selecting item:', id, 'path:', path); @@ -270,4 +296,18 @@ const OpenCollection: React.FC = ({ ); }; +const OpenCollection: React.FC = (props) => { + const storeRef = useRef(null); + + if (!storeRef.current) { + storeRef.current = createOpenCollectionStore(); + } + + return ( + + + + ); +}; + export default OpenCollection; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/index.ts b/packages/oc-core/src/hooks/index.ts index 4b73d492..4fdadef4 100644 --- a/packages/oc-core/src/hooks/index.ts +++ b/packages/oc-core/src/hooks/index.ts @@ -1,4 +1,3 @@ -export { useCollectionData } from './useCollectionData'; export { useTheme } from './useTheme'; export { useMarkdownRenderer } from './useMarkdownRenderer'; export { useRunnerMode } from './useRunnerMode'; \ No newline at end of file diff --git a/packages/oc-core/src/hooks/useCollectionData.ts b/packages/oc-core/src/hooks/useCollectionData.ts deleted file mode 100644 index 3cbb39d7..00000000 --- a/packages/oc-core/src/hooks/useCollectionData.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { useEffect, useState } from 'react'; -import { isYamlFile, parseYaml } from '../utils/yamlUtils'; -import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; - -const loadOpenCollectionData = async (source: string | File): Promise => { - let content: string; - - if (source instanceof File) { - content = await source.text(); - } else if (typeof source === 'string') { - if (source.startsWith('http://') || source.startsWith('https://')) { - const response = await fetch(source); - if (!response.ok) { - throw new Error(`Failed to fetch collection: ${response.statusText}`); - } - content = await response.text(); - } else { - content = source; - } - } else { - throw new Error('Invalid source type for collection'); - } - - try { - return parseYaml(content) as OpenCollectionCollection; - } catch (yamlError) { - try { - return JSON.parse(content) as OpenCollectionCollection; - } catch (jsonError) { - throw new Error('Failed to parse collection as YAML or JSON'); - } - } -}; - -interface UseCollectionDataReturn { - collectionData: OpenCollectionCollection | null; - isLoading: boolean; - error: string | null; -} - -export const useCollectionData = (collection: OpenCollectionCollection | string | File): UseCollectionDataReturn => { - const [collectionData, setCollectionData] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - const loadCollection = async () => { - try { - setIsLoading(true); - - if (collection instanceof File) { - // Handle File objects - const fileName = collection.name.toLowerCase(); - - let data; - if (isYamlFile(fileName)) { - data = await loadOpenCollectionData(collection); - } else { - // Assume JSON - const content = await collection.text(); - data = JSON.parse(content); - } - - setCollectionData(data as OpenCollectionCollection); - } else if (typeof collection === 'string') { - // Check if it's a URL or raw content - if (collection.startsWith('http://') || collection.startsWith('https://')) { - // Handle URLs - const response = await fetch(collection); - if (!response.ok) { - throw new Error(`Failed to load API collection: ${response.statusText}`); - } - - // Check if the URL or content suggests YAML format - const contentType = response.headers.get('content-type') || ''; - const isYamlUrl = isYamlFile(collection); - const isYamlContentType = contentType.includes('yaml') || contentType.includes('yml'); - - let data; - if (isYamlUrl || isYamlContentType) { - // Handle YAML content - const yamlText = await response.text(); - data = await loadOpenCollectionData(yamlText); - } else { - // Default to JSON - data = await response.json(); - } - - setCollectionData(data as OpenCollectionCollection); - } else { - // Handle raw string content (JSON or YAML) - const data = await loadOpenCollectionData(collection); - setCollectionData(data as OpenCollectionCollection); - } - } else { - // Handle collection objects - setCollectionData(collection as OpenCollectionCollection); - } - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load API collection'); - } finally { - setIsLoading(false); - } - }; - - loadCollection(); - }, [collection]); - - return { collectionData, isLoading, error }; -}; \ No newline at end of file diff --git a/packages/oc-core/src/store/hooks.ts b/packages/oc-core/src/store/hooks.ts new file mode 100644 index 00000000..cee42108 --- /dev/null +++ b/packages/oc-core/src/store/hooks.ts @@ -0,0 +1,6 @@ +import { useDispatch, useSelector, type TypedUseSelectorHook } from 'react-redux'; +import type { AppDispatch, RootState } from './store'; + +export const useAppDispatch = () => useDispatch(); +export const useAppSelector: TypedUseSelectorHook = useSelector; + diff --git a/packages/oc-core/src/store/opencollectionSlice.ts b/packages/oc-core/src/store/opencollectionSlice.ts new file mode 100644 index 00000000..ec93daa2 --- /dev/null +++ b/packages/oc-core/src/store/opencollectionSlice.ts @@ -0,0 +1,105 @@ +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; +import type { ActionReducerMapBuilder, PayloadAction } from '@reduxjs/toolkit'; +import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; +import { parseYaml } from '../utils/yamlUtils'; +import type { RootState } from './store'; + +export type CollectionSource = OpenCollectionCollection | string | File; + +interface OpenCollectionState { + data: OpenCollectionCollection | null; + status: 'idle' | 'loading' | 'succeeded' | 'failed'; + error: string | null; +} + +const initialState: OpenCollectionState = { + data: null, + status: 'idle', + error: null, +}; + +const loadOpenCollectionData = async (source: string | File): Promise => { + let content: string; + + if (typeof File !== 'undefined' && source instanceof File) { + content = await source.text(); + } else if (typeof source === 'string') { + if (source.startsWith('http://') || source.startsWith('https://')) { + const response = await fetch(source); + if (!response.ok) { + throw new Error(`Failed to fetch collection: ${response.statusText}`); + } + content = await response.text(); + } else { + content = source; + } + } else { + throw new Error('Invalid source type for collection'); + } + + try { + return parseYaml(content) as OpenCollectionCollection; + } catch (yamlError) { + try { + return JSON.parse(content) as OpenCollectionCollection; + } catch (jsonError) { + throw new Error('Failed to parse collection as YAML or JSON'); + } + } +}; + +export const loadCollection = createAsyncThunk( + 'opencollection/loadCollection', + async (collection: CollectionSource) => { + if (typeof File !== 'undefined' && collection instanceof File) { + return loadOpenCollectionData(collection); + } + + if (typeof collection === 'string') { + return loadOpenCollectionData(collection); + } + + return collection; + } +); + +const opencollectionSlice = createSlice({ + name: 'opencollection', + initialState, + reducers: { + setCollection: (state: OpenCollectionState, action: PayloadAction) => { + state.data = action.payload; + state.status = action.payload ? 'succeeded' : 'idle'; + state.error = null; + }, + clearCollection: (state: OpenCollectionState) => { + state.data = null; + state.status = 'idle'; + state.error = null; + }, + }, + extraReducers: (builder: ActionReducerMapBuilder) => { + builder + .addCase(loadCollection.pending, (state: OpenCollectionState) => { + state.status = 'loading'; + state.error = null; + }) + .addCase(loadCollection.fulfilled, (state: OpenCollectionState, action: PayloadAction) => { + state.data = action.payload; + state.status = 'succeeded'; + state.error = null; + }) + .addCase(loadCollection.rejected, (state: OpenCollectionState, action: ReturnType) => { + state.status = 'failed'; + state.error = action.error.message || 'Failed to load API collection'; + }); + }, +}); + +export const { setCollection, clearCollection } = opencollectionSlice.actions; +export default opencollectionSlice.reducer; + +export const selectCollectionData = (state: RootState) => state.opencollection.data; +export const selectCollectionStatus = (state: RootState) => state.opencollection.status; +export const selectCollectionError = (state: RootState) => state.opencollection.error; + diff --git a/packages/oc-core/src/store/store.ts b/packages/oc-core/src/store/store.ts new file mode 100644 index 00000000..b6a4eea0 --- /dev/null +++ b/packages/oc-core/src/store/store.ts @@ -0,0 +1,14 @@ +import { configureStore } from '@reduxjs/toolkit'; +import opencollectionReducer from './opencollectionSlice'; + +export const createOpenCollectionStore = () => + configureStore({ + reducer: { + opencollection: opencollectionReducer, + }, + }); + +export type AppStore = ReturnType; +export type AppDispatch = AppStore['dispatch']; +export type RootState = ReturnType; + diff --git a/packages/oc-core/src/types/component-types.ts b/packages/oc-core/src/types/component-types.ts deleted file mode 100644 index 41e7bca0..00000000 --- a/packages/oc-core/src/types/component-types.ts +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import type { OpenCollection } from '@opencollection/types'; - -/** - * OpenCollection React component props - */ -export interface OpenCollectionProps { - collection: OpenCollection | string | File; - theme?: 'light' | 'dark' | 'auto'; - logo?: React.ReactNode; - hideHeader?: boolean; -} - From f77bfba820574a55b165474b91786b877d117f17 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 17 Nov 2025 03:24:31 +0530 Subject: [PATCH 035/161] feat: refactor state management by introducing new slices for docs and playground --- .../OpenCollection/OpenCollection.tsx | 155 ++++++-- .../oc-core/src/store/opencollectionSlice.ts | 105 ------ packages/oc-core/src/store/slices/app.ts | 50 +++ packages/oc-core/src/store/slices/docs.ts | 31 ++ .../oc-core/src/store/slices/playground.ts | 31 ++ packages/oc-core/src/store/store.ts | 8 +- packages/oc-core/src/ui/ContentView.tsx | 344 ------------------ packages/oc-core/src/ui/ItemComponent.tsx | 1 - packages/oc-core/tsconfig.json | 6 +- .../oc-core/vite.config.libraries-bundle.ts | 5 + packages/oc-core/vite.config.standalone.ts | 5 + 11 files changed, 262 insertions(+), 479 deletions(-) delete mode 100644 packages/oc-core/src/store/opencollectionSlice.ts create mode 100644 packages/oc-core/src/store/slices/app.ts create mode 100644 packages/oc-core/src/store/slices/docs.ts create mode 100644 packages/oc-core/src/store/slices/playground.ts delete mode 100644 packages/oc-core/src/ui/ContentView.tsx delete mode 100644 packages/oc-core/src/ui/ItemComponent.tsx diff --git a/packages/oc-core/src/components/OpenCollection/OpenCollection.tsx b/packages/oc-core/src/components/OpenCollection/OpenCollection.tsx index b17e1413..8d516953 100644 --- a/packages/oc-core/src/components/OpenCollection/OpenCollection.tsx +++ b/packages/oc-core/src/components/OpenCollection/OpenCollection.tsx @@ -12,17 +12,70 @@ import Sidebar from '../Sidebar/Sidebar'; import AllEndpointsView from '../../ui/AllEndpointsView'; import PlaygroundDrawer from '../Playground/PlaygroudDrawer/PlaygroundDrawer'; import { getItemId, generateSafeId } from '../../utils/itemUtils'; +import { parseYaml } from '../../utils/yamlUtils'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { - loadCollection, - selectCollectionData, + selectDocsCollection, + setDocsCollection, + clearDocsCollection +} from '@slices/docs'; +import { + selectPlaygroundCollection, + setPlaygroundCollection, + clearPlaygroundCollection +} from '@slices/playground'; +import { + selectCollectionStatus, selectCollectionError, - selectCollectionStatus -} from '../../store/opencollectionSlice'; + setCollectionLoading, + setCollectionSucceeded, + setCollectionFailed, + resetCollectionState +} from '@slices/app'; import { createOpenCollectionStore, type AppStore } from '../../store/store'; +const isFileInstance = (value: unknown): value is File => + typeof File !== 'undefined' && value instanceof File; + +const parseCollectionContent = (content: string): OpenCollectionCollection => { + try { + return parseYaml(content) as OpenCollectionCollection; + } catch (yamlError) { + try { + return JSON.parse(content) as OpenCollectionCollection; + } catch (jsonError) { + throw new Error('Failed to parse collection as YAML or JSON'); + } + } +}; + +const resolveCollectionSource = async ( + source: OpenCollectionCollection | string | File +): Promise => { + if (isFileInstance(source)) { + const text = await source.text(); + return parseCollectionContent(text); + } + + if (typeof source === 'string') { + if (source.startsWith('http://') || source.startsWith('https://')) { + const response = await fetch(source); + if (!response.ok) { + throw new Error(`Failed to fetch collection: ${response.statusText}`); + } + const text = await response.text(); + return parseCollectionContent(text); + } + + return parseCollectionContent(source); + } + + return source; +}; + interface DesktopLayoutProps { - collectionData: OpenCollectionCollection | null; + docsCollection: OpenCollectionCollection | null; + playgroundCollection: OpenCollectionCollection | null; logo: React.ReactNode; theme: 'light' | 'dark' | 'auto'; currentPageId: string | null; @@ -36,7 +89,8 @@ interface DesktopLayoutProps { } const DesktopLayout: React.FC = ({ - collectionData, + docsCollection, + playgroundCollection, logo, theme, currentPageId, @@ -50,7 +104,7 @@ const DesktopLayout: React.FC = ({ // Find the selected item for playground const selectedItem = useMemo(() => { - if (!selectedItemId || !collectionData) return null; + if (!selectedItemId || !docsCollection) return null; const findItem = (items: any[]): any => { for (const item of items) { @@ -67,14 +121,14 @@ const DesktopLayout: React.FC = ({ return null; }; - if (collectionData.items) { - const item = findItem(collectionData.items); + if (docsCollection.items) { + const item = findItem(docsCollection.items); if (item && item.type === 'http') { return item as HttpRequest; } } return null; - }, [selectedItemId, collectionData]); + }, [selectedItemId, docsCollection]); // Update playground item when selection changes useEffect(() => { @@ -92,7 +146,8 @@ const DesktopLayout: React.FC = ({ onSelectItem(id); // Find the item to set as playground item if it's an HTTP request - if (collectionData && collectionData.items) { + const collectionItems = playgroundCollection?.items ?? docsCollection?.items; + if (collectionItems) { const findItem = (items: any[]): any => { for (const item of items) { const itemId = getItemId(item); @@ -108,7 +163,7 @@ const DesktopLayout: React.FC = ({ return null; }; - const item = findItem(collectionData.items); + const item = findItem(collectionItems); if (item && item.type === 'http') { setPlaygroundItem(item as HttpRequest); if (openPlayground) { @@ -150,7 +205,7 @@ const DesktopLayout: React.FC = ({ }} > handleItemSelect(id, false)} logo={logo} @@ -164,7 +219,7 @@ const DesktopLayout: React.FC = ({ ref={containerRef} > = ({ setShowPlaygroundDrawer(false)} - collection={collectionData} + collection={playgroundCollection} selectedItem={playgroundItem} onSelectItem={handlePlaygroundItemSelect} theme={theme} @@ -200,24 +255,71 @@ const OpenCollectionContent: React.FC = ({ logo, }) => { const dispatch = useAppDispatch(); - const collectionData = useAppSelector(selectCollectionData); + const docsCollection = useAppSelector(selectDocsCollection); + const playgroundCollection = useAppSelector(selectPlaygroundCollection); const collectionStatus = useAppSelector(selectCollectionStatus); - const error = useAppSelector(selectCollectionError); + const collectionError = useAppSelector(selectCollectionError); const containerRef = useRef(null); useTheme(theme); useEffect(() => { - dispatch(loadCollection(collection)); + let isActive = true; + + const load = async () => { + dispatch(setCollectionLoading()); + + try { + const resolved = await resolveCollectionSource(collection); + if (!isActive) { + return; + } + dispatch(setDocsCollection(resolved)); + dispatch(setPlaygroundCollection(resolved)); + dispatch(setCollectionSucceeded()); + } catch (err) { + if (!isActive) { + return; + } + const message = err instanceof Error ? err.message : 'Failed to load API collection'; + dispatch(setCollectionFailed(message)); + dispatch(clearDocsCollection()); + dispatch(clearPlaygroundCollection()); + } + }; + + if (collection == null) { + dispatch(clearDocsCollection()); + dispatch(clearPlaygroundCollection()); + dispatch(resetCollectionState()); + return () => { + isActive = false; + }; + } + + if (isFileInstance(collection) || typeof collection === 'string') { + void load(); + } else { + dispatch(setDocsCollection(collection as OpenCollectionCollection)); + dispatch(setPlaygroundCollection(collection as OpenCollectionCollection)); + dispatch(setCollectionSucceeded()); + } + + return () => { + isActive = false; + }; }, [collection, dispatch]); - const filteredCollectionItems = collectionData?.items || []; + const filteredCollectionItems = docsCollection?.items || []; // Page selection state const [currentPageId, setCurrentPageId] = useState(null); const [currentPageItem, setCurrentPageItem] = useState(null); - const isLoading = collectionStatus === 'loading' || (collectionStatus === 'idle' && !collectionData); + const isInitialLoad = + collectionStatus === 'idle' && !docsCollection && !playgroundCollection; + const isLoading = collectionStatus === 'loading' || isInitialLoad; + const error = collectionError; // Handle item selection const handleSelectItem = (id: string, path?: string) => { @@ -239,16 +341,16 @@ const OpenCollectionContent: React.FC = ({ return null; }; - if (collectionData && 'items' in collectionData && collectionData.items) { - const item = findItem(collectionData.items); + if (docsCollection && 'items' in docsCollection && docsCollection.items) { + const item = findItem(docsCollection.items); setCurrentPageItem(item); } }; // Set initial page to first root-level request when collection loads useEffect(() => { - if (collectionData && currentPageId === null) { - const items = collectionData.items as any[] | undefined; + if (docsCollection && currentPageId === null) { + const items = docsCollection.items as any[] | undefined; const firstRequest = items?.find((item) => item.type === 'http'); if (firstRequest?.name) { @@ -260,7 +362,7 @@ const OpenCollectionContent: React.FC = ({ } } } - }, [collectionData, currentPageId]); + }, [docsCollection, currentPageId]); const { isRunnerMode, toggleRunnerMode } = useRunnerMode(); @@ -273,7 +375,8 @@ const OpenCollectionContent: React.FC = ({ } const commonProps = { - collectionData, + docsCollection, + playgroundCollection, theme, currentPageId, currentPageItem, diff --git a/packages/oc-core/src/store/opencollectionSlice.ts b/packages/oc-core/src/store/opencollectionSlice.ts deleted file mode 100644 index ec93daa2..00000000 --- a/packages/oc-core/src/store/opencollectionSlice.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; -import type { ActionReducerMapBuilder, PayloadAction } from '@reduxjs/toolkit'; -import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; -import { parseYaml } from '../utils/yamlUtils'; -import type { RootState } from './store'; - -export type CollectionSource = OpenCollectionCollection | string | File; - -interface OpenCollectionState { - data: OpenCollectionCollection | null; - status: 'idle' | 'loading' | 'succeeded' | 'failed'; - error: string | null; -} - -const initialState: OpenCollectionState = { - data: null, - status: 'idle', - error: null, -}; - -const loadOpenCollectionData = async (source: string | File): Promise => { - let content: string; - - if (typeof File !== 'undefined' && source instanceof File) { - content = await source.text(); - } else if (typeof source === 'string') { - if (source.startsWith('http://') || source.startsWith('https://')) { - const response = await fetch(source); - if (!response.ok) { - throw new Error(`Failed to fetch collection: ${response.statusText}`); - } - content = await response.text(); - } else { - content = source; - } - } else { - throw new Error('Invalid source type for collection'); - } - - try { - return parseYaml(content) as OpenCollectionCollection; - } catch (yamlError) { - try { - return JSON.parse(content) as OpenCollectionCollection; - } catch (jsonError) { - throw new Error('Failed to parse collection as YAML or JSON'); - } - } -}; - -export const loadCollection = createAsyncThunk( - 'opencollection/loadCollection', - async (collection: CollectionSource) => { - if (typeof File !== 'undefined' && collection instanceof File) { - return loadOpenCollectionData(collection); - } - - if (typeof collection === 'string') { - return loadOpenCollectionData(collection); - } - - return collection; - } -); - -const opencollectionSlice = createSlice({ - name: 'opencollection', - initialState, - reducers: { - setCollection: (state: OpenCollectionState, action: PayloadAction) => { - state.data = action.payload; - state.status = action.payload ? 'succeeded' : 'idle'; - state.error = null; - }, - clearCollection: (state: OpenCollectionState) => { - state.data = null; - state.status = 'idle'; - state.error = null; - }, - }, - extraReducers: (builder: ActionReducerMapBuilder) => { - builder - .addCase(loadCollection.pending, (state: OpenCollectionState) => { - state.status = 'loading'; - state.error = null; - }) - .addCase(loadCollection.fulfilled, (state: OpenCollectionState, action: PayloadAction) => { - state.data = action.payload; - state.status = 'succeeded'; - state.error = null; - }) - .addCase(loadCollection.rejected, (state: OpenCollectionState, action: ReturnType) => { - state.status = 'failed'; - state.error = action.error.message || 'Failed to load API collection'; - }); - }, -}); - -export const { setCollection, clearCollection } = opencollectionSlice.actions; -export default opencollectionSlice.reducer; - -export const selectCollectionData = (state: RootState) => state.opencollection.data; -export const selectCollectionStatus = (state: RootState) => state.opencollection.status; -export const selectCollectionError = (state: RootState) => state.opencollection.error; - diff --git a/packages/oc-core/src/store/slices/app.ts b/packages/oc-core/src/store/slices/app.ts new file mode 100644 index 00000000..cd360083 --- /dev/null +++ b/packages/oc-core/src/store/slices/app.ts @@ -0,0 +1,50 @@ +import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; +import type { RootState } from '../store'; + +export type CollectionStatus = 'idle' | 'loading' | 'succeeded' | 'failed'; + +export interface AppState { + collectionStatus: CollectionStatus; + collectionError: string | null; +} + +const initialState: AppState = { + collectionStatus: 'idle', + collectionError: null +}; + +const appSlice = createSlice({ + name: 'app', + initialState, + reducers: { + setCollectionLoading: (state: AppState) => { + state.collectionStatus = 'loading'; + state.collectionError = null; + }, + setCollectionSucceeded: (state: AppState) => { + state.collectionStatus = 'succeeded'; + state.collectionError = null; + }, + setCollectionFailed: (state: AppState, action: PayloadAction) => { + state.collectionStatus = 'failed'; + state.collectionError = action.payload; + }, + resetCollectionState: (state: AppState) => { + state.collectionStatus = 'idle'; + state.collectionError = null; + } + } +}); + +export const { + setCollectionLoading, + setCollectionSucceeded, + setCollectionFailed, + resetCollectionState +} = appSlice.actions; +export default appSlice.reducer; + +export const selectCollectionStatus = (state: RootState) => state.app.collectionStatus; +export const selectCollectionError = (state: RootState) => state.app.collectionError; + + diff --git a/packages/oc-core/src/store/slices/docs.ts b/packages/oc-core/src/store/slices/docs.ts new file mode 100644 index 00000000..8f330753 --- /dev/null +++ b/packages/oc-core/src/store/slices/docs.ts @@ -0,0 +1,31 @@ +import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; +import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; +import type { RootState } from '../store'; + +export interface DocsState { + collection: OpenCollectionCollection | null; +} + +const initialState: DocsState = { + collection: null +}; + +const docsSlice = createSlice({ + name: 'docs', + initialState, + reducers: { + setDocsCollection: (state: DocsState, action: PayloadAction) => { + state.collection = action.payload; + }, + clearDocsCollection: (state: DocsState) => { + state.collection = null; + }, + } +}); + +export const { setDocsCollection, clearDocsCollection } = docsSlice.actions; +export default docsSlice.reducer; + +export const selectDocsCollection = (state: RootState) => state.docs.collection; + + diff --git a/packages/oc-core/src/store/slices/playground.ts b/packages/oc-core/src/store/slices/playground.ts new file mode 100644 index 00000000..323fdf5f --- /dev/null +++ b/packages/oc-core/src/store/slices/playground.ts @@ -0,0 +1,31 @@ +import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; +import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; +import type { RootState } from '../store'; + +export interface PlaygroundState { + collection: OpenCollectionCollection | null; +} + +const initialState: PlaygroundState = { + collection: null +}; + +const playgroundSlice = createSlice({ + name: 'playground', + initialState, + reducers: { + setPlaygroundCollection: (state: PlaygroundState, action: PayloadAction) => { + state.collection = action.payload; + }, + clearPlaygroundCollection: (state: PlaygroundState) => { + state.collection = null; + }, + } +}); + +export const { setPlaygroundCollection, clearPlaygroundCollection } = playgroundSlice.actions; +export default playgroundSlice.reducer; + +export const selectPlaygroundCollection = (state: RootState) => state.playground.collection; + + diff --git a/packages/oc-core/src/store/store.ts b/packages/oc-core/src/store/store.ts index b6a4eea0..fb7fd453 100644 --- a/packages/oc-core/src/store/store.ts +++ b/packages/oc-core/src/store/store.ts @@ -1,10 +1,14 @@ import { configureStore } from '@reduxjs/toolkit'; -import opencollectionReducer from './opencollectionSlice'; +import appReducer from '@slices/app'; +import docsReducer from '@slices/docs'; +import playgroundReducer from '@slices/playground'; export const createOpenCollectionStore = () => configureStore({ reducer: { - opencollection: opencollectionReducer, + app: appReducer, + docs: docsReducer, + playground: playgroundReducer, }, }); diff --git a/packages/oc-core/src/ui/ContentView.tsx b/packages/oc-core/src/ui/ContentView.tsx deleted file mode 100644 index 63a357d7..00000000 --- a/packages/oc-core/src/ui/ContentView.tsx +++ /dev/null @@ -1,344 +0,0 @@ -import React, { useMemo } from 'react'; -import MarkdownIt from 'markdown-it'; -import '../styles/index.css'; -import type { Item as OpenCollectionItem, HttpRequest } from '@opencollection/types'; - -interface ApiEndpoint { - id: string; - method: string; - path: string; - name?: string; - description?: string; - headers?: Record; - queryParams?: Record; - requestBody?: any; - brunoUrl?: string; -} - -interface ApiResponse { - status: number; - description?: string; - body?: any; -} - -interface ApiEndpointWithResponses extends ApiEndpoint { - responses?: ApiResponse[]; - isFolder?: boolean; -} - -export interface ContentViewProps { - endpoint: ApiEndpointWithResponses | OpenCollectionItem; - theme?: 'light' | 'dark' | 'auto'; - className?: string; -} - -const ContentView: React.FC = ({ endpoint, theme = 'light', className = '' }) => { - const processedEndpoint = useMemo(() => { - if (endpoint && 'type' in endpoint) { - const item = endpoint as OpenCollectionItem; - const itemName = item.type === 'http' ? (item as HttpRequest).name || item.type : item.type; - const itemId = itemName.replace(/\s+/g, '-').toLowerCase(); - - if (item.type !== 'http') { - return { - id: itemId, - method: item.type.toUpperCase(), - path: itemName, - name: itemName, - description: item.type === 'folder' ? (item as any).docs || '' : '', - isFolder: item.type === 'folder', - } as ApiEndpointWithResponses & { isFolder?: boolean }; - } - - const httpItem = item as HttpRequest; - const normalizedEndpoint: ApiEndpointWithResponses = { - id: itemId, - name: httpItem.name || 'Untitled', - method: httpItem.method || 'GET', - path: httpItem.url || '', - description: httpItem.docs || '', - headers: {}, - queryParams: {}, - }; - - if (httpItem.headers) { - normalizedEndpoint.headers = httpItem.headers.reduce( - (acc: Record, header: any) => { - if (header.disabled !== true) { - acc[header.name] = header.value; - } - return acc; - }, {} - ); - } - - if (httpItem.params) { - normalizedEndpoint.queryParams = httpItem.params.reduce( - (acc: Record, param: any) => { - if (param.enabled !== false) { - acc[param.name] = param.value; - } - return acc; - }, {} - ); - } - - if (httpItem.body && typeof httpItem.body === 'object' && 'data' in httpItem.body) { - const rawBody = httpItem.body as any; - if (rawBody.type === 'json') { - try { - normalizedEndpoint.requestBody = JSON.parse(rawBody.data || '{}'); - } catch { - normalizedEndpoint.requestBody = rawBody.data; - } - } else { - normalizedEndpoint.requestBody = rawBody.data; - } - } - - return normalizedEndpoint; - } - - return endpoint as ApiEndpointWithResponses; - }, [endpoint]); - - const md = useMemo(() => { - const markdownIt = new MarkdownIt({ - html: true, - linkify: true, - typographer: true, - breaks: true, - highlight: function (str: string, lang: string) { - return `
${str}
`; - } - }); - - const defaultRender = markdownIt.renderer.rules.heading_open || function(tokens: any, idx: number, options: any, env: any, self: any) { - return self.renderToken(tokens, idx, options); - }; - - markdownIt.renderer.rules.heading_open = function(tokens: any, idx: number, options: any, env: any, self: any) { - const token = tokens[idx]; - const level = token.tag.substr(1); // h1 -> 1, h2 -> 2, etc. - - // Add custom classes based on heading level - if (level === '1') { - token.attrJoin('class', 'text-2xl font-bold mt-6 mb-3 heading-1'); - } else if (level === '2') { - token.attrJoin('class', 'text-xl font-semibold mt-5 mb-2 heading-2'); - } else if (level === '3') { - token.attrJoin('class', 'text-lg font-semibold mt-4 mb-2 heading-3'); - } else { - token.attrJoin('class', 'font-semibold mt-3 mb-1 heading-4'); - } - - return defaultRender(tokens, idx, options, env, self); - }; - - return markdownIt; - }, []); - - return ( -
- -
-
- {processedEndpoint.isFolder ? ( -
- - - -
- ) : ( -
- {processedEndpoint.method} -
- )} -

- {processedEndpoint.name || processedEndpoint.path.split('/').pop()} -

- {processedEndpoint.brunoUrl && ( - - Open in Bruno - - )} -
-
- {processedEndpoint.path} -
-
- - -
- - {processedEndpoint.description && ( -
-

Documentation

-
-
- )} - - -
-

Request

- - - {processedEndpoint.headers && Object.keys(processedEndpoint.headers).length > 0 && ( -
-

Headers

-
- - - - - - - - - - {Object.entries(processedEndpoint.headers).map(([key, value]) => ( - - - - - - ))} - -
NameValueDescription
{key} - - {String(value)} - -
-
-
- )} - - - {processedEndpoint.queryParams && Object.keys(processedEndpoint.queryParams).length > 0 && ( -
-

Query Parameters

-
- - - - - - - - - - {Object.entries(processedEndpoint.queryParams).map(([key, value]) => ( - - - - - - ))} - -
NameValueDescription
{key} - - {String(value)} - -
-
-
- )} - - - {processedEndpoint.requestBody && ( -
-

Request Body

-
-
{JSON.stringify(processedEndpoint.requestBody, null, 2)}
-
-
- )} -
- - - {processedEndpoint.responses && processedEndpoint.responses.length > 0 && ( -
-

Responses

- - {processedEndpoint.responses.map((response: ApiResponse, index: number) => ( -
-
-

- Status: {response.status} -

- {response.description && ( - - {response.description} - - )} -
- - {response.body && ( -
-
{JSON.stringify(response.body, null, 2)}
-
- )} -
- ))} -
- )} -
-
- ); -}; - -export default ContentView; \ No newline at end of file diff --git a/packages/oc-core/src/ui/ItemComponent.tsx b/packages/oc-core/src/ui/ItemComponent.tsx deleted file mode 100644 index df000bcc..00000000 --- a/packages/oc-core/src/ui/ItemComponent.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from '../components/Docs/Item/Item'; \ No newline at end of file diff --git a/packages/oc-core/tsconfig.json b/packages/oc-core/tsconfig.json index 1e6cc224..e9aa2d06 100644 --- a/packages/oc-core/tsconfig.json +++ b/packages/oc-core/tsconfig.json @@ -16,7 +16,11 @@ "jsx": "react-jsx", "declaration": true, "outDir": "dist", - "module": "ESNext" + "module": "ESNext", + "baseUrl": ".", + "paths": { + "@slices/*": ["src/store/slices/*"] + } }, "include": [ "src" diff --git a/packages/oc-core/vite.config.libraries-bundle.ts b/packages/oc-core/vite.config.libraries-bundle.ts index e643af9c..0b0ac0c4 100644 --- a/packages/oc-core/vite.config.libraries-bundle.ts +++ b/packages/oc-core/vite.config.libraries-bundle.ts @@ -21,6 +21,11 @@ ${chunk.code} // Vite config for bundling libraries similar to bundle-libraries.js export default defineConfig({ + resolve: { + alias: { + '@slices': resolve(__dirname, 'src/store/slices') + } + }, build: { lib: { entry: resolve(__dirname, 'src/scripting/sandbox/quickjs/bundle-entry.ts'), diff --git a/packages/oc-core/vite.config.standalone.ts b/packages/oc-core/vite.config.standalone.ts index fc661076..b256b3f4 100644 --- a/packages/oc-core/vite.config.standalone.ts +++ b/packages/oc-core/vite.config.standalone.ts @@ -7,6 +7,11 @@ const __dirname = fileURLToPath(new URL('.', import.meta.url)); // https://vitejs.dev/config/ export default defineConfig({ + resolve: { + alias: { + '@slices': resolve(__dirname, 'src/store/slices') + } + }, plugins: [ react() ], From 2fac0e5272bdd0406b1362740cd1752d7c432679 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 17 Nov 2025 03:37:00 +0530 Subject: [PATCH 036/161] chore: code cleanup --- .../components/{ => Docs}/Method/Method.tsx | 0 .../{ => Docs}/Method/StyledWrapper.ts | 0 .../PlaygroudDrawer/PlaygroundDrawer.tsx | 6 +- .../Playground/Playground.tsx} | 14 +- .../RequestHeader}/RequestHeader.tsx | 0 .../Playground/RequestPane}/RequestPane.tsx | 4 +- .../Playground/ResponsePane}/ResponsePane.tsx | 4 +- .../src/components/Sidebar/Sidebar.tsx | 2 +- .../src/ui/{ => CodeEditor}/CodeEditor.tsx | 0 packages/oc-core/src/ui/DataTable.tsx | 222 ------------------ .../src/ui/EnvironmentVariablesSection.tsx | 119 ---------- packages/oc-core/src/ui/TableHelpers.tsx | 221 ----------------- packages/oc-core/src/ui/index.ts | 4 - .../oc-core/src/ui/request-runner/index.ts | 4 - 14 files changed, 15 insertions(+), 585 deletions(-) rename packages/oc-core/src/components/{ => Docs}/Method/Method.tsx (100%) rename packages/oc-core/src/components/{ => Docs}/Method/StyledWrapper.ts (100%) rename packages/oc-core/src/{ui/request-runner/RequestRunner.tsx => components/Playground/Playground.tsx} (91%) rename packages/oc-core/src/{ui/request-runner => components/Playground/RequestPane/RequestHeader}/RequestHeader.tsx (100%) rename packages/oc-core/src/{ui/request-runner => components/Playground/RequestPane}/RequestPane.tsx (99%) rename packages/oc-core/src/{ui/request-runner => components/Playground/ResponsePane}/ResponsePane.tsx (98%) rename packages/oc-core/src/ui/{ => CodeEditor}/CodeEditor.tsx (100%) delete mode 100644 packages/oc-core/src/ui/DataTable.tsx delete mode 100644 packages/oc-core/src/ui/EnvironmentVariablesSection.tsx delete mode 100644 packages/oc-core/src/ui/TableHelpers.tsx delete mode 100644 packages/oc-core/src/ui/index.ts delete mode 100644 packages/oc-core/src/ui/request-runner/index.ts diff --git a/packages/oc-core/src/components/Method/Method.tsx b/packages/oc-core/src/components/Docs/Method/Method.tsx similarity index 100% rename from packages/oc-core/src/components/Method/Method.tsx rename to packages/oc-core/src/components/Docs/Method/Method.tsx diff --git a/packages/oc-core/src/components/Method/StyledWrapper.ts b/packages/oc-core/src/components/Docs/Method/StyledWrapper.ts similarity index 100% rename from packages/oc-core/src/components/Method/StyledWrapper.ts rename to packages/oc-core/src/components/Docs/Method/StyledWrapper.ts diff --git a/packages/oc-core/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx b/packages/oc-core/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx index f295db84..42247e58 100644 --- a/packages/oc-core/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx +++ b/packages/oc-core/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx @@ -2,9 +2,9 @@ import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react' import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; import type { Item as OpenCollectionItem, Folder } from '@opencollection/types/collection/item'; import type { HttpRequest } from '@opencollection/types/requests/http'; -import { RequestRunner } from '../../../ui/request-runner'; +import Playground from '../Playground'; import { getItemId, generateSafeId } from '../../../utils/itemUtils'; -import Method from '../../Method/Method'; +import Method from '../../Docs/Method/Method'; import { StyledBackdrop, StyledDrawer, StyledDragBar } from './StyledWrapper'; interface PlaygroundDrawerProps { @@ -434,7 +434,7 @@ const PlaygroundDrawer: React.FC = ({ minHeight: 0 }}> {selectedItem && collection ? ( - void; } -const RequestRunner: React.FC = ({ item, collection, toggleRunnerMode }) => { +const Playground: React.FC = ({ item, collection, toggleRunnerMode }) => { const [editableItem, setEditableItem] = useState(item); const [selectedEnvironment, setSelectedEnvironment] = useState(''); const [response, setResponse] = useState(null); @@ -136,4 +136,4 @@ const RequestRunner: React.FC = ({ item, collection, toggleR ); }; -export default RequestRunner; \ No newline at end of file +export default Playground; \ No newline at end of file diff --git a/packages/oc-core/src/ui/request-runner/RequestHeader.tsx b/packages/oc-core/src/components/Playground/RequestPane/RequestHeader/RequestHeader.tsx similarity index 100% rename from packages/oc-core/src/ui/request-runner/RequestHeader.tsx rename to packages/oc-core/src/components/Playground/RequestPane/RequestHeader/RequestHeader.tsx diff --git a/packages/oc-core/src/ui/request-runner/RequestPane.tsx b/packages/oc-core/src/components/Playground/RequestPane/RequestPane.tsx similarity index 99% rename from packages/oc-core/src/ui/request-runner/RequestPane.tsx rename to packages/oc-core/src/components/Playground/RequestPane/RequestPane.tsx index 78e97067..175f5715 100644 --- a/packages/oc-core/src/ui/request-runner/RequestPane.tsx +++ b/packages/oc-core/src/components/Playground/RequestPane/RequestPane.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import type { HttpRequest } from '@opencollection/types/requests/http'; -import CodeEditor from '../CodeEditor'; -import Tabs from '../Tabs/Tabs'; +import CodeEditor from '../../../ui/CodeEditor/CodeEditor'; +import Tabs from '../../../ui/Tabs/Tabs'; interface RequestPaneProps { item: HttpRequest; diff --git a/packages/oc-core/src/ui/request-runner/ResponsePane.tsx b/packages/oc-core/src/components/Playground/ResponsePane/ResponsePane.tsx similarity index 98% rename from packages/oc-core/src/ui/request-runner/ResponsePane.tsx rename to packages/oc-core/src/components/Playground/ResponsePane/ResponsePane.tsx index a51b62f7..0d968233 100644 --- a/packages/oc-core/src/ui/request-runner/ResponsePane.tsx +++ b/packages/oc-core/src/components/Playground/ResponsePane/ResponsePane.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; -import CodeEditor from '../CodeEditor'; -import Tabs from '../Tabs/Tabs'; +import CodeEditor from '../../../ui/CodeEditor/CodeEditor'; +import Tabs from '../../../ui/Tabs/Tabs'; interface ResponsePaneProps { response: any; diff --git a/packages/oc-core/src/components/Sidebar/Sidebar.tsx b/packages/oc-core/src/components/Sidebar/Sidebar.tsx index 526cc434..69fc7990 100644 --- a/packages/oc-core/src/components/Sidebar/Sidebar.tsx +++ b/packages/oc-core/src/components/Sidebar/Sidebar.tsx @@ -2,7 +2,7 @@ import React, { useState, useMemo, useEffect, useCallback } from 'react'; import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; import type { Item as OpenCollectionItem, Folder } from '@opencollection/types/collection/item'; import type { HttpRequest } from '@opencollection/types/requests/http'; -import Method from '../Method/Method'; +import Method from '../Docs/Method/Method'; import { getItemId, generateSafeId } from '../../utils/itemUtils'; import OpenCollectionLogo from '../../assets/opencollection-logo.svg'; import { SidebarContainer, SidebarItems, SidebarItem } from './StyledWrapper'; diff --git a/packages/oc-core/src/ui/CodeEditor.tsx b/packages/oc-core/src/ui/CodeEditor/CodeEditor.tsx similarity index 100% rename from packages/oc-core/src/ui/CodeEditor.tsx rename to packages/oc-core/src/ui/CodeEditor/CodeEditor.tsx diff --git a/packages/oc-core/src/ui/DataTable.tsx b/packages/oc-core/src/ui/DataTable.tsx deleted file mode 100644 index 52e71227..00000000 --- a/packages/oc-core/src/ui/DataTable.tsx +++ /dev/null @@ -1,222 +0,0 @@ -import React, { useState } from 'react'; - -interface Column { - key: string; - label: string; - render?: (value: any, row: any) => React.ReactNode; - width?: string; -} - -interface DataTableProps { - columns: Column[]; - data: any[]; - title?: string; - icon?: React.ReactNode; - maxVisibleRows?: number; - emptyMessage?: string; - className?: string; -} - -const DataTable: React.FC = ({ - columns, - data, - title, - icon, - maxVisibleRows = 10, - emptyMessage = 'No data available', - className = '' -}) => { - const [isExpanded, setIsExpanded] = useState(false); - const hasLotsOfRows = data.length > maxVisibleRows; - const displayData = hasLotsOfRows && !isExpanded ? data.slice(0, maxVisibleRows) : data; - - const toggleExpansion = () => { - setIsExpanded(prev => !prev); - }; - - if (!data.length) { - return ( -
- {emptyMessage} -
- ); - } - - return ( -
- {(title || icon) && ( -
- {icon} - {title} -
- )} - -
-
- - - - {columns.map((column) => ( - - ))} - - - - {displayData.map((row, rowIndex) => ( - - {columns.map((column) => ( - - ))} - - ))} - -
- {column.label} -
- {column.render ? column.render(row[column.key], row) : row[column.key]} -
-
- - {hasLotsOfRows && !isExpanded && ( -
-
-
- -
-
- )} - - {hasLotsOfRows && isExpanded && ( -
- -
- )} -
-
- ); -}; - -export default DataTable; \ No newline at end of file diff --git a/packages/oc-core/src/ui/EnvironmentVariablesSection.tsx b/packages/oc-core/src/ui/EnvironmentVariablesSection.tsx deleted file mode 100644 index 542e4bdb..00000000 --- a/packages/oc-core/src/ui/EnvironmentVariablesSection.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import React from 'react'; -import DataTable from './DataTable'; - -interface EnvironmentVariable { - name: string; - value: string; - enabled?: boolean; - local?: boolean; - uid?: string; -} - -interface Environment { - name: string; - uid?: string; - variables: EnvironmentVariable[]; -} - -interface EnvironmentVariablesSectionProps { - environments?: Array<{ - name: string; - variables: Array<{ - name: string; - value: string; - enabled: boolean; - type: string; - secret?: boolean; - }>; - uid?: string; - }>; - activeEnvironmentUid?: string; -} - -const EnvironmentVariablesSection: React.FC = ({ - environments, - activeEnvironmentUid -}) => { - if (!environments || environments.length === 0) { - return null; - } - - const columns = [ - { - key: 'name', - label: 'Name', - width: '40%' - }, - { - key: 'value', - label: 'Value', - width: '45%' - }, - { - key: 'enabled', - label: 'Status', - width: '15%', - render: (enabled: boolean) => ( - - {enabled !== false ? 'Enabled' : 'Disabled'} - - ) - } - ]; - - return ( -
-

- Environment Variables -

- -
- {environments.map((env, envIndex) => { - const envId = env.uid || `env-${envIndex}`; - const isActive = activeEnvironmentUid === envId; - - return ( - - Active - - ) : undefined - } - columns={columns} - data={env.variables} - maxVisibleRows={8} - emptyMessage={`No variables defined for ${env.name}`} - /> - ); - })} -
-
- ); -}; - -export default EnvironmentVariablesSection; \ No newline at end of file diff --git a/packages/oc-core/src/ui/TableHelpers.tsx b/packages/oc-core/src/ui/TableHelpers.tsx deleted file mode 100644 index d9ea5d4c..00000000 --- a/packages/oc-core/src/ui/TableHelpers.tsx +++ /dev/null @@ -1,221 +0,0 @@ -import React from 'react'; -import DataTable from './DataTable'; - -interface HeaderItem { - name: string; - value: string; - enabled?: boolean; - description?: string; - uid?: string; -} - -interface VarItem { - name: string; - value: string; - enabled?: boolean; - local?: boolean; - uid?: string; -} - -interface AssertionItem { - name: string; - value: string; - enabled?: boolean; - uid?: string; -} - -interface FormItem { - name: string; - value: string; - enabled?: boolean; -} - -export const HeadersTable: React.FC<{ headers: HeaderItem[] }> = ({ headers }) => { - const columns = [ - { key: 'name', label: 'Name', width: '25%' }, - { key: 'value', label: 'Value', width: '40%' }, - { key: 'description', label: 'Description', width: '25%' }, - { - key: 'enabled', - label: 'Status', - width: '10%', - render: (enabled: boolean) => enabled === false ? ( - - Disabled - - ) : null - } - ]; - - return ( - - - - - } - columns={columns} - data={headers} - emptyMessage="No headers defined" - /> - ); -}; - -export const VarsTable: React.FC<{ vars: VarItem[] }> = ({ vars }) => { - const columns = [ - { key: 'name', label: 'Name', width: '40%' }, - { key: 'value', label: 'Value', width: '45%' }, - { - key: 'enabled', - label: 'Status', - width: '15%', - render: (enabled: boolean) => ( - - {enabled !== false ? 'Enabled' : 'Disabled'} - - ) - } - ]; - - return ( - - - - - - - - } - columns={columns} - data={vars} - emptyMessage="No variables defined" - /> - ); -}; - -export const AssertionsTable: React.FC<{ assertions: AssertionItem[] }> = ({ assertions }) => { - const columns = [ - { key: 'name', label: 'Name', width: '40%' }, - { key: 'value', label: 'Value', width: '45%' }, - { - key: 'enabled', - label: 'Status', - width: '15%', - render: (enabled: boolean) => ( - - {enabled !== false ? 'Enabled' : 'Disabled'} - - ) - } - ]; - - return ( - - - - - } - columns={columns} - data={assertions} - emptyMessage="No assertions defined" - /> - ); -}; - -export const MultipartFormTable: React.FC<{ formData: FormItem[] }> = ({ formData }) => { - const columns = [ - { key: 'name', label: 'Name', width: '40%' }, - { key: 'value', label: 'Value', width: '45%' }, - { - key: 'enabled', - label: 'Status', - width: '15%', - render: (enabled: boolean) => ( - - {enabled !== false ? 'Enabled' : 'Disabled'} - - ) - } - ]; - - return ( - - ); -}; - -export const FormUrlEncodedTable: React.FC<{ formData: FormItem[] }> = ({ formData }) => { - const columns = [ - { key: 'name', label: 'Name', width: '40%' }, - { key: 'value', label: 'Value', width: '45%' }, - { - key: 'enabled', - label: 'Status', - width: '15%', - render: (enabled: boolean) => ( - - {enabled !== false ? 'Enabled' : 'Disabled'} - - ) - } - ]; - - return ( - - ); -}; \ No newline at end of file diff --git a/packages/oc-core/src/ui/index.ts b/packages/oc-core/src/ui/index.ts deleted file mode 100644 index e39bf1dc..00000000 --- a/packages/oc-core/src/ui/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { default as DataTable } from './DataTable'; -export { default as CodeEditor } from './CodeEditor'; -export * from './TableHelpers'; -export * from './request-runner'; \ No newline at end of file diff --git a/packages/oc-core/src/ui/request-runner/index.ts b/packages/oc-core/src/ui/request-runner/index.ts deleted file mode 100644 index 51d27432..00000000 --- a/packages/oc-core/src/ui/request-runner/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { default as RequestRunner } from './RequestRunner'; -export { default as RequestHeader } from './RequestHeader'; -export { default as RequestPane } from './RequestPane'; -export { default as ResponsePane } from './ResponsePane'; \ No newline at end of file From 8f1c5e731d31b1d28fc02beee28843fb34be033f Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 17 Nov 2025 03:52:50 +0530 Subject: [PATCH 037/161] chore: code org refactor --- packages/oc-core/src/components/Docs/Docs.tsx | 135 ++++++++++++++++++ .../components/{ => Docs}/Sidebar/Sidebar.tsx | 8 +- .../{ => Docs}/Sidebar/StyledWrapper.ts | 0 .../OpenCollection/OpenCollection.tsx | 46 ++---- .../src/components/Playground/Playground.tsx | 3 +- packages/oc-core/src/ui/AllEndpointsView.tsx | 106 -------------- 6 files changed, 151 insertions(+), 147 deletions(-) create mode 100644 packages/oc-core/src/components/Docs/Docs.tsx rename packages/oc-core/src/components/{ => Docs}/Sidebar/Sidebar.tsx (97%) rename packages/oc-core/src/components/{ => Docs}/Sidebar/StyledWrapper.ts (100%) delete mode 100644 packages/oc-core/src/ui/AllEndpointsView.tsx diff --git a/packages/oc-core/src/components/Docs/Docs.tsx b/packages/oc-core/src/components/Docs/Docs.tsx new file mode 100644 index 00000000..bf41d154 --- /dev/null +++ b/packages/oc-core/src/components/Docs/Docs.tsx @@ -0,0 +1,135 @@ +import React, { useMemo } from 'react'; +import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; +import Sidebar from './Sidebar/Sidebar'; +import Item from './Item/Item'; +import { getItemId, generateSafeId } from '../../utils/itemUtils'; + +interface DocsProps { + docsCollection: OpenCollectionCollection | null; + selectedItemId: string | null; + onItemSelect: (id: string, openPlayground?: boolean) => void; + logo: React.ReactNode; + theme: 'light' | 'dark' | 'auto'; + containerRef: React.RefObject; + filteredCollectionItems: any[]; +} + +const Docs: React.FC = ({ + docsCollection, + selectedItemId, + onItemSelect, + logo, + theme, + containerRef, + filteredCollectionItems +}) => { + // Flatten all items recursively for rendering + const flattenItems = (items: any[], parentPath = ''): any[] => { + const result: any[] = []; + + for (const item of items) { + const itemId = getItemId(item); + const itemPath = parentPath ? `${parentPath}/${itemId}` : itemId; + + // Add the item itself + result.push(item); + + // If it's a folder, recursively add its children + if (item.type === 'folder') { + const folder = item as any; + if (folder.items && folder.items.length > 0) { + result.push(...flattenItems(folder.items, itemPath)); + } + } + } + + return result; + }; + + const allItems = useMemo(() => { + const items = flattenItems(filteredCollectionItems); + return items; + }, [filteredCollectionItems]); + + const registerSectionRef = (id: string, ref: HTMLDivElement | null) => { + // No-op for now, but can be used for scroll tracking + }; + + return ( + <> +
+ onItemSelect(id, false)} + logo={logo} + theme={theme} + /> +
+ +
+
+ {/* Render all collection items */} + {allItems.map((item, index) => { + const itemId = getItemId(item); + const safeId = generateSafeId(itemId); + const sectionId = `section-${safeId}`; + const isSelected = selectedItemId === safeId || selectedItemId === itemId; + + return ( +
+ { + // Select the item and trigger playground open + onItemSelect(safeId, true); + // Scroll to the item + setTimeout(() => { + const element = document.getElementById(`section-${safeId}`); + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + }, 100); + }} + /> +
+ ); + })} + + {allItems.length === 0 && ( +
+
+ + + +

No content available

+

No endpoints or pages found in this collection.

+
+
+ )} +
+
+ + ); +}; + +export default Docs; + diff --git a/packages/oc-core/src/components/Sidebar/Sidebar.tsx b/packages/oc-core/src/components/Docs/Sidebar/Sidebar.tsx similarity index 97% rename from packages/oc-core/src/components/Sidebar/Sidebar.tsx rename to packages/oc-core/src/components/Docs/Sidebar/Sidebar.tsx index 69fc7990..805513e2 100644 --- a/packages/oc-core/src/components/Sidebar/Sidebar.tsx +++ b/packages/oc-core/src/components/Docs/Sidebar/Sidebar.tsx @@ -2,9 +2,9 @@ import React, { useState, useMemo, useEffect, useCallback } from 'react'; import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; import type { Item as OpenCollectionItem, Folder } from '@opencollection/types/collection/item'; import type { HttpRequest } from '@opencollection/types/requests/http'; -import Method from '../Docs/Method/Method'; -import { getItemId, generateSafeId } from '../../utils/itemUtils'; -import OpenCollectionLogo from '../../assets/opencollection-logo.svg'; +import Method from '../Method/Method'; +import { getItemId, generateSafeId } from '../../../utils/itemUtils'; +import OpenCollectionLogo from '../../../assets/opencollection-logo.svg'; import { SidebarContainer, SidebarItems, SidebarItem } from './StyledWrapper'; interface ApiEndpoint { @@ -218,7 +218,7 @@ const Sidebar: React.FC = ({ return ( {/* Collection name at top */} -
+

{collection?.name || 'API Collection'} diff --git a/packages/oc-core/src/components/Sidebar/StyledWrapper.ts b/packages/oc-core/src/components/Docs/Sidebar/StyledWrapper.ts similarity index 100% rename from packages/oc-core/src/components/Sidebar/StyledWrapper.ts rename to packages/oc-core/src/components/Docs/Sidebar/StyledWrapper.ts diff --git a/packages/oc-core/src/components/OpenCollection/OpenCollection.tsx b/packages/oc-core/src/components/OpenCollection/OpenCollection.tsx index 8d516953..ba1639c5 100644 --- a/packages/oc-core/src/components/OpenCollection/OpenCollection.tsx +++ b/packages/oc-core/src/components/OpenCollection/OpenCollection.tsx @@ -8,9 +8,8 @@ import { useRunnerMode } from '../../hooks'; import type { OpenCollection as IOpenCollection } from '@opencollection/types'; -import Sidebar from '../Sidebar/Sidebar'; -import AllEndpointsView from '../../ui/AllEndpointsView'; import PlaygroundDrawer from '../Playground/PlaygroudDrawer/PlaygroundDrawer'; +import Docs from '../Docs/Docs'; import { getItemId, generateSafeId } from '../../utils/itemUtils'; import { parseYaml } from '../../utils/yamlUtils'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; @@ -195,39 +194,16 @@ const DesktopLayout: React.FC = ({ return (
- {/* Left Pane: Sidebar */} -
- handleItemSelect(id, false)} - logo={logo} - theme={theme} - /> -
- - {/* Middle Pane: All Endpoints View */} -
- handleItemSelect(id, openPlayground || false)} - /> -
- - {/* Bottom Drawer: Playground */} + + setShowPlaygroundDrawer(false)} diff --git a/packages/oc-core/src/components/Playground/Playground.tsx b/packages/oc-core/src/components/Playground/Playground.tsx index fcca20ed..f4082aa3 100644 --- a/packages/oc-core/src/components/Playground/Playground.tsx +++ b/packages/oc-core/src/components/Playground/Playground.tsx @@ -10,10 +10,9 @@ import ResponsePane from './ResponsePane/ResponsePane'; interface PlaygroundProps { item: HttpRequest; collection: OpenCollectionCollection; - toggleRunnerMode?: () => void; } -const Playground: React.FC = ({ item, collection, toggleRunnerMode }) => { +const Playground: React.FC = ({ item, collection }) => { const [editableItem, setEditableItem] = useState(item); const [selectedEnvironment, setSelectedEnvironment] = useState(''); const [response, setResponse] = useState(null); diff --git a/packages/oc-core/src/ui/AllEndpointsView.tsx b/packages/oc-core/src/ui/AllEndpointsView.tsx deleted file mode 100644 index 60b745c7..00000000 --- a/packages/oc-core/src/ui/AllEndpointsView.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import React, { useMemo } from 'react'; -import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; -import Item from '../components/Docs/Item/Item'; -import { getItemId, generateSafeId } from '../utils/itemUtils'; - -interface AllEndpointsViewProps { - collection: OpenCollectionCollection | null; - collectionItems: any[]; - theme: 'light' | 'dark' | 'auto'; - selectedItemId: string | null; - onItemSelect?: (id: string, openPlayground?: boolean) => void; -} - -const AllEndpointsView: React.FC = ({ - collection, - collectionItems, - theme, - selectedItemId, - onItemSelect -}) => { - // Flatten all items recursively for rendering - const flattenItems = (items: any[], parentPath = ''): any[] => { - const result: any[] = []; - - for (const item of items) { - const itemId = getItemId(item); - const itemPath = parentPath ? `${parentPath}/${itemId}` : itemId; - - // Add the item itself - result.push(item); - - // If it's a folder, recursively add its children - if (item.type === 'folder') { - const folder = item as any; - if (folder.items && folder.items.length > 0) { - result.push(...flattenItems(folder.items, itemPath)); - } - } - } - - return result; - }; - - const allItems = useMemo(() => { - const items = flattenItems(collectionItems); - return items; - }, [collectionItems]); - - const registerSectionRef = (id: string, ref: HTMLDivElement | null) => { - // No-op for now, but can be used for scroll tracking - }; - - return ( -
- {/* Render all collection items */} - {allItems.map((item, index) => { - const itemId = getItemId(item); - const safeId = generateSafeId(itemId); - const sectionId = `section-${safeId}`; - const isSelected = selectedItemId === safeId || selectedItemId === itemId; - - return ( -
- { - // Select the item and trigger playground open - onItemSelect?.(safeId, true); - // Scroll to the item - setTimeout(() => { - const element = document.getElementById(`section-${safeId}`); - if (element) { - element.scrollIntoView({ behavior: 'smooth', block: 'start' }); - } - }, 100); - }} - /> -
- ); - })} - - {allItems.length === 0 && ( -
-
- - - -

No content available

-

No endpoints or pages found in this collection.

-
-
- )} -
- ); -}; - -export default AllEndpointsView; - From a45aff690e4302faa864c4cb15af199f319420b2 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 17 Nov 2025 03:54:56 +0530 Subject: [PATCH 038/161] chore: renamed pkg oc-core to oc-docs --- package.json | 2 +- packages/{oc-core => oc-docs}/.gitignore | 0 packages/{oc-core => oc-docs}/eslint.config.js | 0 packages/{oc-core => oc-docs}/package.json | 4 ++-- packages/{oc-core => oc-docs}/postcss.config.cjs | 0 .../{oc-core => oc-docs}/src/assets/opencollection-logo.svg | 0 packages/{oc-core => oc-docs}/src/assets/react.svg | 0 .../src/components/Docs/CodeSnippets/CodeSnippets.tsx | 0 .../src/components/Docs/CodeSnippets/StyledWrapper.ts | 0 packages/{oc-core => oc-docs}/src/components/Docs/Docs.tsx | 0 .../{oc-core => oc-docs}/src/components/Docs/Item/Item.tsx | 0 .../src/components/Docs/Item/Scripts/Scripts.tsx | 0 .../src/components/Docs/Item/Scripts/StyledWrapper.ts | 0 .../src/components/Docs/Item/StyledWrapper.ts | 0 .../src/components/Docs/Method/Method.tsx | 0 .../src/components/Docs/Method/StyledWrapper.ts | 0 .../src/components/Docs/Sidebar/Sidebar.tsx | 0 .../src/components/Docs/Sidebar/StyledWrapper.ts | 0 .../src/components/OpenCollection/OpenCollection.tsx | 0 .../Playground/PlaygroudDrawer/PlaygroundDrawer.tsx | 0 .../components/Playground/PlaygroudDrawer/StyledWrapper.ts | 0 .../src/components/Playground/Playground.tsx | 0 .../src/components/Playground/QueryBar/QueryBar.tsx | 0 .../src/components/Playground/QueryBar/StyledWrapper.ts | 0 .../Playground/RequestPane/RequestHeader/RequestHeader.tsx | 0 .../src/components/Playground/RequestPane/RequestPane.tsx | 0 .../src/components/Playground/ResponsePane/ResponsePane.tsx | 0 .../src/components/Playground/Sidebar/Sidebar.tsx | 0 .../src/components/Playground/Sidebar/StyledWrapper.ts | 0 packages/{oc-core => oc-docs}/src/hooks/index.ts | 0 .../{oc-core => oc-docs}/src/hooks/useMarkdownRenderer.ts | 0 packages/{oc-core => oc-docs}/src/hooks/useRunnerMode.ts | 0 packages/{oc-core => oc-docs}/src/hooks/useTheme.ts | 0 packages/{oc-core => oc-docs}/src/runner/RequestExecutor.ts | 0 packages/{oc-core => oc-docs}/src/runner/ScriptRunner.ts | 0 .../{oc-core => oc-docs}/src/runner/VariableInterpolator.ts | 0 packages/{oc-core => oc-docs}/src/runner/index.ts | 0 .../src/scripting/runtime/script-runtime.spec.ts | 0 .../src/scripting/runtime/script-runtime.ts | 0 .../src/scripting/sandbox/quickjs/bundle-entry.ts | 0 .../src/scripting/sandbox/quickjs/index.ts | 0 .../src/scripting/sandbox/quickjs/shims/bru.ts | 0 .../src/scripting/sandbox/quickjs/shims/bruno-request.ts | 0 .../src/scripting/sandbox/quickjs/shims/bruno-response.ts | 0 .../src/scripting/sandbox/quickjs/shims/console.ts | 0 .../src/scripting/sandbox/quickjs/shims/test.ts | 0 .../src/scripting/sandbox/quickjs/utils/index.ts | 0 packages/{oc-core => oc-docs}/src/scripting/utils/bru.ts | 0 .../{oc-core => oc-docs}/src/scripting/utils/bruno-request.ts | 0 .../src/scripting/utils/bruno-response.ts | 0 packages/{oc-core => oc-docs}/src/scripting/utils/common.ts | 0 .../{oc-core => oc-docs}/src/scripting/utils/query-get.ts | 0 packages/{oc-core => oc-docs}/src/scripting/utils/test.ts | 0 packages/{oc-core => oc-docs}/src/standalone.ts | 0 packages/{oc-core => oc-docs}/src/store/hooks.ts | 0 packages/{oc-core => oc-docs}/src/store/slices/app.ts | 0 packages/{oc-core => oc-docs}/src/store/slices/docs.ts | 0 packages/{oc-core => oc-docs}/src/store/slices/playground.ts | 0 packages/{oc-core => oc-docs}/src/store/store.ts | 0 packages/{oc-core => oc-docs}/src/styles/App.css | 0 packages/{oc-core => oc-docs}/src/styles/index.css | 0 .../{oc-core => oc-docs}/src/ui/CodeEditor/CodeEditor.tsx | 0 packages/{oc-core => oc-docs}/src/ui/MinimalComponents.tsx | 0 packages/{oc-core => oc-docs}/src/ui/Tabs/StyledWrapper.ts | 0 packages/{oc-core => oc-docs}/src/ui/Tabs/Tabs.tsx | 0 packages/{oc-core => oc-docs}/src/utils/fileUtils.ts | 0 packages/{oc-core => oc-docs}/src/utils/itemUtils.ts | 0 packages/{oc-core => oc-docs}/src/utils/yamlUtils.ts | 0 packages/{oc-core => oc-docs}/src/vite-env.d.ts | 0 packages/{oc-core => oc-docs}/standalone.d.ts | 0 packages/{oc-core => oc-docs}/tsconfig.json | 0 packages/{oc-core => oc-docs}/vite.config.libraries-bundle.ts | 0 packages/{oc-core => oc-docs}/vite.config.standalone.ts | 0 73 files changed, 3 insertions(+), 3 deletions(-) rename packages/{oc-core => oc-docs}/.gitignore (100%) rename packages/{oc-core => oc-docs}/eslint.config.js (100%) rename packages/{oc-core => oc-docs}/package.json (98%) rename packages/{oc-core => oc-docs}/postcss.config.cjs (100%) rename packages/{oc-core => oc-docs}/src/assets/opencollection-logo.svg (100%) rename packages/{oc-core => oc-docs}/src/assets/react.svg (100%) rename packages/{oc-core => oc-docs}/src/components/Docs/CodeSnippets/CodeSnippets.tsx (100%) rename packages/{oc-core => oc-docs}/src/components/Docs/CodeSnippets/StyledWrapper.ts (100%) rename packages/{oc-core => oc-docs}/src/components/Docs/Docs.tsx (100%) rename packages/{oc-core => oc-docs}/src/components/Docs/Item/Item.tsx (100%) rename packages/{oc-core => oc-docs}/src/components/Docs/Item/Scripts/Scripts.tsx (100%) rename packages/{oc-core => oc-docs}/src/components/Docs/Item/Scripts/StyledWrapper.ts (100%) rename packages/{oc-core => oc-docs}/src/components/Docs/Item/StyledWrapper.ts (100%) rename packages/{oc-core => oc-docs}/src/components/Docs/Method/Method.tsx (100%) rename packages/{oc-core => oc-docs}/src/components/Docs/Method/StyledWrapper.ts (100%) rename packages/{oc-core => oc-docs}/src/components/Docs/Sidebar/Sidebar.tsx (100%) rename packages/{oc-core => oc-docs}/src/components/Docs/Sidebar/StyledWrapper.ts (100%) rename packages/{oc-core => oc-docs}/src/components/OpenCollection/OpenCollection.tsx (100%) rename packages/{oc-core => oc-docs}/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx (100%) rename packages/{oc-core => oc-docs}/src/components/Playground/PlaygroudDrawer/StyledWrapper.ts (100%) rename packages/{oc-core => oc-docs}/src/components/Playground/Playground.tsx (100%) rename packages/{oc-core => oc-docs}/src/components/Playground/QueryBar/QueryBar.tsx (100%) rename packages/{oc-core => oc-docs}/src/components/Playground/QueryBar/StyledWrapper.ts (100%) rename packages/{oc-core => oc-docs}/src/components/Playground/RequestPane/RequestHeader/RequestHeader.tsx (100%) rename packages/{oc-core => oc-docs}/src/components/Playground/RequestPane/RequestPane.tsx (100%) rename packages/{oc-core => oc-docs}/src/components/Playground/ResponsePane/ResponsePane.tsx (100%) rename packages/{oc-core => oc-docs}/src/components/Playground/Sidebar/Sidebar.tsx (100%) rename packages/{oc-core => oc-docs}/src/components/Playground/Sidebar/StyledWrapper.ts (100%) rename packages/{oc-core => oc-docs}/src/hooks/index.ts (100%) rename packages/{oc-core => oc-docs}/src/hooks/useMarkdownRenderer.ts (100%) rename packages/{oc-core => oc-docs}/src/hooks/useRunnerMode.ts (100%) rename packages/{oc-core => oc-docs}/src/hooks/useTheme.ts (100%) rename packages/{oc-core => oc-docs}/src/runner/RequestExecutor.ts (100%) rename packages/{oc-core => oc-docs}/src/runner/ScriptRunner.ts (100%) rename packages/{oc-core => oc-docs}/src/runner/VariableInterpolator.ts (100%) rename packages/{oc-core => oc-docs}/src/runner/index.ts (100%) rename packages/{oc-core => oc-docs}/src/scripting/runtime/script-runtime.spec.ts (100%) rename packages/{oc-core => oc-docs}/src/scripting/runtime/script-runtime.ts (100%) rename packages/{oc-core => oc-docs}/src/scripting/sandbox/quickjs/bundle-entry.ts (100%) rename packages/{oc-core => oc-docs}/src/scripting/sandbox/quickjs/index.ts (100%) rename packages/{oc-core => oc-docs}/src/scripting/sandbox/quickjs/shims/bru.ts (100%) rename packages/{oc-core => oc-docs}/src/scripting/sandbox/quickjs/shims/bruno-request.ts (100%) rename packages/{oc-core => oc-docs}/src/scripting/sandbox/quickjs/shims/bruno-response.ts (100%) rename packages/{oc-core => oc-docs}/src/scripting/sandbox/quickjs/shims/console.ts (100%) rename packages/{oc-core => oc-docs}/src/scripting/sandbox/quickjs/shims/test.ts (100%) rename packages/{oc-core => oc-docs}/src/scripting/sandbox/quickjs/utils/index.ts (100%) rename packages/{oc-core => oc-docs}/src/scripting/utils/bru.ts (100%) rename packages/{oc-core => oc-docs}/src/scripting/utils/bruno-request.ts (100%) rename packages/{oc-core => oc-docs}/src/scripting/utils/bruno-response.ts (100%) rename packages/{oc-core => oc-docs}/src/scripting/utils/common.ts (100%) rename packages/{oc-core => oc-docs}/src/scripting/utils/query-get.ts (100%) rename packages/{oc-core => oc-docs}/src/scripting/utils/test.ts (100%) rename packages/{oc-core => oc-docs}/src/standalone.ts (100%) rename packages/{oc-core => oc-docs}/src/store/hooks.ts (100%) rename packages/{oc-core => oc-docs}/src/store/slices/app.ts (100%) rename packages/{oc-core => oc-docs}/src/store/slices/docs.ts (100%) rename packages/{oc-core => oc-docs}/src/store/slices/playground.ts (100%) rename packages/{oc-core => oc-docs}/src/store/store.ts (100%) rename packages/{oc-core => oc-docs}/src/styles/App.css (100%) rename packages/{oc-core => oc-docs}/src/styles/index.css (100%) rename packages/{oc-core => oc-docs}/src/ui/CodeEditor/CodeEditor.tsx (100%) rename packages/{oc-core => oc-docs}/src/ui/MinimalComponents.tsx (100%) rename packages/{oc-core => oc-docs}/src/ui/Tabs/StyledWrapper.ts (100%) rename packages/{oc-core => oc-docs}/src/ui/Tabs/Tabs.tsx (100%) rename packages/{oc-core => oc-docs}/src/utils/fileUtils.ts (100%) rename packages/{oc-core => oc-docs}/src/utils/itemUtils.ts (100%) rename packages/{oc-core => oc-docs}/src/utils/yamlUtils.ts (100%) rename packages/{oc-core => oc-docs}/src/vite-env.d.ts (100%) rename packages/{oc-core => oc-docs}/standalone.d.ts (100%) rename packages/{oc-core => oc-docs}/tsconfig.json (100%) rename packages/{oc-core => oc-docs}/vite.config.libraries-bundle.ts (100%) rename packages/{oc-core => oc-docs}/vite.config.standalone.ts (100%) diff --git a/package.json b/package.json index d1d7ebeb..31685c05 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "workspaces": [ "packages/oc-converters", "packages/oc-schema", - "packages/oc-core", + "packages/oc-docs", "packages/oc-express", "packages/oc-schema-explorer", "packages/oc-types" diff --git a/packages/oc-core/.gitignore b/packages/oc-docs/.gitignore similarity index 100% rename from packages/oc-core/.gitignore rename to packages/oc-docs/.gitignore diff --git a/packages/oc-core/eslint.config.js b/packages/oc-docs/eslint.config.js similarity index 100% rename from packages/oc-core/eslint.config.js rename to packages/oc-docs/eslint.config.js diff --git a/packages/oc-core/package.json b/packages/oc-docs/package.json similarity index 98% rename from packages/oc-core/package.json rename to packages/oc-docs/package.json index 0e7de65c..c469aef1 100644 --- a/packages/oc-core/package.json +++ b/packages/oc-docs/package.json @@ -1,6 +1,6 @@ { - "name": "@opencollection/core", - "version": "1.0.0", + "name": "@opencollection/docs", + "version": "0.0.1", "description": "Interactive API documentation component for OpenCollection - supports both React components and standalone JavaScript", "type": "module", "main": "dist/oc-playground.umd.js", diff --git a/packages/oc-core/postcss.config.cjs b/packages/oc-docs/postcss.config.cjs similarity index 100% rename from packages/oc-core/postcss.config.cjs rename to packages/oc-docs/postcss.config.cjs diff --git a/packages/oc-core/src/assets/opencollection-logo.svg b/packages/oc-docs/src/assets/opencollection-logo.svg similarity index 100% rename from packages/oc-core/src/assets/opencollection-logo.svg rename to packages/oc-docs/src/assets/opencollection-logo.svg diff --git a/packages/oc-core/src/assets/react.svg b/packages/oc-docs/src/assets/react.svg similarity index 100% rename from packages/oc-core/src/assets/react.svg rename to packages/oc-docs/src/assets/react.svg diff --git a/packages/oc-core/src/components/Docs/CodeSnippets/CodeSnippets.tsx b/packages/oc-docs/src/components/Docs/CodeSnippets/CodeSnippets.tsx similarity index 100% rename from packages/oc-core/src/components/Docs/CodeSnippets/CodeSnippets.tsx rename to packages/oc-docs/src/components/Docs/CodeSnippets/CodeSnippets.tsx diff --git a/packages/oc-core/src/components/Docs/CodeSnippets/StyledWrapper.ts b/packages/oc-docs/src/components/Docs/CodeSnippets/StyledWrapper.ts similarity index 100% rename from packages/oc-core/src/components/Docs/CodeSnippets/StyledWrapper.ts rename to packages/oc-docs/src/components/Docs/CodeSnippets/StyledWrapper.ts diff --git a/packages/oc-core/src/components/Docs/Docs.tsx b/packages/oc-docs/src/components/Docs/Docs.tsx similarity index 100% rename from packages/oc-core/src/components/Docs/Docs.tsx rename to packages/oc-docs/src/components/Docs/Docs.tsx diff --git a/packages/oc-core/src/components/Docs/Item/Item.tsx b/packages/oc-docs/src/components/Docs/Item/Item.tsx similarity index 100% rename from packages/oc-core/src/components/Docs/Item/Item.tsx rename to packages/oc-docs/src/components/Docs/Item/Item.tsx diff --git a/packages/oc-core/src/components/Docs/Item/Scripts/Scripts.tsx b/packages/oc-docs/src/components/Docs/Item/Scripts/Scripts.tsx similarity index 100% rename from packages/oc-core/src/components/Docs/Item/Scripts/Scripts.tsx rename to packages/oc-docs/src/components/Docs/Item/Scripts/Scripts.tsx diff --git a/packages/oc-core/src/components/Docs/Item/Scripts/StyledWrapper.ts b/packages/oc-docs/src/components/Docs/Item/Scripts/StyledWrapper.ts similarity index 100% rename from packages/oc-core/src/components/Docs/Item/Scripts/StyledWrapper.ts rename to packages/oc-docs/src/components/Docs/Item/Scripts/StyledWrapper.ts diff --git a/packages/oc-core/src/components/Docs/Item/StyledWrapper.ts b/packages/oc-docs/src/components/Docs/Item/StyledWrapper.ts similarity index 100% rename from packages/oc-core/src/components/Docs/Item/StyledWrapper.ts rename to packages/oc-docs/src/components/Docs/Item/StyledWrapper.ts diff --git a/packages/oc-core/src/components/Docs/Method/Method.tsx b/packages/oc-docs/src/components/Docs/Method/Method.tsx similarity index 100% rename from packages/oc-core/src/components/Docs/Method/Method.tsx rename to packages/oc-docs/src/components/Docs/Method/Method.tsx diff --git a/packages/oc-core/src/components/Docs/Method/StyledWrapper.ts b/packages/oc-docs/src/components/Docs/Method/StyledWrapper.ts similarity index 100% rename from packages/oc-core/src/components/Docs/Method/StyledWrapper.ts rename to packages/oc-docs/src/components/Docs/Method/StyledWrapper.ts diff --git a/packages/oc-core/src/components/Docs/Sidebar/Sidebar.tsx b/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx similarity index 100% rename from packages/oc-core/src/components/Docs/Sidebar/Sidebar.tsx rename to packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx diff --git a/packages/oc-core/src/components/Docs/Sidebar/StyledWrapper.ts b/packages/oc-docs/src/components/Docs/Sidebar/StyledWrapper.ts similarity index 100% rename from packages/oc-core/src/components/Docs/Sidebar/StyledWrapper.ts rename to packages/oc-docs/src/components/Docs/Sidebar/StyledWrapper.ts diff --git a/packages/oc-core/src/components/OpenCollection/OpenCollection.tsx b/packages/oc-docs/src/components/OpenCollection/OpenCollection.tsx similarity index 100% rename from packages/oc-core/src/components/OpenCollection/OpenCollection.tsx rename to packages/oc-docs/src/components/OpenCollection/OpenCollection.tsx diff --git a/packages/oc-core/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx b/packages/oc-docs/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx similarity index 100% rename from packages/oc-core/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx rename to packages/oc-docs/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx diff --git a/packages/oc-core/src/components/Playground/PlaygroudDrawer/StyledWrapper.ts b/packages/oc-docs/src/components/Playground/PlaygroudDrawer/StyledWrapper.ts similarity index 100% rename from packages/oc-core/src/components/Playground/PlaygroudDrawer/StyledWrapper.ts rename to packages/oc-docs/src/components/Playground/PlaygroudDrawer/StyledWrapper.ts diff --git a/packages/oc-core/src/components/Playground/Playground.tsx b/packages/oc-docs/src/components/Playground/Playground.tsx similarity index 100% rename from packages/oc-core/src/components/Playground/Playground.tsx rename to packages/oc-docs/src/components/Playground/Playground.tsx diff --git a/packages/oc-core/src/components/Playground/QueryBar/QueryBar.tsx b/packages/oc-docs/src/components/Playground/QueryBar/QueryBar.tsx similarity index 100% rename from packages/oc-core/src/components/Playground/QueryBar/QueryBar.tsx rename to packages/oc-docs/src/components/Playground/QueryBar/QueryBar.tsx diff --git a/packages/oc-core/src/components/Playground/QueryBar/StyledWrapper.ts b/packages/oc-docs/src/components/Playground/QueryBar/StyledWrapper.ts similarity index 100% rename from packages/oc-core/src/components/Playground/QueryBar/StyledWrapper.ts rename to packages/oc-docs/src/components/Playground/QueryBar/StyledWrapper.ts diff --git a/packages/oc-core/src/components/Playground/RequestPane/RequestHeader/RequestHeader.tsx b/packages/oc-docs/src/components/Playground/RequestPane/RequestHeader/RequestHeader.tsx similarity index 100% rename from packages/oc-core/src/components/Playground/RequestPane/RequestHeader/RequestHeader.tsx rename to packages/oc-docs/src/components/Playground/RequestPane/RequestHeader/RequestHeader.tsx diff --git a/packages/oc-core/src/components/Playground/RequestPane/RequestPane.tsx b/packages/oc-docs/src/components/Playground/RequestPane/RequestPane.tsx similarity index 100% rename from packages/oc-core/src/components/Playground/RequestPane/RequestPane.tsx rename to packages/oc-docs/src/components/Playground/RequestPane/RequestPane.tsx diff --git a/packages/oc-core/src/components/Playground/ResponsePane/ResponsePane.tsx b/packages/oc-docs/src/components/Playground/ResponsePane/ResponsePane.tsx similarity index 100% rename from packages/oc-core/src/components/Playground/ResponsePane/ResponsePane.tsx rename to packages/oc-docs/src/components/Playground/ResponsePane/ResponsePane.tsx diff --git a/packages/oc-core/src/components/Playground/Sidebar/Sidebar.tsx b/packages/oc-docs/src/components/Playground/Sidebar/Sidebar.tsx similarity index 100% rename from packages/oc-core/src/components/Playground/Sidebar/Sidebar.tsx rename to packages/oc-docs/src/components/Playground/Sidebar/Sidebar.tsx diff --git a/packages/oc-core/src/components/Playground/Sidebar/StyledWrapper.ts b/packages/oc-docs/src/components/Playground/Sidebar/StyledWrapper.ts similarity index 100% rename from packages/oc-core/src/components/Playground/Sidebar/StyledWrapper.ts rename to packages/oc-docs/src/components/Playground/Sidebar/StyledWrapper.ts diff --git a/packages/oc-core/src/hooks/index.ts b/packages/oc-docs/src/hooks/index.ts similarity index 100% rename from packages/oc-core/src/hooks/index.ts rename to packages/oc-docs/src/hooks/index.ts diff --git a/packages/oc-core/src/hooks/useMarkdownRenderer.ts b/packages/oc-docs/src/hooks/useMarkdownRenderer.ts similarity index 100% rename from packages/oc-core/src/hooks/useMarkdownRenderer.ts rename to packages/oc-docs/src/hooks/useMarkdownRenderer.ts diff --git a/packages/oc-core/src/hooks/useRunnerMode.ts b/packages/oc-docs/src/hooks/useRunnerMode.ts similarity index 100% rename from packages/oc-core/src/hooks/useRunnerMode.ts rename to packages/oc-docs/src/hooks/useRunnerMode.ts diff --git a/packages/oc-core/src/hooks/useTheme.ts b/packages/oc-docs/src/hooks/useTheme.ts similarity index 100% rename from packages/oc-core/src/hooks/useTheme.ts rename to packages/oc-docs/src/hooks/useTheme.ts diff --git a/packages/oc-core/src/runner/RequestExecutor.ts b/packages/oc-docs/src/runner/RequestExecutor.ts similarity index 100% rename from packages/oc-core/src/runner/RequestExecutor.ts rename to packages/oc-docs/src/runner/RequestExecutor.ts diff --git a/packages/oc-core/src/runner/ScriptRunner.ts b/packages/oc-docs/src/runner/ScriptRunner.ts similarity index 100% rename from packages/oc-core/src/runner/ScriptRunner.ts rename to packages/oc-docs/src/runner/ScriptRunner.ts diff --git a/packages/oc-core/src/runner/VariableInterpolator.ts b/packages/oc-docs/src/runner/VariableInterpolator.ts similarity index 100% rename from packages/oc-core/src/runner/VariableInterpolator.ts rename to packages/oc-docs/src/runner/VariableInterpolator.ts diff --git a/packages/oc-core/src/runner/index.ts b/packages/oc-docs/src/runner/index.ts similarity index 100% rename from packages/oc-core/src/runner/index.ts rename to packages/oc-docs/src/runner/index.ts diff --git a/packages/oc-core/src/scripting/runtime/script-runtime.spec.ts b/packages/oc-docs/src/scripting/runtime/script-runtime.spec.ts similarity index 100% rename from packages/oc-core/src/scripting/runtime/script-runtime.spec.ts rename to packages/oc-docs/src/scripting/runtime/script-runtime.spec.ts diff --git a/packages/oc-core/src/scripting/runtime/script-runtime.ts b/packages/oc-docs/src/scripting/runtime/script-runtime.ts similarity index 100% rename from packages/oc-core/src/scripting/runtime/script-runtime.ts rename to packages/oc-docs/src/scripting/runtime/script-runtime.ts diff --git a/packages/oc-core/src/scripting/sandbox/quickjs/bundle-entry.ts b/packages/oc-docs/src/scripting/sandbox/quickjs/bundle-entry.ts similarity index 100% rename from packages/oc-core/src/scripting/sandbox/quickjs/bundle-entry.ts rename to packages/oc-docs/src/scripting/sandbox/quickjs/bundle-entry.ts diff --git a/packages/oc-core/src/scripting/sandbox/quickjs/index.ts b/packages/oc-docs/src/scripting/sandbox/quickjs/index.ts similarity index 100% rename from packages/oc-core/src/scripting/sandbox/quickjs/index.ts rename to packages/oc-docs/src/scripting/sandbox/quickjs/index.ts diff --git a/packages/oc-core/src/scripting/sandbox/quickjs/shims/bru.ts b/packages/oc-docs/src/scripting/sandbox/quickjs/shims/bru.ts similarity index 100% rename from packages/oc-core/src/scripting/sandbox/quickjs/shims/bru.ts rename to packages/oc-docs/src/scripting/sandbox/quickjs/shims/bru.ts diff --git a/packages/oc-core/src/scripting/sandbox/quickjs/shims/bruno-request.ts b/packages/oc-docs/src/scripting/sandbox/quickjs/shims/bruno-request.ts similarity index 100% rename from packages/oc-core/src/scripting/sandbox/quickjs/shims/bruno-request.ts rename to packages/oc-docs/src/scripting/sandbox/quickjs/shims/bruno-request.ts diff --git a/packages/oc-core/src/scripting/sandbox/quickjs/shims/bruno-response.ts b/packages/oc-docs/src/scripting/sandbox/quickjs/shims/bruno-response.ts similarity index 100% rename from packages/oc-core/src/scripting/sandbox/quickjs/shims/bruno-response.ts rename to packages/oc-docs/src/scripting/sandbox/quickjs/shims/bruno-response.ts diff --git a/packages/oc-core/src/scripting/sandbox/quickjs/shims/console.ts b/packages/oc-docs/src/scripting/sandbox/quickjs/shims/console.ts similarity index 100% rename from packages/oc-core/src/scripting/sandbox/quickjs/shims/console.ts rename to packages/oc-docs/src/scripting/sandbox/quickjs/shims/console.ts diff --git a/packages/oc-core/src/scripting/sandbox/quickjs/shims/test.ts b/packages/oc-docs/src/scripting/sandbox/quickjs/shims/test.ts similarity index 100% rename from packages/oc-core/src/scripting/sandbox/quickjs/shims/test.ts rename to packages/oc-docs/src/scripting/sandbox/quickjs/shims/test.ts diff --git a/packages/oc-core/src/scripting/sandbox/quickjs/utils/index.ts b/packages/oc-docs/src/scripting/sandbox/quickjs/utils/index.ts similarity index 100% rename from packages/oc-core/src/scripting/sandbox/quickjs/utils/index.ts rename to packages/oc-docs/src/scripting/sandbox/quickjs/utils/index.ts diff --git a/packages/oc-core/src/scripting/utils/bru.ts b/packages/oc-docs/src/scripting/utils/bru.ts similarity index 100% rename from packages/oc-core/src/scripting/utils/bru.ts rename to packages/oc-docs/src/scripting/utils/bru.ts diff --git a/packages/oc-core/src/scripting/utils/bruno-request.ts b/packages/oc-docs/src/scripting/utils/bruno-request.ts similarity index 100% rename from packages/oc-core/src/scripting/utils/bruno-request.ts rename to packages/oc-docs/src/scripting/utils/bruno-request.ts diff --git a/packages/oc-core/src/scripting/utils/bruno-response.ts b/packages/oc-docs/src/scripting/utils/bruno-response.ts similarity index 100% rename from packages/oc-core/src/scripting/utils/bruno-response.ts rename to packages/oc-docs/src/scripting/utils/bruno-response.ts diff --git a/packages/oc-core/src/scripting/utils/common.ts b/packages/oc-docs/src/scripting/utils/common.ts similarity index 100% rename from packages/oc-core/src/scripting/utils/common.ts rename to packages/oc-docs/src/scripting/utils/common.ts diff --git a/packages/oc-core/src/scripting/utils/query-get.ts b/packages/oc-docs/src/scripting/utils/query-get.ts similarity index 100% rename from packages/oc-core/src/scripting/utils/query-get.ts rename to packages/oc-docs/src/scripting/utils/query-get.ts diff --git a/packages/oc-core/src/scripting/utils/test.ts b/packages/oc-docs/src/scripting/utils/test.ts similarity index 100% rename from packages/oc-core/src/scripting/utils/test.ts rename to packages/oc-docs/src/scripting/utils/test.ts diff --git a/packages/oc-core/src/standalone.ts b/packages/oc-docs/src/standalone.ts similarity index 100% rename from packages/oc-core/src/standalone.ts rename to packages/oc-docs/src/standalone.ts diff --git a/packages/oc-core/src/store/hooks.ts b/packages/oc-docs/src/store/hooks.ts similarity index 100% rename from packages/oc-core/src/store/hooks.ts rename to packages/oc-docs/src/store/hooks.ts diff --git a/packages/oc-core/src/store/slices/app.ts b/packages/oc-docs/src/store/slices/app.ts similarity index 100% rename from packages/oc-core/src/store/slices/app.ts rename to packages/oc-docs/src/store/slices/app.ts diff --git a/packages/oc-core/src/store/slices/docs.ts b/packages/oc-docs/src/store/slices/docs.ts similarity index 100% rename from packages/oc-core/src/store/slices/docs.ts rename to packages/oc-docs/src/store/slices/docs.ts diff --git a/packages/oc-core/src/store/slices/playground.ts b/packages/oc-docs/src/store/slices/playground.ts similarity index 100% rename from packages/oc-core/src/store/slices/playground.ts rename to packages/oc-docs/src/store/slices/playground.ts diff --git a/packages/oc-core/src/store/store.ts b/packages/oc-docs/src/store/store.ts similarity index 100% rename from packages/oc-core/src/store/store.ts rename to packages/oc-docs/src/store/store.ts diff --git a/packages/oc-core/src/styles/App.css b/packages/oc-docs/src/styles/App.css similarity index 100% rename from packages/oc-core/src/styles/App.css rename to packages/oc-docs/src/styles/App.css diff --git a/packages/oc-core/src/styles/index.css b/packages/oc-docs/src/styles/index.css similarity index 100% rename from packages/oc-core/src/styles/index.css rename to packages/oc-docs/src/styles/index.css diff --git a/packages/oc-core/src/ui/CodeEditor/CodeEditor.tsx b/packages/oc-docs/src/ui/CodeEditor/CodeEditor.tsx similarity index 100% rename from packages/oc-core/src/ui/CodeEditor/CodeEditor.tsx rename to packages/oc-docs/src/ui/CodeEditor/CodeEditor.tsx diff --git a/packages/oc-core/src/ui/MinimalComponents.tsx b/packages/oc-docs/src/ui/MinimalComponents.tsx similarity index 100% rename from packages/oc-core/src/ui/MinimalComponents.tsx rename to packages/oc-docs/src/ui/MinimalComponents.tsx diff --git a/packages/oc-core/src/ui/Tabs/StyledWrapper.ts b/packages/oc-docs/src/ui/Tabs/StyledWrapper.ts similarity index 100% rename from packages/oc-core/src/ui/Tabs/StyledWrapper.ts rename to packages/oc-docs/src/ui/Tabs/StyledWrapper.ts diff --git a/packages/oc-core/src/ui/Tabs/Tabs.tsx b/packages/oc-docs/src/ui/Tabs/Tabs.tsx similarity index 100% rename from packages/oc-core/src/ui/Tabs/Tabs.tsx rename to packages/oc-docs/src/ui/Tabs/Tabs.tsx diff --git a/packages/oc-core/src/utils/fileUtils.ts b/packages/oc-docs/src/utils/fileUtils.ts similarity index 100% rename from packages/oc-core/src/utils/fileUtils.ts rename to packages/oc-docs/src/utils/fileUtils.ts diff --git a/packages/oc-core/src/utils/itemUtils.ts b/packages/oc-docs/src/utils/itemUtils.ts similarity index 100% rename from packages/oc-core/src/utils/itemUtils.ts rename to packages/oc-docs/src/utils/itemUtils.ts diff --git a/packages/oc-core/src/utils/yamlUtils.ts b/packages/oc-docs/src/utils/yamlUtils.ts similarity index 100% rename from packages/oc-core/src/utils/yamlUtils.ts rename to packages/oc-docs/src/utils/yamlUtils.ts diff --git a/packages/oc-core/src/vite-env.d.ts b/packages/oc-docs/src/vite-env.d.ts similarity index 100% rename from packages/oc-core/src/vite-env.d.ts rename to packages/oc-docs/src/vite-env.d.ts diff --git a/packages/oc-core/standalone.d.ts b/packages/oc-docs/standalone.d.ts similarity index 100% rename from packages/oc-core/standalone.d.ts rename to packages/oc-docs/standalone.d.ts diff --git a/packages/oc-core/tsconfig.json b/packages/oc-docs/tsconfig.json similarity index 100% rename from packages/oc-core/tsconfig.json rename to packages/oc-docs/tsconfig.json diff --git a/packages/oc-core/vite.config.libraries-bundle.ts b/packages/oc-docs/vite.config.libraries-bundle.ts similarity index 100% rename from packages/oc-core/vite.config.libraries-bundle.ts rename to packages/oc-docs/vite.config.libraries-bundle.ts diff --git a/packages/oc-core/vite.config.standalone.ts b/packages/oc-docs/vite.config.standalone.ts similarity index 100% rename from packages/oc-core/vite.config.standalone.ts rename to packages/oc-docs/vite.config.standalone.ts From e1259b8856bbf9883ec12f6f4ccf9ee43e37898c Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Tue, 18 Nov 2025 02:10:08 +0530 Subject: [PATCH 039/161] feat: move state mgmt of item selection and collapsed to store --- examples/standalone-html/index2.html | 4 +- packages/oc-docs/src/components/Docs/Docs.tsx | 21 ++- .../src/components/Docs/Sidebar/Sidebar.tsx | 78 +++------- .../OpenCollection/OpenCollection.tsx | 141 +++--------------- packages/oc-docs/src/store/slices/docs.ts | 51 ++++++- packages/oc-docs/src/styles/index.css | 1 - packages/oc-docs/src/utils/common.ts | 10 ++ packages/oc-docs/src/utils/items.ts | 70 +++++++++ 8 files changed, 184 insertions(+), 192 deletions(-) create mode 100644 packages/oc-docs/src/utils/common.ts create mode 100644 packages/oc-docs/src/utils/items.ts diff --git a/examples/standalone-html/index2.html b/examples/standalone-html/index2.html index 23f6828c..04c4537a 100644 --- a/examples/standalone-html/index2.html +++ b/examples/standalone-html/index2.html @@ -14,8 +14,8 @@ height: 100vh; } - - + + diff --git a/packages/oc-docs/src/components/Docs/Docs.tsx b/packages/oc-docs/src/components/Docs/Docs.tsx index bf41d154..b2d9fafb 100644 --- a/packages/oc-docs/src/components/Docs/Docs.tsx +++ b/packages/oc-docs/src/components/Docs/Docs.tsx @@ -3,11 +3,11 @@ import type { OpenCollection as OpenCollectionCollection } from '@opencollection import Sidebar from './Sidebar/Sidebar'; import Item from './Item/Item'; import { getItemId, generateSafeId } from '../../utils/itemUtils'; +import { useAppSelector, useAppDispatch } from '../../store/hooks'; +import { selectSelectedItemId, selectItem } from '../../store/slices/docs'; interface DocsProps { docsCollection: OpenCollectionCollection | null; - selectedItemId: string | null; - onItemSelect: (id: string, openPlayground?: boolean) => void; logo: React.ReactNode; theme: 'light' | 'dark' | 'auto'; containerRef: React.RefObject; @@ -16,13 +16,13 @@ interface DocsProps { const Docs: React.FC = ({ docsCollection, - selectedItemId, - onItemSelect, logo, theme, containerRef, filteredCollectionItems }) => { + const dispatch = useAppDispatch(); + const selectedItemId = useAppSelector(selectSelectedItemId); // Flatten all items recursively for rendering const flattenItems = (items: any[], parentPath = ''): any[] => { const result: any[] = []; @@ -67,9 +67,6 @@ const Docs: React.FC = ({ > onItemSelect(id, false)} - logo={logo} theme={theme} />
@@ -82,13 +79,15 @@ const Docs: React.FC = ({ {/* Render all collection items */} {allItems.map((item, index) => { const itemId = getItemId(item); + const itemUuid = (item as any).uuid || itemId; // Use UUID if available, fallback to itemId const safeId = generateSafeId(itemId); const sectionId = `section-${safeId}`; - const isSelected = selectedItemId === safeId || selectedItemId === itemId; + // Compare with UUID first, then fallback to safeId/itemId for backward compatibility + const isSelected = selectedItemId === itemUuid || selectedItemId === safeId || selectedItemId === itemId; return (
@@ -99,8 +98,8 @@ const Docs: React.FC = ({ parentPath="" collection={docsCollection || undefined} onTryClick={() => { - // Select the item and trigger playground open - onItemSelect(safeId, true); + // Select the item by UUID + dispatch(selectItem(itemUuid)); // Scroll to the item setTimeout(() => { const element = document.getElementById(`section-${safeId}`); diff --git a/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx b/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx index 805513e2..d801f20a 100644 --- a/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx +++ b/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx @@ -6,6 +6,8 @@ import Method from '../Method/Method'; import { getItemId, generateSafeId } from '../../../utils/itemUtils'; import OpenCollectionLogo from '../../../assets/opencollection-logo.svg'; import { SidebarContainer, SidebarItems, SidebarItem } from './StyledWrapper'; +import { useAppDispatch, useAppSelector } from '../../../store/hooks'; +import { toggleItem, selectItem, selectSelectedItemId } from '../../../store/slices/docs'; interface ApiEndpoint { id: string; @@ -23,62 +25,23 @@ interface ApiCollection { export interface SidebarProps { collection: OpenCollectionCollection | ApiCollection | null; - activeItemId: string | null; - onSelectItem: (id: string, parentPath?: string) => void; - logo?: React.ReactNode; - className?: string; theme?: 'light' | 'dark' | 'auto'; - isCompact?: boolean; } const Sidebar: React.FC = ({ collection, - activeItemId, - onSelectItem, - logo, - className = '', - theme = 'light', - isCompact = false + theme = 'light' }) => { - const [expandedFolders, setExpandedFolders] = useState>({}); + const dispatch = useAppDispatch(); + const selectedItemId = useAppSelector(selectSelectedItemId); const [hoveredItemId, setHoveredItemId] = useState(null); - useEffect(() => { - if (!collection) return; - - const initExpandedFolders: Record = {}; - - if ('items' in collection) { - const items = collection.items || []; - items.forEach((item: any) => { - if (item.type === 'folder') { - const itemId = getItemId(item); - initExpandedFolders[itemId] = false; - } - }); - } - - setExpandedFolders(initExpandedFolders); - }, [collection]); - - const toggleFolder = useCallback((folderPath: string) => { - setExpandedFolders((prev) => ({ - ...prev, - [folderPath]: !prev[folderPath] - })); - }, []); - - const handleMouseEnter = useCallback((itemId: string) => { - setHoveredItemId(itemId); - }, []); - - const handleMouseLeave = useCallback(() => { - setHoveredItemId(null); - }, []); + const toggleFolder = useCallback((itemUuid: string) => { + dispatch(toggleItem(itemUuid)); + }, [dispatch]); - const handleItemSelect = (id: string, parentPath: string = '') => { - console.log('handleItemSelect', id, 'parentPath:', parentPath); - onSelectItem(id, parentPath); + const handleItemSelect = (uuid: string) => { + dispatch(selectItem(uuid)); }; const normalizedItems = useMemo(() => { @@ -132,18 +95,21 @@ const Sidebar: React.FC = ({ const renderItem = (item: any, level = 0, parentPath = '') => { const itemId = getItemId(item); + const itemUuid = (item as any).uuid || itemId; // Use UUID if available, fallback to itemId const itemName = itemId; const itemPath = parentPath ? `${parentPath}/${itemName}` : itemName; - const fullPathId = parentPath ? `${parentPath.replace(/^\//, '')}/${itemId}` : itemId; + const fullPathId = parentPath ? `${parentPath.replace(/^\//, '')}/${itemUuid}` : itemUuid; const isFolder = item.type === 'folder'; const safeItemId = generateSafeId(itemId); - const isActive = !isFolder && (activeItemId === safeItemId || activeItemId === itemId); + // Use UUID for active state comparison + const isActive = !isFolder && selectedItemId === itemUuid; // Use fullPathId for hover state tracking const isHovered = hoveredItemId === fullPathId; - const isExpanded = expandedFolders[itemId] || false; + // Read isCollapsed from the item itself (defaults to true if not set) + const isExpanded = isFolder ? !((item as any).isCollapsed ?? true) : false; return (
@@ -158,7 +124,7 @@ const Sidebar: React.FC = ({ style={{ paddingLeft: `${level * 16 + 8}px` }} - onClick={() => isFolder ? toggleFolder(itemId) : handleItemSelect(safeItemId, itemPath)} + onClick={() => isFolder ? toggleFolder(itemUuid) : handleItemSelect(itemUuid)} onMouseEnter={() => setHoveredItemId(fullPathId)} onMouseLeave={() => setHoveredItemId(null)} id={`sidebar-item-${fullPathId}`} @@ -216,7 +182,7 @@ const Sidebar: React.FC = ({ }; return ( - + {/* Collection name at top */}
@@ -238,8 +204,10 @@ const Sidebar: React.FC = ({ {filteredEndpoints.map((endpoint: ApiEndpoint) => { const endpointId = endpoint.id || `endpoint-${endpoint.path}`; const safeEndpointId = generateSafeId(endpointId); - const fullPathId = endpoint.path ? `${endpoint.path.replace(/^\//, '')}/${endpointId}` : endpointId; - const isActive = activeItemId === safeEndpointId; + // For endpoints, use the endpoint ID as UUID (they don't have UUIDs assigned) + const endpointUuid = endpointId; + const fullPathId = endpoint.path ? `${endpoint.path.replace(/^\//, '')}/${endpointUuid}` : endpointUuid; + const isActive = selectedItemId === endpointUuid; const isHovered = hoveredItemId === fullPathId; return ( @@ -247,7 +215,7 @@ const Sidebar: React.FC = ({ key={endpointId} as="button" className={`endpoint flex items-center w-full text-left px-3 py-2 ${isActive ? 'active' : ''} ${isHovered ? 'hovered' : ''}`} - onClick={() => handleItemSelect(safeEndpointId, endpoint.path)} + onClick={() => handleItemSelect(endpointUuid)} onMouseEnter={() => setHoveredItemId(fullPathId)} onMouseLeave={() => setHoveredItemId(null)} id={`sidebar-item-${fullPathId}`} diff --git a/packages/oc-docs/src/components/OpenCollection/OpenCollection.tsx b/packages/oc-docs/src/components/OpenCollection/OpenCollection.tsx index ba1639c5..630b725e 100644 --- a/packages/oc-docs/src/components/OpenCollection/OpenCollection.tsx +++ b/packages/oc-docs/src/components/OpenCollection/OpenCollection.tsx @@ -1,7 +1,7 @@ -import React, { useRef, useState, useEffect, useMemo } from 'react'; +import React, { useRef, useState, useEffect } from 'react'; import { Provider } from 'react-redux'; import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; -import type { Item as OpenCollectionItem } from '@opencollection/types/collection/item'; +import type { Item as OpenCollectionItem, Folder } from '@opencollection/types/collection/item'; import type { HttpRequest } from '@opencollection/types/requests/http'; import { useTheme, @@ -16,7 +16,9 @@ import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { selectDocsCollection, setDocsCollection, - clearDocsCollection + clearDocsCollection, + selectItem, + selectSelectedItemId } from '@slices/docs'; import { selectPlaygroundCollection, @@ -77,9 +79,6 @@ interface DesktopLayoutProps { playgroundCollection: OpenCollectionCollection | null; logo: React.ReactNode; theme: 'light' | 'dark' | 'auto'; - currentPageId: string | null; - currentPageItem: OpenCollectionItem | null; - onSelectItem: (id: string, path?: string) => void; containerRef: React.RefObject; filteredCollectionItems: any[]; children?: React.ReactNode; @@ -92,112 +91,46 @@ const DesktopLayout: React.FC = ({ playgroundCollection, logo, theme, - currentPageId, - onSelectItem, containerRef, filteredCollectionItems }) => { - const [selectedItemId, setSelectedItemId] = useState(currentPageId); + const selectedItemId = useAppSelector(selectSelectedItemId); const [playgroundItem, setPlaygroundItem] = useState(null); const [showPlaygroundDrawer, setShowPlaygroundDrawer] = useState(false); - // Find the selected item for playground - const selectedItem = useMemo(() => { - if (!selectedItemId || !docsCollection) return null; - - const findItem = (items: any[]): any => { - for (const item of items) { - const itemId = getItemId(item); - const safeId = generateSafeId(itemId); - if (itemId === selectedItemId || safeId === selectedItemId) { - return item; - } - if (item.type === 'folder' && item.items) { - const found = findItem(item.items); - if (found) return found; - } - } - return null; - }; - - if (docsCollection.items) { - const item = findItem(docsCollection.items); - if (item && item.type === 'http') { - return item as HttpRequest; - } - } - return null; - }, [selectedItemId, docsCollection]); - - // Update playground item when selection changes + // Update playground item when selected item changes useEffect(() => { - if (selectedItem && selectedItem.type === 'http') { - setPlaygroundItem(selectedItem); - // If drawer is open and we select a new HTTP item, update it - if (showPlaygroundDrawer) { - // Item is already set, drawer will update automatically - } - } - }, [selectedItem, showPlaygroundDrawer]); - - const handleItemSelect = (id: string, openPlayground = false) => { - setSelectedItemId(id); - onSelectItem(id); - - // Find the item to set as playground item if it's an HTTP request - const collectionItems = playgroundCollection?.items ?? docsCollection?.items; - if (collectionItems) { + if (selectedItemId && docsCollection) { const findItem = (items: any[]): any => { for (const item of items) { - const itemId = getItemId(item); - const safeId = generateSafeId(itemId); - if (itemId === id || safeId === id) { + const itemUuid = (item as any).uuid; + if (itemUuid === selectedItemId) { return item; } - if (item.type === 'folder' && item.items) { - const found = findItem(item.items); + if ('type' in item && item.type === 'folder' && (item as Folder).items) { + const found = findItem((item as Folder).items || []); if (found) return found; } } return null; }; - const item = findItem(collectionItems); + const item = docsCollection.items ? findItem(docsCollection.items) : null; if (item && item.type === 'http') { setPlaygroundItem(item as HttpRequest); - if (openPlayground) { - setShowPlaygroundDrawer(true); - } } } - - // Scroll to the selected item after a short delay to ensure DOM is updated - setTimeout(() => { - const element = document.getElementById(`section-${id}`); - if (element) { - element.scrollIntoView({ behavior: 'smooth', block: 'start' }); - } - }, 50); - }; + }, [selectedItemId, docsCollection]); const handlePlaygroundItemSelect = (item: HttpRequest) => { // Only update the playground item, don't affect the docs view setPlaygroundItem(item); }; - // Sync with external currentPageId changes - useEffect(() => { - if (currentPageId) { - setSelectedItemId(currentPageId); - } - }, [currentPageId]); - return (
= ({ const playgroundCollection = useAppSelector(selectPlaygroundCollection); const collectionStatus = useAppSelector(selectCollectionStatus); const collectionError = useAppSelector(selectCollectionError); + const selectedItemId = useAppSelector((state) => state.docs.selectedItemId); const containerRef = useRef(null); useTheme(theme); @@ -288,57 +222,27 @@ const OpenCollectionContent: React.FC = ({ const filteredCollectionItems = docsCollection?.items || []; - // Page selection state - const [currentPageId, setCurrentPageId] = useState(null); - const [currentPageItem, setCurrentPageItem] = useState(null); - const isInitialLoad = collectionStatus === 'idle' && !docsCollection && !playgroundCollection; const isLoading = collectionStatus === 'loading' || isInitialLoad; const error = collectionError; - // Handle item selection - const handleSelectItem = (id: string, path?: string) => { - console.log('Selecting item:', id, 'path:', path); - setCurrentPageId(id); - - // Find the item in the collection - const findItem = (items: any[]): any => { - for (const item of items) { - const itemId = item.id || item.uid || item.name || 'unnamed-item'; - if (itemId === id || itemId.toLowerCase().replace(/[^a-z0-9\-_]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') === id) { - return item; - } - if (item.type === 'folder' && item.items) { - const found = findItem(item.items); - if (found) return found; - } - } - return null; - }; - - if (docsCollection && 'items' in docsCollection && docsCollection.items) { - const item = findItem(docsCollection.items); - setCurrentPageItem(item); - } - }; - // Set initial page to first root-level request when collection loads useEffect(() => { - if (docsCollection && currentPageId === null) { + if (docsCollection && selectedItemId === null) { const items = docsCollection.items as any[] | undefined; const firstRequest = items?.find((item) => item.type === 'http'); - if (firstRequest?.name) { - handleSelectItem(firstRequest.name); + if (firstRequest && (firstRequest as any).uuid) { + dispatch(selectItem((firstRequest as any).uuid)); } else if (items && items.length > 0) { const firstItem = items[0]; - if (firstItem && 'name' in firstItem && firstItem.name) { - handleSelectItem(firstItem.name); + if (firstItem && (firstItem as any).uuid) { + dispatch(selectItem((firstItem as any).uuid)); } } } - }, [docsCollection, currentPageId]); + }, [docsCollection, selectedItemId, dispatch]); const { isRunnerMode, toggleRunnerMode } = useRunnerMode(); @@ -354,9 +258,6 @@ const OpenCollectionContent: React.FC = ({ docsCollection, playgroundCollection, theme, - currentPageId, - currentPageItem, - onSelectItem: handleSelectItem, containerRef, filteredCollectionItems }; diff --git a/packages/oc-docs/src/store/slices/docs.ts b/packages/oc-docs/src/store/slices/docs.ts index 8f330753..d7527298 100644 --- a/packages/oc-docs/src/store/slices/docs.ts +++ b/packages/oc-docs/src/store/slices/docs.ts @@ -1,13 +1,35 @@ import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; +import type { Item as OpenCollectionItem, Folder } from '@opencollection/types/collection/item'; import type { RootState } from '../store'; +import { hydrateWithUUIDs, findAndUpdateItem } from '../../utils/items'; export interface DocsState { collection: OpenCollectionCollection | null; + selectedItemId: string | null; } const initialState: DocsState = { - collection: null + collection: null, + selectedItemId: null +}; + +// Helper function to initialize isCollapsed for folders +const initializeCollapsedState = (items: OpenCollectionItem[] | undefined): void => { + if (!items) return; + + for (const item of items) { + if ('type' in item && item.type === 'folder') { + // Initialize isCollapsed to true (collapsed) if not already set + if ((item as any).isCollapsed === undefined) { + (item as any).isCollapsed = true; + } + const folder = item as Folder; + if (folder.items) { + initializeCollapsedState(folder.items); + } + } + } }; const docsSlice = createSlice({ @@ -15,17 +37,40 @@ const docsSlice = createSlice({ initialState, reducers: { setDocsCollection: (state: DocsState, action: PayloadAction) => { - state.collection = action.payload; + // Hydrate collection with UUIDs if collection is provided + const collection = action.payload ? hydrateWithUUIDs(action.payload) : null; + state.collection = collection; + // Initialize isCollapsed for all folders + if (state.collection && state.collection.items) { + initializeCollapsedState(state.collection.items); + } + // Reset selected item when collection changes + state.selectedItemId = null; }, clearDocsCollection: (state: DocsState) => { state.collection = null; + state.selectedItemId = null; + }, + toggleItem: (state: DocsState, action: PayloadAction) => { + const uuid = action.payload; + if (state.collection && state.collection.items) { + findAndUpdateItem(state.collection.items, uuid, (item) => { + // Treat undefined as true (collapsed), then toggle + const currentCollapsed = (item as any).isCollapsed ?? true; + (item as any).isCollapsed = !currentCollapsed; + }); + } + }, + selectItem: (state: DocsState, action: PayloadAction) => { + state.selectedItemId = action.payload; }, } }); -export const { setDocsCollection, clearDocsCollection } = docsSlice.actions; +export const { setDocsCollection, clearDocsCollection, toggleItem, selectItem } = docsSlice.actions; export default docsSlice.reducer; export const selectDocsCollection = (state: RootState) => state.docs.collection; +export const selectSelectedItemId = (state: RootState) => state.docs.selectedItemId; diff --git a/packages/oc-docs/src/styles/index.css b/packages/oc-docs/src/styles/index.css index f734a115..2f241fb2 100644 --- a/packages/oc-docs/src/styles/index.css +++ b/packages/oc-docs/src/styles/index.css @@ -92,7 +92,6 @@ /* Layout dimensions */ --sidebar-width: 260px; - --sidebar-width-compact: 200px; --header-height: 60px; --item-padding-compact: 4px; --item-height-compact: 28px; diff --git a/packages/oc-docs/src/utils/common.ts b/packages/oc-docs/src/utils/common.ts new file mode 100644 index 00000000..819f73ef --- /dev/null +++ b/packages/oc-docs/src/utils/common.ts @@ -0,0 +1,10 @@ +import { customAlphabet } from 'nanoid'; + +// a customized version of nanoid without using _ and - +export const uuid = () => { + // https://github.com/ai/nanoid/blob/main/url-alphabet/index.js + const urlAlphabet = 'useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict'; + const customNanoId = customAlphabet(urlAlphabet, 21); + + return customNanoId(); +}; diff --git a/packages/oc-docs/src/utils/items.ts b/packages/oc-docs/src/utils/items.ts new file mode 100644 index 00000000..a32d74a3 --- /dev/null +++ b/packages/oc-docs/src/utils/items.ts @@ -0,0 +1,70 @@ +import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; +import type { Item as OpenCollectionItem, Folder } from '@opencollection/types/collection/item'; + +/** + * Helper function to find and update an item by UUID + * @param items Array of items to search through + * @param uuid The UUID of the item to find + * @param updater Function to update the found item + * @returns true if item was found and updated, false otherwise + */ +export const findAndUpdateItem = ( + items: OpenCollectionItem[] | undefined, + uuid: string, + updater: (item: any) => void +): boolean => { + if (!items) return false; + + for (const item of items) { + const itemUuid = (item as any).uuid; + if (itemUuid === uuid) { + updater(item); + return true; + } + + if ('type' in item && item.type === 'folder') { + const folder = item as Folder; + if (folder.items && findAndUpdateItem(folder.items, uuid, updater)) { + return true; + } + } + } + + return false; +}; + +/** + * Traverses through the collection and assigns a UUID to each item + * @param collection The OpenCollection to hydrate with UUIDs + * @returns A new collection with UUIDs assigned to each item + */ +export const hydrateWithUUIDs = (collection: OpenCollectionCollection): OpenCollectionCollection => { + const assignUUID = (item: OpenCollectionItem): OpenCollectionItem => { + // Generate UUID if not already present + const uuid = (item as any).uuid || crypto.randomUUID(); + + // Create a new item with UUID + const hydratedItem = { ...item, uuid } as any; + + // If it's a folder, recursively hydrate its children + if ('type' in item && item.type === 'folder') { + const folder = item as Folder; + if (folder.items && folder.items.length > 0) { + hydratedItem.items = folder.items.map(assignUUID); + } + } + + return hydratedItem; + }; + + // Create a new collection object + const hydratedCollection = { ...collection }; + + // Hydrate all items if they exist + if (collection.items && collection.items.length > 0) { + hydratedCollection.items = collection.items.map(assignUUID); + } + + return hydratedCollection; +}; + From 7054f5a20a5d452b58d516516d6a2c7b52c62fb2 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Tue, 18 Nov 2025 03:04:51 +0530 Subject: [PATCH 040/161] chore: ckeanup unused code --- examples/standalone-html/index2.html | 39 +- .../Docs/CodeSnippets/StyledWrapper.ts | 20 - packages/oc-docs/src/components/Docs/Docs.tsx | 66 ++- .../oc-docs/src/components/Docs/Item/Item.tsx | 18 +- .../Docs/Item/Scripts/StyledWrapper.ts | 20 - .../src/components/Docs/Item/StyledWrapper.ts | 19 - .../components/Docs/Method/StyledWrapper.ts | 32 -- .../src/components/Docs/Sidebar/Sidebar.tsx | 130 +----- .../components/Docs/Sidebar/StyledWrapper.ts | 43 +- .../OpenCollection/OpenCollection.tsx | 48 +-- .../PlaygroudDrawer/PlaygroundDrawer.tsx | 4 +- packages/oc-docs/src/hooks/index.ts | 4 +- packages/oc-docs/src/hooks/useRunnerMode.ts | 15 - packages/oc-docs/src/hooks/useTheme.ts | 13 - packages/oc-docs/src/standalone.ts | 20 +- packages/oc-docs/src/styles/index.css | 386 +----------------- .../oc-docs/src/ui/CodeEditor/CodeEditor.tsx | 25 +- 17 files changed, 131 insertions(+), 771 deletions(-) delete mode 100644 packages/oc-docs/src/hooks/useRunnerMode.ts delete mode 100644 packages/oc-docs/src/hooks/useTheme.ts diff --git a/examples/standalone-html/index2.html b/examples/standalone-html/index2.html index 04c4537a..2c079df0 100644 --- a/examples/standalone-html/index2.html +++ b/examples/standalone-html/index2.html @@ -44,8 +44,8 @@ items: - type: "http" name: "Register User" - url: "{{baseUrl}}/auth/register" - method: "POST" + url: "https://jsonplaceholder.typicode.com/users" + method: "GET" headers: - name: "Content-Type" value: "application/json" @@ -58,6 +58,41 @@ "password": "password123" } docs: "Register a new user account" + + - type: "folder" + name: "Verification" + docs: "Account verification endpoints" + items: + - type: "http" + name: "Verify Email" + url: "{{baseUrl}}/auth/verify-email" + method: "POST" + headers: + - name: "Content-Type" + value: "application/json" + body: + type: "json" + data: | + { + "email": "test@example.com", + "verificationCode": "123456" + } + docs: "Verify user email address with verification code" + + - type: "http" + name: "Resend Verification Code" + url: "{{baseUrl}}/auth/resend-verification" + method: "POST" + headers: + - name: "Content-Type" + value: "application/json" + body: + type: "json" + data: | + { + "email": "test@example.com" + } + docs: "Resend email verification code to user" # Login folder - type: "folder" diff --git a/packages/oc-docs/src/components/Docs/CodeSnippets/StyledWrapper.ts b/packages/oc-docs/src/components/Docs/CodeSnippets/StyledWrapper.ts index 2452cb39..19579a94 100644 --- a/packages/oc-docs/src/components/Docs/CodeSnippets/StyledWrapper.ts +++ b/packages/oc-docs/src/components/Docs/CodeSnippets/StyledWrapper.ts @@ -132,26 +132,6 @@ export const StyledWrapper = styled.div` background-color: rgba(34, 197, 94, 0.15); } - .dark & .code-copy-button { - background-color: rgba(255, 255, 255, 0.06); - color: var(--text-secondary); - } - - .dark & .code-copy-button:hover { - background-color: rgba(255, 255, 255, 0.12); - color: var(--text-primary); - } - - .dark & .code-copy-button.copied { - color: #4ade80; - background-color: rgba(34, 197, 94, 0.2); - } - - .dark & .code-copy-button.copied:hover { - background-color: rgba(34, 197, 94, 0.3); - color: #bbf7d0; - } - @media (max-width: 1100px) { .code-samples-container { position: static; diff --git a/packages/oc-docs/src/components/Docs/Docs.tsx b/packages/oc-docs/src/components/Docs/Docs.tsx index b2d9fafb..fc1d5511 100644 --- a/packages/oc-docs/src/components/Docs/Docs.tsx +++ b/packages/oc-docs/src/components/Docs/Docs.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useMemo, useEffect } from 'react'; import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; import Sidebar from './Sidebar/Sidebar'; import Item from './Item/Item'; @@ -8,21 +8,55 @@ import { selectSelectedItemId, selectItem } from '../../store/slices/docs'; interface DocsProps { docsCollection: OpenCollectionCollection | null; - logo: React.ReactNode; - theme: 'light' | 'dark' | 'auto'; - containerRef: React.RefObject; filteredCollectionItems: any[]; + onOpenPlayground?: () => void; } const Docs: React.FC = ({ docsCollection, - logo, - theme, - containerRef, - filteredCollectionItems + filteredCollectionItems, + onOpenPlayground }) => { const dispatch = useAppDispatch(); const selectedItemId = useAppSelector(selectSelectedItemId); + + // Scroll to selected item when it changes + useEffect(() => { + if (selectedItemId && filteredCollectionItems.length > 0) { + // Find the item by UUID to get its safe ID for scrolling + const findItemForScroll = (items: any[]): any => { + for (const item of items) { + const itemUuid = (item as any).uuid; + const itemId = getItemId(item); + const safeId = generateSafeId(itemId); + + // Check if this is the selected item + if (itemUuid === selectedItemId || safeId === selectedItemId || itemId === selectedItemId) { + return { item, safeId }; + } + + // If it's a folder, search recursively + if (item.type === 'folder' && item.items) { + const found = findItemForScroll(item.items); + if (found) return found; + } + } + return null; + }; + + const result = findItemForScroll(filteredCollectionItems); + if (result) { + // Scroll to the item after a short delay to ensure DOM is updated + setTimeout(() => { + const element = document.getElementById(`section-${result.safeId}`); + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + }, 100); + } + } + }, [selectedItemId, filteredCollectionItems]); + // Flatten all items recursively for rendering const flattenItems = (items: any[], parentPath = ''): any[] => { const result: any[] = []; @@ -51,10 +85,6 @@ const Docs: React.FC = ({ return items; }, [filteredCollectionItems]); - const registerSectionRef = (id: string, ref: HTMLDivElement | null) => { - // No-op for now, but can be used for scroll tracking - }; - return ( <>
= ({ borderRight: '1px solid var(--border-color)' }} > - +
{/* Render all collection items */} @@ -93,13 +119,15 @@ const Docs: React.FC = ({ > { // Select the item by UUID dispatch(selectItem(itemUuid)); + // Open the playground drawer + if (onOpenPlayground) { + onOpenPlayground(); + } // Scroll to the item setTimeout(() => { const element = document.getElementById(`section-${safeId}`); diff --git a/packages/oc-docs/src/components/Docs/Item/Item.tsx b/packages/oc-docs/src/components/Docs/Item/Item.tsx index ef6ce1ab..d0cc0d36 100644 --- a/packages/oc-docs/src/components/Docs/Item/Item.tsx +++ b/packages/oc-docs/src/components/Docs/Item/Item.tsx @@ -30,16 +30,12 @@ const methodColors: Record = { const Item = memo(({ item, - registerSectionRef, - theme, parentPath = '', collection, toggleRunnerMode, onTryClick }: { item: any; - registerSectionRef: (id: string, ref: HTMLDivElement | null) => void; - theme: 'light' | 'dark' | 'auto'; parentPath?: string; collection?: any; toggleRunnerMode?: () => void; @@ -50,13 +46,6 @@ const Item = memo(({ const sectionId = generateSectionId(item, parentPath); const baseContainerClass = 'item-container'; - const themedContainerClass = theme ? `${baseContainerClass} ${theme}` : baseContainerClass; - - const sectionRefCallback = useCallback((element: HTMLDivElement | null) => { - if (element) { - registerSectionRef(sectionId, element); - } - }, [sectionId, registerSectionRef]); if (item.type === 'folder') { const folderItem = item as any; @@ -65,7 +54,6 @@ const Item = memo(({
@@ -131,7 +119,6 @@ const Item = memo(({
@@ -179,9 +166,8 @@ const Item = memo(({ return (
@@ -277,7 +263,6 @@ const Item = memo(({
@@ -298,7 +283,6 @@ const Item = memo(({ } return ( - prevProps.theme === nextProps.theme && prevProps.parentPath === nextProps.parentPath ); }); diff --git a/packages/oc-docs/src/components/Docs/Item/Scripts/StyledWrapper.ts b/packages/oc-docs/src/components/Docs/Item/Scripts/StyledWrapper.ts index 8bb1ce0a..57fce6f4 100644 --- a/packages/oc-docs/src/components/Docs/Item/Scripts/StyledWrapper.ts +++ b/packages/oc-docs/src/components/Docs/Item/Scripts/StyledWrapper.ts @@ -111,26 +111,6 @@ export const StyledWrapper = styled.div` background-color: rgba(34, 197, 94, 0.15); } - .dark & .scripts-copy-button { - background-color: rgba(255, 255, 255, 0.06); - color: var(--text-secondary); - } - - .dark & .scripts-copy-button:hover { - background-color: rgba(255, 255, 255, 0.12); - color: var(--text-primary); - } - - .dark & .scripts-copy-button.copied { - color: #4ade80; - background-color: rgba(34, 197, 94, 0.2); - } - - .dark & .scripts-copy-button.copied:hover { - background-color: rgba(34, 197, 94, 0.3); - color: #bbf7d0; - } - .scripts-card .compact-code-view pre { margin: 0; diff --git a/packages/oc-docs/src/components/Docs/Item/StyledWrapper.ts b/packages/oc-docs/src/components/Docs/Item/StyledWrapper.ts index b05294b6..9c6f045b 100644 --- a/packages/oc-docs/src/components/Docs/Item/StyledWrapper.ts +++ b/packages/oc-docs/src/components/Docs/Item/StyledWrapper.ts @@ -209,25 +209,6 @@ export const StyledWrapper = styled.div` background-color: #bfdbfe; } - .dark & .item-type-badge.folder { - background-color: #312e81; - color: #a5b4fc; - } - - .dark & .item-type-badge.script { - background-color: #064e3b; - color: #86efac; - } - - .dark & .badge-try { - color: #bfdbfe; - background-color: #1e3a8a; - } - - .dark & .badge-try:hover { - background-color: #1d4ed8; - } - @media (min-width: 1024px) { .item-content-main { flex-direction: row; diff --git a/packages/oc-docs/src/components/Docs/Method/StyledWrapper.ts b/packages/oc-docs/src/components/Docs/Method/StyledWrapper.ts index 91be2176..2acdbdaf 100644 --- a/packages/oc-docs/src/components/Docs/Method/StyledWrapper.ts +++ b/packages/oc-docs/src/components/Docs/Method/StyledWrapper.ts @@ -42,36 +42,4 @@ export const StyledWrapper = styled.div` background-color: #f0f0f0; color: #4a4a4a; } - - .dark & { - &.get { - background-color: #1a3a5c; - color: #0077ff; - } - - &.post { - background-color: #1a4d1a; - color: #00a060; - } - - &.put { - background-color: #4d2a00; - color: #ff6b00; - } - - &.delete { - background-color: #4d0000; - color: #ff0000; - } - - &.patch { - background-color: #3d003d; - color: #9500b3; - } - - &.options, &.head { - background-color: #2a2a2a; - color: #888888; - } - } `; \ No newline at end of file diff --git a/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx b/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx index d801f20a..4f4bd453 100644 --- a/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx +++ b/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx @@ -1,78 +1,29 @@ -import React, { useState, useMemo, useEffect, useCallback } from 'react'; -import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; +import React from 'react'; +import type { OpenCollection } from '@opencollection/types'; import type { Item as OpenCollectionItem, Folder } from '@opencollection/types/collection/item'; import type { HttpRequest } from '@opencollection/types/requests/http'; import Method from '../Method/Method'; -import { getItemId, generateSafeId } from '../../../utils/itemUtils'; import OpenCollectionLogo from '../../../assets/opencollection-logo.svg'; import { SidebarContainer, SidebarItems, SidebarItem } from './StyledWrapper'; import { useAppDispatch, useAppSelector } from '../../../store/hooks'; -import { toggleItem, selectItem, selectSelectedItemId } from '../../../store/slices/docs'; - -interface ApiEndpoint { - id: string; - method: string; - path: string; - description?: string; -} - -interface ApiCollection { - name: string; - description?: string; - version?: string; - endpoints: ApiEndpoint[]; -} +import { toggleItem, selectItem, selectSelectedItemId, selectDocsCollection } from '../../../store/slices/docs'; export interface SidebarProps { - collection: OpenCollectionCollection | ApiCollection | null; - theme?: 'light' | 'dark' | 'auto'; } -const Sidebar: React.FC = ({ - collection, - theme = 'light' -}) => { +const Sidebar: React.FC = () => { const dispatch = useAppDispatch(); const selectedItemId = useAppSelector(selectSelectedItemId); - const [hoveredItemId, setHoveredItemId] = useState(null); + const collection = useAppSelector(selectDocsCollection); - const toggleFolder = useCallback((itemUuid: string) => { + const toggleFolder = (itemUuid: string) => { dispatch(toggleItem(itemUuid)); - }, [dispatch]); + }; const handleItemSelect = (uuid: string) => { dispatch(selectItem(uuid)); }; - const normalizedItems = useMemo(() => { - if (!collection) return []; - - if ('endpoints' in collection) { - return collection.endpoints.map((endpoint: ApiEndpoint, index: number) => ({ - type: 'http' as const, - name: endpoint.path, - method: endpoint.method, - url: endpoint.path - })); - } else if ('items' in collection) { - return collection.items as any[]; - } - - return []; - }, [collection]); - - const filteredItems = useMemo(() => { - return normalizedItems as OpenCollectionItem[]; - }, [normalizedItems]); - - const filteredEndpoints = useMemo(() => { - if (!collection || !('endpoints' in collection) || !collection.endpoints) { - return []; - } - - return collection.endpoints; - }, [collection]); - const renderFolderIcon = (isExpanded: boolean) => ( = ({ ); const renderItem = (item: any, level = 0, parentPath = '') => { - const itemId = getItemId(item); - const itemUuid = (item as any).uuid || itemId; // Use UUID if available, fallback to itemId - const itemName = itemId; - const itemPath = parentPath ? `${parentPath}/${itemName}` : itemName; - - const fullPathId = parentPath ? `${parentPath.replace(/^\//, '')}/${itemUuid}` : itemUuid; const isFolder = item.type === 'folder'; - const safeItemId = generateSafeId(itemId); // Use UUID for active state comparison - const isActive = !isFolder && selectedItemId === itemUuid; + const isActive = !isFolder && selectedItemId === item.uuid; - // Use fullPathId for hover state tracking - const isHovered = hoveredItemId === fullPathId; // Read isCollapsed from the item itself (defaults to true if not set) const isExpanded = isFolder ? !((item as any).isCollapsed ?? true) : false; return ( -
+
isFolder ? toggleFolder(itemUuid) : handleItemSelect(itemUuid)} - onMouseEnter={() => setHoveredItemId(fullPathId)} - onMouseLeave={() => setHoveredItemId(null)} - id={`sidebar-item-${fullPathId}`} - data-item-id={fullPathId} + onClick={() => isFolder ? toggleFolder(item.uuid) : handleItemSelect(item.uuid)} > {level > 0 && ( @@ -142,7 +79,6 @@ const Sidebar: React.FC = ({ /> )} - {isFolder ? (
{renderFolderIcon(isExpanded)} @@ -156,7 +92,7 @@ const Sidebar: React.FC = ({
- {itemName} + {item.name}
@@ -173,8 +109,7 @@ const Sidebar: React.FC = ({ }} /> - - {((item as Folder).items || []).map((child: OpenCollectionItem) => renderItem(child, level + 1, itemPath))} + {((item as Folder).items || []).map((child: OpenCollectionItem) => renderItem(child, level + 1))}
)}
@@ -193,43 +128,8 @@ const Sidebar: React.FC = ({
- {collection && 'items' in collection && filteredItems && filteredItems.length > 0 && ( - <> - {filteredItems.map((item) => renderItem(item))} - - )} - - {collection && 'endpoints' in collection && filteredEndpoints.length > 0 && ( - <> - {filteredEndpoints.map((endpoint: ApiEndpoint) => { - const endpointId = endpoint.id || `endpoint-${endpoint.path}`; - const safeEndpointId = generateSafeId(endpointId); - // For endpoints, use the endpoint ID as UUID (they don't have UUIDs assigned) - const endpointUuid = endpointId; - const fullPathId = endpoint.path ? `${endpoint.path.replace(/^\//, '')}/${endpointUuid}` : endpointUuid; - const isActive = selectedItemId === endpointUuid; - const isHovered = hoveredItemId === fullPathId; - - return ( - handleItemSelect(endpointUuid)} - onMouseEnter={() => setHoveredItemId(fullPathId)} - onMouseLeave={() => setHoveredItemId(null)} - id={`sidebar-item-${fullPathId}`} - data-item-id={fullPathId} - > - - {endpoint.path} - - ); - })} - + {collection?.items?.length && ( + collection.items.map((item) => renderItem(item)) )} @@ -246,7 +146,7 @@ const Sidebar: React.FC = ({ alt="OpenCollection" className="w-full max-w-[140px] mx-auto" style={{ - filter: theme === 'dark' ? 'invert(1) grayscale(100%)' : 'grayscale(100%)' + filter: 'grayscale(100%)' }} /> diff --git a/packages/oc-docs/src/components/Docs/Sidebar/StyledWrapper.ts b/packages/oc-docs/src/components/Docs/Sidebar/StyledWrapper.ts index 5176b1f7..50efde7b 100644 --- a/packages/oc-docs/src/components/Docs/Sidebar/StyledWrapper.ts +++ b/packages/oc-docs/src/components/Docs/Sidebar/StyledWrapper.ts @@ -24,10 +24,6 @@ export const SidebarContainer = styled.div` border-radius: 10px; } - .dark & ::-webkit-scrollbar-thumb { - background-color: rgba(255, 255, 255, 0.2); - } - /* Sidebar Logo styling */ & .logo { padding: 0 8px 12px 8px; @@ -35,10 +31,6 @@ export const SidebarContainer = styled.div` margin-bottom: 6px; } - .dark & .logo { - border-bottom: 1px solid rgba(255, 255, 255, 0.06); - } - &.compact .logo { padding: 8px; } @@ -78,50 +70,21 @@ export const SidebarItem = styled.div` color: #212121; } - .dark & { - color: rgba(255, 255, 255, 0.6); - } - - .dark &:hover, .dark &.hovered { - background-color: rgba(255, 255, 255, 0.06); - color: rgba(255, 255, 255, 0.9); - } - &.active { background-color: rgba(0, 0, 0, 0.08); color: #212121; font-weight: 500; } - .dark &.active { - background-color: rgba(255, 255, 255, 0.1); - color: #fff; - font-weight: 500; - } - - &.active:hover, &.active.hovered { + &.active:hover { background-color: rgba(0, 0, 0, 0.1); } - .dark &.active:hover, .dark &.active.hovered { - background-color: rgba(255, 255, 255, 0.12); - } - - .dark &.folder { - color: rgba(255, 255, 255, 0.4); - background-color: transparent; - } - &.folder:hover { background-color: transparent; color: #212121; } - .dark &.folder:hover { - background-color: transparent; - color: rgba(255, 255, 255, 0.5); - } - & .method-badge { font-size: 10px; padding: 3px 6px; @@ -136,10 +99,6 @@ export const SidebarItem = styled.div` opacity: 0.85; } - .dark & .method-badge { - opacity: 0.9; - } - ${SidebarContainer}.compact & .method-badge { padding: 2px 4px; min-width: 36px; diff --git a/packages/oc-docs/src/components/OpenCollection/OpenCollection.tsx b/packages/oc-docs/src/components/OpenCollection/OpenCollection.tsx index 630b725e..245cc7f6 100644 --- a/packages/oc-docs/src/components/OpenCollection/OpenCollection.tsx +++ b/packages/oc-docs/src/components/OpenCollection/OpenCollection.tsx @@ -3,14 +3,9 @@ import { Provider } from 'react-redux'; import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; import type { Item as OpenCollectionItem, Folder } from '@opencollection/types/collection/item'; import type { HttpRequest } from '@opencollection/types/requests/http'; -import { - useTheme, - useRunnerMode -} from '../../hooks'; import type { OpenCollection as IOpenCollection } from '@opencollection/types'; import PlaygroundDrawer from '../Playground/PlaygroudDrawer/PlaygroundDrawer'; import Docs from '../Docs/Docs'; -import { getItemId, generateSafeId } from '../../utils/itemUtils'; import { parseYaml } from '../../utils/yamlUtils'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { @@ -77,28 +72,20 @@ const resolveCollectionSource = async ( interface DesktopLayoutProps { docsCollection: OpenCollectionCollection | null; playgroundCollection: OpenCollectionCollection | null; - logo: React.ReactNode; - theme: 'light' | 'dark' | 'auto'; - containerRef: React.RefObject; filteredCollectionItems: any[]; children?: React.ReactNode; - isRunnerMode?: boolean; - toggleRunnerMode?: () => void; } const DesktopLayout: React.FC = ({ docsCollection, playgroundCollection, - logo, - theme, - containerRef, filteredCollectionItems }) => { const selectedItemId = useAppSelector(selectSelectedItemId); const [playgroundItem, setPlaygroundItem] = useState(null); const [showPlaygroundDrawer, setShowPlaygroundDrawer] = useState(false); - // Update playground item when selected item changes + // Update playground item when selected item changes (but don't open drawer) useEffect(() => { if (selectedItemId && docsCollection) { const findItem = (items: any[]): any => { @@ -118,6 +105,7 @@ const DesktopLayout: React.FC = ({ const item = docsCollection.items ? findItem(docsCollection.items) : null; if (item && item.type === 'http') { setPlaygroundItem(item as HttpRequest); + // Don't open drawer automatically - only open when "Try" is clicked } } }, [selectedItemId, docsCollection]); @@ -127,14 +115,16 @@ const DesktopLayout: React.FC = ({ setPlaygroundItem(item); }; + const handleOpenPlayground = () => { + setShowPlaygroundDrawer(true); + }; + return (
= ({ collection={playgroundCollection} selectedItem={playgroundItem} onSelectItem={handlePlaygroundItemSelect} - theme={theme} />
); @@ -154,14 +143,11 @@ const DesktopLayout: React.FC = ({ */ export interface OpenCollectionProps { collection: IOpenCollection | string | File; - theme?: 'light' | 'dark' | 'auto'; logo?: React.ReactNode; } const OpenCollectionContent: React.FC = ({ collection, - theme = 'light', - logo, }) => { const dispatch = useAppDispatch(); const docsCollection = useAppSelector(selectDocsCollection); @@ -169,9 +155,6 @@ const OpenCollectionContent: React.FC = ({ const collectionStatus = useAppSelector(selectCollectionStatus); const collectionError = useAppSelector(selectCollectionError); const selectedItemId = useAppSelector((state) => state.docs.selectedItemId); - const containerRef = useRef(null); - - useTheme(theme); useEffect(() => { let isActive = true; @@ -244,8 +227,6 @@ const OpenCollectionContent: React.FC = ({ } }, [docsCollection, selectedItemId, dispatch]); - const { isRunnerMode, toggleRunnerMode } = useRunnerMode(); - if (isLoading) { return
Loading...
; } @@ -254,23 +235,14 @@ const OpenCollectionContent: React.FC = ({ return
Error: {error}
; } - const commonProps = { + const desktopProps = { docsCollection, playgroundCollection, - theme, - containerRef, - filteredCollectionItems - }; - - const desktopProps = { - ...commonProps, - logo, - isRunnerMode, - toggleRunnerMode + filteredCollectionItems, }; return ( -
+
); diff --git a/packages/oc-docs/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx b/packages/oc-docs/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx index 42247e58..aa10e5e8 100644 --- a/packages/oc-docs/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx +++ b/packages/oc-docs/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx @@ -13,7 +13,6 @@ interface PlaygroundDrawerProps { collection: OpenCollectionCollection | null; selectedItem: HttpRequest | null; onSelectItem: (item: HttpRequest) => void; - theme?: 'light' | 'dark' | 'auto'; } const MIN_HEIGHT = 300; @@ -26,8 +25,7 @@ const PlaygroundDrawer: React.FC = ({ onClose, collection, selectedItem, - onSelectItem, - theme = 'light' + onSelectItem }) => { const [height, setHeight] = useState(() => isOpen ? getDefaultHeight() : 0); const [isDragging, setIsDragging] = useState(false); diff --git a/packages/oc-docs/src/hooks/index.ts b/packages/oc-docs/src/hooks/index.ts index 4fdadef4..a9414cd0 100644 --- a/packages/oc-docs/src/hooks/index.ts +++ b/packages/oc-docs/src/hooks/index.ts @@ -1,3 +1 @@ -export { useTheme } from './useTheme'; -export { useMarkdownRenderer } from './useMarkdownRenderer'; -export { useRunnerMode } from './useRunnerMode'; \ No newline at end of file +export { useMarkdownRenderer } from './useMarkdownRenderer'; \ No newline at end of file diff --git a/packages/oc-docs/src/hooks/useRunnerMode.ts b/packages/oc-docs/src/hooks/useRunnerMode.ts deleted file mode 100644 index 3bb97cc5..00000000 --- a/packages/oc-docs/src/hooks/useRunnerMode.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useState } from 'react'; - -export const useRunnerMode = () => { - const [isRunnerMode, setIsRunnerMode] = useState(false); - - const toggleRunnerMode = () => { - setIsRunnerMode(prev => !prev); - }; - - return { - isRunnerMode, - setIsRunnerMode, - toggleRunnerMode - }; -}; \ No newline at end of file diff --git a/packages/oc-docs/src/hooks/useTheme.ts b/packages/oc-docs/src/hooks/useTheme.ts deleted file mode 100644 index 52a3a7de..00000000 --- a/packages/oc-docs/src/hooks/useTheme.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useEffect } from 'react'; - -export const useTheme = (theme: 'light' | 'dark' | 'auto') => { - useEffect(() => { - const root = document.documentElement; - - root.classList.remove('dark', 'light'); - - if (theme === 'dark' || theme === 'light') { - root.classList.add(theme); - } - }, [theme]); -}; \ No newline at end of file diff --git a/packages/oc-docs/src/standalone.ts b/packages/oc-docs/src/standalone.ts index af032288..0cb0a0ba 100644 --- a/packages/oc-docs/src/standalone.ts +++ b/packages/oc-docs/src/standalone.ts @@ -1,14 +1,27 @@ import React from 'react'; import { createRoot, Root } from 'react-dom/client'; import './styles/index.css'; +// Import Prism and language components to ensure they're bundled +import Prism from 'prismjs'; +import 'prismjs/components/prism-javascript'; +import 'prismjs/components/prism-bash'; +import 'prismjs/components/prism-python'; +import 'prismjs/components/prism-json'; +import 'prismjs/components/prism-xml-doc'; +import 'prismjs/components/prism-http'; +import 'prismjs/components/prism-graphql'; import OpenCollection from './components/OpenCollection/OpenCollection'; import type { OpenCollection as IOpenCollection } from '@opencollection/types'; import { parseCollectionContent } from './utils/yamlUtils'; +// Ensure Prism is available globally for any code that might access it +if (typeof window !== 'undefined') { + (window as any).Prism = Prism; +} + export interface OpenCollectionOptions { target: HTMLElement; opencollection: any; - theme?: 'light' | 'dark' | 'auto'; logo?: string; } @@ -83,7 +96,6 @@ export class OpenCollectionRenderer { this.root.render(React.createElement(OpenCollection, { collection, - theme: this.options.theme || 'light', logo: this.createLogoElement() })); } @@ -93,10 +105,6 @@ export class OpenCollectionRenderer { this.render(); } - public updateTheme(theme: 'light' | 'dark' | 'auto') { - this.options.theme = theme; - this.render(); - } public destroy() { if (this.root) { diff --git a/packages/oc-docs/src/styles/index.css b/packages/oc-docs/src/styles/index.css index 2f241fb2..2999ddee 100644 --- a/packages/oc-docs/src/styles/index.css +++ b/packages/oc-docs/src/styles/index.css @@ -11,42 +11,30 @@ /* Tabs */ --oc-tabs-color: rgb(155, 155, 155); --oc-tabs-active-color: rgb(50, 46, 44) !important; - --oc-tabs-color-dark: #a0a0a0; - --oc-tabs-active-color-dark: #ffffff; /* Background colors */ --oc-sidebar-bg: rgb(250, 250, 250); --background-light: #ffffff; - --background-dark: #1e1e1e; /* Text colors */ --oc-text-primary: rgb(52, 52, 52); --text-light: rgb(52, 52, 52); - --text-dark: #d4d4d4; /* Border colors */ --border-light: #e8e8e8; - --border-dark: #333333; /* Code colors */ --code-bg-light: #ffffff; - --code-bg-dark: #2d2d2d; --code-text-light: #515151; - --code-text-dark: rgb(204, 204, 204); /* Sidebar colors */ --sidebar-bg-light: #FCFCFC; - --sidebar-bg-dark: #1a1a1a; --sidebar-color-light: rgb(52, 52, 52); - --sidebar-color-dark: #ccc; /* Table colors */ --table-header-bg-light: #fafafa; - --table-header-bg-dark: #333333; --table-row-odd-bg-light: #ffffff; - --table-row-odd-bg-dark: #2A2D2F; --table-row-even-bg-light: #ffffff; - --table-row-even-bg-dark: #1e1e1e; /* Status colors */ --success-color: #22c55e; @@ -64,11 +52,8 @@ /* Input and badge colors */ --input-bg-light: #fafafa; - --input-bg-dark: #2d2d2d; --badge-bg-light: #f5f5f5; - --badge-bg-dark: #374151; --badge-text-light: #4b5563; - --badge-text-dark: #d1d5db; /* Apple colors for method badges */ --apple-blue-100: #e6f1ff; @@ -109,7 +94,7 @@ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif; - /* Current theme variables (default to light) */ + /* Theme variables */ --background-color: var(--background-light); --text-primary: var(--text-light); --text-secondary: #6e6e6e; @@ -126,64 +111,6 @@ --badge-text: var(--badge-text-light); } -/* Dark theme variables */ -.dark { - --background-color: var(--background-dark); - --text-primary: var(--text-dark); - --text-secondary: #a0a0a0; - --border-color: var(--border-dark); - --code-bg: var(--code-bg-dark); - --code-text: var(--code-text-dark); - --prose-code-bg: var(--code-bg); - --prose-code-text: var(--code-text); - --table-header-bg: var(--table-header-bg-dark); - --table-row-odd-bg: var(--table-row-odd-bg-dark); - --table-row-even-bg: var(--table-row-even-bg-dark); - --input-bg: var(--input-bg-dark); - --badge-bg: var(--badge-bg-dark); - --badge-text: var(--badge-text-dark); - --oc-tabs-color: var(--oc-tabs-color-dark); - --oc-tabs-active-color: var(--oc-tabs-active-color-dark); -} - -/* Auto theme detection */ -@media (prefers-color-scheme: dark) { - .theme-auto { - --background-color: var(--background-dark); - --text-primary: var(--text-dark); - --text-secondary: #a0a0a0; - --border-color: var(--border-dark); - --code-bg: var(--code-bg-dark); - --code-text: var(--code-text-dark); - --prose-code-bg: var(--code-bg); - --prose-code-text: var(--code-text); - --table-header-bg: var(--table-header-bg-dark); - --table-row-odd-bg: var(--table-row-odd-bg-dark); - --table-row-even-bg: var(--table-row-even-bg-dark); - --input-bg: var(--input-bg-dark); - --badge-bg: var(--badge-bg-dark); - --badge-text: var(--badge-text-dark); - --oc-tabs-color: var(--oc-tabs-color-dark); - --oc-tabs-active-color: var(--oc-tabs-active-color-dark); - } -} - -@media (prefers-color-scheme: light) { - .theme-auto { - --background-color: var(--background-light); - --text-primary: var(--text-light); - --text-secondary: #6e6e6e; - --border-color: var(--border-light); - --code-bg: var(--code-bg-light); - --code-text: var(--code-text-light); - --prose-code-bg: var(--code-bg); - --prose-code-text: var(--code-text); - --table-header-bg: var(--table-header-bg-light); - --table-row-odd-bg: var(--table-row-odd-bg-light); - --table-row-even-bg: var(--table-row-even-bg-light); - } -} - /* Main playground container */ .oc-playground { width: 100%; @@ -202,71 +129,6 @@ line-height: 1.5; } -/* Theme-specific overrides */ -.oc-playground.light { - --background-color: var(--background-light); - --text-primary: var(--text-light); - --text-secondary: #6e6e6e; - --border-color: var(--border-light); - --code-bg: var(--code-bg-light); - --code-text: var(--code-text-light); - --prose-code-bg: var(--code-bg); - --prose-code-text: var(--code-text); - --table-header-bg: var(--table-header-bg-light); - --table-row-odd-bg: var(--table-row-odd-bg-light); - --table-row-even-bg: var(--table-row-even-bg-light); - --input-bg: var(--input-bg-light); - --badge-bg: var(--badge-bg-light); - --badge-text: var(--badge-text-light); -} - -.oc-playground.dark { - --background-color: var(--background-dark); - --text-primary: var(--text-dark); - --text-secondary: #a0a0a0; - --border-color: var(--border-dark); - --code-bg: var(--code-bg-dark); - --code-text: var(--code-text-dark); - --prose-code-bg: var(--code-bg); - --prose-code-text: var(--code-text); - --table-header-bg: var(--table-header-bg-dark); - --table-row-odd-bg: var(--table-row-odd-bg-dark); - --table-row-even-bg: var(--table-row-even-bg-dark); - --input-bg: var(--input-bg-dark); - --badge-bg: var(--badge-bg-dark); - --badge-text: var(--badge-text-dark); -} - -@media (prefers-color-scheme: dark) { - .oc-playground.auto { - --background-color: var(--background-dark); - --text-primary: var(--text-dark); - --text-secondary: #a0a0a0; - --border-color: var(--border-dark); - --code-bg: var(--code-bg-dark); - --code-text: var(--code-text-dark); - --prose-code-bg: var(--code-bg); - --prose-code-text: var(--code-text); - --table-header-bg: var(--table-header-bg-dark); - --table-row-odd-bg: var(--table-row-odd-bg-dark); - --table-row-even-bg: var(--table-row-even-bg-dark); - } -} - -@media (prefers-color-scheme: light) { - .oc-playground.auto { - --background-color: var(--background-light); - --text-primary: var(--text-light); - --text-secondary: #6e6e6e; - --border-color: var(--border-light); - --code-bg: var(--code-bg-light); - --code-text: var(--code-text-light); - --prose-code-bg: var(--code-bg); - --prose-code-text: var(--code-text); - --table-header-bg: var(--table-header-bg-light); - } -} - /* Header styling */ .glass-header { height: var(--header-height); @@ -276,10 +138,6 @@ border-bottom: 1px solid var(--border-color); } -.dark .glass-header { - background-color: rgba(30, 30, 30, 0.8); -} - html, body { font-kerning: none; text-rendering: optimizeSpeed; @@ -314,11 +172,6 @@ html, body { padding: 12px 0 0 0; } - .dark .playground-sidebar { - background-color: var(--sidebar-bg-dark); - color: var(--sidebar-color-dark); - } - .playground-content { flex: 1; overflow-y: auto; @@ -420,63 +273,32 @@ html, body { color: #0057b7; } -.dark .method-badge.get { - background-color: rgba(0, 112, 243, 0.15); - color: #0077ff; -} - .method-badge.post { background-color: rgba(0, 155, 0, 0.09); color: #00824d; } -.dark .method-badge.post { - background-color: rgba(0, 155, 0, 0.15); - color: #00a060; -} - .method-badge.put { background-color: rgba(243, 112, 0, 0.09); color: #b74600; } -.dark .method-badge.put { - background-color: rgba(243, 112, 0, 0.15); - color: #ff6b00; -} - .method-badge.delete { background-color: rgba(243, 0, 0, 0.09); color: #b70000; } -.dark .method-badge.delete { - background-color: rgba(243, 0, 0, 0.15); - color: #ff0000; -} - .method-badge.patch { background-color: rgba(155, 0, 155, 0.09); color: #6b0082; } -.dark .method-badge.patch { - background-color: rgba(155, 0, 155, 0.15); - color: #9500b3; -} - .method-badge.options, .method-badge.head { background-color: rgba(100, 100, 100, 0.09); color: #4a4a4a; } -.dark .method-badge.options, -.dark .method-badge.head { - background-color: rgba(100, 100, 100, 0.15); - color: #888888; -} - /* Code block styling */ .code-block { border-radius: 6px; @@ -489,12 +311,6 @@ html, body { border: 1px solid var(--border-color); } -.dark .code-block { - background-color: var(--code-bg-dark); - border: 1px solid var(--border-dark); - color: var(--code-text-dark); -} - .code-block pre { margin: 0; padding: 0.75rem; @@ -545,31 +361,6 @@ html, body { border-bottom: none; } -.dark .api-table th { - background-color: var(--table-header-bg-dark); - color: var(--code-text-dark); - border-bottom: 1px solid var(--border-dark); -} - -.dark .api-table td { - border-bottom: 1px solid var(--border-dark); - color: var(--text-dark); -} - -.dark .api-table tr:nth-child(odd) { - background-color: var(--table-row-odd-bg-dark); -} - -.dark .api-table tr:nth-child(even) { - background-color: var(--table-row-even-bg-dark); -} - -.dark .api-table input { - background-color: var(--input-bg-dark); - color: var(--code-text-dark); - border: 1px solid var(--border-dark); -} - /* Table row styling */ tr.themed-row:nth-child(odd) { background-color: var(--table-row-odd-bg) !important; @@ -579,15 +370,6 @@ tr.themed-row:nth-child(even) { background-color: var(--table-row-even-bg) !important; } -/* Dark row styling for backward compatibility */ -tr.dark-row:nth-child(odd) { - background-color: var(--table-row-odd-bg-dark) !important; -} - -tr.dark-row:nth-child(even) { - background-color: var(--table-row-even-bg-dark) !important; -} - /* Apple-style rounded corners */ .rounded-apple { border-radius: 10px; @@ -598,18 +380,10 @@ tr.dark-row:nth-child(even) { box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04), 0 0.5px 2px rgba(0, 0, 0, 0.06); } -.dark .shadow-apple-sm { - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2), 0 0.5px 2px rgba(0, 0, 0, 0.3); -} - .shadow-apple-md { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05), 0 1px 3px rgba(0, 0, 0, 0.08); } -.dark .shadow-apple-md { - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3), 0 1px 3px rgba(0, 0, 0, 0.4); -} - /* Apple-style scrollbars */ .oc-playground ::-webkit-scrollbar { width: 8px; @@ -627,11 +401,6 @@ tr.dark-row:nth-child(even) { background-clip: content-box; } -.oc-playground.dark ::-webkit-scrollbar-thumb { - background-color: #4d4d4d; - border-radius: 4px; -} - .oc-playground ::-webkit-scrollbar-thumb:hover { background-color: #5a5a5a; } @@ -648,17 +417,6 @@ tr.dark-row:nth-child(even) { animation: skeleton 1.4s ease infinite; } -.dark .skeleton { - background: linear-gradient( - 90deg, - rgba(45, 45, 45, 0.8) 25%, - rgba(55, 55, 55, 0.8) 37%, - rgba(45, 45, 45, 0.8) 63% - ); - background-size: 400% 100%; - animation: skeleton 1.4s ease infinite; -} - @keyframes skeleton { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } @@ -775,79 +533,6 @@ tr.dark-row:nth-child(even) { color: #39bd7d; /* Bold sage */ } -/* Smooth Software Syntax Highlighting - Dark Theme */ -.dark .token.comment, -.dark .token.prolog, -.dark .token.doctype, -.dark .token.cdata { - color: #8595ab; /* Steel gray */ - font-style: italic; -} - -.dark .token.punctuation { - color: #a5b0c8; /* Light steel */ -} - -.dark .token.property { - color: #529fff; /* Bold periwinkle - for JSON properties */ -} - -.dark .token.tag, -.dark .token.boolean, -.dark .token.number, -.dark .token.constant, -.dark .token.symbol { - color: #50e5d5; /* Bold mint */ -} - -.dark .token.selector, -.dark .token.attr-name, -.dark .token.string, -.dark .token.char, -.dark .token.builtin { - color: #e8b454; /* Bold gold */ -} - -.dark .token.operator, -.dark .token.entity, -.dark .token.url, -.dark .language-css .token.string, -.dark .style .token.string { - color: #a5b0c8; /* Light steel */ -} - -.dark .token.variable { - color: #529fff; /* Bold periwinkle */ -} - -.dark .token.atrule, -.dark .token.attr-value, -.dark .token.keyword { - color: #f47682; /* Bold rose */ -} - -.dark .token.function, -.dark .token.class-name { - color: #529fff; /* Bold periwinkle blue */ -} - -.dark .token.regex, -.dark .token.important { - color: #e8b454; /* Bold gold */ -} - -.dark .token.parameter { - color: #c589e8; /* Bold lavender */ -} - -.dark .token.deleted { - color: #f55d5d; /* Bold coral */ -} - -.dark .token.inserted { - color: #39bd7d; /* Bold sage */ -} - /* Focus styles for accessibility */ :focus-visible { outline: 2px solid var(--primary-color); @@ -884,16 +569,6 @@ tr.dark-row:nth-child(even) { background-color: #e5e7eb; } -.dark .btn-secondary { - background-color: var(--input-bg-dark); - color: var(--text-dark); - border: 1px solid var(--border-dark); -} - -.dark .btn-secondary:hover { - background-color: #3a3a3a; -} - /* Markdown documentation styles */ .markdown-documentation { @@ -1071,10 +746,6 @@ tr.dark-row:nth-child(even) { z-index: 10; } -.dark .console-view.is-loading .code-block::after { - background: rgba(255,255,255,0.05); -} - /* Endpoint info styling */ .endpoint-info { padding: 8px 0; @@ -1098,17 +769,8 @@ tr.dark-row:nth-child(even) { } } -@keyframes sectionHighlightDark { - 0% { - background-color: rgba(84, 109, 229, 0.3); - } - 100% { - background-color: transparent; - } -} - .section-highlight { - animation: sectionHighlightDark 2s ease-out; + animation: sectionHighlight 2s ease-out; border-radius: 8px; } @@ -1209,17 +871,6 @@ tr.dark-row:nth-child(even) { font-weight: 600; } -/* Dark theme adjustments */ -.dark { - --heading-color: var(--text-dark); - --border-color: var(--border-dark); - --card-bg: var(--background-dark); - --code-bg: var(--code-bg); - --table-header-bg: var(--table-header-bg-dark); - --badge-bg: var(--badge-bg-dark); - --badge-color: var(--badge-text-dark); -} - .request-runner-container { @apply px-6 py-4; } @@ -1302,19 +953,11 @@ tr.dark-row:nth-child(even) { } } -.dark .code-tab:hover { - background-color: rgba(255, 255, 255, 0.05); -} - .code-tab.active { color: var(--primary-color); background-color: rgba(0, 112, 243, 0.1); } -.dark .code-tab.active { - background-color: rgba(0, 112, 243, 0.2); -} - .copy-button { @apply flex items-center gap-1 px-2 py-1 text-xs transition-colors rounded; color: var(--text-secondary); @@ -1325,10 +968,6 @@ tr.dark-row:nth-child(even) { } } -.dark .copy-button:hover { - background-color: rgba(255, 255, 255, 0.05); -} - .code-content { @apply p-3 overflow-x-auto; background-color: var(--code-bg); @@ -1359,15 +998,6 @@ tr.dark-row:nth-child(even) { background-color: #9ca3af; } -/* Dark mode scrollbar */ -.dark .code-content:hover::-webkit-scrollbar-thumb { - background-color: #4d4d4d; -} - -.dark .code-content::-webkit-scrollbar-thumb:hover { - background-color: #6b7280; -} - /* Firefox scrollbar */ .code-content { scrollbar-width: thin; @@ -1378,10 +1008,6 @@ tr.dark-row:nth-child(even) { scrollbar-color: #d1d5db transparent; } -.dark .code-content:hover { - scrollbar-color: #4d4d4d transparent; -} - .code-content pre { @apply m-0 text-xs leading-relaxed; color: var(--code-text); @@ -1403,10 +1029,6 @@ tr.dark-row:nth-child(even) { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } -.dark .status-badge:hover { - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); -} - /* Icon Button */ .icon-button { @apply p-2 rounded transition-colors; @@ -1445,10 +1067,6 @@ tr.dark-row:nth-child(even) { } } -.dark .tab-button:hover { - background-color: rgba(255, 255, 255, 0.03); -} - .tab-button.active { color: var(--primary-color); border-color: var(--primary-color); diff --git a/packages/oc-docs/src/ui/CodeEditor/CodeEditor.tsx b/packages/oc-docs/src/ui/CodeEditor/CodeEditor.tsx index 27654dd3..5a5fe8ff 100644 --- a/packages/oc-docs/src/ui/CodeEditor/CodeEditor.tsx +++ b/packages/oc-docs/src/ui/CodeEditor/CodeEditor.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import Editor from '@monaco-editor/react'; interface CodeEditorProps { @@ -16,27 +16,6 @@ const CodeEditor: React.FC = ({ readOnly = false, height = '300px' }) => { - const [theme, setTheme] = useState<'light' | 'dark'>('light'); - - useEffect(() => { - // Detect current theme from document classes - const root = document.documentElement; - const isDark = root.classList.contains('dark'); - setTheme(isDark ? 'dark' : 'light'); - - // Listen for theme changes - const observer = new MutationObserver(() => { - const isDark = root.classList.contains('dark'); - setTheme(isDark ? 'dark' : 'light'); - }); - - observer.observe(root, { - attributes: true, - attributeFilter: ['class'] - }); - - return () => observer.disconnect(); - }, []); const handleChange = (newValue: string | undefined) => { onChange(newValue || ''); @@ -55,7 +34,7 @@ const CodeEditor: React.FC = ({ language={language} value={value} onChange={handleChange} - theme={theme === 'dark' ? 'vs-dark' : 'vs'} + theme="vs" options={{ readOnly, minimap: { enabled: false }, From e3a794197416bc8bdec9b820d787e1c1c8623445 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Tue, 18 Nov 2025 03:15:15 +0530 Subject: [PATCH 041/161] feat: playground sidebar revamp --- .../PlaygroudDrawer/PlaygroundDrawer.tsx | 294 ++++++++---------- .../Playground/QueryBar/StyledWrapper.ts | 6 +- .../RequestHeader/RequestHeader.tsx | 6 - .../components/Playground/Sidebar/Sidebar.tsx | 154 +++++++++ .../Playground/Sidebar/StyledWrapper.ts | 122 ++++++++ 5 files changed, 403 insertions(+), 179 deletions(-) diff --git a/packages/oc-docs/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx b/packages/oc-docs/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx index aa10e5e8..bc3a942b 100644 --- a/packages/oc-docs/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx +++ b/packages/oc-docs/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx @@ -3,8 +3,8 @@ import type { OpenCollection as OpenCollectionCollection } from '@opencollection import type { Item as OpenCollectionItem, Folder } from '@opencollection/types/collection/item'; import type { HttpRequest } from '@opencollection/types/requests/http'; import Playground from '../Playground'; -import { getItemId, generateSafeId } from '../../../utils/itemUtils'; -import Method from '../../Docs/Method/Method'; +import Sidebar from '../Sidebar/Sidebar'; +import { hydrateWithUUIDs, findAndUpdateItem } from '../../../utils/items'; import { StyledBackdrop, StyledDrawer, StyledDragBar } from './StyledWrapper'; interface PlaygroundDrawerProps { @@ -30,166 +30,135 @@ const PlaygroundDrawer: React.FC = ({ const [height, setHeight] = useState(() => isOpen ? getDefaultHeight() : 0); const [isDragging, setIsDragging] = useState(false); const [isCollapsed, setIsCollapsed] = useState(false); - const [expandedFolders, setExpandedFolders] = useState>({}); + const [hydratedCollection, setHydratedCollection] = useState(null); + const [selectedItemId, setSelectedItemId] = useState(null); const drawerRef = useRef(null); const dragStartY = useRef(0); const dragStartHeight = useRef(0); - // Initialize expanded folders + // Hydrate collection with UUIDs and initialize collapsed state useEffect(() => { - if (!collection?.items) return; + if (!collection) { + setHydratedCollection(null); + return; + } - const initExpandedFolders: Record = {}; + const hydrated = hydrateWithUUIDs(collection); - const traverseItems = (items: OpenCollectionItem[]) => { - items.forEach((item) => { - if ((item as any).type === 'folder') { - const itemId = getItemId(item); - initExpandedFolders[itemId] = false; - if ('items' in item && item.items) { - traverseItems(item.items); + // Initialize isCollapsed for all folders + const initializeCollapsedState = (items: OpenCollectionItem[] | undefined): void => { + if (!items) return; + + for (const item of items) { + if ('type' in item && item.type === 'folder') { + // Initialize isCollapsed to true (collapsed) if not already set + if ((item as any).isCollapsed === undefined) { + (item as any).isCollapsed = true; + } + const folder = item as Folder; + if (folder.items) { + initializeCollapsedState(folder.items); } } - }); + } }; - traverseItems(collection.items); - setExpandedFolders(initExpandedFolders); + if (hydrated.items) { + initializeCollapsedState(hydrated.items); + } + + setHydratedCollection(hydrated); }, [collection]); - const toggleFolder = useCallback((folderId: string) => { - setExpandedFolders((prev) => ({ - ...prev, - [folderId]: !prev[folderId] - })); - }, []); - - const renderFolderIcon = (isExpanded: boolean) => ( - - - - ); + // Update selectedItemId when selectedItem changes + useEffect(() => { + if (!selectedItem || !hydratedCollection) { + setSelectedItemId(null); + return; + } + + // Find the UUID of the selected item + const findItemUUID = (items: OpenCollectionItem[] | undefined): string | null => { + if (!items) return null; + + for (const item of items) { + const itemUuid = (item as any).uuid; + // Compare by checking if this is the selected item + if (itemUuid && 'type' in item && (item as any).type === 'http') { + const httpItem = item as HttpRequest; + if (httpItem.name === selectedItem.name && + httpItem.method === selectedItem.method && + httpItem.url === selectedItem.url) { + return itemUuid; + } + } + + if ('type' in item && (item as any).type === 'folder') { + const folder = item as Folder; + if (folder.items) { + const found = findItemUUID(folder.items); + if (found) return found; + } + } + } + + return null; + }; + + const uuid = findItemUUID(hydratedCollection.items); + setSelectedItemId(uuid); + }, [selectedItem, hydratedCollection]); - const renderItem = (item: OpenCollectionItem, level = 0) => { - const itemId = getItemId(item); - const safeItemId = generateSafeId(itemId); - const isFolder = (item as any).type === 'folder'; - const isExpanded = expandedFolders[itemId] || false; - const isSelected = !isFolder && selectedItem && getItemId(selectedItem) === itemId; + const toggleFolder = useCallback((uuid: string) => { + if (!hydratedCollection?.items) return; + + setHydratedCollection((prev) => { + if (!prev) return prev; + + const updated = { ...prev }; + findAndUpdateItem(updated.items, uuid, (item) => { + const currentCollapsed = (item as any).isCollapsed ?? true; + (item as any).isCollapsed = !currentCollapsed; + }); + + return updated; + }); + }, [hydratedCollection]); - if (isFolder) { - const folder = item as Folder; - return ( -
-
toggleFolder(itemId)} - style={{ - display: 'flex', - alignItems: 'center', - gap: '8px', - padding: '8px 12px', - paddingLeft: `${level * 16 + 12}px`, - cursor: 'pointer', - borderRadius: '6px', - marginBottom: '2px', - color: '#212529', - transition: 'all 0.15s ease', - userSelect: 'none' - }} - onMouseEnter={(e) => { - e.currentTarget.style.backgroundColor = '#e9ecef'; - }} - onMouseLeave={(e) => { - e.currentTarget.style.backgroundColor = 'transparent'; - }} - > -
-
- {renderFolderIcon(isExpanded)} -
- - - - - {itemId} - -
-
- {isExpanded && folder.items && ( -
- {folder.items.map((child: OpenCollectionItem) => renderItem(child, level + 1))} -
- )} -
- ); - } else if ((item as any).type === 'http') { - const httpItem = item as HttpRequest; - return ( -
onSelectItem(httpItem)} - style={{ - display: 'flex', - alignItems: 'center', - gap: '8px', - padding: '8px 12px', - paddingLeft: `${level * 16 + 12}px`, - cursor: 'pointer', - borderRadius: '6px', - marginBottom: '2px', - backgroundColor: isSelected ? '#0d6efd' : 'transparent', - color: isSelected ? 'white' : '#212529', - transition: 'all 0.15s ease' - }} - onMouseEnter={(e) => { - if (!isSelected) { - e.currentTarget.style.backgroundColor = '#e9ecef'; - } - }} - onMouseLeave={(e) => { - if (!isSelected) { - e.currentTarget.style.backgroundColor = 'transparent'; - } - }} - > - - - {httpItem.name || itemId} - -
- ); + const handleSelectItem = useCallback((uuid: string) => { + if (!hydratedCollection?.items) return; + + // Find the item by UUID + let foundItem: HttpRequest | null = null; + + const findItem = (items: OpenCollectionItem[] | undefined): boolean => { + if (!items) return false; + + for (const item of items) { + const itemUuid = (item as any).uuid; + if (itemUuid === uuid && 'type' in item && (item as any).type === 'http') { + foundItem = item as HttpRequest; + return true; + } + + if ('type' in item && (item as any).type === 'folder') { + const folder = item as Folder; + if (folder.items && findItem(folder.items)) { + return true; + } + } + } + + return false; + }; + + findItem(hydratedCollection.items); + + if (foundItem) { + onSelectItem(foundItem); } - return null; - }; + }, [hydratedCollection, onSelectItem]); + useEffect(() => { if (isOpen) { @@ -396,30 +365,20 @@ const PlaygroundDrawer: React.FC = ({ {/* Sidebar */}
-
-

- Playground -

-
- -
- {collection?.items && collection.items.map((item) => renderItem(item))} -
+
{/* Main Playground Content */} @@ -435,7 +394,6 @@ const PlaygroundDrawer: React.FC = ({ ) : (
= ({

{item.name || 'Untitled Request'} diff --git a/packages/oc-docs/src/components/Playground/Sidebar/Sidebar.tsx b/packages/oc-docs/src/components/Playground/Sidebar/Sidebar.tsx index e69de29b..ed795299 100644 --- a/packages/oc-docs/src/components/Playground/Sidebar/Sidebar.tsx +++ b/packages/oc-docs/src/components/Playground/Sidebar/Sidebar.tsx @@ -0,0 +1,154 @@ +import React from 'react'; +import type { OpenCollection } from '@opencollection/types'; +import type { Item as OpenCollectionItem, Folder } from '@opencollection/types/collection/item'; +import type { HttpRequest } from '@opencollection/types/requests/http'; +import Method from '../../Docs/Method/Method'; +import OpenCollectionLogo from '../../../assets/opencollection-logo.svg'; +import { SidebarContainer, SidebarItems, SidebarItem } from './StyledWrapper'; + +export interface SidebarProps { + collection: OpenCollection | null; + selectedItemId: string | null; + onSelectItem: (uuid: string) => void; + onToggleFolder: (uuid: string) => void; +} + +const Sidebar: React.FC = ({ + collection, + selectedItemId, + onSelectItem, + onToggleFolder +}) => { + const renderFolderIcon = (isExpanded: boolean) => ( + + + + ); + + const renderItem = (item: any, level = 0, parentPath = '') => { + + const isFolder = item.type === 'folder'; + // Use UUID for active state comparison + const isActive = !isFolder && selectedItemId === item.uuid; + + // Read isCollapsed from the item itself (defaults to true if not set) + const isExpanded = isFolder ? !((item as any).isCollapsed ?? true) : false; + + return ( +
+ isFolder ? onToggleFolder(item.uuid) : onSelectItem(item.uuid)} + > + + {level > 0 && ( +
+ )} + + {isFolder ? ( +
+ {renderFolderIcon(isExpanded)} +
+ ) : ( + + )} + + +
+ {item.name} +
+ + + + {isFolder && isExpanded && (item as Folder).items && ( +
+ +
+ + {((item as Folder).items || []).map((child: OpenCollectionItem) => renderItem(child, level + 1))} +
+ )} +
+ ); + }; + + return ( + + {/* Collection name at top */} +
+
+

+ {collection?.name || 'API Collection'} +

+
+
+ + + {collection?.items?.length && ( + collection.items.map((item) => renderItem(item)) + )} + + + {/* OpenCollection Logo */} +
+ + OpenCollection + +
+
+ ); +}; + +export default Sidebar; + diff --git a/packages/oc-docs/src/components/Playground/Sidebar/StyledWrapper.ts b/packages/oc-docs/src/components/Playground/Sidebar/StyledWrapper.ts index e69de29b..50efde7b 100644 --- a/packages/oc-docs/src/components/Playground/Sidebar/StyledWrapper.ts +++ b/packages/oc-docs/src/components/Playground/Sidebar/StyledWrapper.ts @@ -0,0 +1,122 @@ +import styled from '@emotion/styled'; + +export const SidebarContainer = styled.div` + height: 100%; + display: flex; + flex-direction: column; + + &.compact { + min-width: var(--sidebar-width-compact); + max-width: var(--sidebar-width-compact); + } + + /* Sidebar scrollbar styling */ + & ::-webkit-scrollbar { + width: 6px; + } + + & ::-webkit-scrollbar-track { + background: transparent; + } + + & ::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.2); + border-radius: 10px; + } + + /* Sidebar Logo styling */ + & .logo { + padding: 0 8px 12px 8px; + border-bottom: 1px solid rgba(0, 0, 0, 0.06); + margin-bottom: 6px; + } + + &.compact .logo { + padding: 8px; + } +`; + +export const SidebarItems = styled.div` + padding: 0; + overflow-y: auto; + flex-grow: 1; + + ${SidebarContainer}.compact & { + padding: 0 4px; + } +`; + +export const SidebarItem = styled.div` + cursor: pointer; + transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1); + border-radius: 0; + margin-bottom: 0; + position: relative; + overflow: hidden; + padding: 6px 12px; + display: flex; + align-items: center; +// font-size: 13px; + color: #212121; + + ${SidebarContainer}.compact & { + padding: 4px 8px; + font-size: 12px; + margin-bottom: 1px; + } + + &:hover, &.hovered { + background-color: rgba(0, 0, 0, 0.04); + color: #212121; + } + + &.active { + background-color: rgba(0, 0, 0, 0.08); + color: #212121; + font-weight: 500; + } + + &.active:hover { + background-color: rgba(0, 0, 0, 0.1); + } + + &.folder:hover { + background-color: transparent; + color: #212121; + } + + & .method-badge { + font-size: 10px; + padding: 3px 6px; + border-radius: 4px; + font-weight: 600; + min-width: 42px; + text-align: center; + letter-spacing: 0.03em; + transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1); + text-transform: uppercase; + margin-right: 8px; + opacity: 0.85; + } + + ${SidebarContainer}.compact & .method-badge { + padding: 2px 4px; + min-width: 36px; + font-size: 10px; + } + + /* Fix for click events */ + & * { + pointer-events: none; + } + + & svg { + pointer-events: all; + } + + ${SidebarContainer}.compact & svg { + width: 14px; + height: 14px; + } +`; + From 248629e554250a39dcfbf2bbe3487cf9f2011de9 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Tue, 18 Nov 2025 03:46:17 +0530 Subject: [PATCH 042/161] refactor:playground styling adjustments --- .../src/components/Playground/Playground.tsx | 2 +- .../Playground/QueryBar/QueryBar.tsx | 57 ++++++++-------- .../Playground/QueryBar/StyledWrapper.ts | 54 ++++++++++++--- .../RequestHeader/RequestHeader.tsx | 54 ++++++++++++--- .../Playground/ResponsePane/ResponsePane.tsx | 66 ++++++++++++------- packages/oc-docs/src/styles/index.css | 5 -- 6 files changed, 160 insertions(+), 78 deletions(-) diff --git a/packages/oc-docs/src/components/Playground/Playground.tsx b/packages/oc-docs/src/components/Playground/Playground.tsx index f4082aa3..7ad514e7 100644 --- a/packages/oc-docs/src/components/Playground/Playground.tsx +++ b/packages/oc-docs/src/components/Playground/Playground.tsx @@ -83,7 +83,7 @@ const Playground: React.FC = ({ item, collection }) => { }, [isDragging]); return ( -
+
= ({ item, onSendRequest, isLoading, onI onItemChange({ ...item, url: newUrl }); }; - const handleMethodChange = (newMethod: string) => { - setMethod(newMethod); - onItemChange({ ...item, method: newMethod }); - }; - - const methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']; - const getMethodColor = (method: string) => { const colors: { [key: string]: string } = { - 'GET': '#10b981', - 'POST': '#f59e0b', - 'PUT': '#3b82f6', - 'PATCH': '#8b5cf6', - 'DELETE': '#ef4444', - 'HEAD': '#6b7280', - 'OPTIONS': '#6b7280' + 'GET': '#059669', // muted green + 'POST': '#d97706', // muted orange + 'PUT': '#2563eb', // muted blue + 'PATCH': '#7c3aed', // muted purple + 'DELETE': '#dc2626', // muted red + 'HEAD': '#64748b', // muted gray + 'OPTIONS': '#64748b' // muted gray }; - return colors[method] || '#6b7280'; + return colors[method] || '#64748b'; }; return ( @@ -50,18 +43,19 @@ const QueryBar: React.FC = ({ item, onSendRequest, isLoading, onI height: '36px' }} > -
- + {method} +
= ({ item, onSendRequest, isLoading, onI value={url} onChange={(e) => handleUrlChange(e.target.value)} placeholder="Enter request URL" - className="flex-1 px-3 text-sm" + className="flex-1 px-3 text-xs font-normal" + style={{ + fontFamily: 'var(--font-mono)', + fontSize: '12px', + fontWeight: 400 + }} onKeyPress={(e) => { if (e.key === 'Enter' && url.trim() && !isLoading) { onSendRequest(); @@ -80,10 +79,10 @@ const QueryBar: React.FC = ({ item, onSendRequest, isLoading, onI diff --git a/packages/oc-docs/src/components/Playground/QueryBar/StyledWrapper.ts b/packages/oc-docs/src/components/Playground/QueryBar/StyledWrapper.ts index 2ba66f39..718021c5 100644 --- a/packages/oc-docs/src/components/Playground/QueryBar/StyledWrapper.ts +++ b/packages/oc-docs/src/components/Playground/QueryBar/StyledWrapper.ts @@ -3,11 +3,15 @@ import styled from '@emotion/styled'; export const StyledWrapper = styled.div` border: 1px solid var(--border-color); border-radius: 6px; + overflow: hidden; + transition: border-color 0.25s cubic-bezier(0.4, 0, 0.2, 1), + box-shadow 0.25s cubic-bezier(0.4, 0, 0.2, 1); + background-color: var(--bg-primary); - select { - background-color: transparent; - outline: none; - color: var(--text-primary); + &:focus-within { + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(217, 119, 6, 0.1), + 0 1px 2px rgba(0, 0, 0, 0.05); } input { @@ -15,19 +19,49 @@ export const StyledWrapper = styled.div` background-color: transparent; color: var(--text-primary); border-radius: 0; + border: none; + transition: background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1); &::placeholder { color: var(--text-secondary); + opacity: 0.6; + transition: opacity 0.2s ease; + } + + &:focus::placeholder { + opacity: 0.45; + } + + &:focus { + background-color: rgba(0, 0, 0, 0.01); } } button.send { background-color: var(--primary-color); - border-top-right-radius: var(--oc-radius); - border-bottom-right-radius: var(--oc-radius); - border-top-left-radius: 0; - border-bottom-left-radius: 0; - font-size: 13px; - text-transform: uppercase; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.01em; + transition: background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + + &::before { + content: ''; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 1px; + background: rgba(0, 0, 0, 0.08); + } + + &:hover:not(:disabled) { + background-color: #c2690a; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } } `; \ No newline at end of file diff --git a/packages/oc-docs/src/components/Playground/RequestPane/RequestHeader/RequestHeader.tsx b/packages/oc-docs/src/components/Playground/RequestPane/RequestHeader/RequestHeader.tsx index d712b0f0..d7999deb 100644 --- a/packages/oc-docs/src/components/Playground/RequestPane/RequestHeader/RequestHeader.tsx +++ b/packages/oc-docs/src/components/Playground/RequestPane/RequestHeader/RequestHeader.tsx @@ -19,37 +19,71 @@ const RequestHeader: React.FC = ({ }) => { return (
-
+

{item.name || 'Untitled Request'}

-
- {collection.environments && collection.environments.length > 0 && ( +
+ {(collection as any).environments && (collection as any).environments.length > 0 && ( handleParamChange(index, 'enabled', e.target.checked)} - className="w-4 h-4" - /> - handleParamChange(index, 'name', e.target.value)} - placeholder="Key" - className="flex-1 px-2 py-1 text-sm border rounded" - style={{ - backgroundColor: 'var(--bg-primary)', - borderColor: 'var(--border-color)', - color: 'var(--text-primary)' - }} - /> - handleParamChange(index, 'value', e.target.value)} - placeholder="Value" - className="flex-1 px-2 py-1 text-sm border rounded" - style={{ - backgroundColor: 'var(--bg-primary)', - borderColor: 'var(--border-color)', - color: 'var(--text-primary)' - }} - /> - - {param.type || 'query'} - - -
- )) - )} -
-
- ); - - const renderHeaders = () => ( -
-
-
- - Headers - - -
- - {(item.headers || []).length === 0 ? ( -
- No headers defined. Click "Add Header" to add one. -
- ) : ( - (item.headers || []).map((header, index) => ( -
- handleHeaderChange(index, 'disabled', !e.target.checked)} - className="w-4 h-4" - /> - handleHeaderChange(index, 'name', e.target.value)} - placeholder="Header" - className="flex-1 px-2 py-1 text-sm border rounded" - style={{ - backgroundColor: 'var(--bg-primary)', - borderColor: 'var(--border-color)', - color: 'var(--text-primary)' - }} - /> - handleHeaderChange(index, 'value', e.target.value)} - placeholder="Value" - className="flex-1 px-2 py-1 text-sm border rounded" - style={{ - backgroundColor: 'var(--bg-primary)', - borderColor: 'var(--border-color)', - color: 'var(--text-primary)' - }} - /> - -
- )) - )} -
-
- ); + return ( +
+
+ + Headers + +
+ +
+ ); + }; const renderBody = () => ( -
+
@@ -259,93 +158,31 @@ const RequestPane: React.FC = ({ item, onItemChange }) => { height="300px" /> ) : Array.isArray(item.body) ? ( -
-
- - Form Data - - -
- - {(item.body as any[]).length === 0 ? ( -
- No form fields defined. Click "Add Field" to add one. -
- ) : ( - (item.body as any[]).map((field: any, index: number) => ( -
- { - const updatedBody = [...(item.body as any[])]; - updatedBody[index] = { ...updatedBody[index], enabled: e.target.checked }; - onItemChange({ ...item, body: updatedBody }); - }} - className="w-4 h-4" - /> - { - const updatedBody = [...(item.body as any[])]; - updatedBody[index] = { ...updatedBody[index], name: e.target.value }; - onItemChange({ ...item, body: updatedBody }); - }} - placeholder="Key" - className="flex-1 px-2 py-1 text-sm border rounded" - style={{ - backgroundColor: 'var(--bg-primary)', - borderColor: 'var(--border-color)', - color: 'var(--text-primary)' - }} - /> - { - const updatedBody = [...(item.body as any[])]; - updatedBody[index] = { ...updatedBody[index], value: e.target.value }; - onItemChange({ ...item, body: updatedBody }); - }} - placeholder="Value" - className="flex-1 px-2 py-1 text-sm border rounded" - style={{ - backgroundColor: 'var(--bg-primary)', - borderColor: 'var(--border-color)', - color: 'var(--text-primary)' - }} - /> - + (() => { + const formBodyData: KeyValueRow[] = (item.body as any[]).map((field: any, index: number) => ({ + id: `form-${index}`, + name: field.name || '', + value: field.value || '', + enabled: field.enabled !== false + })); + + return ( +
+
+ + Form Data +
- )) - )} -
+ +
+ ); + })() ) : (
Unsupported body type @@ -356,7 +193,7 @@ const RequestPane: React.FC = ({ item, onItemChange }) => { ); const renderAuth = () => ( -
+
@@ -415,7 +252,7 @@ const RequestPane: React.FC = ({ item, onItemChange }) => { onChange={(e) => { onItemChange({ ...item, - auth: { ...item.auth!, username: e.target.value } + auth: { type: 'basic', username: e.target.value, password: item.auth?.type === 'basic' ? item.auth.password : undefined } }); }} className="flex-1 px-2 py-1 text-sm border rounded" @@ -437,7 +274,7 @@ const RequestPane: React.FC = ({ item, onItemChange }) => { onChange={(e) => { onItemChange({ ...item, - auth: { ...item.auth!, password: e.target.value } + auth: { type: 'basic', password: e.target.value, username: item.auth?.type === 'basic' ? item.auth.username : undefined } }); }} className="flex-1 px-2 py-1 text-sm border rounded" @@ -463,7 +300,7 @@ const RequestPane: React.FC = ({ item, onItemChange }) => { onChange={(e) => { onItemChange({ ...item, - auth: { ...item.auth!, token: e.target.value } + auth: { type: 'bearer', token: e.target.value } }); }} className="flex-1 px-2 py-1 text-sm border rounded" @@ -487,9 +324,10 @@ const RequestPane: React.FC = ({ item, onItemChange }) => { type="text" value={item.auth.key || ''} onChange={(e) => { + const currentAuth = item.auth?.type === 'apikey' ? item.auth : { type: 'apikey' as const }; onItemChange({ ...item, - auth: { ...item.auth!, key: e.target.value } + auth: { ...currentAuth, type: 'apikey', key: e.target.value } }); }} className="flex-1 px-2 py-1 text-sm border rounded" @@ -509,9 +347,10 @@ const RequestPane: React.FC = ({ item, onItemChange }) => { type="text" value={item.auth.value || ''} onChange={(e) => { + const currentAuth = item.auth?.type === 'apikey' ? item.auth : { type: 'apikey' as const }; onItemChange({ ...item, - auth: { ...item.auth!, value: e.target.value } + auth: { ...currentAuth, type: 'apikey', value: e.target.value } }); }} className="flex-1 px-2 py-1 text-sm border rounded" @@ -530,9 +369,11 @@ const RequestPane: React.FC = ({ item, onItemChange }) => { handleFieldChange(index, 'enabled', e.target.checked)} + /> + )} + + )} + + { + inputRefs.current[`${index}-name`] = el; + }} + type="text" + className="text-input" + value={row.name} + placeholder={isLastEmptyRow ? keyPlaceholder : ''} + onChange={(e) => handleFieldChange(index, 'name', e.target.value)} + autoComplete="off" + autoCorrect="off" + autoCapitalize="off" + spellCheck={false} + /> + + + { + inputRefs.current[`${index}-value`] = el; + }} + type="text" + className="text-input" + value={row.value} + placeholder={isLastEmptyRow ? valuePlaceholder : ''} + onChange={(e) => handleFieldChange(index, 'value', e.target.value)} + autoComplete="off" + autoCorrect="off" + autoCapitalize="off" + spellCheck={false} + /> + + {additionalColumns.map(col => ( + + {!isLastEmptyRow && col.render(row, index)} + + ))} + + {!isLastEmptyRow && !disableDelete && ( + + )} + + + ); + })} + + +
+
+ ); +}; + +export default KeyValueTable; + From 22520afc13c676fe5bd63df21ef627f95df67cd1 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sun, 23 Nov 2025 04:56:04 +0530 Subject: [PATCH 045/161] refactor: update schema definitions for request bodies and authentication types --- packages/oc-spec/.gitignore | 3 - packages/oc-spec/src/schema.json | 786 ------------------------------- 2 files changed, 789 deletions(-) delete mode 100644 packages/oc-spec/.gitignore delete mode 100644 packages/oc-spec/src/schema.json diff --git a/packages/oc-spec/.gitignore b/packages/oc-spec/.gitignore deleted file mode 100644 index 6acc570d..00000000 --- a/packages/oc-spec/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules -dist -.DS_Store \ No newline at end of file diff --git a/packages/oc-spec/src/schema.json b/packages/oc-spec/src/schema.json deleted file mode 100644 index 3d0a8983..00000000 --- a/packages/oc-spec/src/schema.json +++ /dev/null @@ -1,786 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schema.opencollection.com/json/draft-07/opencollection/v1.0.0", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the collection" - }, - "description": { - "$ref": "#/$defs/Description" - }, - "environments": { - "type": "array", - "description": "Array of environments", - "items": { - "$ref": "#/$defs/Environment" - } - }, - "items": { - "type": "array", - "description": "Array of items in the collection", - "items": { - "$ref": "#/$defs/Item" - } - }, - "base": { - "type": "object", - "description": "Base configuration for the collection", - "properties": { - "headers": { - "type": "array", - "description": "Array of request headers", - "items": { - "$ref": "#/$defs/RequestHeader" - } - }, - "auth": { - "$ref": "#/$defs/Auth" - }, - "variables": { - "type": "array", - "description": "Array of variables", - "items": { - "$ref": "#/$defs/Variable" - } - }, - "scripts": { - "$ref": "#/$defs/Scripts" - } - }, - "additionalProperties": false - }, - "docs": { - "type": "string", - "description": "The documentation" - } - }, - "$defs": { - "Description": { - "description": "The description", - "oneOf": [ - { - "type": "object", - "properties": { - "content": { - "type": "string", - "description": "The content of the description" - }, - "type": { - "type": "string", - "description": "The MIME type of the content" - } - }, - "required": ["content", "type"], - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "RequestHeader": { - "type": "object", - "description": "A request header with name, value, description, and disabled state", - "properties": { - "name": { - "type": "string", - "description": "The header name" - }, - "value": { - "type": "string", - "description": "The header value" - }, - "description": { - "$ref": "#/$defs/Description" - }, - "disabled": { - "type": "boolean", - "description": "Whether the header is disabled" - } - }, - "required": ["name", "value"], - "additionalProperties": false - }, - "RequestParam": { - "type": "object", - "description": "A request parameter with name, value, description, type, and enabled state", - "properties": { - "name": { - "type": "string", - "description": "The parameter name" - }, - "value": { - "type": "string", - "description": "The parameter value" - }, - "description": { - "$ref": "#/$defs/Description" - }, - "type": { - "type": "string", - "enum": ["query", "path"], - "description": "The type of parameter" - }, - "enabled": { - "type": "boolean", - "description": "Whether the parameter is enabled" - } - }, - "required": ["name", "value", "type"], - "additionalProperties": false - }, - "RequestBody": { - "oneOf": [ - { - "$ref": "#/$defs/RawBody" - }, - { - "$ref": "#/$defs/FormUrlEncodedBody" - }, - { - "$ref": "#/$defs/MultipartFormBody" - }, - { - "$ref": "#/$defs/FileBody" - }, - { - "type": "null" - } - ] - }, - "RawBody": { - "type": "object", - "description": "Raw request body with type and data", - "properties": { - "type": { - "type": "string", - "enum": ["json", "text", "xml", "sparql"], - "description": "The type of raw body content" - }, - "data": { - "type": "string", - "description": "The raw body data" - } - }, - "required": ["type", "data"], - "additionalProperties": false - }, - "FormUrlEncodedBody": { - "type": "array", - "description": "Form URL encoded body as array of key-value pairs", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The form field name" - }, - "value": { - "type": "string", - "description": "The form field value" - }, - "description": { - "$ref": "#/$defs/Description" - }, - "enabled": { - "type": "boolean", - "description": "Whether the form field is enabled" - } - }, - "required": ["name", "value"], - "additionalProperties": false - } - }, - "MultipartFormBody": { - "type": "array", - "description": "Multipart form body as array of form parts", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The form part name" - }, - "type": { - "type": "string", - "enum": ["text", "file"], - "description": "The type of form part" - }, - "value": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ], - "description": "The form part value" - }, - "description": { - "$ref": "#/$defs/Description" - }, - "enabled": { - "type": "boolean", - "description": "Whether the form part is enabled" - } - }, - "required": ["name", "type", "value"], - "additionalProperties": false - } - }, - "FileBody": { - "type": "array", - "description": "File body as array of file objects", - "items": { - "type": "object", - "properties": { - "filePath": { - "type": "string", - "description": "Path to the file" - }, - "contentType": { - "type": "string", - "description": "MIME type of the file" - }, - "selected": { - "type": "boolean", - "description": "Whether the file is selected" - } - }, - "required": ["filePath", "contentType", "selected"], - "additionalProperties": false - } - }, - "Assertion": { - "type": "object", - "description": "An assertion for response validation", - "properties": { - "expression": { - "type": "string", - "description": "The expression to evaluate" - }, - "operator": { - "type": "string", - "description": "The comparison operator" - }, - "value": { - "type": "string", - "description": "The expected value" - }, - "enabled": { - "type": "boolean", - "description": "Whether the assertion is enabled" - }, - "description": { - "$ref": "#/$defs/Description" - } - }, - "required": ["expression", "operator", "value"], - "additionalProperties": false - }, - "Auth": { - "description": "Authentication", - "oneOf": [ - { - "$ref": "#/$defs/AuthAwsV4" - }, - { - "$ref": "#/$defs/AuthBasic" - }, - { - "$ref": "#/$defs/AuthWsse" - }, - { - "$ref": "#/$defs/AuthBearer" - }, - { - "$ref": "#/$defs/AuthDigest" - }, - { - "$ref": "#/$defs/AuthNTLM" - }, - { - "$ref": "#/$defs/AuthApiKey" - } - ] - }, - "AuthAwsV4": { - "type": "object", - "description": "AWS V4 authentication", - "properties": { - "type": { - "type": "string", - "const": "awsv4" - }, - "accessKeyId": { - "type": "string", - "description": "AWS access key ID" - }, - "secretAccessKey": { - "type": "string", - "description": "AWS secret access key" - }, - "sessionToken": { - "type": "string", - "description": "AWS session token" - }, - "service": { - "type": "string", - "description": "AWS service name" - }, - "region": { - "type": "string", - "description": "AWS region" - }, - "profileName": { - "type": "string", - "description": "AWS profile name" - } - }, - "required": ["type"], - "additionalProperties": false - }, - "AuthBasic": { - "type": "object", - "description": "Basic authentication", - "properties": { - "type": { - "type": "string", - "const": "basic" - }, - "username": { - "type": "string", - "description": "Username for basic auth" - }, - "password": { - "type": "string", - "description": "Password for basic auth" - } - }, - "required": ["type"], - "additionalProperties": false - }, - "AuthWsse": { - "type": "object", - "description": "WSSE authentication", - "properties": { - "type": { - "type": "string", - "const": "wsse" - }, - "username": { - "type": "string", - "description": "Username for WSSE auth" - }, - "password": { - "type": "string", - "description": "Password for WSSE auth" - } - }, - "required": ["type"], - "additionalProperties": false - }, - "AuthBearer": { - "type": "object", - "description": "Bearer token authentication", - "properties": { - "type": { - "type": "string", - "const": "bearer" - }, - "token": { - "type": "string", - "description": "Bearer token" - } - }, - "required": ["type"], - "additionalProperties": false - }, - "AuthDigest": { - "type": "object", - "description": "Digest authentication", - "properties": { - "type": { - "type": "string", - "const": "digest" - }, - "username": { - "type": "string", - "description": "Username for digest auth" - }, - "password": { - "type": "string", - "description": "Password for digest auth" - } - }, - "required": ["type"], - "additionalProperties": false - }, - "AuthNTLM": { - "type": "object", - "description": "NTLM authentication", - "properties": { - "type": { - "type": "string", - "const": "ntlm" - }, - "username": { - "type": "string", - "description": "Username for NTLM auth" - }, - "password": { - "type": "string", - "description": "Password for NTLM auth" - }, - "domain": { - "type": "string", - "description": "Domain for NTLM auth" - } - }, - "required": ["type"], - "additionalProperties": false - }, - "AuthApiKey": { - "type": "object", - "description": "API Key authentication", - "properties": { - "type": { - "type": "string", - "const": "apikey" - }, - "key": { - "type": "string", - "description": "API key name" - }, - "value": { - "type": "string", - "description": "API key value" - }, - "placement": { - "type": "string", - "enum": ["header", "query"], - "description": "Where to place the API key" - } - }, - "required": ["type"], - "additionalProperties": false - }, - "Item": { - "oneOf": [ - { - "$ref": "#/$defs/HttpRequest" - }, - { - "$ref": "#/$defs/GraphQLRequest" - }, - { - "$ref": "#/$defs/GrpcRequest" - }, - { - "$ref": "#/$defs/Folder" - }, - { - "$ref": "#/$defs/Script" - } - ] - }, - "HttpRequest": { - "type": "object", - "description": "HTTP request configuration", - "properties": { - "name": { - "type": "string", - "description": "The name of the request" - }, - "type": { - "type": "string", - "const": "http", - "description": "The type of request" - }, - "url": { - "type": "string", - "description": "The URL of the request" - }, - "method": { - "type": "string", - "description": "HTTP method" - }, - "params": { - "type": "array", - "description": "Array of request parameters", - "items": { - "$ref": "#/$defs/RequestParam" - } - }, - "headers": { - "type": "array", - "description": "Array of request headers", - "items": { - "$ref": "#/$defs/RequestHeader" - } - }, - "body": { - "$ref": "#/$defs/RequestBody" - }, - "auth": { - "$ref": "#/$defs/Auth" - }, - "scripts": { - "$ref": "#/$defs/Scripts" - }, - "variables": { - "type": "array", - "description": "Array of variables", - "items": { - "$ref": "#/$defs/Variable" - } - }, - "assertions": { - "type": "array", - "description": "Array of assertions for response validation", - "items": { - "$ref": "#/$defs/Assertion" - } - }, - "docs": { - "type": "string", - "description": "Documentation for this request" - } - }, - "required": ["type"], - "additionalProperties": false - }, - "GraphQLRequest": { - }, - "GrpcRequest": { - }, - "Folder": { - "type": "object", - "description": "A folder for organizing collection items", - "properties": { - "type": { - "type": "string", - "const": "folder" - }, - "Item": { - "oneOf": [ - { - "$ref": "#/$defs/HttpRequest" - }, - { - "$ref": "#/$defs/GraphQLRequest" - }, - { - "$ref": "#/$defs/GrpcRequest" - }, - { - "$ref": "#/$defs/Folder" - }, - { - "$ref": "#/$defs/Script" - } - ] - }, - "headers": { - "type": "array", - "description": "Array of request headers", - "items": { - "$ref": "#/$defs/RequestHeader" - } - }, - "auth": { - "$ref": "#/$defs/Auth" - }, - "variables": { - "type": "array", - "description": "Array of variables", - "items": { - "$ref": "#/$defs/Variable" - } - }, - "scripts": { - "$ref": "#/$defs/Scripts" - }, - "docs": { - "type": "string", - "description": "Documentation for this folder" - } - }, - "additionalProperties": false - }, - "Script": { - "type": "object", - "description": "Javscript module or shared collection scripts", - "properties": { - "type": { - "type": "string", - "const": "script" - }, - "script": { - "type": "string", - "description": "The script" - } - }, - "additionalProperties": false - }, - "Environment": { - "type": "object", - "description": "An environment configuration", - "properties": { - "name": { - "type": "string", - "description": "The name of the environment" - }, - "description": { - "$ref": "#/$defs/Description" - }, - "variables": { - "type": "array", - "description": "Array of environment variables", - "items": { - "$ref": "#/$defs/Variable" - } - } - }, - "required": ["name"], - "additionalProperties": false - }, - "Variable": { - "type": "object", - "description": "A variable with name, value, description, and state flags", - "properties": { - "name": { - "type": "string", - "description": "The variable name" - }, - "value": { - "description": "The variable value", - "oneOf": [ - { - "type": "string" - }, - { - "$ref": "#/$defs/ValueSet" - }, - { - "type": "null" - } - ] - }, - "description": { - "$ref": "#/$defs/Description" - }, - "disabled": { - "type": "boolean", - "description": "Whether the variable is disabled" - }, - "transient": { - "type": "boolean", - "description": "Whether the variable value should be persisted or not" - }, - "default": { - "description": "The default value when transient is true", - "oneOf": [ - { - "type": "string" - }, - { - "$ref": "#/$defs/Value" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - }, - "Value": { - "type": "object", - "description": "A value with data and type information", - "properties": { - "data": { - "type": "string", - "description": "The string representation of the value" - }, - "type": { - "type": "string", - "description": "The type of the value", - "enum": ["string", "number", "boolean", "null", "object"] - } - }, - "additionalProperties": false - }, - "ValueSet": { - "type": "object", - "description": "A value with data, type and variants", - "properties": { - "data": { - "type": "string", - "description": "The string representation of the value" - }, - "type": { - "type": "string", - "description": "The type of the value", - "enum": ["string", "number", "boolean", "null", "object"] - }, - "variants": { - "type": "array", - "description": "Array of variants for this value", - "items": { - "$ref": "#/$defs/Variant" - } - } - }, - "additionalProperties": false - }, - "Variant": { - "type": "object", - "description": "A variant with data and description", - "properties": { - "data": { - "type": "string", - "description": "The data for this variant" - }, - "description": { - "type": "string", - "description": "The description for this variant" - } - }, - "required": ["data"], - "additionalProperties": false - }, - "Scripts": { - "type": "object", - "description": "Scripts for collection execution lifecycle", - "properties": { - "preRequest": { - "type": "string", - "description": "Script to execute before making requests" - }, - "postResponse": { - "type": "string", - "description": "Script to execute after receiving responses" - }, - "tests": { - "type": "string", - "description": "Test scripts to validate responses" - }, - "hooks": { - "type": "string", - "description": "Scripts that can be injected into specific stages of the execution process." - } - }, - "additionalProperties": false - } - } -} \ No newline at end of file From a5667afb332a7de7f6bb76cb70d8740bc81c72d5 Mon Sep 17 00:00:00 2001 From: naman-bruno Date: Wed, 26 Nov 2025 18:45:36 +0530 Subject: [PATCH 046/161] feat: enhance PlaygroundDrawer functionality with improved collapsing behavior (#8) --- .../PlaygroudDrawer/PlaygroundDrawer.tsx | 85 +++++++++++++++---- .../PlaygroudDrawer/StyledWrapper.ts | 3 +- 2 files changed, 71 insertions(+), 17 deletions(-) diff --git a/packages/oc-docs/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx b/packages/oc-docs/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx index 5c1ebe00..6337eebe 100644 --- a/packages/oc-docs/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx +++ b/packages/oc-docs/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx @@ -19,7 +19,8 @@ interface PlaygroundDrawerProps { onCollectionUpdate?: (collection: OpenCollectionCollection) => void; } -const MIN_HEIGHT = 300; +const COLLAPSED_HEIGHT = 41; +const COLLAPSE_THRESHOLD = () => window.innerHeight * 0.4; const getMaxHeight = () => window.innerHeight; const getDefaultHeight = () => window.innerHeight * 0.9; @@ -41,6 +42,8 @@ const PlaygroundDrawer: React.FC = ({ const drawerRef = useRef(null); const dragStartY = useRef(0); const dragStartHeight = useRef(0); + const lastExpandedHeight = useRef(getDefaultHeight()); + const previousSelectedItemRef = useRef(null); // Hydrate collection with UUIDs and preserve collapsed state useEffect(() => { @@ -202,7 +205,9 @@ const PlaygroundDrawer: React.FC = ({ if (isOpen) { // When opening, set to default height immediately setIsCollapsed(false); - setHeight(getDefaultHeight()); + const defaultHeight = getDefaultHeight(); + setHeight(defaultHeight); + lastExpandedHeight.current = defaultHeight; setViewMode('playground'); } else { // When closing, reset height to 0 @@ -211,6 +216,19 @@ const PlaygroundDrawer: React.FC = ({ } }, [isOpen]); + useEffect(() => { + const itemChanged = previousSelectedItemRef.current !== selectedItem; + previousSelectedItemRef.current = selectedItem; + + if (isOpen && isCollapsed && selectedItem && itemChanged) { + setIsCollapsed(false); + const targetHeight = lastExpandedHeight.current > COLLAPSE_THRESHOLD() + ? lastExpandedHeight.current + : getDefaultHeight(); + setHeight(targetHeight); + } + }, [selectedItem, isOpen, isCollapsed]); + const handleEnvironmentsClick = useCallback(() => { setViewMode('environments'); setSelectedItemId(null); @@ -229,16 +247,16 @@ const PlaygroundDrawer: React.FC = ({ // Calculate delta - dragging up (decreasing clientY) increases height const deltaY = dragStartY.current - e.clientY; const maxHeight = getMaxHeight(); - const newHeight = Math.max(MIN_HEIGHT, Math.min(maxHeight, dragStartHeight.current + deltaY)); + const collapseThreshold = COLLAPSE_THRESHOLD(); + const newHeight = Math.max(COLLAPSED_HEIGHT, Math.min(maxHeight, dragStartHeight.current + deltaY)); setHeight(newHeight); - // Auto-collapse if dragged too low - if (newHeight <= MIN_HEIGHT + 30) { + if (newHeight <= collapseThreshold) { setIsCollapsed(true); - setHeight(MIN_HEIGHT); } else { setIsCollapsed(false); + lastExpandedHeight.current = newHeight; } }, [isDragging]); @@ -256,7 +274,10 @@ const PlaygroundDrawer: React.FC = ({ const handleMouseUp = useCallback(() => { setIsDragging(false); - }, []); + if (isCollapsed) { + setHeight(COLLAPSED_HEIGHT); + } + }, [isCollapsed]); useEffect(() => { if (isDragging) { @@ -272,12 +293,19 @@ const PlaygroundDrawer: React.FC = ({ const handleToggleCollapse = useCallback(() => { if (isCollapsed) { setIsCollapsed(false); - setHeight(getDefaultHeight()); + const targetHeight = lastExpandedHeight.current > COLLAPSE_THRESHOLD() + ? lastExpandedHeight.current + : getDefaultHeight(); + setHeight(targetHeight); } else { setIsCollapsed(true); - setHeight(MIN_HEIGHT); + const collapseThreshold = COLLAPSE_THRESHOLD(); + if (height > collapseThreshold) { + lastExpandedHeight.current = height; + } + setHeight(COLLAPSED_HEIGHT); } - }, [isCollapsed]); + }, [isCollapsed, height]); // Don't render if not open if (!isOpen) return null; @@ -294,13 +322,13 @@ const PlaygroundDrawer: React.FC = ({ return ( <> - {/* Backdrop */} + {/* Backdrop - only show when not collapsed */} @@ -309,7 +337,7 @@ const PlaygroundDrawer: React.FC = ({ = ({ e.currentTarget.style.backgroundColor = 'var(--border-color)'; }} /> + + {isCollapsed && selectedItem && ( +
+ + {selectedItem.method} + + {selectedItem.name || ''} +
+ )} +
Date: Thu, 4 Dec 2025 22:05:43 +0530 Subject: [PATCH 047/161] feat: oc-docs playground ui and request execution logic updates (#9) --- examples/standalone-html/index2.html | 19 +- package-lock.json | 1 + packages/oc-docs/index.html | 13 + packages/oc-docs/package.json | 7 +- .../oc-docs/src/components/Docs/Item/Item.tsx | 33 +- .../src/components/Docs/Sidebar/Sidebar.tsx | 2 +- .../OpenCollection/OpenCollection.tsx | 15 +- .../PlaygroudDrawer/PlaygroundDrawer.tsx | 528 ------------------ .../Playground/RequestPane/RequestPane.tsx | 490 ---------------- .../DrawerBackdrop/DrawerBackdrop.tsx | 25 + .../DrawerContent/DrawerContent.tsx | 195 +++++++ .../CollectionSettingsView.tsx | 268 +++++++++ .../DrawerContent/Views/Common/AssertsTab.tsx | 201 +++++++ .../DrawerContent/Views/Common/AuthTab.tsx | 274 +++++++++ .../DrawerContent/Views/Common/BodyTab.tsx | 127 +++++ .../DrawerContent/Views/Common/HeadersTab.tsx | 47 ++ .../DrawerContent/Views/Common/ParamsTab.tsx | 48 ++ .../Views/Common}/RequestHeader.tsx | 2 +- .../Views/Common/ResponseBodyTab.tsx | 47 ++ .../Views/Common/ResponseHeadersTab.tsx | 55 ++ .../DrawerContent/Views/Common/ScriptsTab.tsx | 79 +++ .../Views/Common/TestResultsTab.tsx | 214 +++++++ .../DrawerContent/Views/Common/TestsTab.tsx | 32 ++ .../Views/Common/VariablesTab.tsx | 47 ++ .../DrawerContent/Views/Common/index.ts | 12 + .../EnvironmentsView/EnvironmentsView.tsx | 19 +- .../FolderSettingsView/FolderSettingsView.tsx | 210 +++++++ .../Views/PlaygroundView/PlaygroundView.tsx} | 33 +- .../PlaygroundView}/QueryBar/QueryBar.tsx | 0 .../PlaygroundView}/QueryBar/StyledWrapper.ts | 0 .../RequestHeader/RequestHeader.tsx | 47 ++ .../RequestPane/RequestPane.tsx | 193 +++++++ .../ResponsePane/ResponsePane.tsx | 123 ++-- .../Views/PlaygroundView}/StyledWrapper.ts | 0 .../DrawerDragBar/DrawerDragBar.tsx | 226 ++++++++ .../PlaygroundDrawer/PlaygroundDrawer.tsx | 121 ++++ .../Sidebar/Sidebar.tsx | 119 ++-- .../Sidebar/StyledWrapper.ts | 22 +- packages/oc-docs/src/dev.tsx | 162 ++++++ .../oc-docs/src/runner/RequestExecutor.ts | 30 +- packages/oc-docs/src/runner/ScriptRunner.ts | 451 --------------- .../src/runner/VariableInterpolator.ts | 103 ---- packages/oc-docs/src/runner/index.spec.ts | 148 +++++ packages/oc-docs/src/runner/index.ts | 214 +++++-- packages/oc-docs/src/runner/utils/index.ts | 12 + .../src/runner/utils/request-merger.ts | 177 ++++++ .../oc-docs/src/runner/utils/tree-utils.ts | 51 ++ .../src/runner/utils/variable-interpolator.ts | 244 ++++++++ .../src/runner/utils/variable-merger.ts | 76 +++ .../src/scripting/runtime/assert-runtime.ts | 302 ++++++++++ .../scripting/runtime/script-runtime.spec.ts | 11 +- .../src/scripting/runtime/script-runtime.ts | 12 +- packages/oc-docs/src/scripting/utils/bru.ts | 55 +- .../oc-docs/src/store/slices/playground.ts | 186 +++++- packages/oc-docs/vite.config.ts | 45 ++ packages/oc-docs/vitest.config.ts | 25 + 56 files changed, 4356 insertions(+), 1842 deletions(-) create mode 100644 packages/oc-docs/index.html delete mode 100644 packages/oc-docs/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx delete mode 100644 packages/oc-docs/src/components/Playground/RequestPane/RequestPane.tsx create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/DrawerBackdrop/DrawerBackdrop.tsx create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/DrawerContent.tsx create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/Views/CollectionSettingsView/CollectionSettingsView.tsx create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/Views/Common/AssertsTab.tsx create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/Views/Common/AuthTab.tsx create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/Views/Common/BodyTab.tsx create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/Views/Common/HeadersTab.tsx create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/Views/Common/ParamsTab.tsx rename packages/oc-docs/src/components/{Playground/RequestPane/RequestHeader => PlaygroundDrawer/DrawerContent/Views/Common}/RequestHeader.tsx (99%) create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/Views/Common/ResponseBodyTab.tsx create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/Views/Common/ResponseHeadersTab.tsx create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/Views/Common/ScriptsTab.tsx create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/Views/Common/TestResultsTab.tsx create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/Views/Common/TestsTab.tsx create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/Views/Common/VariablesTab.tsx create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/Views/Common/index.ts rename packages/oc-docs/src/components/{Playground => PlaygroundDrawer/DrawerContent/Views}/EnvironmentsView/EnvironmentsView.tsx (94%) create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/Views/FolderSettingsView/FolderSettingsView.tsx rename packages/oc-docs/src/components/{Playground/Playground.tsx => PlaygroundDrawer/DrawerContent/Views/PlaygroundView/PlaygroundView.tsx} (82%) rename packages/oc-docs/src/components/{Playground => PlaygroundDrawer/DrawerContent/Views/PlaygroundView}/QueryBar/QueryBar.tsx (100%) rename packages/oc-docs/src/components/{Playground => PlaygroundDrawer/DrawerContent/Views/PlaygroundView}/QueryBar/StyledWrapper.ts (100%) create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/Views/PlaygroundView/RequestPane/RequestHeader/RequestHeader.tsx create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/Views/PlaygroundView/RequestPane/RequestPane.tsx rename packages/oc-docs/src/components/{Playground => PlaygroundDrawer/DrawerContent/Views/PlaygroundView}/ResponsePane/ResponsePane.tsx (54%) rename packages/oc-docs/src/components/{Playground/PlaygroudDrawer => PlaygroundDrawer/DrawerContent/Views/PlaygroundView}/StyledWrapper.ts (100%) create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/DrawerDragBar/DrawerDragBar.tsx create mode 100644 packages/oc-docs/src/components/PlaygroundDrawer/PlaygroundDrawer.tsx rename packages/oc-docs/src/components/{Playground => PlaygroundDrawer}/Sidebar/Sidebar.tsx (52%) rename packages/oc-docs/src/components/{Playground => PlaygroundDrawer}/Sidebar/StyledWrapper.ts (88%) create mode 100644 packages/oc-docs/src/dev.tsx delete mode 100644 packages/oc-docs/src/runner/ScriptRunner.ts delete mode 100644 packages/oc-docs/src/runner/VariableInterpolator.ts create mode 100644 packages/oc-docs/src/runner/index.spec.ts create mode 100644 packages/oc-docs/src/runner/utils/index.ts create mode 100644 packages/oc-docs/src/runner/utils/request-merger.ts create mode 100644 packages/oc-docs/src/runner/utils/tree-utils.ts create mode 100644 packages/oc-docs/src/runner/utils/variable-interpolator.ts create mode 100644 packages/oc-docs/src/runner/utils/variable-merger.ts create mode 100644 packages/oc-docs/src/scripting/runtime/assert-runtime.ts create mode 100644 packages/oc-docs/vite.config.ts create mode 100644 packages/oc-docs/vitest.config.ts diff --git a/examples/standalone-html/index2.html b/examples/standalone-html/index2.html index 2c079df0..f2e938d0 100644 --- a/examples/standalone-html/index2.html +++ b/examples/standalone-html/index2.html @@ -24,17 +24,22 @@ + + diff --git a/packages/oc-docs/package.json b/packages/oc-docs/package.json index c469aef1..1442bf81 100644 --- a/packages/oc-docs/package.json +++ b/packages/oc-docs/package.json @@ -38,7 +38,7 @@ "server": "node server.js", "dev:all": "concurrently \"npm run dev\" \"npm run server\"", "build:lib-bundle": "vite build --config vite.config.libraries-bundle.ts", - "prebuild": "npm run build:bundle", + "prebuild": "npm run build:lib-bundle", "build": "npm run clean && tsc --emitDeclarationOnly && vite build", "build:standalone": "npm run clean:standalone && vite build --config vite.config.standalone.ts", "build:all": "npm run build && npm run build:standalone", @@ -55,8 +55,8 @@ "@emotion/styled": "^11.14.1", "@monaco-editor/react": "^4.7.0", "@opencollection/types": "0.0.1", - "@reduxjs/toolkit": "^2.10.1", "@prantlf/jsonlint": "^16.0.0", + "@reduxjs/toolkit": "^2.10.1", "@tailwindcss/vite": "^4.0.9", "@types/js-yaml": "^4.0.9", "@types/markdown-it": "^14.1.2", @@ -72,13 +72,14 @@ "quickjs-emscripten": "~0.31.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-redux": "^9.2.0", "react-markdown": "^10.0.0", + "react-redux": "^9.2.0", "react-router-dom": "^7.3.0", "remark-gfm": "^4.0.1", "strip-json-comments": "^3.1.1" }, "devDependencies": { + "@emotion/babel-plugin": "~11.13.5", "@eslint/js": "^9.21.0", "@tailwindcss/postcss": "^4.1.13", "@tailwindcss/typography": "^0.5.10", diff --git a/packages/oc-docs/src/components/Docs/Item/Item.tsx b/packages/oc-docs/src/components/Docs/Item/Item.tsx index d0cc0d36..2fcfc553 100644 --- a/packages/oc-docs/src/components/Docs/Item/Item.tsx +++ b/packages/oc-docs/src/components/Docs/Item/Item.tsx @@ -234,8 +234,37 @@ const Item = memo(({

Body

{ + const bodyData = (endpoint.body as any).data; + const bodyType = (endpoint.body as any).type; + + // Handle different body types + if (bodyType === 'form-urlencoded' && Array.isArray(bodyData)) { + // Convert FormUrlEncodedEntry[] to string + return bodyData + .filter((entry: any) => entry.disabled !== true) + .map((entry: any) => `${encodeURIComponent(entry.name)}=${encodeURIComponent(entry.value)}`) + .join('&'); + } else if (bodyType === 'multipart-form' && Array.isArray(bodyData)) { + // Convert MultipartFormEntry[] to readable format + return bodyData + .filter((entry: any) => entry.disabled !== true) + .map((entry: any) => `${entry.name}: ${entry.value}`) + .join('\n'); + } else if (typeof bodyData === 'string') { + // Handle string data (json, text, xml, etc.) + return bodyData; + } else { + // Fallback: stringify objects + return JSON.stringify(bodyData, null, 2); + } + })()} + language={(() => { + const bodyType = (endpoint.body as any).type; + if (bodyType === 'form-urlencoded') return 'text'; + if (bodyType === 'multipart-form') return 'text'; + return bodyType || 'json'; + })()} />
)} diff --git a/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx b/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx index 4f4bd453..0d4019a8 100644 --- a/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx +++ b/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx @@ -122,7 +122,7 @@ const Sidebar: React.FC = () => {

- {collection?.name || 'API Collection'} + {collection?.info?.name || 'API Collection'}

diff --git a/packages/oc-docs/src/components/OpenCollection/OpenCollection.tsx b/packages/oc-docs/src/components/OpenCollection/OpenCollection.tsx index 27c852bf..6a0afce2 100644 --- a/packages/oc-docs/src/components/OpenCollection/OpenCollection.tsx +++ b/packages/oc-docs/src/components/OpenCollection/OpenCollection.tsx @@ -4,7 +4,7 @@ import type { OpenCollection as OpenCollectionCollection } from '@opencollection import type { Item as OpenCollectionItem, Folder } from '@opencollection/types/collection/item'; import type { HttpRequest } from '@opencollection/types/requests/http'; import type { OpenCollection as IOpenCollection } from '@opencollection/types'; -import PlaygroundDrawer from '../Playground/PlaygroudDrawer/PlaygroundDrawer'; +import PlaygroundDrawer from '../PlaygroundDrawer/PlaygroundDrawer'; import Docs from '../Docs/Docs'; import { parseYaml } from '../../utils/yamlUtils'; import { hydrateWithUUIDs } from '../../utils/items'; @@ -99,7 +99,7 @@ const DesktopLayout: React.FC = ({ filteredCollectionItems }) => { const selectedItemId = useAppSelector(selectSelectedItemId); - const [playgroundItem, setPlaygroundItem] = useState(null); + const [playgroundItem, setPlaygroundItem] = useState(null); const [showPlaygroundDrawer, setShowPlaygroundDrawer] = useState(false); const prevSelectedItemIdRef = useRef(selectedItemId); const playgroundItemUuidRef = useRef(undefined); @@ -120,14 +120,14 @@ const DesktopLayout: React.FC = ({ } const item = findItemByUuid(playgroundCollection.items, selectedItemId); - if (item && item.type === 'http') { - setPlaygroundItem(item as HttpRequest); + if (item && (item.type === 'http' || item.type === 'folder')) { + setPlaygroundItem(item as HttpRequest | Folder); // Don't open drawer automatically - only open when "Try" is clicked } } }, [selectedItemId, playgroundCollection]); - const handlePlaygroundItemSelect = useCallback((item: HttpRequest) => { + const handlePlaygroundItemSelect = useCallback((item: HttpRequest | Folder) => { // Only update the playground item, don't affect the docs view setPlaygroundItem(item); }, []); @@ -135,10 +135,6 @@ const DesktopLayout: React.FC = ({ const handleOpenPlayground = useCallback(() => { setShowPlaygroundDrawer(true); }, []); - - const handleCollectionUpdate = useCallback((updatedCollection: OpenCollectionCollection) => { - dispatch(setPlaygroundCollection(updatedCollection)); - }, [dispatch]); return (
@@ -154,7 +150,6 @@ const DesktopLayout: React.FC = ({ collection={playgroundCollection} selectedItem={playgroundItem} onSelectItem={handlePlaygroundItemSelect} - onCollectionUpdate={handleCollectionUpdate} />
); diff --git a/packages/oc-docs/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx b/packages/oc-docs/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx deleted file mode 100644 index 6337eebe..00000000 --- a/packages/oc-docs/src/components/Playground/PlaygroudDrawer/PlaygroundDrawer.tsx +++ /dev/null @@ -1,528 +0,0 @@ -import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react'; -import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; -import type { Item as OpenCollectionItem, Folder } from '@opencollection/types/collection/item'; -import type { HttpRequest } from '@opencollection/types/requests/http'; -import Playground from '../Playground'; -import Sidebar from '../Sidebar/Sidebar'; -import EnvironmentsView from '../EnvironmentsView/EnvironmentsView'; -import { hydrateWithUUIDs, findAndUpdateItem } from '../../../utils/items'; -import { StyledBackdrop, StyledDrawer, StyledDragBar } from './StyledWrapper'; - -type ViewMode = 'playground' | 'environments'; - -interface PlaygroundDrawerProps { - isOpen: boolean; - onClose: () => void; - collection: OpenCollectionCollection | null; - selectedItem: HttpRequest | null; - onSelectItem: (item: HttpRequest) => void; - onCollectionUpdate?: (collection: OpenCollectionCollection) => void; -} - -const COLLAPSED_HEIGHT = 41; -const COLLAPSE_THRESHOLD = () => window.innerHeight * 0.4; - -const getMaxHeight = () => window.innerHeight; -const getDefaultHeight = () => window.innerHeight * 0.9; - -const PlaygroundDrawer: React.FC = ({ - isOpen, - onClose, - collection, - selectedItem, - onSelectItem, - onCollectionUpdate -}) => { - const [height, setHeight] = useState(() => isOpen ? getDefaultHeight() : 0); - const [isDragging, setIsDragging] = useState(false); - const [isCollapsed, setIsCollapsed] = useState(false); - const [hydratedCollection, setHydratedCollection] = useState(null); - const [selectedItemId, setSelectedItemId] = useState(null); - const [viewMode, setViewMode] = useState('playground'); - const drawerRef = useRef(null); - const dragStartY = useRef(0); - const dragStartHeight = useRef(0); - const lastExpandedHeight = useRef(getDefaultHeight()); - const previousSelectedItemRef = useRef(null); - - // Hydrate collection with UUIDs and preserve collapsed state - useEffect(() => { - if (!collection) { - setHydratedCollection(null); - return; - } - - const hydrated = hydrateWithUUIDs(collection); - - const preserveCollapsedState = ( - newItems: OpenCollectionItem[], - oldItems: OpenCollectionItem[] - ): void => { - for (const newItem of newItems) { - const newUuid = (newItem as any).uuid; - - const oldItem = oldItems.find((old: any) => old.uuid === newUuid); - - if ('type' in newItem && newItem.type === 'folder') { - if (oldItem && 'type' in oldItem && oldItem.type === 'folder') { - (newItem as any).isCollapsed = (oldItem as any).isCollapsed; - - const newFolder = newItem as Folder; - const oldFolder = oldItem as Folder; - if (newFolder.items && oldFolder.items) { - preserveCollapsedState(newFolder.items, oldFolder.items); - } - } else { - if ((newItem as any).isCollapsed === undefined) { - (newItem as any).isCollapsed = true; - } - } - } - } - }; - - const initializeCollapsedState = (items: OpenCollectionItem[] | undefined): void => { - if (!items) return; - - for (const item of items) { - if ('type' in item && item.type === 'folder') { - // Initialize isCollapsed to true (collapsed) if not already set - if ((item as any).isCollapsed === undefined) { - (item as any).isCollapsed = true; - } - const folder = item as Folder; - if (folder.items) { - initializeCollapsedState(folder.items); - } - } - } - }; - - // Preserve existing collapsed states from previous hydrated collection - if (hydratedCollection?.items && hydrated.items) { - preserveCollapsedState(hydrated.items, hydratedCollection.items); - } else if (hydrated.items) { - initializeCollapsedState(hydrated.items); - } - - setHydratedCollection(hydrated); - }, [collection]); - - // Update selectedItemId when selectedItem changes - useEffect(() => { - if (!selectedItem) { - setSelectedItemId(null); - return; - } - - const itemWithUuid = selectedItem as any; - if (itemWithUuid.uuid) { - setSelectedItemId(itemWithUuid.uuid); - return; - } - - if (!hydratedCollection?.items) { - setSelectedItemId(null); - return; - } - - const findItemUUID = (items: OpenCollectionItem[]): string | null => { - for (const item of items) { - const itemUuid = (item as any).uuid; - // Compare by checking if this is the selected item - if (itemUuid && 'type' in item && item.type === 'http') { - const httpItem = item as HttpRequest; - if (httpItem.name === selectedItem.name && - httpItem.method === selectedItem.method && - httpItem.url === selectedItem.url) { - return itemUuid; - } - } - - if ('type' in item && item.type === 'folder') { - const folder = item as Folder; - if (folder.items) { - const found = findItemUUID(folder.items); - if (found) return found; - } - } - } - - return null; - }; - - const uuid = findItemUUID(hydratedCollection.items); - setSelectedItemId(uuid); - }, [selectedItem, hydratedCollection]); - - const toggleFolder = useCallback((uuid: string) => { - setHydratedCollection((prev) => { - if (!prev?.items) return prev; - - const updated = { ...prev }; - findAndUpdateItem(updated.items, uuid, (item) => { - const currentCollapsed = (item as any).isCollapsed ?? true; - (item as any).isCollapsed = !currentCollapsed; - }); - - return updated; - }); - }, []); - - const handleSelectItem = useCallback((uuid: string) => { - if (!hydratedCollection?.items) return; - - setViewMode('playground'); - - const findItem = (items: OpenCollectionItem[]): HttpRequest | null => { - for (const item of items) { - const itemUuid = (item as any).uuid; - if (itemUuid === uuid && 'type' in item && item.type === 'http') { - return item as HttpRequest; - } - - if ('type' in item && item.type === 'folder') { - const folder = item as Folder; - if (folder.items) { - const found = findItem(folder.items); - if (found) return found; - } - } - } - - return null; - }; - - const foundItem = findItem(hydratedCollection.items); - - if (foundItem) { - onSelectItem(foundItem); - } - }, [hydratedCollection, onSelectItem]); - - - useEffect(() => { - if (isOpen) { - // When opening, set to default height immediately - setIsCollapsed(false); - const defaultHeight = getDefaultHeight(); - setHeight(defaultHeight); - lastExpandedHeight.current = defaultHeight; - setViewMode('playground'); - } else { - // When closing, reset height to 0 - setHeight(0); - setViewMode('playground'); - } - }, [isOpen]); - - useEffect(() => { - const itemChanged = previousSelectedItemRef.current !== selectedItem; - previousSelectedItemRef.current = selectedItem; - - if (isOpen && isCollapsed && selectedItem && itemChanged) { - setIsCollapsed(false); - const targetHeight = lastExpandedHeight.current > COLLAPSE_THRESHOLD() - ? lastExpandedHeight.current - : getDefaultHeight(); - setHeight(targetHeight); - } - }, [selectedItem, isOpen, isCollapsed]); - - const handleEnvironmentsClick = useCallback(() => { - setViewMode('environments'); - setSelectedItemId(null); - }, []); - - const handleDragStart = useCallback((e: React.MouseEvent) => { - setIsDragging(true); - dragStartY.current = e.clientY; - dragStartHeight.current = height; - e.preventDefault(); - }, [height]); - - const handleMouseMove = useCallback((e: MouseEvent) => { - if (!isDragging) return; - - // Calculate delta - dragging up (decreasing clientY) increases height - const deltaY = dragStartY.current - e.clientY; - const maxHeight = getMaxHeight(); - const collapseThreshold = COLLAPSE_THRESHOLD(); - const newHeight = Math.max(COLLAPSED_HEIGHT, Math.min(maxHeight, dragStartHeight.current + deltaY)); - - setHeight(newHeight); - - if (newHeight <= collapseThreshold) { - setIsCollapsed(true); - } else { - setIsCollapsed(false); - lastExpandedHeight.current = newHeight; - } - }, [isDragging]); - - // Update MAX_HEIGHT when window resizes - useEffect(() => { - const handleResize = () => { - // MAX_HEIGHT is now window.innerHeight, which is already correct - // But we need to ensure height doesn't exceed it - setHeight((prev) => Math.min(prev, window.innerHeight)); - }; - - window.addEventListener('resize', handleResize); - return () => window.removeEventListener('resize', handleResize); - }, []); - - const handleMouseUp = useCallback(() => { - setIsDragging(false); - if (isCollapsed) { - setHeight(COLLAPSED_HEIGHT); - } - }, [isCollapsed]); - - useEffect(() => { - if (isDragging) { - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); - return () => { - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); - }; - } - }, [isDragging, handleMouseMove, handleMouseUp]); - - const handleToggleCollapse = useCallback(() => { - if (isCollapsed) { - setIsCollapsed(false); - const targetHeight = lastExpandedHeight.current > COLLAPSE_THRESHOLD() - ? lastExpandedHeight.current - : getDefaultHeight(); - setHeight(targetHeight); - } else { - setIsCollapsed(true); - const collapseThreshold = COLLAPSE_THRESHOLD(); - if (height > collapseThreshold) { - lastExpandedHeight.current = height; - } - setHeight(COLLAPSED_HEIGHT); - } - }, [isCollapsed, height]); - - // Don't render if not open - if (!isOpen) return null; - - const methodColors: Record = { - 'GET': '#16a34a', - 'POST': '#2563eb', - 'PUT': '#f97316', - 'PATCH': '#8b5cf6', - 'DELETE': '#dc2626', - 'HEAD': '#6b7280', - 'OPTIONS': '#6b7280' - }; - - return ( - <> - {/* Backdrop - only show when not collapsed */} - - - {/* Drawer */} - - {/* Drag Bar */} - -
{ - e.currentTarget.style.backgroundColor = 'var(--text-secondary)'; - }} - onMouseLeave={(e) => { - e.currentTarget.style.backgroundColor = 'var(--border-color)'; - }} - /> - - {isCollapsed && selectedItem && ( -
- - {selectedItem.method} - - {selectedItem.name || ''} -
- )} - -
- - -
- - - {/* Drawer Content */} - {!isCollapsed && ( -
- {/* Sidebar */} -
- -
- - {/* Main Playground Content */} -
- {viewMode === 'playground' ? ( - selectedItem && collection ? ( - - ) : ( -
-
-

Select an endpoint from the sidebar to get started

-
-
- ) - ) : ( - - )} -
-
- )} - - - ); -}; - -export default PlaygroundDrawer; - diff --git a/packages/oc-docs/src/components/Playground/RequestPane/RequestPane.tsx b/packages/oc-docs/src/components/Playground/RequestPane/RequestPane.tsx deleted file mode 100644 index bb6cabf7..00000000 --- a/packages/oc-docs/src/components/Playground/RequestPane/RequestPane.tsx +++ /dev/null @@ -1,490 +0,0 @@ -import React, { useState } from 'react'; -import type { HttpRequest } from '@opencollection/types/requests/http'; -import CodeEditor from '../../../ui/CodeEditor/CodeEditor'; -import Tabs from '../../../ui/Tabs/Tabs'; -import KeyValueTable, { KeyValueRow } from '../../../ui/KeyValueTable/KeyValueTable'; - -interface RequestPaneProps { - item: HttpRequest; - onItemChange: (item: HttpRequest) => void; -} - -const RequestPane: React.FC = ({ item, onItemChange }) => { - const [activeTab, setActiveTab] = useState('params'); - - const handleParamsChange = (params: KeyValueRow[]) => { - const updatedParams = params.map(p => ({ - name: p.name, - value: p.value, - disabled: !p.enabled, - type: 'query' as const - })); - onItemChange({ ...item, params: updatedParams }); - }; - - const handleHeadersChange = (headers: KeyValueRow[]) => { - const updatedHeaders = headers.map(h => ({ - name: h.name, - value: h.value, - disabled: !h.enabled - })); - onItemChange({ ...item, headers: updatedHeaders }); - }; - - const handleFormBodyChange = (formData: KeyValueRow[]) => { - const updatedBody = formData.map(f => ({ - name: f.name, - value: f.value, - enabled: f.enabled - })); - onItemChange({ ...item, body: updatedBody }); - }; - - const renderParams = () => { - const paramsData: KeyValueRow[] = (item.params || []).map((param, index) => ({ - id: `param-${index}`, - name: param.name || '', - value: param.value || '', - enabled: !param.disabled, - type: param.type || 'query' - })); - - return ( -
-
- - Query Parameters - -
- -
- ); - }; - - const renderHeaders = () => { - const headersData: KeyValueRow[] = (item.headers || []).map((header, index) => ({ - id: `header-${index}`, - name: header.name || '', - value: header.value || '', - enabled: !header.disabled - })); - - return ( -
-
- - Headers - -
- -
- ); - }; - - const renderBody = () => ( -
-
-
-
- - Body Type: - - -
-
- - {!item.body ? ( -
- No body content. Select a body type to add content. -
- ) : 'data' in item.body && typeof item.body.data === 'string' ? ( - { - if ('data' in item.body!) { - onItemChange({ - ...item, - body: { ...item.body, data: value } - }); - } - }} - language={item.body.type === 'json' ? 'json' : item.body.type === 'xml' ? 'xml' : 'text'} - height="300px" - /> - ) : Array.isArray(item.body) ? ( - (() => { - const formBodyData: KeyValueRow[] = (item.body as any[]).map((field: any, index: number) => ({ - id: `form-${index}`, - name: field.name || '', - value: field.value || '', - enabled: field.enabled !== false - })); - - return ( -
-
- - Form Data - -
- -
- ); - })() - ) : ( -
- Unsupported body type -
- )} -
-
- ); - - const renderAuth = () => ( -
-
-
- - Authentication - - -
- - {!item.auth ? ( -
- No authentication configured. Select an auth type to configure. -
- ) : ( -
-
- - Type: {item.auth.type} - -
- - {item.auth.type === 'basic' && ( - <> -
- - { - onItemChange({ - ...item, - auth: { type: 'basic', username: e.target.value, password: item.auth?.type === 'basic' ? item.auth.password : undefined } - }); - }} - className="flex-1 px-2 py-1 text-sm border rounded" - style={{ - backgroundColor: 'var(--bg-secondary)', - borderColor: 'var(--border-color)', - color: 'var(--text-primary)' - }} - placeholder="Enter username" - /> -
-
- - { - onItemChange({ - ...item, - auth: { type: 'basic', password: e.target.value, username: item.auth?.type === 'basic' ? item.auth.username : undefined } - }); - }} - className="flex-1 px-2 py-1 text-sm border rounded" - style={{ - backgroundColor: 'var(--bg-secondary)', - borderColor: 'var(--border-color)', - color: 'var(--text-primary)' - }} - placeholder="Enter password" - /> -
- - )} - - {item.auth.type === 'bearer' && ( -
- - { - onItemChange({ - ...item, - auth: { type: 'bearer', token: e.target.value } - }); - }} - className="flex-1 px-2 py-1 text-sm border rounded" - style={{ - backgroundColor: 'var(--bg-secondary)', - borderColor: 'var(--border-color)', - color: 'var(--text-primary)' - }} - placeholder="Enter bearer token" - /> -
- )} - - {item.auth.type === 'apikey' && ( - <> -
- - { - const currentAuth = item.auth?.type === 'apikey' ? item.auth : { type: 'apikey' as const }; - onItemChange({ - ...item, - auth: { ...currentAuth, type: 'apikey', key: e.target.value } - }); - }} - className="flex-1 px-2 py-1 text-sm border rounded" - style={{ - backgroundColor: 'var(--bg-secondary)', - borderColor: 'var(--border-color)', - color: 'var(--text-primary)' - }} - placeholder="Enter API key name" - /> -
-
- - { - const currentAuth = item.auth?.type === 'apikey' ? item.auth : { type: 'apikey' as const }; - onItemChange({ - ...item, - auth: { ...currentAuth, type: 'apikey', value: e.target.value } - }); - }} - className="flex-1 px-2 py-1 text-sm border rounded" - style={{ - backgroundColor: 'var(--bg-secondary)', - borderColor: 'var(--border-color)', - color: 'var(--text-primary)' - }} - placeholder="Enter API key value" - /> -
-
- - -
- - )} - - {!['basic', 'bearer', 'apikey'].includes(item.auth.type) && ( -
- Configuration for {item.auth.type} auth is not yet implemented -
- )} -
- )} -
-
- ); - - const renderScripts = () => ( -
-
-
-

- Pre-request Script -

- { - const scripts = item.scripts || {}; - onItemChange({ - ...item, - scripts: { ...scripts, preRequest: value } - }); - }} - language="javascript" - height="150px" - /> -
- -
-

- Post-response Script -

- { - const scripts = item.scripts || {}; - onItemChange({ - ...item, - scripts: { ...scripts, postResponse: value } - }); - }} - language="javascript" - height="150px" - /> -
- - -
-
- ); - - const renderTests = () => ( -
-
-

- Tests -

- { - const scripts = item.scripts || {}; - onItemChange({ - ...item, - scripts: { ...scripts, tests: value } - }); - }} - language="javascript" - height="400px" - /> -
-
- ); - - const tabs = [ - { id: 'params', label: 'Params', contentIndicator: item.params?.length || undefined, content: renderParams() }, - { id: 'headers', label: 'Headers', contentIndicator: item.headers?.length || undefined, content: renderHeaders() }, - { id: 'body', label: 'Body', content: renderBody() }, - { id: 'auth', label: 'Auth', content: renderAuth() }, - { id: 'scripts', label: 'Scripts', content: renderScripts() }, - { id: 'tests', label: 'Tests', content: renderTests() } - ]; - - return ( -
- -
- ); -}; - -export default RequestPane; \ No newline at end of file diff --git a/packages/oc-docs/src/components/PlaygroundDrawer/DrawerBackdrop/DrawerBackdrop.tsx b/packages/oc-docs/src/components/PlaygroundDrawer/DrawerBackdrop/DrawerBackdrop.tsx new file mode 100644 index 00000000..b7c3a8ff --- /dev/null +++ b/packages/oc-docs/src/components/PlaygroundDrawer/DrawerBackdrop/DrawerBackdrop.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { StyledBackdrop } from '../DrawerContent/Views/PlaygroundView/StyledWrapper'; + +interface DrawerBackdropProps { + isOpen: boolean; + isCollapsed: boolean; + onClose: () => void; +} + +const DrawerBackdrop: React.FC = ({ isOpen, isCollapsed, onClose }) => { + return ( + + ); +}; + +export default DrawerBackdrop; + diff --git a/packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/DrawerContent.tsx b/packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/DrawerContent.tsx new file mode 100644 index 00000000..214eae47 --- /dev/null +++ b/packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/DrawerContent.tsx @@ -0,0 +1,195 @@ +import React, { useCallback } from 'react'; +import type { OpenCollection as OpenCollectionCollection } from '@opencollection/types'; +import type { Item as OpenCollectionItem, Folder } from '@opencollection/types/collection/item'; +import type { HttpRequest } from '@opencollection/types/requests/http'; +import Sidebar from '../Sidebar/Sidebar'; +import Playground from './Views/PlaygroundView/PlaygroundView'; +import EnvironmentsView from './Views/EnvironmentsView/EnvironmentsView'; +import FolderSettingsView from './Views/FolderSettingsView/FolderSettingsView'; +import CollectionSettingsView from './Views/CollectionSettingsView/CollectionSettingsView'; +import { useAppDispatch, useAppSelector } from '../../../store/hooks'; +import { + selectHydratedCollection, + selectViewMode, + selectSelectedItemId, + selectSelectedEnvironment, + setViewMode, + setSelectedItemId, + setSelectedEnvironment, + toggleFolderCollapse +} from '@slices/playground'; + +interface DrawerContentProps { + collection: OpenCollectionCollection | null; + selectedItem: HttpRequest | Folder | null; + onSelectItem: (item: HttpRequest | Folder) => void; +} + +const DrawerContent: React.FC = ({ collection, selectedItem, onSelectItem }) => { + const dispatch = useAppDispatch(); + const hydratedCollection = useAppSelector(selectHydratedCollection); + const viewMode = useAppSelector(selectViewMode); + const selectedItemId = useAppSelector(selectSelectedItemId); + const selectedEnvironment = useAppSelector(selectSelectedEnvironment); + + const handleToggleFolder = useCallback((uuid: string) => { + dispatch(toggleFolderCollapse(uuid)); + }, [dispatch]); + + const handleSelectItem = useCallback((uuid: string) => { + if (!hydratedCollection?.items) return; + + const findItem = (items: OpenCollectionItem[]): HttpRequest | Folder | null => { + for (const item of items) { + const itemUuid = (item as any).uuid; + if (itemUuid === uuid) { + if ('type' in item && (item.type === 'http' || item.type === 'folder')) { + return item as HttpRequest | Folder; + } + } + + if ('type' in item && item.type === 'folder') { + const folder = item as Folder; + if (folder.items) { + const found = findItem(folder.items); + if (found) return found; + } + } + } + + return null; + }; + + const foundItem = findItem(hydratedCollection.items); + + if (foundItem) { + // Set view mode based on item type + if (foundItem.type === 'folder') { + dispatch(setViewMode('folder-settings')); + } else { + dispatch(setViewMode('playground')); + } + + onSelectItem(foundItem); + } + }, [hydratedCollection, onSelectItem, dispatch]); + + const handleEnvironmentsClick = useCallback(() => { + dispatch(setViewMode('environments')); + dispatch(setSelectedItemId(null)); + }, [dispatch]); + + const handleCollectionSettingsClick = useCallback(() => { + dispatch(setViewMode('collection-settings')); + dispatch(setSelectedItemId(null)); + }, [dispatch]); + + const handleEnvironmentChange = useCallback((env: string) => { + dispatch(setSelectedEnvironment(env)); + }, [dispatch]); + + return ( +
+ {/* Sidebar */} +
+ +
+ + {/* Main Playground Content */} +
+ {viewMode === 'playground' ? ( + selectedItem && selectedItem.type === 'http' && collection ? ( + + ) : ( +
+
+

Select an endpoint from the sidebar to get started

+
+
+ ) + ) : viewMode === 'folder-settings' ? ( + selectedItem && selectedItem.type === 'folder' && collection ? ( + + ) : ( +
+
+

Select a folder from the sidebar to configure its settings

+
+
+ ) + ) : viewMode === 'collection-settings' ? ( + collection ? ( + + ) : ( +
+
+

No collection available for settings

+
+
+ ) + ) : ( + + )} +
+
+ ); +}; + +export default DrawerContent; + diff --git a/packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/Views/CollectionSettingsView/CollectionSettingsView.tsx b/packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/Views/CollectionSettingsView/CollectionSettingsView.tsx new file mode 100644 index 00000000..284d1885 --- /dev/null +++ b/packages/oc-docs/src/components/PlaygroundDrawer/DrawerContent/Views/CollectionSettingsView/CollectionSettingsView.tsx @@ -0,0 +1,268 @@ +import React, { useState } from 'react'; +import type { OpenCollection } from '@opencollection/types'; +import Tabs from '../../../../../ui/Tabs/Tabs'; +import { type KeyValueRow } from '../../../../../ui/KeyValueTable/KeyValueTable'; +import { HeadersTab, VariablesTab, AuthTab, ScriptsTab } from '../Common'; +import { useAppDispatch } from '../../../../../store/hooks'; +import { updateCollectionSettings } from '@slices/playground'; + +interface CollectionSettingsProps { + collection: OpenCollection; +} + +const CollectionSettings: React.FC = ({ collection }) => { + const dispatch = useAppDispatch(); + const [activeTab, setActiveTab] = useState('headers'); + + const handleHeadersChange = (headers: KeyValueRow[]) => { + const updatedHeaders = headers.map(h => ({ + name: h.name, + value: h.value, + disabled: !h.enabled + })); + + const updatedCollection = { + ...collection, + request: { + ...collection.request, + headers: updatedHeaders + } + }; + dispatch(updateCollectionSettings(updatedCollection)); + }; + + const handleVariablesChange = (variables: KeyValueRow[]) => { + const updatedVariables = variables.map(v => ({ + name: v.name, + value: v.value, + disabled: !v.enabled + })); + + const updatedCollection = { + ...collection, + request: { + ...collection.request, + variables: updatedVariables + } + }; + dispatch(updateCollectionSettings(updatedCollection)); + }; + + const handleScriptChange = (scriptType: 'preRequest' | 'postResponse' | 'tests', value: string) => { + const updatedCollection = { + ...collection, + request: { + ...collection.request, + scripts: { + ...collection.request?.scripts, + [scriptType]: value + } + } + }; + dispatch(updateCollectionSettings(updatedCollection)); + }; + + const handleAuthChange = (authType: string) => { + let auth: any = 'inherit'; + + if (authType !== 'none' && authType !== 'inherit') { + auth = { type: authType }; + } else if (authType === 'none') { + auth = undefined; + } + + const updatedCollection = { + ...collection, + request: { + ...collection.request, + auth + } + }; + dispatch(updateCollectionSettings(updatedCollection)); + }; + + const handleCollectionChange = (updatedCollection: OpenCollection) => { + dispatch(updateCollectionSettings(updatedCollection)); + }; + + const handleInfoChange = (field: 'name' | 'summary' | 'version', value: string) => { + const updatedCollection = { + ...collection, + info: { + ...collection.info, + [field]: value + } + }; + dispatch(updateCollectionSettings(updatedCollection)); + }; + + const renderInfo = () => { + const info = collection.info || {}; + + return ( +
+
+ + Collection Information + + + Basic metadata about your collection + +
+ +
+
+ + handleInfoChange('name', e.target.value)} + placeholder="Collection name" + className="w-full px-2.5 py-1.5 text-sm border rounded" + style={{ + backgroundColor: 'var(--bg-secondary)', + borderColor: 'var(--border-color)', + color: 'var(--text-primary)' + }} + /> +
+ +
+ +