diff --git a/docs/3.config/0.index.md b/docs/3.config/0.index.md index 997496d3a8..dd1cd05055 100644 --- a/docs/3.config/0.index.md +++ b/docs/3.config/0.index.md @@ -40,7 +40,10 @@ Enable experimental features. #### `openAPI` -Enable `/_nitro/swagger` and `/_nitro/openapi.json` endpoints. +Enable `/_nitro/scalar` and `/_nitro/swagger` and `/_nitro/openapi.json` endpoints. + +> [!IMPORTANT] +> [Scalar](https://scalar.com/) support is currently available in [nightly channel](/guide/nightly). #### `wasm` diff --git a/playground/nitro.config.ts b/playground/nitro.config.ts index a763adbd67..2a9dd131e6 100644 --- a/playground/nitro.config.ts +++ b/playground/nitro.config.ts @@ -1 +1 @@ -export default defineNitroConfig({}); \ No newline at end of file +export default defineNitroConfig({}); diff --git a/src/options.ts b/src/options.ts index 4198e5b2b2..e6ec41b9cc 100644 --- a/src/options.ts +++ b/src/options.ts @@ -387,6 +387,10 @@ export async function loadOptions( route: "/_nitro/openapi.json", handler: "#internal/nitro/routes/openapi", }); + options.handlers.push({ + route: "/_nitro/scalar", + handler: "#internal/nitro/routes/scalar", + }); options.handlers.push({ route: "/_nitro/swagger", handler: "#internal/nitro/routes/swagger", diff --git a/src/runtime/routes/scalar.ts b/src/runtime/routes/scalar.ts new file mode 100644 index 0000000000..b22a314f8b --- /dev/null +++ b/src/runtime/routes/scalar.ts @@ -0,0 +1,183 @@ +import { eventHandler } from "h3"; + +// https://github.com/scalar/scalar + +// Served as /_nitro/scalar +export default eventHandler((event) => { + const title = "Nitro Scalar API Reference"; + + return /* html */ ` + + + + + + ${title} + + + + + + + `; +}); + +const customTheme = /* css */ `/* basic theme */ + .light-mode, + .light-mode .dark-mode { + --theme-background-1: #fff; + --theme-background-2: #fafafa; + --theme-background-3: rgb(245 245 245); + + --theme-color-1: #2a2f45; + --theme-color-2: #757575; + --theme-color-3: #8e8e8e; + + --theme-color-accent: #ef4444; + --theme-background-accent: transparent; + + --theme-border-color: rgba(0, 0, 0, 0.1); + } + .dark-mode { + --theme-background-1: #171717; + --theme-background-2: #262626; + --theme-background-3: #2e2e2e; + + --theme-color-1: rgba(255, 255, 255, 0.9); + --theme-color-2: rgba(255, 255, 255, 0.62); + --theme-color-3: rgba(255, 255, 255, 0.44); + + --theme-color-accent: #f87171; + --theme-background-accent: transparent; + + --theme-border-color: rgba(255, 255, 255, 0.1); + } + + /* Document Sidebar */ + .light-mode .t-doc__sidebar, + .dark-mode .t-doc__sidebar { + --sidebar-background-1: var(--theme-background-1); + --sidebar-color-1: var(--theme-color-1); + --sidebar-color-2: var(--theme-color-2); + --sidebar-border-color: var(--theme-border-color); + + --sidebar-item-hover-background: transparent; + --sidebar-item-hover-color: var(--sidebar-color-1); + + --sidebar-item-active-background: var(--theme-background-accent); + --sidebar-color-active: var(--theme-color-accent); + + --sidebar-search-background: transparent; + --sidebar-search-color: var(--theme-color-3); + --sidebar-search-border-color: var(--theme-border-color); + } + + /* advanced */ + .light-mode .dark-mode, + .light-mode { + --theme-color-green: #91b859; + --theme-color-red: #e53935; + --theme-color-yellow: #e2931d; + --theme-color-blue: #6182b8; + --theme-color-orange: #f76d47; + --theme-color-purple: #9c3eda; + } + .dark-mode { + --theme-color-green: #c3e88d; + --theme-color-red: #f07178; + --theme-color-yellow: #ffcb6b; + --theme-color-blue: #82aaff; + --theme-color-orange: #f78c6c; + --theme-color-purple: #c792ea; + } + /* custom-theme */ + .section-container:nth-of-type(2) + ~ .section-container + .scalar-card + .scalar-card-header { + --theme-background-2: var(--theme-background-1) !important; + } + .section-flare { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + animation: spin 39s linear infinite; + transition: all 0.3s ease-in-out; + opacity: 1; + } + .section-flare-item:nth-of-type(1), + .section-flare-item:nth-of-type(2), + .section-flare-item:nth-of-type(3) { + content: ""; + width: 1000px; + height: 1000px; + position: absolute; + top: 0; + right: 0; + border-radius: 50%; + background: var(--default-theme-background-1); + display: block; + opacity: 1; + filter: blur(48px); + -webkit-backface-visibility: hidden; + -webkit-perspective: 1000; + -webkit-transform: translate3d(0, 0, 0); + -webkit-transform: translateZ(0); + perspective: 1000; + transform: translate3d(0, 0, 0); + transform: translateZ(0); + } + .section-flare-item:nth-of-type(2) { + top: initial; + right: initial; + background: #ef4444; + width: 700px; + height: 700px; + opacity: 0.3; + animation: sectionflare 37s linear infinite; + } + .section-flare-item:nth-of-type(1) { + top: initial; + right: initial; + bottom: 0; + left: -20px; + background: #ef4444; + width: 500px; + height: 500px; + opacity: 0.3; + animation: sectionflare2 38s linear infinite; + } + @keyframes sectionflare { + 0%, + 100% { + transform: translate3d(0, 0, 0); + } + 50% { + transform: translate3d(525px, 525px, 0); + } + } + @keyframes sectionflare2 { + 0%, + 100% { + transform: translate3d(700px, 700px, 0); + } + 50% { + transform: translate3d(0, 0, 0); + } + } + @keyframes spin { + 100% { + transform: rotate(360deg); + } + } + .section-container:nth-of-type(2) { + overflow: hidden; + }`; diff --git a/src/runtime/routes/swagger.ts b/src/runtime/routes/swagger.ts index 6750bd6ab6..46cec5896e 100644 --- a/src/runtime/routes/swagger.ts +++ b/src/runtime/routes/swagger.ts @@ -6,7 +6,7 @@ import { eventHandler } from "h3"; export default eventHandler((event) => { const title = "Nitro Swagger UI"; const CDN_BASE = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@^4"; - return html` + return /* html */ ` @@ -38,7 +38,3 @@ export default eventHandler((event) => { `; }); - -function html(str, ...args) { - return String.raw(str, ...args); -}