From cbe06d3d0764a2eeff85568cb26ce79c4abd06a6 Mon Sep 17 00:00:00 2001 From: Robbie Davis Date: Fri, 13 Mar 2026 21:41:26 -0400 Subject: [PATCH 1/4] Add read-only API reference and Swagger UI tweaks Replace the minimal bundled Swagger page with a full read-only API reference page. Import generated metadata, add an intro section with download/source actions and examples for opening Swagger on a local Listenarr instance, and render a read-only Swagger UI by fetching the bundled OpenAPI JSON into memory and stripping the spec description. Hide interactive/authorization/try-out controls and tweak Swagger UI settings (docExpansion, filter, supportedSubmitMethods). Propagate the same initializer change to the static swagger-initializer and the build script. Also update styles for the new layout and components, and change the homepage callout button text (and remove a hero logo image). Includes utility helpers (fetchSwaggerSpec, stripSwaggerDescription, formatTimestamp) and improved error messaging. --- scripts/sync-listenarr.mjs | 13 +- src/pages/api/index.js | 120 ++++++++++-- src/pages/api/index.module.css | 266 ++++++++++++++++----------- src/pages/index.js | 3 +- static/api-ui/swagger-initializer.js | 13 +- 5 files changed, 294 insertions(+), 121 deletions(-) diff --git a/scripts/sync-listenarr.mjs b/scripts/sync-listenarr.mjs index 6417a5b..28716a5 100644 --- a/scripts/sync-listenarr.mjs +++ b/scripts/sync-listenarr.mjs @@ -267,9 +267,18 @@ async function bundleSwaggerUi(port) { writeFileSync( path.join(apiDir, 'swagger-initializer.js'), - `window.onload = function () { + `window.onload = async function () { + const response = await fetch('./openapi.json'); + const spec = await response.json(); + window.ui = SwaggerUIBundle({ - url: './openapi.json', + spec: { + ...spec, + info: { + ...(spec.info || {}), + description: '' + } + }, dom_id: '#swagger-ui', deepLinking: true, presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset], diff --git a/src/pages/api/index.js b/src/pages/api/index.js index dc155db..aa243c8 100644 --- a/src/pages/api/index.js +++ b/src/pages/api/index.js @@ -3,9 +3,17 @@ import BrowserOnly from '@docusaurus/BrowserOnly'; import Layout from '@theme/Layout'; import useBaseUrl from '@docusaurus/useBaseUrl'; +import metadata from '@site/src/data/listenarr.generated.json'; import styles from './index.module.css'; -function SwaggerApp({assetBase}) { +const swaggerExamples = [ + 'http://localhost:5000/swagger/', + 'http://:/swagger/', + 'https://listenarr.example.com/swagger/', + 'https://listenarr.example.com//swagger/', +]; + +function SwaggerReference({assetBase}) { const containerRef = useRef(null); const [errorMessage, setErrorMessage] = useState(''); @@ -18,16 +26,21 @@ function SwaggerApp({assetBase}) { Promise.all([ ensureScript(`${assetBase}swagger-ui-bundle.js`, 'listenarr-swagger-ui-bundle'), ensureScript(`${assetBase}swagger-ui-standalone-preset.js`, 'listenarr-swagger-ui-standalone'), + fetchSwaggerSpec(`${assetBase}openapi.json`), ]) - .then(() => { - if (cancelled || !containerRef.current || !window.SwaggerUIBundle) { + .then(([, , spec]) => { + if (cancelled || !containerRef.current || !window.SwaggerUIBundle || !spec) { return; } window.SwaggerUIBundle({ - url: `${assetBase}openapi.json`, + spec: stripSwaggerDescription(spec), domNode: containerRef.current, deepLinking: true, + docExpansion: 'list', + filter: true, + displayOperationId: false, + supportedSubmitMethods: [], presets: [window.SwaggerUIBundle.presets.apis, window.SwaggerUIStandalonePreset], layout: 'StandaloneLayout', }); @@ -35,7 +48,7 @@ function SwaggerApp({assetBase}) { .catch((error) => { if (!cancelled) { const message = error instanceof Error ? error.message : String(error); - setErrorMessage(`Failed to load Swagger UI: ${message}`); + setErrorMessage(`Failed to load API reference: ${message}`); } }); @@ -91,18 +104,101 @@ function ensureStylesheet(href, id) { document.head.appendChild(stylesheet); } -export default function ApiUiPage() { +function fetchSwaggerSpec(url) { + return fetch(url).then((response) => { + if (!response.ok) { + throw new Error(`Unable to load ${url}`); + } + + return response.json(); + }); +} + +function stripSwaggerDescription(spec) { + return { + ...spec, + info: { + ...(spec.info || {}), + description: '', + }, + }; +} + +function formatTimestamp(value) { + const date = new Date(value); + + if (Number.isNaN(date.getTime())) { + return value; + } + + return new Intl.DateTimeFormat('en-US', { + dateStyle: 'long', + timeStyle: 'short', + }).format(date); +} + +export default function ApiPage() { const assetBase = useBaseUrl('/api-ui/'); + const openApiUrl = useBaseUrl('/api-ui/openapi.json'); + const generatedAt = formatTimestamp(metadata.generatedAt); return ( - +
-
- Loading Swagger UI...
}> - {() => } - -
+
+
+

Listenarr API documentation

+

+ This page renders the bundled OpenAPI snapshot as a read-only endpoint reference. + For interactive requests and write actions, open Swagger on your own Listenarr + instance. +

+ +
+ +
+
+

Open Swagger on your own instance

+

+ Use the same host, port, and optional base path as your Listenarr web app, then + append /swagger/. +

+

+ Use your own instance when you need live requests, login, or write operations. +

+
+                  {swaggerExamples.join('\n')}
+                
+
+
+
+ +
+
+
+

Endpoint reference

+

+ Read-only Swagger rendering of the bundled OpenAPI document. Execution is + disabled on this docs site. +

+
+
+
+ Loading API reference...
}> + {() => } + + +
diff --git a/src/pages/api/index.module.css b/src/pages/api/index.module.css index c1d49ce..59a8911 100644 --- a/src/pages/api/index.module.css +++ b/src/pages/api/index.module.css @@ -2,39 +2,151 @@ padding: 2.5rem 0 4rem; } -.header { +.introSection { + display: grid; + grid-template-columns: minmax(0, 1.2fr) minmax(21rem, 0.8fr); + gap: 1.25rem; + align-items: stretch; + margin-bottom: 2rem; +} + +.hero { display: flex; - gap: 1rem; - justify-content: space-between; - align-items: end; - margin-bottom: 1.5rem; + flex-direction: column; + justify-content: center; + min-width: 0; } -.header h1 { +.kicker { margin-bottom: 0.5rem; - color: #ffffff; + color: var(--brand-300); + font-size: 0.85rem; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.hero h1 { + margin-bottom: 0.85rem; + color: var(--text-primary); letter-spacing: -0.04em; } -.header p:last-child { +.lead { + margin-bottom: 1.5rem; + color: var(--text-secondary); + font-size: 1.05rem; max-width: 44rem; +} + +.actions { + display: flex; + flex-wrap: wrap; + gap: 0.9rem; +} + +.grid { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 1rem; + min-width: 0; +} + +.card { + height: 100%; + padding: 1.5rem; + border: 1px solid var(--border-subtle); + border-radius: 12px; + background: rgba(35, 35, 35, 0.86); + box-shadow: var(--shadow-card); +} + +.card h2 { + margin-bottom: 0.85rem; + color: var(--text-primary); + font-size: 1.15rem; +} + +.card p:last-child { margin-bottom: 0; } -.kicker { - margin-bottom: 0.5rem; - color: var(--brand-300); +.cardNote { + margin-bottom: 1rem; +} + +.steps { + margin: 0 0 1rem; + padding-left: 1.25rem; + color: var(--text-secondary); +} + +.steps li + li { + margin-top: 0.5rem; +} + +.codeBlock { + margin: 0; + padding: 1rem; + border: 1px solid var(--border-subtle); + border-radius: 10px; + background: #11161d; + overflow-x: auto; +} + +.codeBlock code { + color: #f4f8fc; + font-size: 0.95rem; + white-space: pre; +} + +.metaList { + display: grid; + gap: 0.85rem; + margin: 0; +} + +.metaList div { + display: grid; + gap: 0.2rem; +} + +.metaList dt { + color: var(--text-muted); font-size: 0.85rem; - font-weight: 700; - letter-spacing: 0.12em; text-transform: uppercase; + letter-spacing: 0.08em; +} + +.metaList dd { + margin: 0; + color: var(--text-primary); + font-weight: 600; +} + +.referenceSection { + margin-top: 2rem; +} + +.referenceHeader { + margin-bottom: 1rem; +} + +.referenceHeader h2 { + margin-bottom: 0.4rem; + color: var(--text-primary); +} + +.referenceHeader p { + margin-bottom: 0; + color: var(--text-secondary); } .swaggerShell { min-height: 78vh; overflow: hidden; border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 8px; + border-radius: 12px; background: #232323; box-shadow: 0 10px 24px rgba(0, 0, 0, 0.24); } @@ -70,71 +182,21 @@ background: #161b22; } -.swaggerShell :global(.swagger-ui .dialog-ux .backdrop-ux) { - background: rgba(0, 0, 0, 0.82); -} - -.swaggerShell :global(.swagger-ui .dialog-ux .modal-ux) { - background: #1b222c; - border: 1px solid rgba(255, 255, 255, 0.08); - box-shadow: 0 20px 50px rgba(0, 0, 0, 0.45); - color: #ffffff; -} - -.swaggerShell :global(.swagger-ui .dialog-ux .modal-ux-header) { - background: #11161d; - border-bottom: 1px solid rgba(255, 255, 255, 0.08); -} - -.swaggerShell :global(.swagger-ui .dialog-ux .modal-ux-content) { - background: #1b222c; -} - -.swaggerShell :global(.swagger-ui .dialog-ux .modal-ux-header h3), -.swaggerShell :global(.swagger-ui .dialog-ux .modal-ux-content h4), -.swaggerShell :global(.swagger-ui .dialog-ux .modal-ux-content p), -.swaggerShell :global(.swagger-ui .dialog-ux .modal-ux-content label), -.swaggerShell :global(.swagger-ui .dialog-ux .modal-ux-content small), -.swaggerShell :global(.swagger-ui .scopes h2), -.swaggerShell :global(.swagger-ui .auth-container h4) { - color: #ffffff; -} - -.swaggerShell :global(.swagger-ui .scopes h2 a), -.swaggerShell :global(.swagger-ui .auth-container .wrapper p), -.swaggerShell :global(.swagger-ui .auth-container .wrapper label) { - color: #90caf9; -} - -.swaggerShell :global(.swagger-ui .dialog-ux .modal-ux-header .close-modal), -.swaggerShell :global(.swagger-ui .dialog-ux .modal-ux-header .close-modal svg) { - color: #ffffff; - fill: currentColor; -} - -.swaggerShell :global(.swagger-ui .auth-container) { - background: #161b22; - border-bottom: 1px solid rgba(255, 255, 255, 0.08); -} - -.swaggerShell :global(.swagger-ui .auth-container:last-of-type) { - border-bottom: 0; -} - -.swaggerShell :global(.swagger-ui .auth-container .errors) { - background: rgba(249, 62, 62, 0.14); - border: 1px solid rgba(249, 62, 62, 0.35); - color: #ffd4d4; +.swaggerShell :global(.swagger-ui .scheme-container) { + display: none; } -.swaggerShell :global(.swagger-ui .scheme-container) { - background: #11161d; - box-shadow: none; - border-bottom: 1px solid rgba(255, 255, 255, 0.08); +.swaggerShell :global(.swagger-ui .scheme-container .btn.authorize), +.swaggerShell :global(.swagger-ui .authorize), +.swaggerShell :global(.swagger-ui .auth-wrapper), +.swaggerShell :global(.swagger-ui .auth-container), +.swaggerShell :global(.swagger-ui .try-out), +.swaggerShell :global(.swagger-ui .execute-wrapper) { + display: none; } .swaggerShell :global(.swagger-ui .info) { - margin: unset; + margin: 0; padding: 20px 0; } @@ -142,24 +204,14 @@ color: #fff; } -.swaggerShell :global(.swagger-ui .scheme-container) { - margin: 30px; - padding: unset; - border: unset; -} - .swaggerShell :global(.swagger-ui .opblock .opblock-section-header) { - background: hsla(0,0%,100%,.1); + background: hsla(0, 0%, 100%, 0.1); } .swaggerShell :global(.swagger-ui .opblock .opblock-section-header h4) { color: #fff; } -.swaggerShell :global(.swagger-ui .btn) { - color: #fff; -} - .swaggerShell :global(.swagger-ui .opblock-description-wrapper p) { color: #fff; } @@ -224,12 +276,17 @@ .swaggerShell :global(.swagger-ui .model-toggle:after), .swaggerShell :global(.swagger-ui .expand-operation svg), .swaggerShell :global(.swagger-ui .opblock-control-arrow svg), -.swaggerShell :global(.swagger-ui .authorization__btn svg), .swaggerShell :global(.swagger-ui .copy-to-clipboard svg) { color: #cccccc; fill: currentColor; } +.swaggerShell :global(.swagger-ui .authorization__btn), +.swaggerShell :global(.swagger-ui .authorization__btn svg) { + color: #90caf9; + fill: currentColor; +} + .swaggerShell :global(.swagger-ui .opblock .opblock-summary-description), .swaggerShell :global(.swagger-ui .opblock .opblock-summary-operation-id), .swaggerShell :global(.swagger-ui .opblock .opblock-summary-path), @@ -293,6 +350,7 @@ .swaggerShell :global(.swagger-ui table thead tr td), .swaggerShell :global(.swagger-ui .responses-table tbody tr td) { border-color: rgba(255, 255, 255, 0.08); + padding: 0.85rem 1rem !important; } .swaggerShell :global(.swagger-ui .btn), @@ -304,21 +362,6 @@ border-radius: 8px; } -.swaggerShell :global(.swagger-ui .btn.authorize), -.swaggerShell :global(.swagger-ui .btn.execute) { - border: 1px solid #1976d2; - background: #2196f3; - color: #ffffff; - box-shadow: 0 2px 8px rgba(33, 150, 243, 0.12); -} - -.swaggerShell :global(.swagger-ui .btn.authorize:hover), -.swaggerShell :global(.swagger-ui .btn.execute:hover) { - background: #1976d2; - box-shadow: 0 4px 12px rgba(33, 150, 243, 0.14); -} - -.swaggerShell :global(.swagger-ui .btn.cancel), .swaggerShell :global(.swagger-ui .download-url-wrapper .select-label select), .swaggerShell :global(.swagger-ui select) { background: #2a2f37; @@ -356,8 +399,25 @@ } @media (max-width: 996px) { - .header { + .introSection { + grid-template-columns: 1fr; + } + + .grid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 640px) { + .page { + padding-top: 2rem; + } + + .actions { flex-direction: column; - align-items: flex-start; + } + + .actions :global(.button) { + width: 100%; } } diff --git a/src/pages/index.js b/src/pages/index.js index 7c84072..d5e148b 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -241,7 +241,6 @@ function HomepageHeader() {
- Listenarr

Simplify your audiobook experience

Centralize discovery, downloads, metadata, and library organization into one self-hosted application built for audiobook collectors. @@ -478,7 +477,7 @@ function ApiCallout() {

- View API documentation + View API guide View Listenarr source diff --git a/static/api-ui/swagger-initializer.js b/static/api-ui/swagger-initializer.js index 7bef78f..ff87ac6 100644 --- a/static/api-ui/swagger-initializer.js +++ b/static/api-ui/swagger-initializer.js @@ -1,6 +1,15 @@ -window.onload = function () { +window.onload = async function () { + const response = await fetch('./openapi.json'); + const spec = await response.json(); + window.ui = SwaggerUIBundle({ - url: './openapi.json', + spec: { + ...spec, + info: { + ...(spec.info || {}), + description: '' + } + }, dom_id: '#swagger-ui', deepLinking: true, presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset], From 7014955d9bd5e460b6b8dd34e118d5ccc91f415f Mon Sep 17 00:00:00 2001 From: Robbie Davis Date: Fri, 13 Mar 2026 21:42:07 -0400 Subject: [PATCH 2/4] Clarify API docs and use transparent navbar Update documentation to clarify the bundled API guide: reword intro, rename the API reference, add a download link for the bundled OpenAPI JSON, and add instructions for opening Swagger on a local Listenarr instance (emphasizing the GitHub Pages site is read-only). Tweak refresh wording for the OpenAPI snapshot. In CSS, make the navbar transparent and embed GitHub/Discord SVG masks as data URIs (removing external image path refs) to improve styling and asset reliability. --- docs/intro.md | 4 ++-- docs/reference/api.md | 30 +++++++++++++++++++++++------- src/css/custom.css | 13 +++++++------ 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/docs/intro.md b/docs/intro.md index b9c6947..6119eaa 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -5,7 +5,7 @@ slug: / # Listenarr documentation -Listenarr is a self-hosted audiobook automation server built to streamline the path from discovery to a clean, organized library. This site covers installation, first-run setup, the main configuration screens, and the bundled API UI. +Listenarr is a self-hosted audiobook automation server built to streamline the path from discovery to a clean, organized library. This site covers installation, first-run setup, the main configuration screens, and the Listenarr API. ## What you can do with Listenarr @@ -29,4 +29,4 @@ Listenarr is a self-hosted audiobook automation server built to streamline the p ## API -Open the bundled [API documentation](/api/) to inspect and test the current OpenAPI document that ships with this docs site. +Open the [API guide](pathname:///api/) to review the bundled OpenAPI snapshot and see how to use Swagger on your own Listenarr instance. diff --git a/docs/reference/api.md b/docs/reference/api.md index 5e73504..d593784 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -2,21 +2,37 @@ sidebar_position: 1 --- -# API UI +# API -Listenarr ships with a Swagger UI for the REST API. This docs site bundles that UI and the current OpenAPI document directly from the Listenarr repository during the documentation build. +Listenarr ships with a Swagger UI for the REST API. This docs site publishes a read-only endpoint reference plus the current bundled OpenAPI document generated from the Listenarr repository during the docs build. -## Open the bundled UI +## On this docs site -[Launch the Listenarr API UI](/api/) +- [Open the API guide](pathname:///api/) +- [Download the bundled OpenAPI JSON](pathname:///api-ui/openapi.json) -## Authentication +The GitHub Pages site is documentation-only. It is useful for reviewing routes, models, and response shapes, but it is not the right place to make live requests against your own Listenarr server. -The API supports session-token and API-key flows. The bundled Swagger UI includes the same authorization mechanisms exposed by Listenarr's own development Swagger page. +## Open Swagger on your own instance + +Open Swagger on the same origin as your Listenarr app: + +```text +http://localhost:5000/swagger/ +http://:/swagger/ +https://listenarr.example.com/swagger/ +https://listenarr.example.com//swagger/ +``` + +Use that in-instance Swagger page when you want to: + +- test requests against your own server +- sign in and work with your instance's own auth flow +- use write endpoints that require the `X-XSRF-TOKEN` flow ## Refresh behavior -The bundled API UI is regenerated when: +The bundled OpenAPI snapshot is regenerated when: - this documentation site is pushed - the Listenarr release workflow dispatches a docs rebuild diff --git a/src/css/custom.css b/src/css/custom.css index 8891c94..59963ef 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -43,7 +43,7 @@ --ifm-link-color: var(--brand-300); --ifm-code-font-size: 95%; --ifm-line-height-base: 1.65; - --ifm-navbar-background-color: rgba(26, 26, 26, 0.92); + --ifm-navbar-background-color: transparent; --ifm-navbar-link-color: var(--text-secondary); --ifm-footer-background-color: #111111; --ifm-footer-color: var(--text-secondary); @@ -75,7 +75,7 @@ --ifm-card-background-color: var(--bg-tertiary); --ifm-font-color-base: var(--text-secondary); --ifm-heading-color: var(--text-primary); - --ifm-navbar-background-color: rgba(26, 26, 26, 0.92); + --ifm-navbar-background-color: transparent; --ifm-footer-background-color: #111111; --ifm-menu-color-active: #ffffff; --docusaurus-highlighted-code-line-bg: rgba(var(--brand-rgb), 0.12); @@ -95,6 +95,7 @@ body { } .navbar { + background-color: transparent; backdrop-filter: blur(18px); border-bottom: 1px solid var(--border-subtle); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2); @@ -163,13 +164,13 @@ body { } .navbar__items--right .header-github-link::before { - mask-image: url('/img/listenarr/github-mark.svg'); - -webkit-mask-image: url('/img/listenarr/github-mark.svg'); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 0C3.58 0 0 3.58 0 8a8 8 0 0 0 5.47 7.59c.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.5-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.68 7.68 0 0 1 4 0c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z'/%3E%3C/svg%3E"); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 0C3.58 0 0 3.58 0 8a8 8 0 0 0 5.47 7.59c.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.5-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.68 7.68 0 0 1 4 0c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z'/%3E%3C/svg%3E"); } .navbar__items--right .header-discord-link::before { - mask-image: url('/img/listenarr/discord-mark.svg'); - -webkit-mask-image: url('/img/listenarr/discord-mark.svg'); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M13.55 2.91A13.86 13.86 0 0 0 10.2 1.9a.05.05 0 0 0-.06.03c-.14.25-.3.58-.41.84a12.79 12.79 0 0 0-3.46 0 8.37 8.37 0 0 0-.42-.84.05.05 0 0 0-.06-.03 13.83 13.83 0 0 0-3.35 1.02.04.04 0 0 0-.02.02C.53 5.74-.12 8.48.2 11.19a.06.06 0 0 0 .02.04 13.97 13.97 0 0 0 4.11 2.08.05.05 0 0 0 .06-.02c.32-.44.61-.9.86-1.39a.05.05 0 0 0-.03-.07 9.18 9.18 0 0 1-1.31-.63.05.05 0 0 1-.01-.08l.26-.2a.05.05 0 0 1 .05-.01 9.93 9.93 0 0 0 8.48 0 .05.05 0 0 1 .05 0c.09.07.17.14.26.2a.05.05 0 0 1-.01.08 8.62 8.62 0 0 1-1.31.63.05.05 0 0 0-.03.07c.25.49.54.95.86 1.38a.05.05 0 0 0 .06.02 13.92 13.92 0 0 0 4.11-2.08.05.05 0 0 0 .02-.04c.38-3.13-.64-5.85-2.21-8.25a.04.04 0 0 0-.02-.02ZM5.35 9.56c-.83 0-1.5-.76-1.5-1.69 0-.93.66-1.69 1.5-1.69.84 0 1.52.77 1.5 1.69 0 .93-.66 1.69-1.5 1.69Zm5.3 0c-.83 0-1.5-.76-1.5-1.69 0-.93.66-1.69 1.5-1.69.84 0 1.52.77 1.5 1.69 0 .93-.66 1.69-1.5 1.69Z'/%3E%3C/svg%3E"); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M13.55 2.91A13.86 13.86 0 0 0 10.2 1.9a.05.05 0 0 0-.06.03c-.14.25-.3.58-.41.84a12.79 12.79 0 0 0-3.46 0 8.37 8.37 0 0 0-.42-.84.05.05 0 0 0-.06-.03 13.83 13.83 0 0 0-3.35 1.02.04.04 0 0 0-.02.02C.53 5.74-.12 8.48.2 11.19a.06.06 0 0 0 .02.04 13.97 13.97 0 0 0 4.11 2.08.05.05 0 0 0 .06-.02c.32-.44.61-.9.86-1.39a.05.05 0 0 0-.03-.07 9.18 9.18 0 0 1-1.31-.63.05.05 0 0 1-.01-.08l.26-.2a.05.05 0 0 1 .05-.01 9.93 9.93 0 0 0 8.48 0 .05.05 0 0 1 .05 0c.09.07.17.14.26.2a.05.05 0 0 1-.01.08 8.62 8.62 0 0 1-1.31.63.05.05 0 0 0-.03.07c.25.49.54.95.86 1.38a.05.05 0 0 0 .06.02 13.92 13.92 0 0 0 4.11-2.08.05.05 0 0 0 .02-.04c.38-3.13-.64-5.85-2.21-8.25a.04.04 0 0 0-.02-.02ZM5.35 9.56c-.83 0-1.5-.76-1.5-1.69 0-.93.66-1.69 1.5-1.69.84 0 1.52.77 1.5 1.69 0 .93-.66 1.69-1.5 1.69Zm5.3 0c-.83 0-1.5-.76-1.5-1.69 0-.93.66-1.69 1.5-1.69.84 0 1.52.77 1.5 1.69 0 .93-.66 1.69-1.5 1.69Z'/%3E%3C/svg%3E"); } .navbar__items--right .header-github-link:hover, From f075a482767f969b0a5544218ebe36f73d4a6732 Mon Sep 17 00:00:00 2001 From: Robbie Davis Date: Sat, 14 Mar 2026 23:36:29 -0400 Subject: [PATCH 3/4] Add local search and mobile navbar sidebar Integrate @easyops-cn/docusaurus-search-local (config, dependency and lockfile changes) and add a navbar search item with accompanying UI styles. Introduce mobile navbar sidebar components (PrimaryMenu, SecondaryMenu, MobileSidebar) and responsive CSS for the sidebar/search. Remove product-tour and reference docs (features.md, api.md, development.md) and their sidebar entries. Update the API docs page copy and examples to note Swagger is development-only and default to port 4545. Bump Node engine requirement to >=20.18.1 and refresh audimeta.trending.json generated timestamp. Lockfile updated to include search plugin dependencies. --- docs/product-tour/features.md | 33 -- docs/reference/api.md | 38 -- docs/reference/development.md | 27 - docusaurus.config.js | 17 + package-lock.json | 557 ++++++++++++++++++ package.json | 3 +- sidebars.js | 15 - src/css/custom.css | 166 ++++++ src/data/audimeta.trending.json | 2 +- src/pages/api/index.js | 25 +- .../Navbar/MobileSidebar/PrimaryMenu/index.js | 32 + .../MobileSidebar/SecondaryMenu/index.js | 35 ++ src/theme/Navbar/MobileSidebar/index.js | 25 + 13 files changed, 849 insertions(+), 126 deletions(-) delete mode 100644 docs/product-tour/features.md delete mode 100644 docs/reference/api.md delete mode 100644 docs/reference/development.md create mode 100644 src/theme/Navbar/MobileSidebar/PrimaryMenu/index.js create mode 100644 src/theme/Navbar/MobileSidebar/SecondaryMenu/index.js create mode 100644 src/theme/Navbar/MobileSidebar/index.js diff --git a/docs/product-tour/features.md b/docs/product-tour/features.md deleted file mode 100644 index 300c046..0000000 --- a/docs/product-tour/features.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Features - -Listenarr combines search, download orchestration, metadata enrichment, and library management in a single server. - -## Core capabilities - -### Multi-source search - -Search across multiple torrent and NZB indexers from one interface, then send results directly to a supported client. - -### Download automation - -Track download progress, reconcile completed jobs, and keep the library in sync without bouncing between separate tools. - -### Metadata enrichment - -Pull richer audiobook details and artwork from external metadata providers to improve matching and browsing. - -### Real-time feedback - -The web UI exposes activity, logs, and progress updates so operational issues surface quickly. - -### Library organization - -Use configurable naming patterns and folder structures to keep your collection tidy and consistent. - -### Responsive management UI - -The main interface is designed to work across desktop and mobile so you can manage the server without a desktop-only workflow. diff --git a/docs/reference/api.md b/docs/reference/api.md deleted file mode 100644 index d593784..0000000 --- a/docs/reference/api.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -sidebar_position: 1 ---- - -# API - -Listenarr ships with a Swagger UI for the REST API. This docs site publishes a read-only endpoint reference plus the current bundled OpenAPI document generated from the Listenarr repository during the docs build. - -## On this docs site - -- [Open the API guide](pathname:///api/) -- [Download the bundled OpenAPI JSON](pathname:///api-ui/openapi.json) - -The GitHub Pages site is documentation-only. It is useful for reviewing routes, models, and response shapes, but it is not the right place to make live requests against your own Listenarr server. - -## Open Swagger on your own instance - -Open Swagger on the same origin as your Listenarr app: - -```text -http://localhost:5000/swagger/ -http://:/swagger/ -https://listenarr.example.com/swagger/ -https://listenarr.example.com//swagger/ -``` - -Use that in-instance Swagger page when you want to: - -- test requests against your own server -- sign in and work with your instance's own auth flow -- use write endpoints that require the `X-XSRF-TOKEN` flow - -## Refresh behavior - -The bundled OpenAPI snapshot is regenerated when: - -- this documentation site is pushed -- the Listenarr release workflow dispatches a docs rebuild diff --git a/docs/reference/development.md b/docs/reference/development.md deleted file mode 100644 index 59e3db1..0000000 --- a/docs/reference/development.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -sidebar_position: 2 ---- - -# Development notes - -This docs site is intentionally coupled to the Listenarr repository so the API reference stays current with the product. - -## Sync process - -The `scripts/sync-listenarr.mjs` script: - -1. Publishes the Listenarr API into a temporary directory. -2. Boots the published API in development mode. -3. Fetches the OpenAPI document and Swagger UI assets from that running instance. -4. Writes the bundled API files into this site's `static/api-ui/` directory. -5. Refreshes the generated Listenarr version metadata used by the homepage. - -## Local sync - -Run the sync script against a local Listenarr checkout: - -```bash -npm run sync:listenarr -- --repo ../Listenarr -``` - -If you point the script at another checkout or tag, the docs build will bundle that ref instead. diff --git a/docusaurus.config.js b/docusaurus.config.js index 7b69c0c..63231a5 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -45,6 +45,19 @@ const config = { ], ], + themes: [ + [ + require.resolve('@easyops-cn/docusaurus-search-local'), + { + indexBlog: false, + indexPages: true, + docsRouteBasePath: '/docs', + highlightSearchTermsOnTargetPage: true, + explicitSearchResultPath: true, + }, + ], + ], + themeConfig: /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ ({ @@ -67,6 +80,10 @@ const config = { label: 'API', position: 'left', }, + { + type: 'search', + position: 'right', + }, { href: 'https://github.com/Listenarrs/Listenarr', className: 'header-github-link', diff --git a/package-lock.json b/package-lock.json index 2ba267d..c9b774e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/preset-classic": "3.9.2", + "@easyops-cn/docusaurus-search-local": "^0.55.1", "@mdx-js/react": "^3.1.0", "clsx": "^2.1.1", "prism-react-renderer": "^2.4.1", @@ -4050,6 +4051,156 @@ "node": ">=20.0" } }, + "node_modules/@easyops-cn/autocomplete.js": { + "version": "0.38.1", + "resolved": "https://registry.npmjs.org/@easyops-cn/autocomplete.js/-/autocomplete.js-0.38.1.tgz", + "integrity": "sha512-drg76jS6syilOUmVNkyo1c7ZEBPcPuK+aJA7AksM5ZIIbV57DMHCywiCr+uHyv8BE5jUTU98j/H7gVrkHrWW3Q==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "immediate": "^3.2.3" + } + }, + "node_modules/@easyops-cn/docusaurus-search-local": { + "version": "0.55.1", + "resolved": "https://registry.npmjs.org/@easyops-cn/docusaurus-search-local/-/docusaurus-search-local-0.55.1.tgz", + "integrity": "sha512-jmBKj1J+tajqNrCvECwKCQYTWwHVZDGApy8lLOYEPe+Dm0/f3Ccdw8BP5/OHNpltr7WDNY2roQXn+TWn2f1kig==", + "license": "MIT", + "dependencies": { + "@docusaurus/plugin-content-docs": "^2 || ^3", + "@docusaurus/theme-translations": "^2 || ^3", + "@docusaurus/utils": "^2 || ^3", + "@docusaurus/utils-common": "^2 || ^3", + "@docusaurus/utils-validation": "^2 || ^3", + "@easyops-cn/autocomplete.js": "^0.38.1", + "@node-rs/jieba": "^1.6.0", + "cheerio": "^1.0.0", + "clsx": "^2.1.1", + "comlink": "^4.4.2", + "debug": "^4.2.0", + "fs-extra": "^10.0.0", + "klaw-sync": "^6.0.0", + "lunr": "^2.3.9", + "lunr-languages": "^1.4.0", + "mark.js": "^8.11.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "@docusaurus/theme-common": "^2 || ^3", + "open-ask-ai": "^0.7.3", + "react": "^16.14.0 || ^17 || ^18 || ^19", + "react-dom": "^16.14.0 || 17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "open-ask-ai": { + "optional": true + } + } + }, + "node_modules/@easyops-cn/docusaurus-search-local/node_modules/cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/@easyops-cn/docusaurus-search-local/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@easyops-cn/docusaurus-search-local/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@easyops-cn/docusaurus-search-local/node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/@emnapi/core": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", + "integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", + "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -4618,6 +4769,18 @@ "react": ">=16" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, "node_modules/@noble/hashes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", @@ -4630,6 +4793,271 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@node-rs/jieba": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@node-rs/jieba/-/jieba-1.10.4.tgz", + "integrity": "sha512-GvDgi8MnBiyWd6tksojej8anIx18244NmIOc1ovEw8WKNUejcccLfyu8vj66LWSuoZuKILVtNsOy4jvg3aoxIw==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@node-rs/jieba-android-arm-eabi": "1.10.4", + "@node-rs/jieba-android-arm64": "1.10.4", + "@node-rs/jieba-darwin-arm64": "1.10.4", + "@node-rs/jieba-darwin-x64": "1.10.4", + "@node-rs/jieba-freebsd-x64": "1.10.4", + "@node-rs/jieba-linux-arm-gnueabihf": "1.10.4", + "@node-rs/jieba-linux-arm64-gnu": "1.10.4", + "@node-rs/jieba-linux-arm64-musl": "1.10.4", + "@node-rs/jieba-linux-x64-gnu": "1.10.4", + "@node-rs/jieba-linux-x64-musl": "1.10.4", + "@node-rs/jieba-wasm32-wasi": "1.10.4", + "@node-rs/jieba-win32-arm64-msvc": "1.10.4", + "@node-rs/jieba-win32-ia32-msvc": "1.10.4", + "@node-rs/jieba-win32-x64-msvc": "1.10.4" + } + }, + "node_modules/@node-rs/jieba-android-arm-eabi": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-android-arm-eabi/-/jieba-android-arm-eabi-1.10.4.tgz", + "integrity": "sha512-MhyvW5N3Fwcp385d0rxbCWH42kqDBatQTyP8XbnYbju2+0BO/eTeCCLYj7Agws4pwxn2LtdldXRSKavT7WdzNA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-android-arm64": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-android-arm64/-/jieba-android-arm64-1.10.4.tgz", + "integrity": "sha512-XyDwq5+rQ+Tk55A+FGi6PtJbzf974oqnpyCcCPzwU3QVXJCa2Rr4Lci+fx8oOpU4plT3GuD+chXMYLsXipMgJA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-darwin-arm64": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-darwin-arm64/-/jieba-darwin-arm64-1.10.4.tgz", + "integrity": "sha512-G++RYEJ2jo0rxF9626KUy90wp06TRUjAsvY/BrIzEOX/ingQYV/HjwQzNPRR1P1o32a6/U8RGo7zEBhfdybL6w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-darwin-x64": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-darwin-x64/-/jieba-darwin-x64-1.10.4.tgz", + "integrity": "sha512-MmDNeOb2TXIZCPyWCi2upQnZpPjAxw5ZGEj6R8kNsPXVFALHIKMa6ZZ15LCOkSTsKXVC17j2t4h+hSuyYb6qfQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-freebsd-x64": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-freebsd-x64/-/jieba-freebsd-x64-1.10.4.tgz", + "integrity": "sha512-/x7aVQ8nqUWhpXU92RZqd333cq639i/olNpd9Z5hdlyyV5/B65LLy+Je2B2bfs62PVVm5QXRpeBcZqaHelp/bg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-linux-arm-gnueabihf": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-linux-arm-gnueabihf/-/jieba-linux-arm-gnueabihf-1.10.4.tgz", + "integrity": "sha512-crd2M35oJBRLkoESs0O6QO3BBbhpv+tqXuKsqhIG94B1d02RVxtRIvSDwO33QurxqSdvN9IeSnVpHbDGkuXm3g==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-linux-arm64-gnu": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-linux-arm64-gnu/-/jieba-linux-arm64-gnu-1.10.4.tgz", + "integrity": "sha512-omIzNX1psUzPcsdnUhGU6oHeOaTCuCjUgOA/v/DGkvWC1jLcnfXe4vdYbtXMh4XOCuIgS1UCcvZEc8vQLXFbXQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-linux-arm64-musl": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-linux-arm64-musl/-/jieba-linux-arm64-musl-1.10.4.tgz", + "integrity": "sha512-Y/tiJ1+HeS5nnmLbZOE+66LbsPOHZ/PUckAYVeLlQfpygLEpLYdlh0aPpS5uiaWMjAXYZYdFkpZHhxDmSLpwpw==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-linux-x64-gnu": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-linux-x64-gnu/-/jieba-linux-x64-gnu-1.10.4.tgz", + "integrity": "sha512-WZO8ykRJpWGE9MHuZpy1lu3nJluPoeB+fIJJn5CWZ9YTVhNDWoCF4i/7nxz1ntulINYGQ8VVuCU9LD86Mek97g==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-linux-x64-musl": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-linux-x64-musl/-/jieba-linux-x64-musl-1.10.4.tgz", + "integrity": "sha512-uBBD4S1rGKcgCyAk6VCKatEVQb6EDD5I40v/DxODi5CuZVCANi9m5oee/MQbAoaX7RydA2f0OSCE9/tcwXEwUg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-wasm32-wasi": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-wasm32-wasi/-/jieba-wasm32-wasi-1.10.4.tgz", + "integrity": "sha512-Y2umiKHjuIJy0uulNDz9SDYHdfq5Hmy7jY5nORO99B4pySKkcrMjpeVrmWXJLIsEKLJwcCXHxz8tjwU5/uhz0A==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@node-rs/jieba-win32-arm64-msvc": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-win32-arm64-msvc/-/jieba-win32-arm64-msvc-1.10.4.tgz", + "integrity": "sha512-nwMtViFm4hjqhz1it/juQnxpXgqlGltCuWJ02bw70YUDMDlbyTy3grCJPpQQpueeETcALUnTxda8pZuVrLRcBA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-win32-ia32-msvc": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-win32-ia32-msvc/-/jieba-win32-ia32-msvc-1.10.4.tgz", + "integrity": "sha512-DCAvLx7Z+W4z5oKS+7vUowAJr0uw9JBw8x1Y23Xs/xMA4Em+OOSiaF5/tCJqZUCJ8uC4QeImmgDFiBqGNwxlyA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-win32-x64-msvc": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-win32-x64-msvc/-/jieba-win32-x64-msvc-1.10.4.tgz", + "integrity": "sha512-+sqemSfS1jjb+Tt7InNbNzrRh1Ua3vProVvC4BZRPg010/leCbGFFiQHpzcPRfpxAXZrzG5Y0YBTsPzN/I4yHQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -5179,6 +5607,16 @@ "node": ">=14.16" } }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -6854,6 +7292,12 @@ "node": ">=10" } }, + "node_modules/comlink": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.2.tgz", + "integrity": "sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g==", + "license": "Apache-2.0" + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -8058,6 +8502,31 @@ "node": ">= 0.8" } }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/encoding-sniffer/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.20.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", @@ -9772,6 +10241,12 @@ "node": ">=16.x" } }, + "node_modules/immediate": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", + "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==", + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -10358,6 +10833,15 @@ "node": ">=0.10.0" } }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -10537,6 +11021,24 @@ "yallist": "^3.0.2" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "license": "MIT" + }, + "node_modules/lunr-languages": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/lunr-languages/-/lunr-languages-1.14.0.tgz", + "integrity": "sha512-hWUAb2KqM3L7J5bcrngszzISY4BxrXn/Xhbb9TTCJYEGqlR1nG67/M14sp09+PTIRklobrn57IAxcdcO/ZFyNA==", + "license": "MPL-1.1" + }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "license": "MIT" + }, "node_modules/markdown-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", @@ -13497,6 +13999,18 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parse5/node_modules/entities": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", @@ -17291,6 +17805,15 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/undici": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.1.tgz", + "integrity": "sha512-5xoBibbmnjlcR3jdqtY2Lnx7WbrD/tHlT01TmvqZUFVc9Q1w4+j5hbnapTqbcXITMH1ovjq/W7BkqBilHiVAaA==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "7.18.2", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", @@ -18210,6 +18733,40 @@ "node": ">=0.8.0" } }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index e63bc9c..cd76c2b 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/preset-classic": "3.9.2", + "@easyops-cn/docusaurus-search-local": "^0.55.1", "@mdx-js/react": "^3.1.0", "clsx": "^2.1.1", "prism-react-renderer": "^2.4.1", @@ -24,7 +25,7 @@ "react-dom": "^19.0.0" }, "engines": { - "node": ">=20.0" + "node": ">=20.18.1" }, "browserslist": { "production": [ diff --git a/sidebars.js b/sidebars.js index 09acd81..4b19f3e 100644 --- a/sidebars.js +++ b/sidebars.js @@ -24,21 +24,6 @@ const sidebars = { 'configuration/general-settings', ], }, - { - type: 'category', - label: 'Product Tour', - items: [ - 'product-tour/features', - ], - }, - { - type: 'category', - label: 'Reference', - items: [ - 'reference/api', - 'reference/development', - ], - }, ], }; diff --git a/src/css/custom.css b/src/css/custom.css index 59963ef..60c5223 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -56,7 +56,20 @@ --ifm-button-border-radius: 8px; --ifm-global-radius: 8px; --ifm-global-shadow-lw: var(--shadow-card); + --ifm-navbar-search-input-background-color: rgba(255, 255, 255, 0.04); + --ifm-navbar-search-input-color: var(--text-primary); + --ifm-navbar-search-input-placeholder-color: var(--text-muted); --docusaurus-highlighted-code-line-bg: rgba(var(--brand-rgb), 0.12); + --search-local-modal-background: rgba(18, 18, 18, 0.98); + --search-local-modal-shadow: 0 24px 50px rgba(0, 0, 0, 0.42); + --search-local-hit-background: rgba(255, 255, 255, 0.04); + --search-local-hit-color: var(--text-primary); + --search-local-hit-shadow: inset 0 0 0 1px var(--border-subtle); + --search-local-highlight-color: var(--brand-300); + --search-local-hit-active-color: var(--text-primary); + --search-local-muted-color: var(--text-muted); + --search-local-modal-width: min(36rem, calc(100vw - 2rem)); + --search-local-modal-width-sm: min(22rem, calc(100vw - 2rem)); --listenarr-ink: var(--text-primary); --listenarr-muted: var(--text-muted); --listenarr-primary-deep: var(--brand-500); @@ -180,6 +193,44 @@ body { color: var(--text-primary); } +.navbar__search { + margin-left: 0.75rem; +} + +.navbar__search-input { + width: clamp(12rem, 20vw, 16rem); + height: 2.5rem; + padding: 0 3.75rem 0 2.5rem; + border: 1px solid var(--border-subtle); + border-radius: 999px; + background-color: var(--ifm-navbar-search-input-background-color); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='%23999999' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='9' cy='9' r='5.5'/%3E%3Cpath d='M13.2 13.2 17 17'/%3E%3C/svg%3E"); + background-position: 0.9rem center; + background-repeat: no-repeat; + background-size: 1rem; + color: var(--ifm-navbar-search-input-color); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); + transition: + border-color 0.18s ease, + background-color 0.18s ease, + box-shadow 0.18s ease; +} + +.navbar__search-input::placeholder { + color: var(--ifm-navbar-search-input-placeholder-color); +} + +.navbar__search-input:hover { + border-color: rgba(var(--brand-rgb), 0.28); + background-color: rgba(255, 255, 255, 0.06); +} + +.navbar__search-input:focus { + border-color: rgba(var(--brand-rgb), 0.5); + background-color: rgba(15, 15, 15, 0.92); + box-shadow: 0 0 0 3px rgba(var(--brand-rgb), 0.12); +} + .button { border-radius: 8px; font-weight: 600; @@ -349,3 +400,118 @@ table tr:nth-child(2n) { .markdown > h3 { scroll-margin-top: 6rem; } + +@media (max-width: 996px) { + .navbar { + height: auto; + min-height: var(--ifm-navbar-height); + } + + .navbar__inner { + align-items: center; + column-gap: 0.75rem; + row-gap: 0; + flex-wrap: nowrap; + } + + .navbar__inner > .navbar__items:first-child { + flex: 1 1 auto; + min-width: 0; + } + + .navbar__brand { + margin-right: 0; + } + + .navbar__title { + display: none; + } + + .navbar__toggle { + display: inherit; + } + + .navbar__search { + margin-left: 0; + width: auto; + } + + .navbar__items--right [class*='navbarSearchContainer'] { + position: static; + right: auto; + width: auto; + padding: 0; + } + + .navbar__items--right .header-github-link, + .navbar__items--right .header-discord-link { + display: none; + } + + .navbar__items--right { + flex: 0 1 auto; + justify-content: flex-end; + min-width: 0; + } + + .navbar__search-input { + width: min(12rem, calc(100vw - 8.5rem)); + } + + .navbar-sidebar { + background: + linear-gradient(180deg, rgba(9, 17, 24, 0.98), rgba(15, 15, 15, 0.98)), + var(--bg-primary); + border-left: 1px solid var(--border-subtle); + backdrop-filter: blur(24px); + height: 100vh; + height: 100dvh; + } + + .navbar-sidebar__brand { + background: rgba(9, 17, 24, 0.94); + border-bottom: 1px solid var(--border-subtle); + box-shadow: none; + } + + .navbar-sidebar__items { + display: block; + height: calc(100% - var(--ifm-navbar-height)); + overflow-y: auto; + } + + .navbar-sidebar__item { + width: 100%; + padding: 0.9rem; + } + + .navbar-sidebar .menu__list { + display: grid; + gap: 0.4rem; + } + + .navbar-sidebar .menu__list-item:not(:first-child) { + margin-top: 0; + } + + .navbar-sidebar .menu__link { + padding: 0.9rem 1rem; + background: rgba(255, 255, 255, 0.025); + color: var(--text-primary); + } + + .navbar-sidebar .menu__link:hover, + .navbar-sidebar .menu__link--active { + background: rgba(var(--brand-rgb), 0.14); + } +} + +@media (max-width: 576px) { + .navbar__search-input { + width: min(9.5rem, calc(100vw - 8rem)); + } + + .navbar__items--right .navbar__search-input:not(:focus) { + width: min(9.5rem, calc(100vw - 8rem)); + } +} diff --git a/src/data/audimeta.trending.json b/src/data/audimeta.trending.json index 8e92347..3db53a2 100644 --- a/src/data/audimeta.trending.json +++ b/src/data/audimeta.trending.json @@ -1,6 +1,6 @@ { "endpoint": "https://audimeta.de/search?products_sort_by=BestSellers®ion=us&limit=18&page=0&cache=true", - "generatedAt": "2026-03-13T21:27:36.444Z", + "generatedAt": "2026-03-15T03:34:33.715Z", "region": "us", "total": 18, "books": [ diff --git a/src/pages/api/index.js b/src/pages/api/index.js index aa243c8..9d3173c 100644 --- a/src/pages/api/index.js +++ b/src/pages/api/index.js @@ -7,10 +7,9 @@ import metadata from '@site/src/data/listenarr.generated.json'; import styles from './index.module.css'; const swaggerExamples = [ - 'http://localhost:5000/swagger/', - 'http://:/swagger/', - 'https://listenarr.example.com/swagger/', - 'https://listenarr.example.com//swagger/', + 'http://localhost:4545/swagger/', + 'http://:4545/swagger/', + 'http://:/swagger/', ]; function SwaggerReference({assetBase}) { @@ -145,7 +144,7 @@ export default function ApiPage() { return ( + description="Read-only Listenarr API reference plus guidance for the development-only Swagger UI.">
@@ -153,8 +152,9 @@ export default function ApiPage() {

Listenarr API documentation

This page renders the bundled OpenAPI snapshot as a read-only endpoint reference. - For interactive requests and write actions, open Swagger on your own Listenarr - instance. + Listenarr's built-in Swagger UI is currently enabled only for Development + builds, so this docs site is the reliable reference for normal installed + instances.

@@ -168,13 +168,16 @@ export default function ApiPage() {
-

Open Swagger on your own instance

+

Use Swagger in a local development instance

- Use the same host, port, and optional base path as your Listenarr web app, then - append /swagger/. + Listenarr mounts Swagger only when the app runs in Development. By + default the API listens on port 4545, unless you override it with{' '} + --urls, so the built-in Swagger UI is usually available at{' '} + /swagger/ on that same origin.

- Use your own instance when you need live requests, login, or write operations. + Normal installed or production-style instances do not expose /swagger/{' '} + by default.

                   {swaggerExamples.join('\n')}
diff --git a/src/theme/Navbar/MobileSidebar/PrimaryMenu/index.js b/src/theme/Navbar/MobileSidebar/PrimaryMenu/index.js
new file mode 100644
index 0000000..857ce19
--- /dev/null
+++ b/src/theme/Navbar/MobileSidebar/PrimaryMenu/index.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import {ErrorCauseBoundary, useThemeConfig} from '@docusaurus/theme-common';
+import {useNavbarMobileSidebar} from '@docusaurus/theme-common/internal';
+import NavbarItem from '@theme/NavbarItem';
+
+function useNavbarItems() {
+  return useThemeConfig().navbar.items;
+}
+
+export default function NavbarMobilePrimaryMenu() {
+  const mobileSidebar = useNavbarMobileSidebar();
+  const items = useNavbarItems().filter((item) => item.type !== 'search');
+
+  return (
+    
    + {items.map((item, i) => ( + + new Error( + `A mobile navbar item failed to render. +Please double-check the following navbar item (themeConfig.navbar.items) of your Docusaurus config: +${JSON.stringify(item, null, 2)}`, + {cause: error}, + ) + }> + mobileSidebar.toggle()} /> + + ))} +
+ ); +} diff --git a/src/theme/Navbar/MobileSidebar/SecondaryMenu/index.js b/src/theme/Navbar/MobileSidebar/SecondaryMenu/index.js new file mode 100644 index 0000000..82f0768 --- /dev/null +++ b/src/theme/Navbar/MobileSidebar/SecondaryMenu/index.js @@ -0,0 +1,35 @@ +import React from 'react'; +import {ErrorCauseBoundary, useThemeConfig} from '@docusaurus/theme-common'; +import {useNavbarSecondaryMenu} from '@docusaurus/theme-common/internal'; +import Translate from '@docusaurus/Translate'; +import NavbarMobileSidebarPrimaryMenu from '@theme/Navbar/MobileSidebar/PrimaryMenu'; + +function SecondaryMenuBackButton(props) { + return ( + + ); +} + +export default function NavbarMobileSidebarSecondaryMenu() { + const isPrimaryMenuEmpty = useThemeConfig().navbar.items.length === 0; + const secondaryMenu = useNavbarSecondaryMenu(); + + if (!secondaryMenu.content) { + return ; + } + + return ( + new Error('The mobile secondary navbar menu failed to render.')}> + {!isPrimaryMenuEmpty && ( + secondaryMenu.hide()} /> + )} + {secondaryMenu.content} + + ); +} diff --git a/src/theme/Navbar/MobileSidebar/index.js b/src/theme/Navbar/MobileSidebar/index.js new file mode 100644 index 0000000..0893d00 --- /dev/null +++ b/src/theme/Navbar/MobileSidebar/index.js @@ -0,0 +1,25 @@ +import React from 'react'; +import {useLockBodyScroll, useNavbarMobileSidebar} from '@docusaurus/theme-common/internal'; +import NavbarMobileSidebarHeader from '@theme/Navbar/MobileSidebar/Header'; +import NavbarMobileSidebarPrimaryMenu from '@theme/Navbar/MobileSidebar/PrimaryMenu'; + +export default function NavbarMobileSidebar() { + const mobileSidebar = useNavbarMobileSidebar(); + + useLockBodyScroll(mobileSidebar.shown); + + if (!mobileSidebar.shouldRender) { + return null; + } + + return ( +
+ +
+
+ +
+
+
+ ); +} From 2720b75734e80615d8e9c7d167787101f29fb46b Mon Sep 17 00:00:00 2001 From: Robbie Davis Date: Sat, 14 Mar 2026 23:51:34 -0400 Subject: [PATCH 4/4] Address PR review feedback --- docs/intro.md | 2 +- scripts/sync-listenarr.mjs | 31 +++++++++++++------ src/pages/api/index.js | 4 +++ src/pages/api/index.module.css | 6 ++++ .../MobileSidebar/SecondaryMenu/index.js | 7 ++++- src/theme/Navbar/MobileSidebar/index.js | 15 +++++---- static/api-ui/swagger-initializer.js | 31 +++++++++++++------ 7 files changed, 68 insertions(+), 28 deletions(-) diff --git a/docs/intro.md b/docs/intro.md index 6119eaa..3647419 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -29,4 +29,4 @@ Listenarr is a self-hosted audiobook automation server built to streamline the p ## API -Open the [API guide](pathname:///api/) to review the bundled OpenAPI snapshot and see how to use Swagger on your own Listenarr instance. +Open the [API guide](/api/) to review the bundled OpenAPI snapshot and see how to use Swagger on your own Listenarr instance. diff --git a/scripts/sync-listenarr.mjs b/scripts/sync-listenarr.mjs index 28716a5..3fe40de 100644 --- a/scripts/sync-listenarr.mjs +++ b/scripts/sync-listenarr.mjs @@ -268,17 +268,30 @@ async function bundleSwaggerUi(port) { writeFileSync( path.join(apiDir, 'swagger-initializer.js'), `window.onload = async function () { - const response = await fetch('./openapi.json'); - const spec = await response.json(); + let specConfig = { url: './openapi.json' }; - window.ui = SwaggerUIBundle({ - spec: { - ...spec, - info: { - ...(spec.info || {}), - description: '' + try { + const response = await fetch('./openapi.json'); + if (!response.ok) { + throw new Error(\`Unable to load ./openapi.json: \${response.status}\`); + } + + const spec = await response.json(); + specConfig = { + spec: { + ...spec, + info: { + ...(spec.info || {}), + description: '' + } } - }, + }; + } catch (error) { + console.error('Failed to preload Listenarr OpenAPI spec for Swagger UI.', error); + } + + window.ui = SwaggerUIBundle({ + ...specConfig, dom_id: '#swagger-ui', deepLinking: true, presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset], diff --git a/src/pages/api/index.js b/src/pages/api/index.js index 9d3173c..47989a6 100644 --- a/src/pages/api/index.js +++ b/src/pages/api/index.js @@ -164,6 +164,10 @@ export default function ApiPage() { View Listenarr source
+

+ Bundled spec generated {generatedAt} from Listenarr {metadata.version} ( + {metadata.commit}). +

diff --git a/src/pages/api/index.module.css b/src/pages/api/index.module.css index 59a8911..b9d39a8 100644 --- a/src/pages/api/index.module.css +++ b/src/pages/api/index.module.css @@ -45,6 +45,12 @@ gap: 0.9rem; } +.metaNote { + margin: 1rem 0 0; + color: var(--text-muted); + font-size: 0.9rem; +} + .grid { display: grid; grid-template-columns: minmax(0, 1fr); diff --git a/src/theme/Navbar/MobileSidebar/SecondaryMenu/index.js b/src/theme/Navbar/MobileSidebar/SecondaryMenu/index.js index 82f0768..9dfb731 100644 --- a/src/theme/Navbar/MobileSidebar/SecondaryMenu/index.js +++ b/src/theme/Navbar/MobileSidebar/SecondaryMenu/index.js @@ -25,7 +25,12 @@ export default function NavbarMobileSidebarSecondaryMenu() { } return ( - new Error('The mobile secondary navbar menu failed to render.')}> + + new Error('The mobile secondary navbar menu failed to render.', { + cause: error, + }) + }> {!isPrimaryMenuEmpty && ( secondaryMenu.hide()} /> )} diff --git a/src/theme/Navbar/MobileSidebar/index.js b/src/theme/Navbar/MobileSidebar/index.js index 0893d00..9e4a34e 100644 --- a/src/theme/Navbar/MobileSidebar/index.js +++ b/src/theme/Navbar/MobileSidebar/index.js @@ -1,7 +1,9 @@ import React from 'react'; import {useLockBodyScroll, useNavbarMobileSidebar} from '@docusaurus/theme-common/internal'; +import NavbarMobileSidebarLayout from '@theme/Navbar/MobileSidebar/Layout'; import NavbarMobileSidebarHeader from '@theme/Navbar/MobileSidebar/Header'; import NavbarMobileSidebarPrimaryMenu from '@theme/Navbar/MobileSidebar/PrimaryMenu'; +import NavbarMobileSidebarSecondaryMenu from '@theme/Navbar/MobileSidebar/SecondaryMenu'; export default function NavbarMobileSidebar() { const mobileSidebar = useNavbarMobileSidebar(); @@ -13,13 +15,10 @@ export default function NavbarMobileSidebar() { } return ( -
- -
-
- -
-
-
+ } + primaryMenu={} + secondaryMenu={} + /> ); } diff --git a/static/api-ui/swagger-initializer.js b/static/api-ui/swagger-initializer.js index ff87ac6..101cb4e 100644 --- a/static/api-ui/swagger-initializer.js +++ b/static/api-ui/swagger-initializer.js @@ -1,15 +1,28 @@ window.onload = async function () { - const response = await fetch('./openapi.json'); - const spec = await response.json(); + let specConfig = { url: './openapi.json' }; - window.ui = SwaggerUIBundle({ - spec: { - ...spec, - info: { - ...(spec.info || {}), - description: '' + try { + const response = await fetch('./openapi.json'); + if (!response.ok) { + throw new Error(`Unable to load ./openapi.json: ${response.status}`); + } + + const spec = await response.json(); + specConfig = { + spec: { + ...spec, + info: { + ...(spec.info || {}), + description: '' + } } - }, + }; + } catch (error) { + console.error('Failed to preload Listenarr OpenAPI spec for Swagger UI.', error); + } + + window.ui = SwaggerUIBundle({ + ...specConfig, dom_id: '#swagger-ui', deepLinking: true, presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],