From 9e2fa65956f1523708c48287228281cfb8d9adf1 Mon Sep 17 00:00:00 2001 From: Teuz Date: Tue, 3 Feb 2026 19:56:22 +0100 Subject: [PATCH 01/35] Add Swagger UI Extensions: API Counter for Swashbuckle/NSwag - Introduce new extension packages for Swashbuckle and NSwag - Add API Counter feature to append operation counts to tag descriptions - Implement Swashbuckle filter and NSwag processor with extension methods - Provide sample projects and controllers demonstrating usage - Add comprehensive unit tests for filters, processors, and extensions - Update README, contributing guide, and add package docs - Add GitHub issue and PR templates for bugs, features, and questions - Update solution and project files to include new extensions and tests - Ensure compatibility with .NET 8, 9, and 10 --- .github/ISSUE_TEMPLATE/bug_report.yml | 147 ++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 9 +- .github/ISSUE_TEMPLATE/feature_request.yml | 105 ++++++++++ .github/ISSUE_TEMPLATE/question.yml | 67 ++++++ .github/pull_request_template.md | 65 ++++++ CONTRIBUTING.md | 126 ++++++++++-- README.md | 95 ++++++++- SwaggerUI.Themes.sln | 37 +++- .../Controllers/OrdersController.cs | 27 +++ .../Controllers/ProductsController.cs | 36 ++++ .../Controllers/UsersController.cs | 54 +++++ .../Program.cs | 29 +++ .../Properties/launchSettings.json | 25 +++ ...ple.AspNetCore.SwaggerUI.Extensions.csproj | 28 +++ .../appsettings.Development.json | 8 + .../appsettings.json | 9 + .../Program.cs | 10 +- .../Program.cs | 10 +- .../Controllers/OrdersController.cs | 27 +++ .../Controllers/ProductsController.cs | 36 ++++ .../Controllers/UsersController.cs | 54 +++++ .../Program.cs | 30 +++ .../Properties/launchSettings.json | 25 +++ .../Sample.NSwag.AspNetCore.Extensions.csproj | 27 +++ .../appsettings.Development.json | 8 + .../appsettings.json | 9 + .../AspNetCore.SwaggerUI.Extensions.csproj | 30 +++ ...endOperationCountToTagDescriptionFilter.cs | 92 +++++++++ .../SwaggerGenOptionsExtensions.cs | 35 ++++ .../package-readme.md | 44 ++++ .../readme.txt | 23 +++ ...endOperationCountToTagDescriptionFilter.cs | 93 +++++++++ .../SwaggerGenOptionsExtensions.cs | 35 ++++ ...OperationCountToTagDescriptionProcessor.cs | 94 +++++++++ ...nApiDocumentGeneratorSettingsExtensions.cs | 36 ++++ .../NSwag.AspNetCore.Extensions.csproj | 30 +++ .../package-readme.md | 44 ++++ src/NSwag.AspNetCore.Extensions/readme.txt | 23 +++ ...OperationCountToTagDescriptionProcessor.cs | 95 +++++++++ ...nApiDocumentGeneratorSettingsExtensions.cs | 36 ++++ ...erationCountToTagDescriptionFilterTests.cs | 190 ++++++++++++++++++ ...tionCountToTagDescriptionProcessorTests.cs | 182 +++++++++++++++++ ...AspNetCore.Swagger.Extensions.Tests.csproj | 40 ++++ ...ocumentGeneratorSettingsExtensionsTests.cs | 42 ++++ .../SwaggerGenOptionsExtensionsTests.cs | 43 ++++ 45 files changed, 2283 insertions(+), 27 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/question.yml create mode 100644 .github/pull_request_template.md create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/OrdersController.cs create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/ProductsController.cs create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/UsersController.cs create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Extensions/Program.cs create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Extensions/Properties/launchSettings.json create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Extensions/Sample.AspNetCore.SwaggerUI.Extensions.csproj create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Extensions/appsettings.Development.json create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Extensions/appsettings.json create mode 100644 samples/Sample.NSwag.AspNetCore.Extensions/Controllers/OrdersController.cs create mode 100644 samples/Sample.NSwag.AspNetCore.Extensions/Controllers/ProductsController.cs create mode 100644 samples/Sample.NSwag.AspNetCore.Extensions/Controllers/UsersController.cs create mode 100644 samples/Sample.NSwag.AspNetCore.Extensions/Program.cs create mode 100644 samples/Sample.NSwag.AspNetCore.Extensions/Properties/launchSettings.json create mode 100644 samples/Sample.NSwag.AspNetCore.Extensions/Sample.NSwag.AspNetCore.Extensions.csproj create mode 100644 samples/Sample.NSwag.AspNetCore.Extensions/appsettings.Development.json create mode 100644 samples/Sample.NSwag.AspNetCore.Extensions/appsettings.json create mode 100644 src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj create mode 100644 src/AspNetCore.SwaggerUI.Extensions/AspNetCore/Swagger/Extensions/Filters/AppendOperationCountToTagDescriptionFilter.cs create mode 100644 src/AspNetCore.SwaggerUI.Extensions/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs create mode 100644 src/AspNetCore.SwaggerUI.Extensions/package-readme.md create mode 100644 src/AspNetCore.SwaggerUI.Extensions/readme.txt create mode 100644 src/AspNetCore.SwaggerUI.Themes/AspNetCore/Swagger/Themes/Filters/AppendOperationCountToTagDescriptionFilter.cs create mode 100644 src/AspNetCore.SwaggerUI.Themes/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs create mode 100644 src/NSwag.AspNetCore.Extensions/AspNetCore/Swagger/Extensions/Processors/AppendOperationCountToTagDescriptionProcessor.cs create mode 100644 src/NSwag.AspNetCore.Extensions/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs create mode 100644 src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj create mode 100644 src/NSwag.AspNetCore.Extensions/package-readme.md create mode 100644 src/NSwag.AspNetCore.Extensions/readme.txt create mode 100644 src/NSwag.AspNetCore.Themes/AspNetCore/Swagger/Themes/Processors/AppendOperationCountToTagDescriptionProcessor.cs create mode 100644 src/NSwag.AspNetCore.Themes/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs create mode 100644 tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionFilterTests.cs create mode 100644 tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionProcessorTests.cs create mode 100644 tests/AspNetCore.Swagger.Extensions.Tests/AspNetCore.Swagger.Extensions.Tests.csproj create mode 100644 tests/AspNetCore.Swagger.Extensions.Tests/AspNetCoreOpenApiDocumentGeneratorSettingsExtensionsTests.cs create mode 100644 tests/AspNetCore.Swagger.Extensions.Tests/SwaggerGenOptionsExtensionsTests.cs diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..51ed46c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,147 @@ +name: "?? Bug Report" +description: "Report a bug or issue with SwaggerUI.Themes or SwaggerUI.Extensions" +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thank you for reporting an issue! Please fill out the information below to help us investigate. + + - type: dropdown + id: package_type + attributes: + label: "Package Type" + description: "Which type of package are you reporting an issue with?" + options: + - "Theme (AspNetCore.SwaggerUI.Themes)" + - "Theme (NSwag.AspNetCore.Themes)" + - "Extension (AspNetCore.SwaggerUI.Extensions)" + - "Extension (NSwag.AspNetCore.Extensions)" + - "Common (AspNetCore.Swagger.Themes.Common)" + - "Unsure" + validations: + required: true + + - type: dropdown + id: integration + attributes: + label: "Integration Framework" + description: "Which Swagger integration are you using?" + options: + - "Swashbuckle.AspNetCore" + - "NSwag.AspNetCore" + - "Both" + - "Not applicable" + validations: + required: true + + - type: input + id: dotnet_version + attributes: + label: ".NET Version" + description: "Which .NET version are you using? (e.g., 8.0, 9.0, 10.0)" + placeholder: "e.g., 8.0" + validations: + required: true + + - type: input + id: package_version + attributes: + label: "Package Version" + description: "Which version of the package are you using?" + placeholder: "e.g., 3.0.0" + validations: + required: true + + - type: textarea + id: description + attributes: + label: "Description" + description: "Clear and concise description of the bug" + placeholder: "What happened?" + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: "Steps to Reproduce" + description: "Step-by-step instructions to reproduce the issue" + placeholder: | + 1. Configure the package with... + 2. Navigate to... + 3. See error... + validations: + required: true + + - type: textarea + id: expected_behavior + attributes: + label: "Expected Behavior" + description: "What should happen?" + placeholder: "Describe the expected behavior" + validations: + required: true + + - type: textarea + id: actual_behavior + attributes: + label: "Actual Behavior" + description: "What actually happens?" + placeholder: "Describe what actually happens" + validations: + required: true + + - type: dropdown + id: browser + attributes: + label: "Browser (if UI-related)" + description: "Which browser are you using? (Only relevant for theme/UI issues)" + options: + - "Chrome" + - "Firefox" + - "Safari" + - "Edge" + - "Other" + - "Not applicable" + + - type: textarea + id: code_snippet + attributes: + label: "Code Snippet" + description: "Relevant code that demonstrates the issue (optional)" + render: csharp + placeholder: | + // Your code here + app.UseSwaggerUI(Theme.Dark); + + - type: textarea + id: error_logs + attributes: + label: "Error Logs or Stack Trace" + description: "Any relevant error messages or stack traces (optional)" + render: plain_text + + - type: textarea + id: screenshots + attributes: + label: "Screenshots" + description: "Screenshots showing the issue (optional, for UI-related issues)" + + - type: textarea + id: additional_context + attributes: + label: "Additional Context" + description: "Any other information that might be helpful" + placeholder: "Add any other context about the problem here" + + - type: checkboxes + id: checklist + attributes: + label: "Verification" + options: + - label: "I have searched existing issues and found no duplicates" + required: true + - label: "I have provided clear reproduction steps" + required: true + - label: "I have included relevant error messages or logs" diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index a49eab2..7557f1f 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1 +1,8 @@ -blank_issues_enabled: true \ No newline at end of file +ο»Ώblank_issues_enabled: false +contact_links: + - name: "❓ Ask a Question" + url: "https://github.com/teociaps/SwaggerUI.Themes/discussions" + about: "Have a question or want to discuss? Use GitHub Discussions!" + - name: "πŸ“š View Documentation" + url: "https://github.com/teociaps/SwaggerUI.Themes/wiki" + about: "Check the wiki for comprehensive documentation and guides" \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..2cea18d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,105 @@ +name: "? Feature Request" +description: "Suggest a new feature or enhancement for SwaggerUI.Themes or SwaggerUI.Extensions" +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: | + Thank you for suggesting a feature! Please provide details to help us understand your idea. + + - type: dropdown + id: feature_type + attributes: + label: "Feature Type" + description: "What category is your feature request for?" + options: + - "Theme (visual/styling enhancement)" + - "Extension (functional capability)" + - "Documentation" + - "Developer Experience" + - "Other" + validations: + required: true + + - type: dropdown + id: package_scope + attributes: + label: "Package Scope" + description: "Which packages should this feature apply to?" + options: + - "Swashbuckle only (AspNetCore.SwaggerUI.*)" + - "NSwag only (NSwag.AspNetCore.*)" + - "Both Swashbuckle and NSwag" + - "Common/Shared" + - "Not sure" + validations: + required: true + + - type: textarea + id: problem + attributes: + label: "Problem Statement" + description: "What problem does this feature solve? What is the use case?" + placeholder: | + Current situation: ... + Problem: ... + Why this matters: ... + validations: + required: true + + - type: textarea + id: solution + attributes: + label: "Proposed Solution" + description: "Describe how you envision this feature working" + placeholder: | + Describe the ideal behavior/UI/API + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: "Alternative Solutions" + description: "Are there any alternative approaches? (optional)" + placeholder: "Have you considered other ways to solve this?" + + - type: textarea + id: example_usage + attributes: + label: "Example Usage" + description: "How would users interact with this feature? (optional)" + render: csharp + placeholder: | + // Example code showing how the feature would be used + app.UseSwaggerUI(Theme.Dark, c => + { + c.MyNewFeature(); + }); + + - type: textarea + id: design_considerations + attributes: + label: "Design Considerations" + description: "Any thoughts on API design, backward compatibility, or performance? (optional)" + placeholder: "Backward compatibility impact, performance considerations, naming suggestions, etc." + + - type: checkboxes + id: checklist + attributes: + label: "Verification" + options: + - label: "I have searched existing feature requests and found no duplicates" + required: true + - label: "This feature aligns with the project scope" + required: true + + - type: textarea + id: additional_context + attributes: + label: "Additional Context" + description: "Any other information or references? (optional)" + placeholder: | + - Related issues: #123 + - Similar features in other projects: ... + - References: ... diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 0000000..ab86ca8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,67 @@ +name: "? Question/Discussion" +description: "Ask a question or start a discussion about using the packages" +labels: ["question"] +body: + - type: markdown + attributes: + value: | + Have a question about how to use SwaggerUI.Themes or SwaggerUI.Extensions? Please ask away! + + - type: dropdown + id: package_type + attributes: + label: "Package" + description: "Which package is your question about?" + options: + - "AspNetCore.SwaggerUI.Themes" + - "NSwag.AspNetCore.Themes" + - "AspNetCore.SwaggerUI.Extensions" + - "NSwag.AspNetCore.Extensions" + - "General/Not sure" + validations: + required: true + + - type: input + id: dotnet_version + attributes: + label: ".NET Version" + description: "Which .NET version are you using?" + placeholder: "e.g., 8.0, 9.0, 10.0" + + - type: input + id: package_version + attributes: + label: "Package Version" + description: "Which version of the package are you using?" + placeholder: "e.g., 3.0.0" + + - type: textarea + id: question + attributes: + label: "Your Question" + description: "What would you like to know?" + placeholder: "Ask your question here..." + validations: + required: true + + - type: textarea + id: context + attributes: + label: "Context" + description: "What are you trying to achieve? Include any relevant code." + render: csharp + placeholder: | + // Your code/setup here + + - type: textarea + id: what_you_tried + attributes: + label: "What Have You Tried?" + description: "What solutions have you already attempted?" + placeholder: "Describe any troubleshooting steps you've already taken" + + - type: textarea + id: additional_info + attributes: + label: "Additional Information" + description: "Any other details that might be helpful? (optional)" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..70f60d7 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,65 @@ +## Description + + + +## Type of Change + +- [ ] ?? **Theme** (visual/styling updates) +- [ ] ?? **Extension** (functional capability) +- [ ] ?? **Documentation** (docs/comments only) +- [ ] ?? **Bug Fix** (fixes an issue) +- [ ] ?? **Refactoring** (internal improvements) +- [ ] ?? **Tests** (test updates/additions) + +## Related Issue + +Fixes # or Relates to # + +## Package(s) Affected + +- [ ] AspNetCore.SwaggerUI.Themes +- [ ] NSwag.AspNetCore.Themes +- [ ] AspNetCore.SwaggerUI.Extensions +- [ ] NSwag.AspNetCore.Extensions +- [ ] AspNetCore.Swagger.Themes.Common + +## Testing + +Please describe the testing done to verify this change: + + + +- [ ] I have built the solution (`dotnet build -c Release`) +- [ ] I have run the test suite (`dotnet test`) +- [ ] I have tested with relevant sample projects +- [ ] All changes work across .NET 8, 9, and 10 (if applicable) + +## Checklist + +- [ ] My code follows the style guidelines of this project +- [ ] I have added/updated tests for my changes +- [ ] I have updated relevant documentation (README, CONTRIBUTING, wiki, etc.) +- [ ] My changes do not break any existing functionality +- [ ] I have verified no new compiler warnings are introduced + +## Theme-Specific (if applicable) + +- [ ] Minified CSS/JS files have been updated +- [ ] Minified file headers are correctly formatted (`/*Name*/...`) +- [ ] All placeholders (e.g., `{$THEME_SWITCHER}`) are preserved in minified files +- [ ] Theme is discoverable and testable + +## Extension-Specific (if applicable) + +- [ ] Implementation exists for both Swashbuckle and NSwag (if cross-platform) +- [ ] Extension works independently and with other extensions +- [ ] Tests cover the new functionality +- [ ] Sample applications demonstrate the feature + +## Screenshots/Demo + + + +## Additional Context + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0380fa7..0f78073 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,12 +35,18 @@ This project and everyone participating in it are governed by our [Code of Condu Understanding the project layout helps you navigate the codebase: - **`src/AspNetCore.Swagger.Themes.Common/`** - Core theme system shared between packages -- **`src/AspNetCore.SwaggerUI.Themes/`** - Swashbuckle.AspNetCore integration -- **`src/NSwag.AspNetCore.Themes/`** - NSwag.AspNetCore integration +- **`src/AspNetCore.SwaggerUI.Themes/`** - Swashbuckle.AspNetCore theme integration +- **`src/NSwag.AspNetCore.Themes/`** - NSwag.AspNetCore theme integration +- **`src/AspNetCore.SwaggerUI.Extensions/`** - Swashbuckle.AspNetCore functional extensions +- **`src/NSwag.AspNetCore.Extensions/`** - NSwag.AspNetCore functional extensions - **`samples/`** - Example applications demonstrating usage - **`tests/`** - Unit and integration tests -## Working with Themes +--- + +## Contributing Themes + +Themes customize the visual appearance of Swagger UI. When contributing a new theme: ### Adding or Modifying Themes @@ -107,6 +113,82 @@ git add src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/* git commit -m "Add new ocean theme" ``` +### Theme Organization + +When adding example custom themes, use subfolders for clarity: + +``` +samples/YourSample/SwaggerThemes/ +β”œβ”€β”€ CompanyThemes/ +β”‚ └── corporate-blue.css +└── SeasonalThemes/ + └── holiday-red.css +``` + +This demonstrates best practices for users organizing their own themes. + +### Standalone vs. Regular Themes + +- **Regular themes** - Depend on `common.css` and may use JavaScript features +- **Standalone themes** - Prefix with `standalone.` (e.g., `standalone.custom.css`) for zero dependencies + +Choose based on whether you need shared styles or prefer total independence. + +--- + +## Contributing Extensions + +Extensions add functional capabilities to Swagger UI beyond theming. When contributing a new extension: + +### Planning an Extension + +Before implementing, consider: + +1. **Scope** - Is it a focused feature or part of a larger system? +2. **Compatibility** - Does it work with both Swashbuckle and NSwag? +3. **Integration points** - Where should it hook into the pipeline? +4. **User value** - What problem does it solve for users? + +### Implementation Guidelines + +Extensions should be implemented for both integrations: + +- **Swashbuckle implementation** - `src/AspNetCore.SwaggerUI.Extensions/` + - Add service extension methods in `Microsoft.Extensions.DependencyInjection/` + - Add filters in `AspNetCore/Swagger/Extensions/Filters/` + +- **NSwag implementation** - `src/NSwag.AspNetCore.Extensions/` + - Add service extension methods in `Microsoft.Extensions.DependencyInjection/` + - Add processors in `AspNetCore/Swagger/Extensions/Processors/` + +### Extension Structure Example + +``` +src/AspNetCore.SwaggerUI.Extensions/ +β”œβ”€β”€ Microsoft/Extensions/DependencyInjection/ +β”‚ └── SwaggerGenOptionsExtensions.cs // Service configuration +β”œβ”€β”€ AspNetCore/Swagger/Extensions/ +β”‚ └── Filters/ +β”‚ └── MyFeatureFilter.cs // Swagger/OpenAPI filter +└── package-readme.md // NuGet package documentation + +src/NSwag.AspNetCore.Extensions/ +β”œβ”€β”€ Microsoft/Extensions/DependencyInjection/ +β”‚ └── AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs +β”œβ”€β”€ AspNetCore/Swagger/Extensions/ +β”‚ └── Processors/ +β”‚ └── MyFeatureProcessor.cs // NSwag processor +└── package-readme.md +``` + +### Testing Extensions + +1. Add tests in the appropriate test project +2. Test with both sample applications +3. Verify the extension works independently and alongside other extensions + +--- + ## Testing Your Changes Before submitting a pull request: @@ -126,8 +208,9 @@ dotnet test ``` All tests must pass, including: -- Minified header format tests -- Theme discovery tests +- Minified header format tests (for themes) +- Theme discovery tests (for themes) +- Extension functionality tests (for extensions) - Integration tests ### 3. Test with Sample Applications @@ -140,15 +223,22 @@ dotnet run --project samples/Sample.AspNetCore.SwaggerUI.Swashbuckle # NSwag sample dotnet run --project samples/Sample.AspNetCore.SwaggerUI.NSwag + +# Swashbuckle Extensions sample +dotnet run --project samples/Sample.AspNetCore.SwaggerUI.Extensions + +# NSwag Extensions sample +dotnet run --project samples/Sample.NSwag.AspNetCore.Extensions ``` ### 4. Manual Verification Open the sample app in your browser and verify: -- βœ… Themes render correctly -- βœ… Theme switcher dropdown appears and works (if enabled) -- βœ… Custom themes are discovered automatically +- βœ… Themes render correctly (if theme changes) +- βœ… Theme switcher dropdown appears and works (if theme changes) +- βœ… Custom themes are discovered automatically (if theme changes) +- βœ… Extensions work as expected (if extension changes) - βœ… Advanced features work (pinnable topbar, back-to-top, etc.) - βœ… No console errors in browser developer tools - βœ… Standalone themes load without extra dependencies @@ -164,9 +254,10 @@ Open the sample app in your browser and verify: ### Good PR Practices -- Use descriptive titles: `feat: add sunset theme` not `update css` +- Use descriptive titles: `feat: add sunset theme` or `feat: add api metadata extension` - Reference issues: `Fixes #123` or `Closes #456` -- Include screenshots for visual changes +- Include screenshots for visual changes (themes) +- Include before/after examples for functional changes (extensions) - Test across .NET 8, 9, and 10 if possible ## Reporting Bugs @@ -177,20 +268,26 @@ Found a bug? Please open an issue with: - **Steps to reproduce** the issue - **Expected vs. actual behavior** - **Environment details:** - - Which package (Swashbuckle or NSwag) + - Which package (Themes or Extensions) + - Which integration (Swashbuckle or NSwag) - .NET version - Browser (if UI-related) - **Screenshots or code snippets** if applicable +See the bug report template for a structured approach. + ## Suggesting Features We welcome feature suggestions! When proposing a feature: - **Describe the use case** - What problem does it solve? +- **Specify the scope** - Is it for Themes, Extensions, or both? - **Explain the benefit** - How does it improve the project? - **Consider backward compatibility** - Will it break existing code? - **Offer to help** - Can you contribute the implementation? +See the feature request template for a structured approach. + ## Development Tips ### Quick Testing Loop @@ -216,13 +313,6 @@ samples/YourSample/SwaggerThemes/ This demonstrates best practices for users organizing their own themes. -### Standalone vs. Regular Themes - -- **Regular themes** - Depend on `common.css` and may use JavaScript features -- **Standalone themes** - Prefix with `standalone.` (e.g., `standalone.custom.css`) for zero dependencies - -Choose based on whether you need shared styles or prefer total independence. - ## Questions? Not sure about something? Feel free to: diff --git a/README.md b/README.md index aad1d79..85070cd 100644 --- a/README.md +++ b/README.md @@ -29,11 +29,24 @@ _Note: GIF framerate is limited. Actual transitions are buttery smooth!_ --- +## πŸ“¦ Available Packages + +### Theme Packages +Customize the look and feel of your Swagger UI with runtime theme switching and custom theme creation. + | Package | Purpose | NuGet | |---------|---------|-------| | **AspNetCore.SwaggerUI.Themes** | For [Swashbuckle.AspNetCore][swashbuckle-link] | [![swashbuckle-nuget]][swashbuckle-nuget-link] | | **NSwag.AspNetCore.Themes** | For [NSwag.AspNetCore][nswag-link] | [![nswag-nuget]][nswag-nuget-link] | +### Extension Packages +Enhance your Swagger UI with additional functional capabilities beyond theming. + +| Package | Purpose | NuGet | +|---------|---------|-------| +| **AspNetCore.SwaggerUI.Extensions** | Functional enhancements for [Swashbuckle.AspNetCore][swashbuckle-link] | [![swashbuckle-ext-nuget]][swashbuckle-ext-nuget-link] | +| **NSwag.AspNetCore.Extensions** | Functional enhancements for [NSwag.AspNetCore][nswag-link] | [![nswag-ext-nuget]][nswag-ext-nuget-link] | + --- @@ -47,12 +60,19 @@ _Note: GIF framerate is limited. Actual transitions are buttery smooth!_ ## πŸš€ Quick Start ```bash -# Install package +# Install theme package dotnet add package AspNetCore.SwaggerUI.Themes # or dotnet add package NSwag.AspNetCore.Themes + +# Or install extensions package +dotnet add package AspNetCore.SwaggerUI.Extensions +# or +dotnet add package NSwag.AspNetCore.Extensions ``` +### Themes Quick Start + ```csharp // Apply a theme - that's it! app.UseSwaggerUI(Theme.Dark); // Swashbuckle @@ -63,8 +83,26 @@ app.UseSwaggerUi(Theme.Dark); // NSwag app.UseSwaggerUI(Theme.Dark, c => c.EnableThemeSwitcher()); ``` +### Extensions Quick Start + +```csharp +// Add extensions to your configuration +builder.Services.AddSwaggerGen(c => +{ + c.AppendOperationCountToTags(); // Swashbuckle +}); + +// or for NSwag +builder.Services.AddOpenApiDocument(c => +{ + c.AppendOperationCountToTags(); // NSwag +}); +``` + + ## ✨ Features +### Themes - πŸ”₯ **[Theme Switcher](https://github.com/teociaps/SwaggerUI.Themes/wiki/Feature-Dynamic-Theme-Switcher)** - Switch built-in and custom themes dynamically without page reload - **[Built-in Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Predefined-Themes)** - Choose from predefined themes ready to use @@ -73,11 +111,16 @@ app.UseSwaggerUI(Theme.Dark, c => c.EnableThemeSwitcher()); - **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Enhance your documentation with powerful UI capabilities -- _...discover more in the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki/Features)!_ +### Extensions +- **API Counter** - Automatically display operation counts in tag descriptions for better API surface area visibility + +- _More extensions coming soon!_ + +_Discover more features in the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki/Features)!_ ## πŸ“š Basic Usage Examples -### Swashbuckle +### Swashbuckle Themes ```csharp using AspNetCore.Swagger.Themes; @@ -102,7 +145,7 @@ app.UseSwaggerUI(Theme.Dark, c => ... ``` -### NSwag +### NSwag Themes ```csharp using AspNetCore.Swagger.Themes; @@ -127,6 +170,44 @@ app.UseSwaggerUi(Theme.Dark, c => ... ``` +### Swashbuckle Extensions + +```csharp +using AspNetCore.Swagger.Extensions; + +... + +builder.Services.AddSwaggerGen(c => +{ + // Add operation count to tag descriptions + c.AppendOperationCountToTags(); + + // Or with custom template + c.AppendOperationCountToTags(" [{0} endpoints]"); +}); + +... +``` + +### NSwag Extensions + +```csharp +using AspNetCore.Swagger.Extensions; + +... + +builder.Services.AddOpenApiDocument(c => +{ + // Add operation count to tag descriptions + c.AppendOperationCountToTags(); + + // Or with custom template + c.AppendOperationCountToTags(" [{0} endpoints]"); +}); + +... +``` + ### πŸ’‘ Build your own Custom Theme ```csharp @@ -151,7 +232,7 @@ _Learn advanced usages in the [Wiki](https://github.com/teociaps/SwaggerUI.Theme ## 🀝 Contributing -Contributions are welcome! See the [Contributing Guide](CONTRIBUTING.md) for details. +Contributions are welcome! See the [Contributing Guide](CONTRIBUTING.md) for details on contributing themes and extensions. ## πŸ“œ License @@ -168,7 +249,11 @@ MIT Licensed - see [LICENSE](LICENSE) for details. [swashbuckle-nuget]: https://img.shields.io/nuget/v/AspNetCore.SwaggerUI.Themes?logo=nuget&label=Version&color=blue [swashbuckle-nuget-link]: https://www.nuget.org/packages/AspNetCore.SwaggerUI.Themes/ +[swashbuckle-ext-nuget]: https://img.shields.io/nuget/v/AspNetCore.SwaggerUI.Extensions?logo=nuget&label=Version&color=blue +[swashbuckle-ext-nuget-link]: https://www.nuget.org/packages/AspNetCore.SwaggerUI.Extensions/ [nswag-nuget]: https://img.shields.io/nuget/v/NSwag.AspNetCore.Themes?logo=nuget&label=Version&color=blue [nswag-nuget-link]: https://www.nuget.org/packages/NSwag.AspNetCore.Themes/ +[nswag-ext-nuget]: https://img.shields.io/nuget/v/NSwag.AspNetCore.Extensions?logo=nuget&label=Version&color=blue +[nswag-ext-nuget-link]: https://www.nuget.org/packages/NSwag.AspNetCore.Extensions/ [swashbuckle-link]: https://github.com/domaindrivendev/Swashbuckle.AspNetCore [nswag-link]: https://github.com/RicoSuter/NSwag \ No newline at end of file diff --git a/SwaggerUI.Themes.sln b/SwaggerUI.Themes.sln index fc8d08a..c94ef4c 100644 --- a/SwaggerUI.Themes.sln +++ b/SwaggerUI.Themes.sln @@ -1,7 +1,7 @@ ο»Ώ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.0.11205.157 d18.0 +VisualStudioVersion = 18.0.11205.157 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore.SwaggerUI.Themes", "src\AspNetCore.SwaggerUI.Themes\AspNetCore.SwaggerUI.Themes.csproj", "{5FC2CEDA-5475-4096-BDEC-1C9BFC933FB8}" EndProject @@ -29,6 +29,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NSwag.AspNetCore.Themes", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore.Swagger.Themes.Common", "src\AspNetCore.Swagger.Themes.Common\AspNetCore.Swagger.Themes.Common.csproj", "{DCD47D0E-ADD5-4B6A-BDAA-DD6559EB384D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCore.SwaggerUI.Extensions", "src\AspNetCore.SwaggerUI.Extensions\AspNetCore.SwaggerUI.Extensions.csproj", "{8E498417-7FF6-E0E3-9BC1-AB5197733AFB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NSwag.AspNetCore.Extensions", "src\NSwag.AspNetCore.Extensions\NSwag.AspNetCore.Extensions.csproj", "{605058F0-1D5E-C122-1ABD-B73D053665B7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCore.Swagger.Extensions.Tests", "tests\AspNetCore.Swagger.Extensions.Tests\AspNetCore.Swagger.Extensions.Tests.csproj", "{ECD67BCE-D96A-B1D4-48F1-95AF9F6A49E1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.AspNetCore.SwaggerUI.Extensions", "samples\Sample.AspNetCore.SwaggerUI.Extensions\Sample.AspNetCore.SwaggerUI.Extensions.csproj", "{B90ED39A-C413-8F86-152F-7D2EB7AAB349}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.NSwag.AspNetCore.Extensions", "samples\Sample.NSwag.AspNetCore.Extensions\Sample.NSwag.AspNetCore.Extensions.csproj", "{9050F5CA-0F00-25EA-236F-8659C3AD917F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -59,6 +69,26 @@ Global {DCD47D0E-ADD5-4B6A-BDAA-DD6559EB384D}.Debug|Any CPU.Build.0 = Debug|Any CPU {DCD47D0E-ADD5-4B6A-BDAA-DD6559EB384D}.Release|Any CPU.ActiveCfg = Release|Any CPU {DCD47D0E-ADD5-4B6A-BDAA-DD6559EB384D}.Release|Any CPU.Build.0 = Release|Any CPU + {8E498417-7FF6-E0E3-9BC1-AB5197733AFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8E498417-7FF6-E0E3-9BC1-AB5197733AFB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8E498417-7FF6-E0E3-9BC1-AB5197733AFB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8E498417-7FF6-E0E3-9BC1-AB5197733AFB}.Release|Any CPU.Build.0 = Release|Any CPU + {605058F0-1D5E-C122-1ABD-B73D053665B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {605058F0-1D5E-C122-1ABD-B73D053665B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {605058F0-1D5E-C122-1ABD-B73D053665B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {605058F0-1D5E-C122-1ABD-B73D053665B7}.Release|Any CPU.Build.0 = Release|Any CPU + {ECD67BCE-D96A-B1D4-48F1-95AF9F6A49E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ECD67BCE-D96A-B1D4-48F1-95AF9F6A49E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ECD67BCE-D96A-B1D4-48F1-95AF9F6A49E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ECD67BCE-D96A-B1D4-48F1-95AF9F6A49E1}.Release|Any CPU.Build.0 = Release|Any CPU + {B90ED39A-C413-8F86-152F-7D2EB7AAB349}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B90ED39A-C413-8F86-152F-7D2EB7AAB349}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B90ED39A-C413-8F86-152F-7D2EB7AAB349}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B90ED39A-C413-8F86-152F-7D2EB7AAB349}.Release|Any CPU.Build.0 = Release|Any CPU + {9050F5CA-0F00-25EA-236F-8659C3AD917F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9050F5CA-0F00-25EA-236F-8659C3AD917F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9050F5CA-0F00-25EA-236F-8659C3AD917F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9050F5CA-0F00-25EA-236F-8659C3AD917F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -70,6 +100,11 @@ Global {E8C376AC-9933-4ECF-92D8-66BC706FD6D0} = {FF08B180-6923-46E0-8426-99391CFB47CD} {38614F76-C962-4CDC-9C9D-25469B92CDE2} = {19896985-FCB0-4EA0-8E5F-FF3902AC13FC} {DCD47D0E-ADD5-4B6A-BDAA-DD6559EB384D} = {19896985-FCB0-4EA0-8E5F-FF3902AC13FC} + {8E498417-7FF6-E0E3-9BC1-AB5197733AFB} = {19896985-FCB0-4EA0-8E5F-FF3902AC13FC} + {605058F0-1D5E-C122-1ABD-B73D053665B7} = {19896985-FCB0-4EA0-8E5F-FF3902AC13FC} + {ECD67BCE-D96A-B1D4-48F1-95AF9F6A49E1} = {B89F6FD2-68B6-4A70-A070-FF22FE42DDE4} + {B90ED39A-C413-8F86-152F-7D2EB7AAB349} = {FF08B180-6923-46E0-8426-99391CFB47CD} + {9050F5CA-0F00-25EA-236F-8659C3AD917F} = {FF08B180-6923-46E0-8426-99391CFB47CD} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {660A434B-79DE-4C3F-892C-333338978C59} diff --git a/samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/OrdersController.cs b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/OrdersController.cs new file mode 100644 index 0000000..8db5b48 --- /dev/null +++ b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/OrdersController.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Controllers; + +[ApiController] +[Route("api/[controller]")] +[Tags("Orders")] +public class OrdersController : ControllerBase +{ + /// + /// Gets all orders + /// + [HttpGet] + public IActionResult GetAll() + { + return Ok(new[] { new { Id = 1, Total = 150.00 }, new { Id = 2, Total = 200.00 } }); + } + + /// + /// Gets an order by ID + /// + [HttpGet("{id}")] + public IActionResult GetById(int id) + { + return Ok(new { Id = id, Total = 150.00, Status = "Pending" }); + } +} \ No newline at end of file diff --git a/samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/ProductsController.cs b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/ProductsController.cs new file mode 100644 index 0000000..ef7b567 --- /dev/null +++ b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/ProductsController.cs @@ -0,0 +1,36 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Controllers; + +[ApiController] +[Route("api/[controller]")] +[Tags("Products")] +public class ProductsController : ControllerBase +{ + /// + /// Gets all products + /// + [HttpGet] + public IActionResult GetAll() + { + return Ok(new[] { new { Id = 1, Name = "Product A" }, new { Id = 2, Name = "Product B" } }); + } + + /// + /// Gets a product by ID + /// + [HttpGet("{id}")] + public IActionResult GetById(int id) + { + return Ok(new { Id = id, Name = "Product", Price = 99.99 }); + } + + /// + /// Creates a new product + /// + [HttpPost] + public IActionResult Create([FromBody] object product) + { + return CreatedAtAction(nameof(GetById), new { id = 1 }, product); + } +} \ No newline at end of file diff --git a/samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/UsersController.cs b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/UsersController.cs new file mode 100644 index 0000000..68160dd --- /dev/null +++ b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/UsersController.cs @@ -0,0 +1,54 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Controllers; + +[ApiController] +[Route("api/[controller]")] +[Tags("Users")] +public class UsersController : ControllerBase +{ + /// + /// Gets all users + /// + [HttpGet] + public IActionResult GetAll() + { + return Ok(new[] { new { Id = 1, Name = "John" }, new { Id = 2, Name = "Jane" } }); + } + + /// + /// Gets a user by ID + /// + [HttpGet("{id}")] + public IActionResult GetById(int id) + { + return Ok(new { Id = id, Name = "John Doe" }); + } + + /// + /// Creates a new user + /// + [HttpPost] + public IActionResult Create([FromBody] object user) + { + return CreatedAtAction(nameof(GetById), new { id = 1 }, user); + } + + /// + /// Updates an existing user + /// + [HttpPut("{id}")] + public IActionResult Update(int id, [FromBody] object user) + { + return NoContent(); + } + + /// + /// Deletes a user + /// + [HttpDelete("{id}")] + public IActionResult Delete(int id) + { + return NoContent(); + } +} \ No newline at end of file diff --git a/samples/Sample.AspNetCore.SwaggerUI.Extensions/Program.cs b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Program.cs new file mode 100644 index 0000000..dcaf26b --- /dev/null +++ b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Program.cs @@ -0,0 +1,29 @@ +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddControllers(); + +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(c => +{ + c.SwaggerDoc("v1", new() { Title = "Sample API - Extensions Demo", Version = "v1" }); + + // API Counter Extension + // Automatically appends operation count to tag descriptions + c.AppendOperationCountToTags(); + + // Or with custom template: + // c.AppendOperationCountToTags(" [{0} endpoints]"); + // c.AppendOperationCountToTags(" (APIs: {0})"); +}); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); +app.MapControllers(); + +await app.RunAsync(); \ No newline at end of file diff --git a/samples/Sample.AspNetCore.SwaggerUI.Extensions/Properties/launchSettings.json b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Properties/launchSettings.json new file mode 100644 index 0000000..d0318af --- /dev/null +++ b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Properties/launchSettings.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5100", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7100;http://localhost:5100", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/Sample.AspNetCore.SwaggerUI.Extensions/Sample.AspNetCore.SwaggerUI.Extensions.csproj b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Sample.AspNetCore.SwaggerUI.Extensions.csproj new file mode 100644 index 0000000..2fbe57d --- /dev/null +++ b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Sample.AspNetCore.SwaggerUI.Extensions.csproj @@ -0,0 +1,28 @@ + + + + + + true + false + false + + + + + + + + + + + + + + + + + + + + diff --git a/samples/Sample.AspNetCore.SwaggerUI.Extensions/appsettings.Development.json b/samples/Sample.AspNetCore.SwaggerUI.Extensions/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/samples/Sample.AspNetCore.SwaggerUI.Extensions/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/Sample.AspNetCore.SwaggerUI.Extensions/appsettings.json b/samples/Sample.AspNetCore.SwaggerUI.Extensions/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/samples/Sample.AspNetCore.SwaggerUI.Extensions/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs index e631f48..ad35290 100644 --- a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs +++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs @@ -5,7 +5,15 @@ builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddOpenApiDocument(OpenApiDocGenConfigurer.Configure); +builder.Services.AddOpenApiDocument(c => +{ + OpenApiDocGenConfigurer.Configure(c); + + // πŸ“Š API Counter: Server-side operation count + c.AppendOperationCountToTags(); + // Or with custom template: + // c.AppendOperationCountToTags(" [{0} endpoints]"); +}); var app = builder.Build(); diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs index 037bf54..d711aa8 100644 --- a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs +++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs @@ -5,7 +5,15 @@ builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(SwaggerGenConfigurer.Configure); +builder.Services.AddSwaggerGen(c => +{ + SwaggerGenConfigurer.Configure(c); + + // πŸ“Š API Counter: Server-side operation count + c.AppendOperationCountToTags(); + // Or with custom template: + // c.AppendOperationCountToTags(" [{0} endpoints]"); +}); var app = builder.Build(); diff --git a/samples/Sample.NSwag.AspNetCore.Extensions/Controllers/OrdersController.cs b/samples/Sample.NSwag.AspNetCore.Extensions/Controllers/OrdersController.cs new file mode 100644 index 0000000..8db5b48 --- /dev/null +++ b/samples/Sample.NSwag.AspNetCore.Extensions/Controllers/OrdersController.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Controllers; + +[ApiController] +[Route("api/[controller]")] +[Tags("Orders")] +public class OrdersController : ControllerBase +{ + /// + /// Gets all orders + /// + [HttpGet] + public IActionResult GetAll() + { + return Ok(new[] { new { Id = 1, Total = 150.00 }, new { Id = 2, Total = 200.00 } }); + } + + /// + /// Gets an order by ID + /// + [HttpGet("{id}")] + public IActionResult GetById(int id) + { + return Ok(new { Id = id, Total = 150.00, Status = "Pending" }); + } +} \ No newline at end of file diff --git a/samples/Sample.NSwag.AspNetCore.Extensions/Controllers/ProductsController.cs b/samples/Sample.NSwag.AspNetCore.Extensions/Controllers/ProductsController.cs new file mode 100644 index 0000000..ef7b567 --- /dev/null +++ b/samples/Sample.NSwag.AspNetCore.Extensions/Controllers/ProductsController.cs @@ -0,0 +1,36 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Controllers; + +[ApiController] +[Route("api/[controller]")] +[Tags("Products")] +public class ProductsController : ControllerBase +{ + /// + /// Gets all products + /// + [HttpGet] + public IActionResult GetAll() + { + return Ok(new[] { new { Id = 1, Name = "Product A" }, new { Id = 2, Name = "Product B" } }); + } + + /// + /// Gets a product by ID + /// + [HttpGet("{id}")] + public IActionResult GetById(int id) + { + return Ok(new { Id = id, Name = "Product", Price = 99.99 }); + } + + /// + /// Creates a new product + /// + [HttpPost] + public IActionResult Create([FromBody] object product) + { + return CreatedAtAction(nameof(GetById), new { id = 1 }, product); + } +} \ No newline at end of file diff --git a/samples/Sample.NSwag.AspNetCore.Extensions/Controllers/UsersController.cs b/samples/Sample.NSwag.AspNetCore.Extensions/Controllers/UsersController.cs new file mode 100644 index 0000000..68160dd --- /dev/null +++ b/samples/Sample.NSwag.AspNetCore.Extensions/Controllers/UsersController.cs @@ -0,0 +1,54 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Controllers; + +[ApiController] +[Route("api/[controller]")] +[Tags("Users")] +public class UsersController : ControllerBase +{ + /// + /// Gets all users + /// + [HttpGet] + public IActionResult GetAll() + { + return Ok(new[] { new { Id = 1, Name = "John" }, new { Id = 2, Name = "Jane" } }); + } + + /// + /// Gets a user by ID + /// + [HttpGet("{id}")] + public IActionResult GetById(int id) + { + return Ok(new { Id = id, Name = "John Doe" }); + } + + /// + /// Creates a new user + /// + [HttpPost] + public IActionResult Create([FromBody] object user) + { + return CreatedAtAction(nameof(GetById), new { id = 1 }, user); + } + + /// + /// Updates an existing user + /// + [HttpPut("{id}")] + public IActionResult Update(int id, [FromBody] object user) + { + return NoContent(); + } + + /// + /// Deletes a user + /// + [HttpDelete("{id}")] + public IActionResult Delete(int id) + { + return NoContent(); + } +} \ No newline at end of file diff --git a/samples/Sample.NSwag.AspNetCore.Extensions/Program.cs b/samples/Sample.NSwag.AspNetCore.Extensions/Program.cs new file mode 100644 index 0000000..508b5f8 --- /dev/null +++ b/samples/Sample.NSwag.AspNetCore.Extensions/Program.cs @@ -0,0 +1,30 @@ +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddControllers(); + +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddOpenApiDocument(c => +{ + c.Title = "Sample API - Extensions Demo"; + c.Version = "v1"; + + // API Counter Extension + // Automatically appends operation count to tag descriptions + c.AppendOperationCountToTags(); + + // Or with custom template: + // c.AppendOperationCountToTags(" [{0} endpoints]"); + // c.AppendOperationCountToTags(" (APIs: {0})"); +}); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseOpenApi(); + app.UseSwaggerUi(); +} + +app.UseHttpsRedirection(); +app.MapControllers(); + +await app.RunAsync(); \ No newline at end of file diff --git a/samples/Sample.NSwag.AspNetCore.Extensions/Properties/launchSettings.json b/samples/Sample.NSwag.AspNetCore.Extensions/Properties/launchSettings.json new file mode 100644 index 0000000..bc6351d --- /dev/null +++ b/samples/Sample.NSwag.AspNetCore.Extensions/Properties/launchSettings.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5101", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7101;http://localhost:5101", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/Sample.NSwag.AspNetCore.Extensions/Sample.NSwag.AspNetCore.Extensions.csproj b/samples/Sample.NSwag.AspNetCore.Extensions/Sample.NSwag.AspNetCore.Extensions.csproj new file mode 100644 index 0000000..6bf3dd3 --- /dev/null +++ b/samples/Sample.NSwag.AspNetCore.Extensions/Sample.NSwag.AspNetCore.Extensions.csproj @@ -0,0 +1,27 @@ + + + + + + true + false + false + + + + + + + + + + + + + + + + + + + diff --git a/samples/Sample.NSwag.AspNetCore.Extensions/appsettings.Development.json b/samples/Sample.NSwag.AspNetCore.Extensions/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/samples/Sample.NSwag.AspNetCore.Extensions/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/Sample.NSwag.AspNetCore.Extensions/appsettings.json b/samples/Sample.NSwag.AspNetCore.Extensions/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/samples/Sample.NSwag.AspNetCore.Extensions/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj b/src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj new file mode 100644 index 0000000..23f304c --- /dev/null +++ b/src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj @@ -0,0 +1,30 @@ + + + + + + + + 1 + 0 + 0 + + $(MajorVersion).$(MinorVersion).$(PatchVersion)$(VersionSuffix) + + + + AspNetCore.SwaggerUI.Extensions + + Enhance your ASP.NET Core Swagger UI with functional extensions like API counter and more. Extends Swashbuckle.AspNetCore with additional capabilities. + + + swagger;swagger-ui;swaggerui;swashbuckle;aspnetcore;webapi;openapi;extension;api-counter;operation-count;tag-extensions + + true + + + + + + + diff --git a/src/AspNetCore.SwaggerUI.Extensions/AspNetCore/Swagger/Extensions/Filters/AppendOperationCountToTagDescriptionFilter.cs b/src/AspNetCore.SwaggerUI.Extensions/AspNetCore/Swagger/Extensions/Filters/AppendOperationCountToTagDescriptionFilter.cs new file mode 100644 index 0000000..02aa737 --- /dev/null +++ b/src/AspNetCore.SwaggerUI.Extensions/AspNetCore/Swagger/Extensions/Filters/AppendOperationCountToTagDescriptionFilter.cs @@ -0,0 +1,92 @@ +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace AspNetCore.Swagger.Extensions.Filters; + +/// +/// Document filter that appends operation count to tag descriptions in Swagger UI. +/// +/// +/// This filter modifies the OpenAPI document at generation time to include +/// the number of operations (endpoints) in each tag's description. +/// +public sealed class AppendOperationCountToTagDescriptionFilter : IDocumentFilter +{ + private readonly string _messageTemplate; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The message template to use. Must contain {0} placeholder for the count. + /// Default: " (operations: {0})" + /// + /// + /// Thrown when does not contain {0} placeholder. + /// + public AppendOperationCountToTagDescriptionFilter(string messageTemplate = " (operations: {0})") + { + ArgumentException.ThrowIfNullOrWhiteSpace(messageTemplate); + + if (!messageTemplate.Contains("{0}")) + { + throw new ArgumentException("The message template must contain '{0}' placeholder.", nameof(messageTemplate)); + } + + _messageTemplate = messageTemplate; + } + + /// + /// Applies the filter to the OpenAPI document. + /// + /// The OpenAPI document to modify. + /// The document filter context. + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + // Count operations per tag + var tagOperationCount = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var path in swaggerDoc.Paths.Values) + { + foreach (var operation in path.Operations.Values) + { + if (operation?.Tags is null || operation.Tags.Count == 0) + continue; + + foreach (var tag in operation.Tags.Select(t => t.Name)) + { + if (tagOperationCount.TryGetValue(tag, out int value)) + tagOperationCount[tag] = ++value; + else + tagOperationCount[tag] = 1; + } + } + } + + // If no counts found, nothing to do + if (tagOperationCount.Count == 0) + return; + + // Ensure Tags collection exists + swaggerDoc.Tags ??= []; + + // Create tags if they don't exist + foreach (var tagName in tagOperationCount.Keys) + { + if (!swaggerDoc.Tags.Any(t => t.Name.Equals(tagName, StringComparison.OrdinalIgnoreCase))) + { + swaggerDoc.Tags.Add(new OpenApiTag { Name = tagName }); + } + } + + // Append count to tag descriptions + foreach (var tag in swaggerDoc.Tags) + { + if (tagOperationCount.TryGetValue(tag.Name, out var count)) + { + tag.Description ??= string.Empty; + tag.Description += string.Format(_messageTemplate, count); + } + } + } +} \ No newline at end of file diff --git a/src/AspNetCore.SwaggerUI.Extensions/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs b/src/AspNetCore.SwaggerUI.Extensions/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs new file mode 100644 index 0000000..26b41d5 --- /dev/null +++ b/src/AspNetCore.SwaggerUI.Extensions/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs @@ -0,0 +1,35 @@ +using AspNetCore.Swagger.Extensions.Filters; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Extension methods for configuring Swagger generation with operation count feature. +/// +public static class SwaggerGenOptionsExtensions +{ + /// + /// Appends operation count to tag descriptions in Swagger UI. + /// + /// The Swagger generation options. + /// + /// Optional message template. Must contain {0} placeholder for the count. + /// Default: " (operations: {0})" + /// + /// + /// + /// services.AddSwaggerGen(c => + /// { + /// c.AppendOperationCountToTags(); + /// // Or with custom template: + /// c.AppendOperationCountToTags(" [{0} endpoints]"); + /// }); + /// + /// + public static void AppendOperationCountToTags( + this SwaggerGenOptions options, + string messageTemplate = " (operations: {0})") + { + options.DocumentFilter(messageTemplate); + } +} \ No newline at end of file diff --git a/src/AspNetCore.SwaggerUI.Extensions/package-readme.md b/src/AspNetCore.SwaggerUI.Extensions/package-readme.md new file mode 100644 index 0000000..1f0dce6 --- /dev/null +++ b/src/AspNetCore.SwaggerUI.Extensions/package-readme.md @@ -0,0 +1,44 @@ +# AspNetCore.SwaggerUI.Extensions + +Enhance your ASP.NET Core Swagger UI with functional extensions that go beyond theming. + +## Features + +- **API Counter** - Automatically display operation counts in tag descriptions +- More extensions coming soon! + +## Installation + +```bash +dotnet add package AspNetCore.SwaggerUI.Extensions +``` + +## Quick Start + +### API Counter + +Add operation counts to your Swagger tags: + +```csharp +builder.Services.AddSwaggerGen(c => +{ + c.AppendOperationCountToTags(); + // Or with custom template: + c.AppendOperationCountToTags(" [{0} endpoints]"); +}); +``` + +This will append the operation count to each tag's description, helping users understand the API surface area at a glance. + +## Documentation + +For more information and advanced usage, visit the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki). + +## Related Packages + +- **AspNetCore.SwaggerUI.Themes** - Runtime theme switching and customization for Swashbuckle +- **NSwag.AspNetCore.Extensions** - Extensions for NSwag users + +## License + +MIT Licensed - see [LICENSE](https://github.com/teociaps/SwaggerUI.Themes/blob/main/LICENSE) for details. diff --git a/src/AspNetCore.SwaggerUI.Extensions/readme.txt b/src/AspNetCore.SwaggerUI.Extensions/readme.txt new file mode 100644 index 0000000..84661bf --- /dev/null +++ b/src/AspNetCore.SwaggerUI.Extensions/readme.txt @@ -0,0 +1,23 @@ +Thank you for installing AspNetCore.SwaggerUI.Extensions! + +QUICK START +----------- +Add operation counts to your Swagger tags: + + builder.Services.AddSwaggerGen(c => + { + c.AppendOperationCountToTags(); + }); + +DOCUMENTATION +------------- +Full documentation: https://github.com/teociaps/SwaggerUI.Themes/wiki + +RELATED PACKAGES +---------------- +- AspNetCore.SwaggerUI.Themes - For runtime theme switching +- NSwag.AspNetCore.Extensions - For NSwag users + +SUPPORT +------- +Report issues: https://github.com/teociaps/SwaggerUI.Themes/issues diff --git a/src/AspNetCore.SwaggerUI.Themes/AspNetCore/Swagger/Themes/Filters/AppendOperationCountToTagDescriptionFilter.cs b/src/AspNetCore.SwaggerUI.Themes/AspNetCore/Swagger/Themes/Filters/AppendOperationCountToTagDescriptionFilter.cs new file mode 100644 index 0000000..5c730cb --- /dev/null +++ b/src/AspNetCore.SwaggerUI.Themes/AspNetCore/Swagger/Themes/Filters/AppendOperationCountToTagDescriptionFilter.cs @@ -0,0 +1,93 @@ +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace AspNetCore.Swagger.Themes.Filters; + +/// +/// Document filter that appends operation count to tag descriptions in Swagger UI. +/// +/// +/// This filter modifies the OpenAPI document at generation time to include +/// the number of operations (endpoints) in each tag's description. +/// Inspired by: https://github.com/unchase/Unchase.Swashbuckle.AspNetCore.Extensions +/// +public sealed class AppendOperationCountToTagDescriptionFilter : IDocumentFilter +{ + private readonly string _messageTemplate; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The message template to use. Must contain {0} placeholder for the count. + /// Default: " (operations: {0})" + /// + /// + /// Thrown when does not contain {0} placeholder. + /// + public AppendOperationCountToTagDescriptionFilter(string messageTemplate = " (operations: {0})") + { + ArgumentException.ThrowIfNullOrWhiteSpace(messageTemplate); + + if (!messageTemplate.Contains("{0}")) + { + throw new ArgumentException("The message template must contain '{0}' placeholder.", nameof(messageTemplate)); + } + + _messageTemplate = messageTemplate; + } + + /// + /// Applies the filter to the OpenAPI document. + /// + /// The OpenAPI document to modify. + /// The document filter context. + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + // Count operations per tag + var tagOperationCount = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var path in swaggerDoc.Paths.Values) + { + foreach (var operation in path.Operations.Values) + { + if (operation?.Tags is null || operation.Tags.Count == 0) + continue; + + foreach (var tag in operation.Tags) + { + if (tagOperationCount.ContainsKey(tag.Name)) + tagOperationCount[tag.Name]++; + else + tagOperationCount[tag.Name] = 1; + } + } + } + + // If no counts found, nothing to do + if (tagOperationCount.Count == 0) + return; + + // Ensure Tags collection exists + swaggerDoc.Tags ??= new List(); + + // Create tags if they don't exist + foreach (var tagName in tagOperationCount.Keys) + { + if (!swaggerDoc.Tags.Any(t => t.Name.Equals(tagName, StringComparison.OrdinalIgnoreCase))) + { + swaggerDoc.Tags.Add(new OpenApiTag { Name = tagName }); + } + } + + // Append count to tag descriptions + foreach (var tag in swaggerDoc.Tags) + { + if (tagOperationCount.TryGetValue(tag.Name, out var count)) + { + tag.Description ??= string.Empty; + tag.Description += string.Format(_messageTemplate, count); + } + } + } +} diff --git a/src/AspNetCore.SwaggerUI.Themes/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs b/src/AspNetCore.SwaggerUI.Themes/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs new file mode 100644 index 0000000..42b6295 --- /dev/null +++ b/src/AspNetCore.SwaggerUI.Themes/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs @@ -0,0 +1,35 @@ +using AspNetCore.Swagger.Themes.Filters; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Extension methods for configuring Swagger generation with operation count feature. +/// +public static class SwaggerGenOptionsExtensions +{ + /// + /// Appends operation count to tag descriptions in Swagger UI. + /// + /// The Swagger generation options. + /// + /// Optional message template. Must contain {0} placeholder for the count. + /// Default: " (operations: {0})" + /// + /// + /// + /// services.AddSwaggerGen(c => + /// { + /// c.AppendOperationCountToTags(); + /// // Or with custom template: + /// c.AppendOperationCountToTags(" [{0} endpoints]"); + /// }); + /// + /// + public static void AppendOperationCountToTags( + this SwaggerGenOptions options, + string messageTemplate = " (operations: {0})") + { + options.DocumentFilter(messageTemplate); + } +} diff --git a/src/NSwag.AspNetCore.Extensions/AspNetCore/Swagger/Extensions/Processors/AppendOperationCountToTagDescriptionProcessor.cs b/src/NSwag.AspNetCore.Extensions/AspNetCore/Swagger/Extensions/Processors/AppendOperationCountToTagDescriptionProcessor.cs new file mode 100644 index 0000000..f26cc8e --- /dev/null +++ b/src/NSwag.AspNetCore.Extensions/AspNetCore/Swagger/Extensions/Processors/AppendOperationCountToTagDescriptionProcessor.cs @@ -0,0 +1,94 @@ +using NSwag; +using NSwag.Generation.Processors; +using NSwag.Generation.Processors.Contexts; + +namespace AspNetCore.Swagger.Extensions.Processors; + +/// +/// Document processor that appends operation count to tag descriptions in Swagger UI. +/// +/// +/// This processor modifies the OpenAPI document at generation time to include +/// the number of operations (endpoints) in each tag's description. +/// +public sealed class AppendOperationCountToTagDescriptionProcessor : IDocumentProcessor +{ + private readonly string _messageTemplate; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The message template to use. Must contain {0} placeholder for the count. + /// Default: " (operations: {0})" + /// + /// + /// Thrown when does not contain {0} placeholder. + /// + public AppendOperationCountToTagDescriptionProcessor(string messageTemplate = " (operations: {0})") + { + ArgumentException.ThrowIfNullOrWhiteSpace(messageTemplate); + + if (!messageTemplate.Contains("{0}")) + { + throw new ArgumentException("The message template must contain '{0}' placeholder.", nameof(messageTemplate)); + } + + _messageTemplate = messageTemplate; + } + + /// + /// Processes the OpenAPI document. + /// + /// The document processor context. + public void Process(DocumentProcessorContext context) + { + var document = context.Document; + + // Count operations per tag + var tagOperationCount = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var path in document.Paths.Values) + { + foreach (var operation in path.Values) + { + if (operation?.Tags is null || operation.Tags.Count == 0) + continue; + + foreach (var tag in operation.Tags) + { + if (tagOperationCount.TryGetValue(tag, out int value)) + tagOperationCount[tag] = ++value; + else + tagOperationCount[tag] = 1; + } + } + } + + // If no counts found, nothing to do + if (tagOperationCount.Count == 0) + return; + + // Ensure Tags collection exists + document.Tags ??= new List(); + + // Create tags if they don't exist + foreach (var tagName in tagOperationCount.Keys) + { + if (!document.Tags.Any(t => t.Name.Equals(tagName, StringComparison.OrdinalIgnoreCase))) + { + document.Tags.Add(new NSwag.OpenApiTag { Name = tagName }); + } + } + + // Append count to tag descriptions + foreach (var tag in document.Tags) + { + if (tagOperationCount.TryGetValue(tag.Name, out var count)) + { + tag.Description ??= string.Empty; + tag.Description += string.Format(_messageTemplate, count); + } + } + } +} \ No newline at end of file diff --git a/src/NSwag.AspNetCore.Extensions/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs b/src/NSwag.AspNetCore.Extensions/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs new file mode 100644 index 0000000..b299410 --- /dev/null +++ b/src/NSwag.AspNetCore.Extensions/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs @@ -0,0 +1,36 @@ +using AspNetCore.Swagger.Extensions.Processors; +using NSwag.Generation.AspNetCore; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Extension methods for configuring OpenAPI document generation with operation count feature. +/// +public static class AspNetCoreOpenApiDocumentGeneratorSettingsExtensions +{ + /// + /// Appends operation count to tag descriptions in Swagger UI. + /// + /// The OpenAPI document generator settings. + /// + /// Optional message template. Must contain {0} placeholder for the count. + /// Default: " (operations: {0})" + /// + /// + /// + /// services.AddOpenApiDocument(c => + /// { + /// c.AppendOperationCountToTags(); + /// // Or with custom template: + /// c.AppendOperationCountToTags(" [{0} endpoints]"); + /// }); + /// + /// + public static void AppendOperationCountToTags( + this AspNetCoreOpenApiDocumentGeneratorSettings settings, + string messageTemplate = " (operations: {0})") + { + settings.DocumentProcessors.Add( + new AppendOperationCountToTagDescriptionProcessor(messageTemplate)); + } +} diff --git a/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj b/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj new file mode 100644 index 0000000..e64715c --- /dev/null +++ b/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj @@ -0,0 +1,30 @@ + + + + + + + + 1 + 0 + 0 + + $(MajorVersion).$(MinorVersion).$(PatchVersion)$(VersionSuffix) + + + + NSwag.AspNetCore.Extensions + + Enhance your ASP.NET Core Swagger UI with functional extensions like API counter and more. Extends NSwag.AspNetCore with additional capabilities. + + + swagger;swagger-ui;swaggerui;nswag;aspnetcore;webapi;openapi;extension;api-counter;operation-count;tag-extensions + + true + + + + + + + diff --git a/src/NSwag.AspNetCore.Extensions/package-readme.md b/src/NSwag.AspNetCore.Extensions/package-readme.md new file mode 100644 index 0000000..8161fe1 --- /dev/null +++ b/src/NSwag.AspNetCore.Extensions/package-readme.md @@ -0,0 +1,44 @@ +# NSwag.AspNetCore.Extensions + +Enhance your ASP.NET Core Swagger UI with functional extensions that go beyond theming. + +## Features + +- **API Counter** - Automatically display operation counts in tag descriptions +- More extensions coming soon! + +## Installation + +```bash +dotnet add package NSwag.AspNetCore.Extensions +``` + +## Quick Start + +### API Counter + +Add operation counts to your Swagger tags: + +```csharp +builder.Services.AddOpenApiDocument(c => +{ + c.AppendOperationCountToTags(); + // Or with custom template: + c.AppendOperationCountToTags(" [{0} endpoints]"); +}); +``` + +This will append the operation count to each tag's description, helping users understand the API surface area at a glance. + +## Documentation + +For more information and advanced usage, visit the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki). + +## Related Packages + +- **NSwag.AspNetCore.Themes** - Runtime theme switching and customization for NSwag +- **AspNetCore.SwaggerUI.Extensions** - Extensions for Swashbuckle users + +## License + +MIT Licensed - see [LICENSE](https://github.com/teociaps/SwaggerUI.Themes/blob/main/LICENSE) for details. diff --git a/src/NSwag.AspNetCore.Extensions/readme.txt b/src/NSwag.AspNetCore.Extensions/readme.txt new file mode 100644 index 0000000..2c922f6 --- /dev/null +++ b/src/NSwag.AspNetCore.Extensions/readme.txt @@ -0,0 +1,23 @@ +Thank you for installing NSwag.AspNetCore.Extensions! + +QUICK START +----------- +Add operation counts to your Swagger tags: + + builder.Services.AddOpenApiDocument(c => + { + c.AppendOperationCountToTags(); + }); + +DOCUMENTATION +------------- +Full documentation: https://github.com/teociaps/SwaggerUI.Themes/wiki + +RELATED PACKAGES +---------------- +- NSwag.AspNetCore.Themes - For runtime theme switching +- AspNetCore.SwaggerUI.Extensions - For Swashbuckle users + +SUPPORT +------- +Report issues: https://github.com/teociaps/SwaggerUI.Themes/issues diff --git a/src/NSwag.AspNetCore.Themes/AspNetCore/Swagger/Themes/Processors/AppendOperationCountToTagDescriptionProcessor.cs b/src/NSwag.AspNetCore.Themes/AspNetCore/Swagger/Themes/Processors/AppendOperationCountToTagDescriptionProcessor.cs new file mode 100644 index 0000000..e180725 --- /dev/null +++ b/src/NSwag.AspNetCore.Themes/AspNetCore/Swagger/Themes/Processors/AppendOperationCountToTagDescriptionProcessor.cs @@ -0,0 +1,95 @@ +using NSwag; +using NSwag.Generation.Processors; +using NSwag.Generation.Processors.Contexts; + +namespace AspNetCore.Swagger.Themes.Processors; + +/// +/// Document processor that appends operation count to tag descriptions in Swagger UI. +/// +/// +/// This processor modifies the OpenAPI document at generation time to include +/// the number of operations (endpoints) in each tag's description. +/// Inspired by: https://github.com/unchase/Unchase.Swashbuckle.AspNetCore.Extensions +/// +public sealed class AppendOperationCountToTagDescriptionProcessor : IDocumentProcessor +{ + private readonly string _messageTemplate; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The message template to use. Must contain {0} placeholder for the count. + /// Default: " (operations: {0})" + /// + /// + /// Thrown when does not contain {0} placeholder. + /// + public AppendOperationCountToTagDescriptionProcessor(string messageTemplate = " (operations: {0})") + { + ArgumentException.ThrowIfNullOrWhiteSpace(messageTemplate); + + if (!messageTemplate.Contains("{0}")) + { + throw new ArgumentException("The message template must contain '{0}' placeholder.", nameof(messageTemplate)); + } + + _messageTemplate = messageTemplate; + } + + /// + /// Processes the OpenAPI document. + /// + /// The document processor context. + public void Process(DocumentProcessorContext context) + { + var document = context.Document; + + // Count operations per tag + var tagOperationCount = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var path in document.Paths.Values) + { + foreach (var operation in path.Values) + { + if (operation?.Tags is null || operation.Tags.Count == 0) + continue; + + foreach (var tag in operation.Tags) + { + if (tagOperationCount.ContainsKey(tag)) + tagOperationCount[tag]++; + else + tagOperationCount[tag] = 1; + } + } + } + + // If no counts found, nothing to do + if (tagOperationCount.Count == 0) + return; + + // Ensure Tags collection exists + document.Tags ??= new List(); + + // Create tags if they don't exist + foreach (var tagName in tagOperationCount.Keys) + { + if (!document.Tags.Any(t => t.Name.Equals(tagName, StringComparison.OrdinalIgnoreCase))) + { + document.Tags.Add(new NSwag.OpenApiTag { Name = tagName }); + } + } + + // Append count to tag descriptions + foreach (var tag in document.Tags) + { + if (tagOperationCount.TryGetValue(tag.Name, out var count)) + { + tag.Description ??= string.Empty; + tag.Description += string.Format(_messageTemplate, count); + } + } + } +} diff --git a/src/NSwag.AspNetCore.Themes/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs b/src/NSwag.AspNetCore.Themes/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs new file mode 100644 index 0000000..7142869 --- /dev/null +++ b/src/NSwag.AspNetCore.Themes/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs @@ -0,0 +1,36 @@ +using AspNetCore.Swagger.Themes.Processors; +using NSwag.Generation.AspNetCore; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Extension methods for configuring OpenAPI document generation with operation count feature. +/// +public static class AspNetCoreOpenApiDocumentGeneratorSettingsExtensions +{ + /// + /// Appends operation count to tag descriptions in Swagger UI. + /// + /// The OpenAPI document generator settings. + /// + /// Optional message template. Must contain {0} placeholder for the count. + /// Default: " (operations: {0})" + /// + /// + /// + /// services.AddOpenApiDocument(c => + /// { + /// c.AppendOperationCountToTags(); + /// // Or with custom template: + /// c.AppendOperationCountToTags(" [{0} endpoints]"); + /// }); + /// + /// + public static void AppendOperationCountToTags( + this AspNetCoreOpenApiDocumentGeneratorSettings settings, + string messageTemplate = " (operations: {0})") + { + settings.DocumentProcessors.Add( + new AppendOperationCountToTagDescriptionProcessor(messageTemplate)); + } +} diff --git a/tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionFilterTests.cs b/tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionFilterTests.cs new file mode 100644 index 0000000..5758846 --- /dev/null +++ b/tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionFilterTests.cs @@ -0,0 +1,190 @@ +using AspNetCore.Swagger.Extensions.Filters; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.OpenApi.Models; +using Shouldly; +using Swashbuckle.AspNetCore.SwaggerGen; +using System.Text.Json; +using Xunit; + +namespace AspNetCore.Swagger.Extensions.Tests; + +public class AppendOperationCountToTagDescriptionFilterTests +{ + [Fact] + public void Constructor_WithValidTemplate_Succeeds() + { + // Act + var filter = new AppendOperationCountToTagDescriptionFilter(" (operations: {0})"); + + // Assert + filter.ShouldNotBeNull(); + } + + [Fact] + public void Constructor_WithNullTemplate_ThrowsArgumentException() + { + // Act & Assert + Should.Throw(() => + new AppendOperationCountToTagDescriptionFilter(null!)); + } + + [Fact] + public void Constructor_WithEmptyTemplate_ThrowsArgumentException() + { + // Act & Assert + Should.Throw(() => + new AppendOperationCountToTagDescriptionFilter("")); + } + + [Fact] + public void Constructor_WithoutPlaceholder_ThrowsArgumentException() + { + // Act & Assert + var exception = Should.Throw(() => + new AppendOperationCountToTagDescriptionFilter(" operations")); + exception.Message.ShouldContain("{0}"); + } + + [Fact] + public void Apply_WithEmptyDocument_DoesNothing() + { + // Arrange + var filter = new AppendOperationCountToTagDescriptionFilter(); + var document = new OpenApiDocument + { + Paths = [] + }; + var context = CreateContext(); + + // Act + filter.Apply(document, context); + + // Assert + document.Tags.ShouldBeEmpty(); + } + + [Fact] + public void Apply_WithOperations_AppendsCount() + { + // Arrange + var filter = new AppendOperationCountToTagDescriptionFilter(); + var document = CreateDocumentWithTags(); + var context = CreateContext(); + + // Act + filter.Apply(document, context); + + // Assert + document.Tags.ShouldNotBeEmpty(); + var userTag = document.Tags.FirstOrDefault(t => t.Name == "Users"); + userTag.ShouldNotBeNull(); + userTag.Description.ShouldContain("(operations: 2)"); + } + + [Fact] + public void Apply_WithCustomTemplate_UsesTemplate() + { + // Arrange + var filter = new AppendOperationCountToTagDescriptionFilter(" [{0} endpoints]"); + var document = CreateDocumentWithTags(); + var context = CreateContext(); + + // Act + filter.Apply(document, context); + + // Assert + var userTag = document.Tags.FirstOrDefault(t => t.Name == "Users"); + userTag.ShouldNotBeNull(); + userTag.Description.ShouldContain("[2 endpoints]"); + } + + [Fact] + public void Apply_WithExistingDescription_AppendsCount() + { + // Arrange + var filter = new AppendOperationCountToTagDescriptionFilter(); + var document = CreateDocumentWithTags(); + document.Tags = new List + { + new() { Name = "Users", Description = "User management" } + }; + var context = CreateContext(); + + // Act + filter.Apply(document, context); + + // Assert + var userTag = document.Tags.FirstOrDefault(t => t.Name == "Users"); + userTag.ShouldNotBeNull(); + userTag.Description.ShouldBe("User management (operations: 2)"); + } + + [Fact] + public void Apply_WithMultipleTags_CountsCorrectly() + { + // Arrange + var filter = new AppendOperationCountToTagDescriptionFilter(); + var document = new OpenApiDocument + { + Paths = new OpenApiPaths + { + ["/users"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [OperationType.Get] = new() { Tags = new List { new() { Name = "Users" } } }, + [OperationType.Post] = new() { Tags = new List { new() { Name = "Users" } } } + } + }, + ["/products"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [OperationType.Get] = new() { Tags = new List { new() { Name = "Products" } } } + } + } + } + }; + var context = CreateContext(); + + // Act + filter.Apply(document, context); + + // Assert + document.Tags.Count.ShouldBe(2); + document.Tags.First(t => t.Name == "Users").Description.ShouldContain("(operations: 2)"); + document.Tags.First(t => t.Name == "Products").Description.ShouldContain("(operations: 1)"); + } + + private static DocumentFilterContext CreateContext() + { + return new DocumentFilterContext( + new List(), + new SchemaGenerator(new SchemaGeneratorOptions(), new JsonSerializerDataContractResolver(new JsonSerializerOptions())), + new SchemaRepository()); + } + + private static OpenApiDocument CreateDocumentWithTags() + { + return new OpenApiDocument + { + Paths = new OpenApiPaths + { + ["/users"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [OperationType.Get] = new() + { + Tags = new List { new() { Name = "Users" } } + }, + [OperationType.Post] = new() + { + Tags = new List { new() { Name = "Users" } } + } + } + } + } + }; + } +} \ No newline at end of file diff --git a/tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionProcessorTests.cs b/tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionProcessorTests.cs new file mode 100644 index 0000000..3f4b3d5 --- /dev/null +++ b/tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionProcessorTests.cs @@ -0,0 +1,182 @@ +using AspNetCore.Swagger.Extensions.Processors; +using NSwag; +using NSwag.Generation.Processors.Contexts; +using Shouldly; +using Xunit; + +namespace AspNetCore.Swagger.Extensions.Tests; + +public class AppendOperationCountToTagDescriptionProcessorTests +{ + [Fact] + public void Constructor_WithValidTemplate_Succeeds() + { + // Act + var processor = new AppendOperationCountToTagDescriptionProcessor(" (operations: {0})"); + + // Assert + processor.ShouldNotBeNull(); + } + + [Fact] + public void Constructor_WithNullTemplate_ThrowsArgumentException() + { + // Act & Assert + Should.Throw(() => + new AppendOperationCountToTagDescriptionProcessor(null!)); + } + + [Fact] + public void Constructor_WithEmptyTemplate_ThrowsArgumentException() + { + // Act & Assert + Should.Throw(() => + new AppendOperationCountToTagDescriptionProcessor("")); + } + + [Fact] + public void Constructor_WithoutPlaceholder_ThrowsArgumentException() + { + // Act & Assert + var exception = Should.Throw(() => + new AppendOperationCountToTagDescriptionProcessor(" operations")); + exception.Message.ShouldContain("{0}"); + } + + [Fact] + public void Process_WithEmptyDocument_DoesNothing() + { + // Arrange + var processor = new AppendOperationCountToTagDescriptionProcessor(); + var document = new OpenApiDocument + { + Tags = [] + }; + var context = CreateContext(document); + + // Act + processor.Process(context); + + // Assert + document.Tags.ShouldBeEmpty(); + } + + [Fact] + public void Process_WithOperations_AppendsCount() + { + // Arrange + var processor = new AppendOperationCountToTagDescriptionProcessor(); + var document = CreateDocumentWithTags(); + var context = CreateContext(document); + + // Act + processor.Process(context); + + // Assert + document.Tags.ShouldNotBeEmpty(); + var userTag = document.Tags.FirstOrDefault(t => t.Name == "Users"); + userTag.ShouldNotBeNull(); + userTag.Description.ShouldContain("(operations: 2)"); + } + + [Fact] + public void Process_WithCustomTemplate_UsesTemplate() + { + // Arrange + var processor = new AppendOperationCountToTagDescriptionProcessor(" [{0} endpoints]"); + var document = CreateDocumentWithTags(); + var context = CreateContext(document); + + // Act + processor.Process(context); + + // Assert + var userTag = document.Tags.FirstOrDefault(t => t.Name == "Users"); + userTag.ShouldNotBeNull(); + userTag.Description.ShouldContain("[2 endpoints]"); + } + + [Fact] + public void Process_WithExistingDescription_AppendsCount() + { + // Arrange + var processor = new AppendOperationCountToTagDescriptionProcessor(); + var document = CreateDocumentWithTags(); + document.Tags = new List + { + new() { Name = "Users", Description = "User management" } + }; + var context = CreateContext(document); + + // Act + processor.Process(context); + + // Assert + var userTag = document.Tags.FirstOrDefault(t => t.Name == "Users"); + userTag.ShouldNotBeNull(); + userTag.Description.ShouldBe("User management (operations: 2)"); + } + + [Fact] + public void Process_WithMultipleTags_CountsCorrectly() + { + // Arrange + var processor = new AppendOperationCountToTagDescriptionProcessor(); + var document = new OpenApiDocument(); + document.Paths.Add("/users", new OpenApiPathItem + { + { + OpenApiOperationMethod.Get, + new OpenApiOperation { Tags = new List { "Users" } } + }, + { + OpenApiOperationMethod.Post, + new OpenApiOperation { Tags = new List { "Users" } } + } + }); + document.Paths.Add("/products", new OpenApiPathItem + { + { + OpenApiOperationMethod.Get, + new OpenApiOperation { Tags = new List { "Products" } } + } + }); + var context = CreateContext(document); + + // Act + processor.Process(context); + + // Assert + document.Tags.Count.ShouldBe(2); + document.Tags.First(t => t.Name == "Users").Description.ShouldContain("(operations: 2)"); + document.Tags.First(t => t.Name == "Products").Description.ShouldContain("(operations: 1)"); + } + + private static DocumentProcessorContext CreateContext(OpenApiDocument document) + { + return new DocumentProcessorContext( + document, + new List(), + new List(), + null!, + null!, + null!); + } + + private static OpenApiDocument CreateDocumentWithTags() + { + var document = new OpenApiDocument(); + document.Paths.Add("/users", new OpenApiPathItem + { + { + OpenApiOperationMethod.Get, + new OpenApiOperation { Tags = new List { "Users" } } + }, + { + OpenApiOperationMethod.Post, + new OpenApiOperation { Tags = new List { "Users" } } + } + }); + return document; + } +} \ No newline at end of file diff --git a/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCore.Swagger.Extensions.Tests.csproj b/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCore.Swagger.Extensions.Tests.csproj new file mode 100644 index 0000000..f16b453 --- /dev/null +++ b/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCore.Swagger.Extensions.Tests.csproj @@ -0,0 +1,40 @@ +ο»Ώ + + + + + false + true + $(MSBuildProjectName.Replace(" ", "_")) + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + diff --git a/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCoreOpenApiDocumentGeneratorSettingsExtensionsTests.cs b/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCoreOpenApiDocumentGeneratorSettingsExtensionsTests.cs new file mode 100644 index 0000000..6fdd55a --- /dev/null +++ b/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCoreOpenApiDocumentGeneratorSettingsExtensionsTests.cs @@ -0,0 +1,42 @@ +using AspNetCore.Swagger.Extensions.Processors; +using Microsoft.Extensions.DependencyInjection; +using NSwag.Generation.AspNetCore; +using Shouldly; +using Xunit; + +namespace AspNetCore.Swagger.Extensions.Tests; + +public class AspNetCoreOpenApiDocumentGeneratorSettingsExtensionsTests +{ + [Fact] + public void AppendOperationCountToTags_AddsProcessor() + { + // Arrange + var settings = new AspNetCoreOpenApiDocumentGeneratorSettings(); + + // Act + settings.AppendOperationCountToTags(); + + // Assert + settings.DocumentProcessors.ShouldNotBeEmpty(); + settings.DocumentProcessors.ShouldContain(p => + p.GetType() == typeof(AppendOperationCountToTagDescriptionProcessor)); + } + + [Fact] + public void AppendOperationCountToTags_WithCustomTemplate_AddsProcessorWithTemplate() + { + // Arrange + var settings = new AspNetCoreOpenApiDocumentGeneratorSettings(); + var customTemplate = " [{0} endpoints]"; + + // Act + settings.AppendOperationCountToTags(customTemplate); + + // Assert + settings.DocumentProcessors.ShouldNotBeEmpty(); + var processor = settings.DocumentProcessors + .FirstOrDefault(p => p.GetType() == typeof(AppendOperationCountToTagDescriptionProcessor)); + processor.ShouldNotBeNull(); + } +} \ No newline at end of file diff --git a/tests/AspNetCore.Swagger.Extensions.Tests/SwaggerGenOptionsExtensionsTests.cs b/tests/AspNetCore.Swagger.Extensions.Tests/SwaggerGenOptionsExtensionsTests.cs new file mode 100644 index 0000000..610b7e6 --- /dev/null +++ b/tests/AspNetCore.Swagger.Extensions.Tests/SwaggerGenOptionsExtensionsTests.cs @@ -0,0 +1,43 @@ +using AspNetCore.Swagger.Extensions.Filters; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Swashbuckle.AspNetCore.SwaggerGen; +using Xunit; + +namespace AspNetCore.Swagger.Extensions.Tests; + +public class SwaggerGenOptionsExtensionsTests +{ + [Fact] + public void AppendOperationCountToTags_AddsFilter() + { + // Arrange + var options = new SwaggerGenOptions(); + + // Act + options.AppendOperationCountToTags(); + + // Assert + options.DocumentFilterDescriptors.ShouldNotBeEmpty(); + options.DocumentFilterDescriptors.ShouldContain(d => + d.Type == typeof(AppendOperationCountToTagDescriptionFilter)); + } + + [Fact] + public void AppendOperationCountToTags_WithCustomTemplate_AddsFilterWithTemplate() + { + // Arrange + var options = new SwaggerGenOptions(); + var customTemplate = " [{0} endpoints]"; + + // Act + options.AppendOperationCountToTags(customTemplate); + + // Assert + options.DocumentFilterDescriptors.ShouldNotBeEmpty(); + var descriptor = options.DocumentFilterDescriptors + .FirstOrDefault(d => d.Type == typeof(AppendOperationCountToTagDescriptionFilter)); + descriptor.ShouldNotBeNull(); + descriptor.Arguments.ShouldContain(customTemplate); + } +} \ No newline at end of file From 5229ad9d9b63e21c2e50c8cd39218197e2e534c5 Mon Sep 17 00:00:00 2001 From: Teuz Date: Tue, 3 Feb 2026 19:56:22 +0100 Subject: [PATCH 02/35] Add Swagger UI Extensions: API Counter for Swashbuckle/NSwag - Introduce new extension packages for Swashbuckle and NSwag - Add API Counter feature to append operation counts to tag descriptions - Implement Swashbuckle filter and NSwag processor with extension methods - Provide sample projects and controllers demonstrating usage - Add comprehensive unit tests for filters, processors, and extensions - Update README, contributing guide, and add package docs - Add GitHub issue and PR templates for bugs, features, and questions - Update solution and project files to include new extensions and tests - Ensure compatibility with .NET 8, 9, and 10 --- .github/ISSUE_TEMPLATE/bug_report.yml | 147 ++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 9 +- .github/ISSUE_TEMPLATE/feature_request.yml | 105 ++++++++++ .github/ISSUE_TEMPLATE/question.yml | 67 ++++++ .github/pull_request_template.md | 65 ++++++ CONTRIBUTING.md | 126 ++++++++++-- README.md | 95 ++++++++- SwaggerUI.Themes.sln | 37 +++- .../Controllers/OrdersController.cs | 27 +++ .../Controllers/ProductsController.cs | 36 ++++ .../Controllers/UsersController.cs | 54 +++++ .../Program.cs | 29 +++ .../Properties/launchSettings.json | 25 +++ ...ple.AspNetCore.SwaggerUI.Extensions.csproj | 28 +++ .../appsettings.Development.json | 8 + .../appsettings.json | 9 + .../Program.cs | 10 +- .../Program.cs | 10 +- .../Controllers/OrdersController.cs | 27 +++ .../Controllers/ProductsController.cs | 36 ++++ .../Controllers/UsersController.cs | 54 +++++ .../Program.cs | 30 +++ .../Properties/launchSettings.json | 25 +++ .../Sample.NSwag.AspNetCore.Extensions.csproj | 27 +++ .../appsettings.Development.json | 8 + .../appsettings.json | 9 + .../AspNetCore.SwaggerUI.Extensions.csproj | 30 +++ ...endOperationCountToTagDescriptionFilter.cs | 92 +++++++++ .../SwaggerGenOptionsExtensions.cs | 35 ++++ .../package-readme.md | 44 ++++ .../readme.txt | 23 +++ ...endOperationCountToTagDescriptionFilter.cs | 93 +++++++++ .../SwaggerGenOptionsExtensions.cs | 35 ++++ ...OperationCountToTagDescriptionProcessor.cs | 94 +++++++++ ...nApiDocumentGeneratorSettingsExtensions.cs | 36 ++++ .../NSwag.AspNetCore.Extensions.csproj | 30 +++ .../package-readme.md | 44 ++++ src/NSwag.AspNetCore.Extensions/readme.txt | 23 +++ ...OperationCountToTagDescriptionProcessor.cs | 95 +++++++++ ...nApiDocumentGeneratorSettingsExtensions.cs | 36 ++++ ...erationCountToTagDescriptionFilterTests.cs | 190 ++++++++++++++++++ ...tionCountToTagDescriptionProcessorTests.cs | 182 +++++++++++++++++ ...AspNetCore.Swagger.Extensions.Tests.csproj | 40 ++++ ...ocumentGeneratorSettingsExtensionsTests.cs | 42 ++++ .../SwaggerGenOptionsExtensionsTests.cs | 43 ++++ 45 files changed, 2283 insertions(+), 27 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/question.yml create mode 100644 .github/pull_request_template.md create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/OrdersController.cs create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/ProductsController.cs create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/UsersController.cs create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Extensions/Program.cs create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Extensions/Properties/launchSettings.json create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Extensions/Sample.AspNetCore.SwaggerUI.Extensions.csproj create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Extensions/appsettings.Development.json create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Extensions/appsettings.json create mode 100644 samples/Sample.NSwag.AspNetCore.Extensions/Controllers/OrdersController.cs create mode 100644 samples/Sample.NSwag.AspNetCore.Extensions/Controllers/ProductsController.cs create mode 100644 samples/Sample.NSwag.AspNetCore.Extensions/Controllers/UsersController.cs create mode 100644 samples/Sample.NSwag.AspNetCore.Extensions/Program.cs create mode 100644 samples/Sample.NSwag.AspNetCore.Extensions/Properties/launchSettings.json create mode 100644 samples/Sample.NSwag.AspNetCore.Extensions/Sample.NSwag.AspNetCore.Extensions.csproj create mode 100644 samples/Sample.NSwag.AspNetCore.Extensions/appsettings.Development.json create mode 100644 samples/Sample.NSwag.AspNetCore.Extensions/appsettings.json create mode 100644 src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj create mode 100644 src/AspNetCore.SwaggerUI.Extensions/AspNetCore/Swagger/Extensions/Filters/AppendOperationCountToTagDescriptionFilter.cs create mode 100644 src/AspNetCore.SwaggerUI.Extensions/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs create mode 100644 src/AspNetCore.SwaggerUI.Extensions/package-readme.md create mode 100644 src/AspNetCore.SwaggerUI.Extensions/readme.txt create mode 100644 src/AspNetCore.SwaggerUI.Themes/AspNetCore/Swagger/Themes/Filters/AppendOperationCountToTagDescriptionFilter.cs create mode 100644 src/AspNetCore.SwaggerUI.Themes/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs create mode 100644 src/NSwag.AspNetCore.Extensions/AspNetCore/Swagger/Extensions/Processors/AppendOperationCountToTagDescriptionProcessor.cs create mode 100644 src/NSwag.AspNetCore.Extensions/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs create mode 100644 src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj create mode 100644 src/NSwag.AspNetCore.Extensions/package-readme.md create mode 100644 src/NSwag.AspNetCore.Extensions/readme.txt create mode 100644 src/NSwag.AspNetCore.Themes/AspNetCore/Swagger/Themes/Processors/AppendOperationCountToTagDescriptionProcessor.cs create mode 100644 src/NSwag.AspNetCore.Themes/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs create mode 100644 tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionFilterTests.cs create mode 100644 tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionProcessorTests.cs create mode 100644 tests/AspNetCore.Swagger.Extensions.Tests/AspNetCore.Swagger.Extensions.Tests.csproj create mode 100644 tests/AspNetCore.Swagger.Extensions.Tests/AspNetCoreOpenApiDocumentGeneratorSettingsExtensionsTests.cs create mode 100644 tests/AspNetCore.Swagger.Extensions.Tests/SwaggerGenOptionsExtensionsTests.cs diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..51ed46c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,147 @@ +name: "?? Bug Report" +description: "Report a bug or issue with SwaggerUI.Themes or SwaggerUI.Extensions" +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thank you for reporting an issue! Please fill out the information below to help us investigate. + + - type: dropdown + id: package_type + attributes: + label: "Package Type" + description: "Which type of package are you reporting an issue with?" + options: + - "Theme (AspNetCore.SwaggerUI.Themes)" + - "Theme (NSwag.AspNetCore.Themes)" + - "Extension (AspNetCore.SwaggerUI.Extensions)" + - "Extension (NSwag.AspNetCore.Extensions)" + - "Common (AspNetCore.Swagger.Themes.Common)" + - "Unsure" + validations: + required: true + + - type: dropdown + id: integration + attributes: + label: "Integration Framework" + description: "Which Swagger integration are you using?" + options: + - "Swashbuckle.AspNetCore" + - "NSwag.AspNetCore" + - "Both" + - "Not applicable" + validations: + required: true + + - type: input + id: dotnet_version + attributes: + label: ".NET Version" + description: "Which .NET version are you using? (e.g., 8.0, 9.0, 10.0)" + placeholder: "e.g., 8.0" + validations: + required: true + + - type: input + id: package_version + attributes: + label: "Package Version" + description: "Which version of the package are you using?" + placeholder: "e.g., 3.0.0" + validations: + required: true + + - type: textarea + id: description + attributes: + label: "Description" + description: "Clear and concise description of the bug" + placeholder: "What happened?" + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: "Steps to Reproduce" + description: "Step-by-step instructions to reproduce the issue" + placeholder: | + 1. Configure the package with... + 2. Navigate to... + 3. See error... + validations: + required: true + + - type: textarea + id: expected_behavior + attributes: + label: "Expected Behavior" + description: "What should happen?" + placeholder: "Describe the expected behavior" + validations: + required: true + + - type: textarea + id: actual_behavior + attributes: + label: "Actual Behavior" + description: "What actually happens?" + placeholder: "Describe what actually happens" + validations: + required: true + + - type: dropdown + id: browser + attributes: + label: "Browser (if UI-related)" + description: "Which browser are you using? (Only relevant for theme/UI issues)" + options: + - "Chrome" + - "Firefox" + - "Safari" + - "Edge" + - "Other" + - "Not applicable" + + - type: textarea + id: code_snippet + attributes: + label: "Code Snippet" + description: "Relevant code that demonstrates the issue (optional)" + render: csharp + placeholder: | + // Your code here + app.UseSwaggerUI(Theme.Dark); + + - type: textarea + id: error_logs + attributes: + label: "Error Logs or Stack Trace" + description: "Any relevant error messages or stack traces (optional)" + render: plain_text + + - type: textarea + id: screenshots + attributes: + label: "Screenshots" + description: "Screenshots showing the issue (optional, for UI-related issues)" + + - type: textarea + id: additional_context + attributes: + label: "Additional Context" + description: "Any other information that might be helpful" + placeholder: "Add any other context about the problem here" + + - type: checkboxes + id: checklist + attributes: + label: "Verification" + options: + - label: "I have searched existing issues and found no duplicates" + required: true + - label: "I have provided clear reproduction steps" + required: true + - label: "I have included relevant error messages or logs" diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index a49eab2..7557f1f 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1 +1,8 @@ -blank_issues_enabled: true \ No newline at end of file +ο»Ώblank_issues_enabled: false +contact_links: + - name: "❓ Ask a Question" + url: "https://github.com/teociaps/SwaggerUI.Themes/discussions" + about: "Have a question or want to discuss? Use GitHub Discussions!" + - name: "πŸ“š View Documentation" + url: "https://github.com/teociaps/SwaggerUI.Themes/wiki" + about: "Check the wiki for comprehensive documentation and guides" \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..2cea18d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,105 @@ +name: "? Feature Request" +description: "Suggest a new feature or enhancement for SwaggerUI.Themes or SwaggerUI.Extensions" +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: | + Thank you for suggesting a feature! Please provide details to help us understand your idea. + + - type: dropdown + id: feature_type + attributes: + label: "Feature Type" + description: "What category is your feature request for?" + options: + - "Theme (visual/styling enhancement)" + - "Extension (functional capability)" + - "Documentation" + - "Developer Experience" + - "Other" + validations: + required: true + + - type: dropdown + id: package_scope + attributes: + label: "Package Scope" + description: "Which packages should this feature apply to?" + options: + - "Swashbuckle only (AspNetCore.SwaggerUI.*)" + - "NSwag only (NSwag.AspNetCore.*)" + - "Both Swashbuckle and NSwag" + - "Common/Shared" + - "Not sure" + validations: + required: true + + - type: textarea + id: problem + attributes: + label: "Problem Statement" + description: "What problem does this feature solve? What is the use case?" + placeholder: | + Current situation: ... + Problem: ... + Why this matters: ... + validations: + required: true + + - type: textarea + id: solution + attributes: + label: "Proposed Solution" + description: "Describe how you envision this feature working" + placeholder: | + Describe the ideal behavior/UI/API + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: "Alternative Solutions" + description: "Are there any alternative approaches? (optional)" + placeholder: "Have you considered other ways to solve this?" + + - type: textarea + id: example_usage + attributes: + label: "Example Usage" + description: "How would users interact with this feature? (optional)" + render: csharp + placeholder: | + // Example code showing how the feature would be used + app.UseSwaggerUI(Theme.Dark, c => + { + c.MyNewFeature(); + }); + + - type: textarea + id: design_considerations + attributes: + label: "Design Considerations" + description: "Any thoughts on API design, backward compatibility, or performance? (optional)" + placeholder: "Backward compatibility impact, performance considerations, naming suggestions, etc." + + - type: checkboxes + id: checklist + attributes: + label: "Verification" + options: + - label: "I have searched existing feature requests and found no duplicates" + required: true + - label: "This feature aligns with the project scope" + required: true + + - type: textarea + id: additional_context + attributes: + label: "Additional Context" + description: "Any other information or references? (optional)" + placeholder: | + - Related issues: #123 + - Similar features in other projects: ... + - References: ... diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 0000000..ab86ca8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,67 @@ +name: "? Question/Discussion" +description: "Ask a question or start a discussion about using the packages" +labels: ["question"] +body: + - type: markdown + attributes: + value: | + Have a question about how to use SwaggerUI.Themes or SwaggerUI.Extensions? Please ask away! + + - type: dropdown + id: package_type + attributes: + label: "Package" + description: "Which package is your question about?" + options: + - "AspNetCore.SwaggerUI.Themes" + - "NSwag.AspNetCore.Themes" + - "AspNetCore.SwaggerUI.Extensions" + - "NSwag.AspNetCore.Extensions" + - "General/Not sure" + validations: + required: true + + - type: input + id: dotnet_version + attributes: + label: ".NET Version" + description: "Which .NET version are you using?" + placeholder: "e.g., 8.0, 9.0, 10.0" + + - type: input + id: package_version + attributes: + label: "Package Version" + description: "Which version of the package are you using?" + placeholder: "e.g., 3.0.0" + + - type: textarea + id: question + attributes: + label: "Your Question" + description: "What would you like to know?" + placeholder: "Ask your question here..." + validations: + required: true + + - type: textarea + id: context + attributes: + label: "Context" + description: "What are you trying to achieve? Include any relevant code." + render: csharp + placeholder: | + // Your code/setup here + + - type: textarea + id: what_you_tried + attributes: + label: "What Have You Tried?" + description: "What solutions have you already attempted?" + placeholder: "Describe any troubleshooting steps you've already taken" + + - type: textarea + id: additional_info + attributes: + label: "Additional Information" + description: "Any other details that might be helpful? (optional)" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..70f60d7 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,65 @@ +## Description + + + +## Type of Change + +- [ ] ?? **Theme** (visual/styling updates) +- [ ] ?? **Extension** (functional capability) +- [ ] ?? **Documentation** (docs/comments only) +- [ ] ?? **Bug Fix** (fixes an issue) +- [ ] ?? **Refactoring** (internal improvements) +- [ ] ?? **Tests** (test updates/additions) + +## Related Issue + +Fixes # or Relates to # + +## Package(s) Affected + +- [ ] AspNetCore.SwaggerUI.Themes +- [ ] NSwag.AspNetCore.Themes +- [ ] AspNetCore.SwaggerUI.Extensions +- [ ] NSwag.AspNetCore.Extensions +- [ ] AspNetCore.Swagger.Themes.Common + +## Testing + +Please describe the testing done to verify this change: + + + +- [ ] I have built the solution (`dotnet build -c Release`) +- [ ] I have run the test suite (`dotnet test`) +- [ ] I have tested with relevant sample projects +- [ ] All changes work across .NET 8, 9, and 10 (if applicable) + +## Checklist + +- [ ] My code follows the style guidelines of this project +- [ ] I have added/updated tests for my changes +- [ ] I have updated relevant documentation (README, CONTRIBUTING, wiki, etc.) +- [ ] My changes do not break any existing functionality +- [ ] I have verified no new compiler warnings are introduced + +## Theme-Specific (if applicable) + +- [ ] Minified CSS/JS files have been updated +- [ ] Minified file headers are correctly formatted (`/*Name*/...`) +- [ ] All placeholders (e.g., `{$THEME_SWITCHER}`) are preserved in minified files +- [ ] Theme is discoverable and testable + +## Extension-Specific (if applicable) + +- [ ] Implementation exists for both Swashbuckle and NSwag (if cross-platform) +- [ ] Extension works independently and with other extensions +- [ ] Tests cover the new functionality +- [ ] Sample applications demonstrate the feature + +## Screenshots/Demo + + + +## Additional Context + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0380fa7..0f78073 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,12 +35,18 @@ This project and everyone participating in it are governed by our [Code of Condu Understanding the project layout helps you navigate the codebase: - **`src/AspNetCore.Swagger.Themes.Common/`** - Core theme system shared between packages -- **`src/AspNetCore.SwaggerUI.Themes/`** - Swashbuckle.AspNetCore integration -- **`src/NSwag.AspNetCore.Themes/`** - NSwag.AspNetCore integration +- **`src/AspNetCore.SwaggerUI.Themes/`** - Swashbuckle.AspNetCore theme integration +- **`src/NSwag.AspNetCore.Themes/`** - NSwag.AspNetCore theme integration +- **`src/AspNetCore.SwaggerUI.Extensions/`** - Swashbuckle.AspNetCore functional extensions +- **`src/NSwag.AspNetCore.Extensions/`** - NSwag.AspNetCore functional extensions - **`samples/`** - Example applications demonstrating usage - **`tests/`** - Unit and integration tests -## Working with Themes +--- + +## Contributing Themes + +Themes customize the visual appearance of Swagger UI. When contributing a new theme: ### Adding or Modifying Themes @@ -107,6 +113,82 @@ git add src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/* git commit -m "Add new ocean theme" ``` +### Theme Organization + +When adding example custom themes, use subfolders for clarity: + +``` +samples/YourSample/SwaggerThemes/ +β”œβ”€β”€ CompanyThemes/ +β”‚ └── corporate-blue.css +└── SeasonalThemes/ + └── holiday-red.css +``` + +This demonstrates best practices for users organizing their own themes. + +### Standalone vs. Regular Themes + +- **Regular themes** - Depend on `common.css` and may use JavaScript features +- **Standalone themes** - Prefix with `standalone.` (e.g., `standalone.custom.css`) for zero dependencies + +Choose based on whether you need shared styles or prefer total independence. + +--- + +## Contributing Extensions + +Extensions add functional capabilities to Swagger UI beyond theming. When contributing a new extension: + +### Planning an Extension + +Before implementing, consider: + +1. **Scope** - Is it a focused feature or part of a larger system? +2. **Compatibility** - Does it work with both Swashbuckle and NSwag? +3. **Integration points** - Where should it hook into the pipeline? +4. **User value** - What problem does it solve for users? + +### Implementation Guidelines + +Extensions should be implemented for both integrations: + +- **Swashbuckle implementation** - `src/AspNetCore.SwaggerUI.Extensions/` + - Add service extension methods in `Microsoft.Extensions.DependencyInjection/` + - Add filters in `AspNetCore/Swagger/Extensions/Filters/` + +- **NSwag implementation** - `src/NSwag.AspNetCore.Extensions/` + - Add service extension methods in `Microsoft.Extensions.DependencyInjection/` + - Add processors in `AspNetCore/Swagger/Extensions/Processors/` + +### Extension Structure Example + +``` +src/AspNetCore.SwaggerUI.Extensions/ +β”œβ”€β”€ Microsoft/Extensions/DependencyInjection/ +β”‚ └── SwaggerGenOptionsExtensions.cs // Service configuration +β”œβ”€β”€ AspNetCore/Swagger/Extensions/ +β”‚ └── Filters/ +β”‚ └── MyFeatureFilter.cs // Swagger/OpenAPI filter +└── package-readme.md // NuGet package documentation + +src/NSwag.AspNetCore.Extensions/ +β”œβ”€β”€ Microsoft/Extensions/DependencyInjection/ +β”‚ └── AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs +β”œβ”€β”€ AspNetCore/Swagger/Extensions/ +β”‚ └── Processors/ +β”‚ └── MyFeatureProcessor.cs // NSwag processor +└── package-readme.md +``` + +### Testing Extensions + +1. Add tests in the appropriate test project +2. Test with both sample applications +3. Verify the extension works independently and alongside other extensions + +--- + ## Testing Your Changes Before submitting a pull request: @@ -126,8 +208,9 @@ dotnet test ``` All tests must pass, including: -- Minified header format tests -- Theme discovery tests +- Minified header format tests (for themes) +- Theme discovery tests (for themes) +- Extension functionality tests (for extensions) - Integration tests ### 3. Test with Sample Applications @@ -140,15 +223,22 @@ dotnet run --project samples/Sample.AspNetCore.SwaggerUI.Swashbuckle # NSwag sample dotnet run --project samples/Sample.AspNetCore.SwaggerUI.NSwag + +# Swashbuckle Extensions sample +dotnet run --project samples/Sample.AspNetCore.SwaggerUI.Extensions + +# NSwag Extensions sample +dotnet run --project samples/Sample.NSwag.AspNetCore.Extensions ``` ### 4. Manual Verification Open the sample app in your browser and verify: -- βœ… Themes render correctly -- βœ… Theme switcher dropdown appears and works (if enabled) -- βœ… Custom themes are discovered automatically +- βœ… Themes render correctly (if theme changes) +- βœ… Theme switcher dropdown appears and works (if theme changes) +- βœ… Custom themes are discovered automatically (if theme changes) +- βœ… Extensions work as expected (if extension changes) - βœ… Advanced features work (pinnable topbar, back-to-top, etc.) - βœ… No console errors in browser developer tools - βœ… Standalone themes load without extra dependencies @@ -164,9 +254,10 @@ Open the sample app in your browser and verify: ### Good PR Practices -- Use descriptive titles: `feat: add sunset theme` not `update css` +- Use descriptive titles: `feat: add sunset theme` or `feat: add api metadata extension` - Reference issues: `Fixes #123` or `Closes #456` -- Include screenshots for visual changes +- Include screenshots for visual changes (themes) +- Include before/after examples for functional changes (extensions) - Test across .NET 8, 9, and 10 if possible ## Reporting Bugs @@ -177,20 +268,26 @@ Found a bug? Please open an issue with: - **Steps to reproduce** the issue - **Expected vs. actual behavior** - **Environment details:** - - Which package (Swashbuckle or NSwag) + - Which package (Themes or Extensions) + - Which integration (Swashbuckle or NSwag) - .NET version - Browser (if UI-related) - **Screenshots or code snippets** if applicable +See the bug report template for a structured approach. + ## Suggesting Features We welcome feature suggestions! When proposing a feature: - **Describe the use case** - What problem does it solve? +- **Specify the scope** - Is it for Themes, Extensions, or both? - **Explain the benefit** - How does it improve the project? - **Consider backward compatibility** - Will it break existing code? - **Offer to help** - Can you contribute the implementation? +See the feature request template for a structured approach. + ## Development Tips ### Quick Testing Loop @@ -216,13 +313,6 @@ samples/YourSample/SwaggerThemes/ This demonstrates best practices for users organizing their own themes. -### Standalone vs. Regular Themes - -- **Regular themes** - Depend on `common.css` and may use JavaScript features -- **Standalone themes** - Prefix with `standalone.` (e.g., `standalone.custom.css`) for zero dependencies - -Choose based on whether you need shared styles or prefer total independence. - ## Questions? Not sure about something? Feel free to: diff --git a/README.md b/README.md index aad1d79..85070cd 100644 --- a/README.md +++ b/README.md @@ -29,11 +29,24 @@ _Note: GIF framerate is limited. Actual transitions are buttery smooth!_ --- +## πŸ“¦ Available Packages + +### Theme Packages +Customize the look and feel of your Swagger UI with runtime theme switching and custom theme creation. + | Package | Purpose | NuGet | |---------|---------|-------| | **AspNetCore.SwaggerUI.Themes** | For [Swashbuckle.AspNetCore][swashbuckle-link] | [![swashbuckle-nuget]][swashbuckle-nuget-link] | | **NSwag.AspNetCore.Themes** | For [NSwag.AspNetCore][nswag-link] | [![nswag-nuget]][nswag-nuget-link] | +### Extension Packages +Enhance your Swagger UI with additional functional capabilities beyond theming. + +| Package | Purpose | NuGet | +|---------|---------|-------| +| **AspNetCore.SwaggerUI.Extensions** | Functional enhancements for [Swashbuckle.AspNetCore][swashbuckle-link] | [![swashbuckle-ext-nuget]][swashbuckle-ext-nuget-link] | +| **NSwag.AspNetCore.Extensions** | Functional enhancements for [NSwag.AspNetCore][nswag-link] | [![nswag-ext-nuget]][nswag-ext-nuget-link] | + --- @@ -47,12 +60,19 @@ _Note: GIF framerate is limited. Actual transitions are buttery smooth!_ ## πŸš€ Quick Start ```bash -# Install package +# Install theme package dotnet add package AspNetCore.SwaggerUI.Themes # or dotnet add package NSwag.AspNetCore.Themes + +# Or install extensions package +dotnet add package AspNetCore.SwaggerUI.Extensions +# or +dotnet add package NSwag.AspNetCore.Extensions ``` +### Themes Quick Start + ```csharp // Apply a theme - that's it! app.UseSwaggerUI(Theme.Dark); // Swashbuckle @@ -63,8 +83,26 @@ app.UseSwaggerUi(Theme.Dark); // NSwag app.UseSwaggerUI(Theme.Dark, c => c.EnableThemeSwitcher()); ``` +### Extensions Quick Start + +```csharp +// Add extensions to your configuration +builder.Services.AddSwaggerGen(c => +{ + c.AppendOperationCountToTags(); // Swashbuckle +}); + +// or for NSwag +builder.Services.AddOpenApiDocument(c => +{ + c.AppendOperationCountToTags(); // NSwag +}); +``` + + ## ✨ Features +### Themes - πŸ”₯ **[Theme Switcher](https://github.com/teociaps/SwaggerUI.Themes/wiki/Feature-Dynamic-Theme-Switcher)** - Switch built-in and custom themes dynamically without page reload - **[Built-in Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Predefined-Themes)** - Choose from predefined themes ready to use @@ -73,11 +111,16 @@ app.UseSwaggerUI(Theme.Dark, c => c.EnableThemeSwitcher()); - **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Enhance your documentation with powerful UI capabilities -- _...discover more in the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki/Features)!_ +### Extensions +- **API Counter** - Automatically display operation counts in tag descriptions for better API surface area visibility + +- _More extensions coming soon!_ + +_Discover more features in the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki/Features)!_ ## πŸ“š Basic Usage Examples -### Swashbuckle +### Swashbuckle Themes ```csharp using AspNetCore.Swagger.Themes; @@ -102,7 +145,7 @@ app.UseSwaggerUI(Theme.Dark, c => ... ``` -### NSwag +### NSwag Themes ```csharp using AspNetCore.Swagger.Themes; @@ -127,6 +170,44 @@ app.UseSwaggerUi(Theme.Dark, c => ... ``` +### Swashbuckle Extensions + +```csharp +using AspNetCore.Swagger.Extensions; + +... + +builder.Services.AddSwaggerGen(c => +{ + // Add operation count to tag descriptions + c.AppendOperationCountToTags(); + + // Or with custom template + c.AppendOperationCountToTags(" [{0} endpoints]"); +}); + +... +``` + +### NSwag Extensions + +```csharp +using AspNetCore.Swagger.Extensions; + +... + +builder.Services.AddOpenApiDocument(c => +{ + // Add operation count to tag descriptions + c.AppendOperationCountToTags(); + + // Or with custom template + c.AppendOperationCountToTags(" [{0} endpoints]"); +}); + +... +``` + ### πŸ’‘ Build your own Custom Theme ```csharp @@ -151,7 +232,7 @@ _Learn advanced usages in the [Wiki](https://github.com/teociaps/SwaggerUI.Theme ## 🀝 Contributing -Contributions are welcome! See the [Contributing Guide](CONTRIBUTING.md) for details. +Contributions are welcome! See the [Contributing Guide](CONTRIBUTING.md) for details on contributing themes and extensions. ## πŸ“œ License @@ -168,7 +249,11 @@ MIT Licensed - see [LICENSE](LICENSE) for details. [swashbuckle-nuget]: https://img.shields.io/nuget/v/AspNetCore.SwaggerUI.Themes?logo=nuget&label=Version&color=blue [swashbuckle-nuget-link]: https://www.nuget.org/packages/AspNetCore.SwaggerUI.Themes/ +[swashbuckle-ext-nuget]: https://img.shields.io/nuget/v/AspNetCore.SwaggerUI.Extensions?logo=nuget&label=Version&color=blue +[swashbuckle-ext-nuget-link]: https://www.nuget.org/packages/AspNetCore.SwaggerUI.Extensions/ [nswag-nuget]: https://img.shields.io/nuget/v/NSwag.AspNetCore.Themes?logo=nuget&label=Version&color=blue [nswag-nuget-link]: https://www.nuget.org/packages/NSwag.AspNetCore.Themes/ +[nswag-ext-nuget]: https://img.shields.io/nuget/v/NSwag.AspNetCore.Extensions?logo=nuget&label=Version&color=blue +[nswag-ext-nuget-link]: https://www.nuget.org/packages/NSwag.AspNetCore.Extensions/ [swashbuckle-link]: https://github.com/domaindrivendev/Swashbuckle.AspNetCore [nswag-link]: https://github.com/RicoSuter/NSwag \ No newline at end of file diff --git a/SwaggerUI.Themes.sln b/SwaggerUI.Themes.sln index fc8d08a..c94ef4c 100644 --- a/SwaggerUI.Themes.sln +++ b/SwaggerUI.Themes.sln @@ -1,7 +1,7 @@ ο»Ώ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.0.11205.157 d18.0 +VisualStudioVersion = 18.0.11205.157 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore.SwaggerUI.Themes", "src\AspNetCore.SwaggerUI.Themes\AspNetCore.SwaggerUI.Themes.csproj", "{5FC2CEDA-5475-4096-BDEC-1C9BFC933FB8}" EndProject @@ -29,6 +29,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NSwag.AspNetCore.Themes", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore.Swagger.Themes.Common", "src\AspNetCore.Swagger.Themes.Common\AspNetCore.Swagger.Themes.Common.csproj", "{DCD47D0E-ADD5-4B6A-BDAA-DD6559EB384D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCore.SwaggerUI.Extensions", "src\AspNetCore.SwaggerUI.Extensions\AspNetCore.SwaggerUI.Extensions.csproj", "{8E498417-7FF6-E0E3-9BC1-AB5197733AFB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NSwag.AspNetCore.Extensions", "src\NSwag.AspNetCore.Extensions\NSwag.AspNetCore.Extensions.csproj", "{605058F0-1D5E-C122-1ABD-B73D053665B7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCore.Swagger.Extensions.Tests", "tests\AspNetCore.Swagger.Extensions.Tests\AspNetCore.Swagger.Extensions.Tests.csproj", "{ECD67BCE-D96A-B1D4-48F1-95AF9F6A49E1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.AspNetCore.SwaggerUI.Extensions", "samples\Sample.AspNetCore.SwaggerUI.Extensions\Sample.AspNetCore.SwaggerUI.Extensions.csproj", "{B90ED39A-C413-8F86-152F-7D2EB7AAB349}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.NSwag.AspNetCore.Extensions", "samples\Sample.NSwag.AspNetCore.Extensions\Sample.NSwag.AspNetCore.Extensions.csproj", "{9050F5CA-0F00-25EA-236F-8659C3AD917F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -59,6 +69,26 @@ Global {DCD47D0E-ADD5-4B6A-BDAA-DD6559EB384D}.Debug|Any CPU.Build.0 = Debug|Any CPU {DCD47D0E-ADD5-4B6A-BDAA-DD6559EB384D}.Release|Any CPU.ActiveCfg = Release|Any CPU {DCD47D0E-ADD5-4B6A-BDAA-DD6559EB384D}.Release|Any CPU.Build.0 = Release|Any CPU + {8E498417-7FF6-E0E3-9BC1-AB5197733AFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8E498417-7FF6-E0E3-9BC1-AB5197733AFB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8E498417-7FF6-E0E3-9BC1-AB5197733AFB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8E498417-7FF6-E0E3-9BC1-AB5197733AFB}.Release|Any CPU.Build.0 = Release|Any CPU + {605058F0-1D5E-C122-1ABD-B73D053665B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {605058F0-1D5E-C122-1ABD-B73D053665B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {605058F0-1D5E-C122-1ABD-B73D053665B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {605058F0-1D5E-C122-1ABD-B73D053665B7}.Release|Any CPU.Build.0 = Release|Any CPU + {ECD67BCE-D96A-B1D4-48F1-95AF9F6A49E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ECD67BCE-D96A-B1D4-48F1-95AF9F6A49E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ECD67BCE-D96A-B1D4-48F1-95AF9F6A49E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ECD67BCE-D96A-B1D4-48F1-95AF9F6A49E1}.Release|Any CPU.Build.0 = Release|Any CPU + {B90ED39A-C413-8F86-152F-7D2EB7AAB349}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B90ED39A-C413-8F86-152F-7D2EB7AAB349}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B90ED39A-C413-8F86-152F-7D2EB7AAB349}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B90ED39A-C413-8F86-152F-7D2EB7AAB349}.Release|Any CPU.Build.0 = Release|Any CPU + {9050F5CA-0F00-25EA-236F-8659C3AD917F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9050F5CA-0F00-25EA-236F-8659C3AD917F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9050F5CA-0F00-25EA-236F-8659C3AD917F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9050F5CA-0F00-25EA-236F-8659C3AD917F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -70,6 +100,11 @@ Global {E8C376AC-9933-4ECF-92D8-66BC706FD6D0} = {FF08B180-6923-46E0-8426-99391CFB47CD} {38614F76-C962-4CDC-9C9D-25469B92CDE2} = {19896985-FCB0-4EA0-8E5F-FF3902AC13FC} {DCD47D0E-ADD5-4B6A-BDAA-DD6559EB384D} = {19896985-FCB0-4EA0-8E5F-FF3902AC13FC} + {8E498417-7FF6-E0E3-9BC1-AB5197733AFB} = {19896985-FCB0-4EA0-8E5F-FF3902AC13FC} + {605058F0-1D5E-C122-1ABD-B73D053665B7} = {19896985-FCB0-4EA0-8E5F-FF3902AC13FC} + {ECD67BCE-D96A-B1D4-48F1-95AF9F6A49E1} = {B89F6FD2-68B6-4A70-A070-FF22FE42DDE4} + {B90ED39A-C413-8F86-152F-7D2EB7AAB349} = {FF08B180-6923-46E0-8426-99391CFB47CD} + {9050F5CA-0F00-25EA-236F-8659C3AD917F} = {FF08B180-6923-46E0-8426-99391CFB47CD} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {660A434B-79DE-4C3F-892C-333338978C59} diff --git a/samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/OrdersController.cs b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/OrdersController.cs new file mode 100644 index 0000000..8db5b48 --- /dev/null +++ b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/OrdersController.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Controllers; + +[ApiController] +[Route("api/[controller]")] +[Tags("Orders")] +public class OrdersController : ControllerBase +{ + /// + /// Gets all orders + /// + [HttpGet] + public IActionResult GetAll() + { + return Ok(new[] { new { Id = 1, Total = 150.00 }, new { Id = 2, Total = 200.00 } }); + } + + /// + /// Gets an order by ID + /// + [HttpGet("{id}")] + public IActionResult GetById(int id) + { + return Ok(new { Id = id, Total = 150.00, Status = "Pending" }); + } +} \ No newline at end of file diff --git a/samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/ProductsController.cs b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/ProductsController.cs new file mode 100644 index 0000000..ef7b567 --- /dev/null +++ b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/ProductsController.cs @@ -0,0 +1,36 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Controllers; + +[ApiController] +[Route("api/[controller]")] +[Tags("Products")] +public class ProductsController : ControllerBase +{ + /// + /// Gets all products + /// + [HttpGet] + public IActionResult GetAll() + { + return Ok(new[] { new { Id = 1, Name = "Product A" }, new { Id = 2, Name = "Product B" } }); + } + + /// + /// Gets a product by ID + /// + [HttpGet("{id}")] + public IActionResult GetById(int id) + { + return Ok(new { Id = id, Name = "Product", Price = 99.99 }); + } + + /// + /// Creates a new product + /// + [HttpPost] + public IActionResult Create([FromBody] object product) + { + return CreatedAtAction(nameof(GetById), new { id = 1 }, product); + } +} \ No newline at end of file diff --git a/samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/UsersController.cs b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/UsersController.cs new file mode 100644 index 0000000..68160dd --- /dev/null +++ b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Controllers/UsersController.cs @@ -0,0 +1,54 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Controllers; + +[ApiController] +[Route("api/[controller]")] +[Tags("Users")] +public class UsersController : ControllerBase +{ + /// + /// Gets all users + /// + [HttpGet] + public IActionResult GetAll() + { + return Ok(new[] { new { Id = 1, Name = "John" }, new { Id = 2, Name = "Jane" } }); + } + + /// + /// Gets a user by ID + /// + [HttpGet("{id}")] + public IActionResult GetById(int id) + { + return Ok(new { Id = id, Name = "John Doe" }); + } + + /// + /// Creates a new user + /// + [HttpPost] + public IActionResult Create([FromBody] object user) + { + return CreatedAtAction(nameof(GetById), new { id = 1 }, user); + } + + /// + /// Updates an existing user + /// + [HttpPut("{id}")] + public IActionResult Update(int id, [FromBody] object user) + { + return NoContent(); + } + + /// + /// Deletes a user + /// + [HttpDelete("{id}")] + public IActionResult Delete(int id) + { + return NoContent(); + } +} \ No newline at end of file diff --git a/samples/Sample.AspNetCore.SwaggerUI.Extensions/Program.cs b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Program.cs new file mode 100644 index 0000000..dcaf26b --- /dev/null +++ b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Program.cs @@ -0,0 +1,29 @@ +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddControllers(); + +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(c => +{ + c.SwaggerDoc("v1", new() { Title = "Sample API - Extensions Demo", Version = "v1" }); + + // API Counter Extension + // Automatically appends operation count to tag descriptions + c.AppendOperationCountToTags(); + + // Or with custom template: + // c.AppendOperationCountToTags(" [{0} endpoints]"); + // c.AppendOperationCountToTags(" (APIs: {0})"); +}); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); +app.MapControllers(); + +await app.RunAsync(); \ No newline at end of file diff --git a/samples/Sample.AspNetCore.SwaggerUI.Extensions/Properties/launchSettings.json b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Properties/launchSettings.json new file mode 100644 index 0000000..d0318af --- /dev/null +++ b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Properties/launchSettings.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5100", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7100;http://localhost:5100", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/Sample.AspNetCore.SwaggerUI.Extensions/Sample.AspNetCore.SwaggerUI.Extensions.csproj b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Sample.AspNetCore.SwaggerUI.Extensions.csproj new file mode 100644 index 0000000..2fbe57d --- /dev/null +++ b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Sample.AspNetCore.SwaggerUI.Extensions.csproj @@ -0,0 +1,28 @@ + + + + + + true + false + false + + + + + + + + + + + + + + + + + + + + diff --git a/samples/Sample.AspNetCore.SwaggerUI.Extensions/appsettings.Development.json b/samples/Sample.AspNetCore.SwaggerUI.Extensions/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/samples/Sample.AspNetCore.SwaggerUI.Extensions/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/Sample.AspNetCore.SwaggerUI.Extensions/appsettings.json b/samples/Sample.AspNetCore.SwaggerUI.Extensions/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/samples/Sample.AspNetCore.SwaggerUI.Extensions/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs index 503ef0b..e31f52c 100644 --- a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs +++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs @@ -5,7 +5,15 @@ builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddOpenApiDocument(OpenApiDocGenConfigurer.Configure); +builder.Services.AddOpenApiDocument(c => +{ + OpenApiDocGenConfigurer.Configure(c); + + // πŸ“Š API Counter: Server-side operation count + c.AppendOperationCountToTags(); + // Or with custom template: + // c.AppendOperationCountToTags(" [{0} endpoints]"); +}); var app = builder.Build(); diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs index e9974d9..c85f618 100644 --- a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs +++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs @@ -5,7 +5,15 @@ builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(SwaggerGenConfigurer.Configure); +builder.Services.AddSwaggerGen(c => +{ + SwaggerGenConfigurer.Configure(c); + + // πŸ“Š API Counter: Server-side operation count + c.AppendOperationCountToTags(); + // Or with custom template: + // c.AppendOperationCountToTags(" [{0} endpoints]"); +}); var app = builder.Build(); diff --git a/samples/Sample.NSwag.AspNetCore.Extensions/Controllers/OrdersController.cs b/samples/Sample.NSwag.AspNetCore.Extensions/Controllers/OrdersController.cs new file mode 100644 index 0000000..8db5b48 --- /dev/null +++ b/samples/Sample.NSwag.AspNetCore.Extensions/Controllers/OrdersController.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Controllers; + +[ApiController] +[Route("api/[controller]")] +[Tags("Orders")] +public class OrdersController : ControllerBase +{ + /// + /// Gets all orders + /// + [HttpGet] + public IActionResult GetAll() + { + return Ok(new[] { new { Id = 1, Total = 150.00 }, new { Id = 2, Total = 200.00 } }); + } + + /// + /// Gets an order by ID + /// + [HttpGet("{id}")] + public IActionResult GetById(int id) + { + return Ok(new { Id = id, Total = 150.00, Status = "Pending" }); + } +} \ No newline at end of file diff --git a/samples/Sample.NSwag.AspNetCore.Extensions/Controllers/ProductsController.cs b/samples/Sample.NSwag.AspNetCore.Extensions/Controllers/ProductsController.cs new file mode 100644 index 0000000..ef7b567 --- /dev/null +++ b/samples/Sample.NSwag.AspNetCore.Extensions/Controllers/ProductsController.cs @@ -0,0 +1,36 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Controllers; + +[ApiController] +[Route("api/[controller]")] +[Tags("Products")] +public class ProductsController : ControllerBase +{ + /// + /// Gets all products + /// + [HttpGet] + public IActionResult GetAll() + { + return Ok(new[] { new { Id = 1, Name = "Product A" }, new { Id = 2, Name = "Product B" } }); + } + + /// + /// Gets a product by ID + /// + [HttpGet("{id}")] + public IActionResult GetById(int id) + { + return Ok(new { Id = id, Name = "Product", Price = 99.99 }); + } + + /// + /// Creates a new product + /// + [HttpPost] + public IActionResult Create([FromBody] object product) + { + return CreatedAtAction(nameof(GetById), new { id = 1 }, product); + } +} \ No newline at end of file diff --git a/samples/Sample.NSwag.AspNetCore.Extensions/Controllers/UsersController.cs b/samples/Sample.NSwag.AspNetCore.Extensions/Controllers/UsersController.cs new file mode 100644 index 0000000..68160dd --- /dev/null +++ b/samples/Sample.NSwag.AspNetCore.Extensions/Controllers/UsersController.cs @@ -0,0 +1,54 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Controllers; + +[ApiController] +[Route("api/[controller]")] +[Tags("Users")] +public class UsersController : ControllerBase +{ + /// + /// Gets all users + /// + [HttpGet] + public IActionResult GetAll() + { + return Ok(new[] { new { Id = 1, Name = "John" }, new { Id = 2, Name = "Jane" } }); + } + + /// + /// Gets a user by ID + /// + [HttpGet("{id}")] + public IActionResult GetById(int id) + { + return Ok(new { Id = id, Name = "John Doe" }); + } + + /// + /// Creates a new user + /// + [HttpPost] + public IActionResult Create([FromBody] object user) + { + return CreatedAtAction(nameof(GetById), new { id = 1 }, user); + } + + /// + /// Updates an existing user + /// + [HttpPut("{id}")] + public IActionResult Update(int id, [FromBody] object user) + { + return NoContent(); + } + + /// + /// Deletes a user + /// + [HttpDelete("{id}")] + public IActionResult Delete(int id) + { + return NoContent(); + } +} \ No newline at end of file diff --git a/samples/Sample.NSwag.AspNetCore.Extensions/Program.cs b/samples/Sample.NSwag.AspNetCore.Extensions/Program.cs new file mode 100644 index 0000000..508b5f8 --- /dev/null +++ b/samples/Sample.NSwag.AspNetCore.Extensions/Program.cs @@ -0,0 +1,30 @@ +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddControllers(); + +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddOpenApiDocument(c => +{ + c.Title = "Sample API - Extensions Demo"; + c.Version = "v1"; + + // API Counter Extension + // Automatically appends operation count to tag descriptions + c.AppendOperationCountToTags(); + + // Or with custom template: + // c.AppendOperationCountToTags(" [{0} endpoints]"); + // c.AppendOperationCountToTags(" (APIs: {0})"); +}); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseOpenApi(); + app.UseSwaggerUi(); +} + +app.UseHttpsRedirection(); +app.MapControllers(); + +await app.RunAsync(); \ No newline at end of file diff --git a/samples/Sample.NSwag.AspNetCore.Extensions/Properties/launchSettings.json b/samples/Sample.NSwag.AspNetCore.Extensions/Properties/launchSettings.json new file mode 100644 index 0000000..bc6351d --- /dev/null +++ b/samples/Sample.NSwag.AspNetCore.Extensions/Properties/launchSettings.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5101", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7101;http://localhost:5101", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/Sample.NSwag.AspNetCore.Extensions/Sample.NSwag.AspNetCore.Extensions.csproj b/samples/Sample.NSwag.AspNetCore.Extensions/Sample.NSwag.AspNetCore.Extensions.csproj new file mode 100644 index 0000000..6bf3dd3 --- /dev/null +++ b/samples/Sample.NSwag.AspNetCore.Extensions/Sample.NSwag.AspNetCore.Extensions.csproj @@ -0,0 +1,27 @@ + + + + + + true + false + false + + + + + + + + + + + + + + + + + + + diff --git a/samples/Sample.NSwag.AspNetCore.Extensions/appsettings.Development.json b/samples/Sample.NSwag.AspNetCore.Extensions/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/samples/Sample.NSwag.AspNetCore.Extensions/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/Sample.NSwag.AspNetCore.Extensions/appsettings.json b/samples/Sample.NSwag.AspNetCore.Extensions/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/samples/Sample.NSwag.AspNetCore.Extensions/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj b/src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj new file mode 100644 index 0000000..23f304c --- /dev/null +++ b/src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj @@ -0,0 +1,30 @@ + + + + + + + + 1 + 0 + 0 + + $(MajorVersion).$(MinorVersion).$(PatchVersion)$(VersionSuffix) + + + + AspNetCore.SwaggerUI.Extensions + + Enhance your ASP.NET Core Swagger UI with functional extensions like API counter and more. Extends Swashbuckle.AspNetCore with additional capabilities. + + + swagger;swagger-ui;swaggerui;swashbuckle;aspnetcore;webapi;openapi;extension;api-counter;operation-count;tag-extensions + + true + + + + + + + diff --git a/src/AspNetCore.SwaggerUI.Extensions/AspNetCore/Swagger/Extensions/Filters/AppendOperationCountToTagDescriptionFilter.cs b/src/AspNetCore.SwaggerUI.Extensions/AspNetCore/Swagger/Extensions/Filters/AppendOperationCountToTagDescriptionFilter.cs new file mode 100644 index 0000000..02aa737 --- /dev/null +++ b/src/AspNetCore.SwaggerUI.Extensions/AspNetCore/Swagger/Extensions/Filters/AppendOperationCountToTagDescriptionFilter.cs @@ -0,0 +1,92 @@ +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace AspNetCore.Swagger.Extensions.Filters; + +/// +/// Document filter that appends operation count to tag descriptions in Swagger UI. +/// +/// +/// This filter modifies the OpenAPI document at generation time to include +/// the number of operations (endpoints) in each tag's description. +/// +public sealed class AppendOperationCountToTagDescriptionFilter : IDocumentFilter +{ + private readonly string _messageTemplate; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The message template to use. Must contain {0} placeholder for the count. + /// Default: " (operations: {0})" + /// + /// + /// Thrown when does not contain {0} placeholder. + /// + public AppendOperationCountToTagDescriptionFilter(string messageTemplate = " (operations: {0})") + { + ArgumentException.ThrowIfNullOrWhiteSpace(messageTemplate); + + if (!messageTemplate.Contains("{0}")) + { + throw new ArgumentException("The message template must contain '{0}' placeholder.", nameof(messageTemplate)); + } + + _messageTemplate = messageTemplate; + } + + /// + /// Applies the filter to the OpenAPI document. + /// + /// The OpenAPI document to modify. + /// The document filter context. + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + // Count operations per tag + var tagOperationCount = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var path in swaggerDoc.Paths.Values) + { + foreach (var operation in path.Operations.Values) + { + if (operation?.Tags is null || operation.Tags.Count == 0) + continue; + + foreach (var tag in operation.Tags.Select(t => t.Name)) + { + if (tagOperationCount.TryGetValue(tag, out int value)) + tagOperationCount[tag] = ++value; + else + tagOperationCount[tag] = 1; + } + } + } + + // If no counts found, nothing to do + if (tagOperationCount.Count == 0) + return; + + // Ensure Tags collection exists + swaggerDoc.Tags ??= []; + + // Create tags if they don't exist + foreach (var tagName in tagOperationCount.Keys) + { + if (!swaggerDoc.Tags.Any(t => t.Name.Equals(tagName, StringComparison.OrdinalIgnoreCase))) + { + swaggerDoc.Tags.Add(new OpenApiTag { Name = tagName }); + } + } + + // Append count to tag descriptions + foreach (var tag in swaggerDoc.Tags) + { + if (tagOperationCount.TryGetValue(tag.Name, out var count)) + { + tag.Description ??= string.Empty; + tag.Description += string.Format(_messageTemplate, count); + } + } + } +} \ No newline at end of file diff --git a/src/AspNetCore.SwaggerUI.Extensions/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs b/src/AspNetCore.SwaggerUI.Extensions/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs new file mode 100644 index 0000000..26b41d5 --- /dev/null +++ b/src/AspNetCore.SwaggerUI.Extensions/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs @@ -0,0 +1,35 @@ +using AspNetCore.Swagger.Extensions.Filters; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Extension methods for configuring Swagger generation with operation count feature. +/// +public static class SwaggerGenOptionsExtensions +{ + /// + /// Appends operation count to tag descriptions in Swagger UI. + /// + /// The Swagger generation options. + /// + /// Optional message template. Must contain {0} placeholder for the count. + /// Default: " (operations: {0})" + /// + /// + /// + /// services.AddSwaggerGen(c => + /// { + /// c.AppendOperationCountToTags(); + /// // Or with custom template: + /// c.AppendOperationCountToTags(" [{0} endpoints]"); + /// }); + /// + /// + public static void AppendOperationCountToTags( + this SwaggerGenOptions options, + string messageTemplate = " (operations: {0})") + { + options.DocumentFilter(messageTemplate); + } +} \ No newline at end of file diff --git a/src/AspNetCore.SwaggerUI.Extensions/package-readme.md b/src/AspNetCore.SwaggerUI.Extensions/package-readme.md new file mode 100644 index 0000000..1f0dce6 --- /dev/null +++ b/src/AspNetCore.SwaggerUI.Extensions/package-readme.md @@ -0,0 +1,44 @@ +# AspNetCore.SwaggerUI.Extensions + +Enhance your ASP.NET Core Swagger UI with functional extensions that go beyond theming. + +## Features + +- **API Counter** - Automatically display operation counts in tag descriptions +- More extensions coming soon! + +## Installation + +```bash +dotnet add package AspNetCore.SwaggerUI.Extensions +``` + +## Quick Start + +### API Counter + +Add operation counts to your Swagger tags: + +```csharp +builder.Services.AddSwaggerGen(c => +{ + c.AppendOperationCountToTags(); + // Or with custom template: + c.AppendOperationCountToTags(" [{0} endpoints]"); +}); +``` + +This will append the operation count to each tag's description, helping users understand the API surface area at a glance. + +## Documentation + +For more information and advanced usage, visit the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki). + +## Related Packages + +- **AspNetCore.SwaggerUI.Themes** - Runtime theme switching and customization for Swashbuckle +- **NSwag.AspNetCore.Extensions** - Extensions for NSwag users + +## License + +MIT Licensed - see [LICENSE](https://github.com/teociaps/SwaggerUI.Themes/blob/main/LICENSE) for details. diff --git a/src/AspNetCore.SwaggerUI.Extensions/readme.txt b/src/AspNetCore.SwaggerUI.Extensions/readme.txt new file mode 100644 index 0000000..84661bf --- /dev/null +++ b/src/AspNetCore.SwaggerUI.Extensions/readme.txt @@ -0,0 +1,23 @@ +Thank you for installing AspNetCore.SwaggerUI.Extensions! + +QUICK START +----------- +Add operation counts to your Swagger tags: + + builder.Services.AddSwaggerGen(c => + { + c.AppendOperationCountToTags(); + }); + +DOCUMENTATION +------------- +Full documentation: https://github.com/teociaps/SwaggerUI.Themes/wiki + +RELATED PACKAGES +---------------- +- AspNetCore.SwaggerUI.Themes - For runtime theme switching +- NSwag.AspNetCore.Extensions - For NSwag users + +SUPPORT +------- +Report issues: https://github.com/teociaps/SwaggerUI.Themes/issues diff --git a/src/AspNetCore.SwaggerUI.Themes/AspNetCore/Swagger/Themes/Filters/AppendOperationCountToTagDescriptionFilter.cs b/src/AspNetCore.SwaggerUI.Themes/AspNetCore/Swagger/Themes/Filters/AppendOperationCountToTagDescriptionFilter.cs new file mode 100644 index 0000000..5c730cb --- /dev/null +++ b/src/AspNetCore.SwaggerUI.Themes/AspNetCore/Swagger/Themes/Filters/AppendOperationCountToTagDescriptionFilter.cs @@ -0,0 +1,93 @@ +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace AspNetCore.Swagger.Themes.Filters; + +/// +/// Document filter that appends operation count to tag descriptions in Swagger UI. +/// +/// +/// This filter modifies the OpenAPI document at generation time to include +/// the number of operations (endpoints) in each tag's description. +/// Inspired by: https://github.com/unchase/Unchase.Swashbuckle.AspNetCore.Extensions +/// +public sealed class AppendOperationCountToTagDescriptionFilter : IDocumentFilter +{ + private readonly string _messageTemplate; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The message template to use. Must contain {0} placeholder for the count. + /// Default: " (operations: {0})" + /// + /// + /// Thrown when does not contain {0} placeholder. + /// + public AppendOperationCountToTagDescriptionFilter(string messageTemplate = " (operations: {0})") + { + ArgumentException.ThrowIfNullOrWhiteSpace(messageTemplate); + + if (!messageTemplate.Contains("{0}")) + { + throw new ArgumentException("The message template must contain '{0}' placeholder.", nameof(messageTemplate)); + } + + _messageTemplate = messageTemplate; + } + + /// + /// Applies the filter to the OpenAPI document. + /// + /// The OpenAPI document to modify. + /// The document filter context. + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + // Count operations per tag + var tagOperationCount = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var path in swaggerDoc.Paths.Values) + { + foreach (var operation in path.Operations.Values) + { + if (operation?.Tags is null || operation.Tags.Count == 0) + continue; + + foreach (var tag in operation.Tags) + { + if (tagOperationCount.ContainsKey(tag.Name)) + tagOperationCount[tag.Name]++; + else + tagOperationCount[tag.Name] = 1; + } + } + } + + // If no counts found, nothing to do + if (tagOperationCount.Count == 0) + return; + + // Ensure Tags collection exists + swaggerDoc.Tags ??= new List(); + + // Create tags if they don't exist + foreach (var tagName in tagOperationCount.Keys) + { + if (!swaggerDoc.Tags.Any(t => t.Name.Equals(tagName, StringComparison.OrdinalIgnoreCase))) + { + swaggerDoc.Tags.Add(new OpenApiTag { Name = tagName }); + } + } + + // Append count to tag descriptions + foreach (var tag in swaggerDoc.Tags) + { + if (tagOperationCount.TryGetValue(tag.Name, out var count)) + { + tag.Description ??= string.Empty; + tag.Description += string.Format(_messageTemplate, count); + } + } + } +} diff --git a/src/AspNetCore.SwaggerUI.Themes/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs b/src/AspNetCore.SwaggerUI.Themes/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs new file mode 100644 index 0000000..42b6295 --- /dev/null +++ b/src/AspNetCore.SwaggerUI.Themes/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs @@ -0,0 +1,35 @@ +using AspNetCore.Swagger.Themes.Filters; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Extension methods for configuring Swagger generation with operation count feature. +/// +public static class SwaggerGenOptionsExtensions +{ + /// + /// Appends operation count to tag descriptions in Swagger UI. + /// + /// The Swagger generation options. + /// + /// Optional message template. Must contain {0} placeholder for the count. + /// Default: " (operations: {0})" + /// + /// + /// + /// services.AddSwaggerGen(c => + /// { + /// c.AppendOperationCountToTags(); + /// // Or with custom template: + /// c.AppendOperationCountToTags(" [{0} endpoints]"); + /// }); + /// + /// + public static void AppendOperationCountToTags( + this SwaggerGenOptions options, + string messageTemplate = " (operations: {0})") + { + options.DocumentFilter(messageTemplate); + } +} diff --git a/src/NSwag.AspNetCore.Extensions/AspNetCore/Swagger/Extensions/Processors/AppendOperationCountToTagDescriptionProcessor.cs b/src/NSwag.AspNetCore.Extensions/AspNetCore/Swagger/Extensions/Processors/AppendOperationCountToTagDescriptionProcessor.cs new file mode 100644 index 0000000..f26cc8e --- /dev/null +++ b/src/NSwag.AspNetCore.Extensions/AspNetCore/Swagger/Extensions/Processors/AppendOperationCountToTagDescriptionProcessor.cs @@ -0,0 +1,94 @@ +using NSwag; +using NSwag.Generation.Processors; +using NSwag.Generation.Processors.Contexts; + +namespace AspNetCore.Swagger.Extensions.Processors; + +/// +/// Document processor that appends operation count to tag descriptions in Swagger UI. +/// +/// +/// This processor modifies the OpenAPI document at generation time to include +/// the number of operations (endpoints) in each tag's description. +/// +public sealed class AppendOperationCountToTagDescriptionProcessor : IDocumentProcessor +{ + private readonly string _messageTemplate; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The message template to use. Must contain {0} placeholder for the count. + /// Default: " (operations: {0})" + /// + /// + /// Thrown when does not contain {0} placeholder. + /// + public AppendOperationCountToTagDescriptionProcessor(string messageTemplate = " (operations: {0})") + { + ArgumentException.ThrowIfNullOrWhiteSpace(messageTemplate); + + if (!messageTemplate.Contains("{0}")) + { + throw new ArgumentException("The message template must contain '{0}' placeholder.", nameof(messageTemplate)); + } + + _messageTemplate = messageTemplate; + } + + /// + /// Processes the OpenAPI document. + /// + /// The document processor context. + public void Process(DocumentProcessorContext context) + { + var document = context.Document; + + // Count operations per tag + var tagOperationCount = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var path in document.Paths.Values) + { + foreach (var operation in path.Values) + { + if (operation?.Tags is null || operation.Tags.Count == 0) + continue; + + foreach (var tag in operation.Tags) + { + if (tagOperationCount.TryGetValue(tag, out int value)) + tagOperationCount[tag] = ++value; + else + tagOperationCount[tag] = 1; + } + } + } + + // If no counts found, nothing to do + if (tagOperationCount.Count == 0) + return; + + // Ensure Tags collection exists + document.Tags ??= new List(); + + // Create tags if they don't exist + foreach (var tagName in tagOperationCount.Keys) + { + if (!document.Tags.Any(t => t.Name.Equals(tagName, StringComparison.OrdinalIgnoreCase))) + { + document.Tags.Add(new NSwag.OpenApiTag { Name = tagName }); + } + } + + // Append count to tag descriptions + foreach (var tag in document.Tags) + { + if (tagOperationCount.TryGetValue(tag.Name, out var count)) + { + tag.Description ??= string.Empty; + tag.Description += string.Format(_messageTemplate, count); + } + } + } +} \ No newline at end of file diff --git a/src/NSwag.AspNetCore.Extensions/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs b/src/NSwag.AspNetCore.Extensions/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs new file mode 100644 index 0000000..b299410 --- /dev/null +++ b/src/NSwag.AspNetCore.Extensions/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs @@ -0,0 +1,36 @@ +using AspNetCore.Swagger.Extensions.Processors; +using NSwag.Generation.AspNetCore; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Extension methods for configuring OpenAPI document generation with operation count feature. +/// +public static class AspNetCoreOpenApiDocumentGeneratorSettingsExtensions +{ + /// + /// Appends operation count to tag descriptions in Swagger UI. + /// + /// The OpenAPI document generator settings. + /// + /// Optional message template. Must contain {0} placeholder for the count. + /// Default: " (operations: {0})" + /// + /// + /// + /// services.AddOpenApiDocument(c => + /// { + /// c.AppendOperationCountToTags(); + /// // Or with custom template: + /// c.AppendOperationCountToTags(" [{0} endpoints]"); + /// }); + /// + /// + public static void AppendOperationCountToTags( + this AspNetCoreOpenApiDocumentGeneratorSettings settings, + string messageTemplate = " (operations: {0})") + { + settings.DocumentProcessors.Add( + new AppendOperationCountToTagDescriptionProcessor(messageTemplate)); + } +} diff --git a/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj b/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj new file mode 100644 index 0000000..e64715c --- /dev/null +++ b/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj @@ -0,0 +1,30 @@ + + + + + + + + 1 + 0 + 0 + + $(MajorVersion).$(MinorVersion).$(PatchVersion)$(VersionSuffix) + + + + NSwag.AspNetCore.Extensions + + Enhance your ASP.NET Core Swagger UI with functional extensions like API counter and more. Extends NSwag.AspNetCore with additional capabilities. + + + swagger;swagger-ui;swaggerui;nswag;aspnetcore;webapi;openapi;extension;api-counter;operation-count;tag-extensions + + true + + + + + + + diff --git a/src/NSwag.AspNetCore.Extensions/package-readme.md b/src/NSwag.AspNetCore.Extensions/package-readme.md new file mode 100644 index 0000000..8161fe1 --- /dev/null +++ b/src/NSwag.AspNetCore.Extensions/package-readme.md @@ -0,0 +1,44 @@ +# NSwag.AspNetCore.Extensions + +Enhance your ASP.NET Core Swagger UI with functional extensions that go beyond theming. + +## Features + +- **API Counter** - Automatically display operation counts in tag descriptions +- More extensions coming soon! + +## Installation + +```bash +dotnet add package NSwag.AspNetCore.Extensions +``` + +## Quick Start + +### API Counter + +Add operation counts to your Swagger tags: + +```csharp +builder.Services.AddOpenApiDocument(c => +{ + c.AppendOperationCountToTags(); + // Or with custom template: + c.AppendOperationCountToTags(" [{0} endpoints]"); +}); +``` + +This will append the operation count to each tag's description, helping users understand the API surface area at a glance. + +## Documentation + +For more information and advanced usage, visit the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki). + +## Related Packages + +- **NSwag.AspNetCore.Themes** - Runtime theme switching and customization for NSwag +- **AspNetCore.SwaggerUI.Extensions** - Extensions for Swashbuckle users + +## License + +MIT Licensed - see [LICENSE](https://github.com/teociaps/SwaggerUI.Themes/blob/main/LICENSE) for details. diff --git a/src/NSwag.AspNetCore.Extensions/readme.txt b/src/NSwag.AspNetCore.Extensions/readme.txt new file mode 100644 index 0000000..2c922f6 --- /dev/null +++ b/src/NSwag.AspNetCore.Extensions/readme.txt @@ -0,0 +1,23 @@ +Thank you for installing NSwag.AspNetCore.Extensions! + +QUICK START +----------- +Add operation counts to your Swagger tags: + + builder.Services.AddOpenApiDocument(c => + { + c.AppendOperationCountToTags(); + }); + +DOCUMENTATION +------------- +Full documentation: https://github.com/teociaps/SwaggerUI.Themes/wiki + +RELATED PACKAGES +---------------- +- NSwag.AspNetCore.Themes - For runtime theme switching +- AspNetCore.SwaggerUI.Extensions - For Swashbuckle users + +SUPPORT +------- +Report issues: https://github.com/teociaps/SwaggerUI.Themes/issues diff --git a/src/NSwag.AspNetCore.Themes/AspNetCore/Swagger/Themes/Processors/AppendOperationCountToTagDescriptionProcessor.cs b/src/NSwag.AspNetCore.Themes/AspNetCore/Swagger/Themes/Processors/AppendOperationCountToTagDescriptionProcessor.cs new file mode 100644 index 0000000..e180725 --- /dev/null +++ b/src/NSwag.AspNetCore.Themes/AspNetCore/Swagger/Themes/Processors/AppendOperationCountToTagDescriptionProcessor.cs @@ -0,0 +1,95 @@ +using NSwag; +using NSwag.Generation.Processors; +using NSwag.Generation.Processors.Contexts; + +namespace AspNetCore.Swagger.Themes.Processors; + +/// +/// Document processor that appends operation count to tag descriptions in Swagger UI. +/// +/// +/// This processor modifies the OpenAPI document at generation time to include +/// the number of operations (endpoints) in each tag's description. +/// Inspired by: https://github.com/unchase/Unchase.Swashbuckle.AspNetCore.Extensions +/// +public sealed class AppendOperationCountToTagDescriptionProcessor : IDocumentProcessor +{ + private readonly string _messageTemplate; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The message template to use. Must contain {0} placeholder for the count. + /// Default: " (operations: {0})" + /// + /// + /// Thrown when does not contain {0} placeholder. + /// + public AppendOperationCountToTagDescriptionProcessor(string messageTemplate = " (operations: {0})") + { + ArgumentException.ThrowIfNullOrWhiteSpace(messageTemplate); + + if (!messageTemplate.Contains("{0}")) + { + throw new ArgumentException("The message template must contain '{0}' placeholder.", nameof(messageTemplate)); + } + + _messageTemplate = messageTemplate; + } + + /// + /// Processes the OpenAPI document. + /// + /// The document processor context. + public void Process(DocumentProcessorContext context) + { + var document = context.Document; + + // Count operations per tag + var tagOperationCount = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var path in document.Paths.Values) + { + foreach (var operation in path.Values) + { + if (operation?.Tags is null || operation.Tags.Count == 0) + continue; + + foreach (var tag in operation.Tags) + { + if (tagOperationCount.ContainsKey(tag)) + tagOperationCount[tag]++; + else + tagOperationCount[tag] = 1; + } + } + } + + // If no counts found, nothing to do + if (tagOperationCount.Count == 0) + return; + + // Ensure Tags collection exists + document.Tags ??= new List(); + + // Create tags if they don't exist + foreach (var tagName in tagOperationCount.Keys) + { + if (!document.Tags.Any(t => t.Name.Equals(tagName, StringComparison.OrdinalIgnoreCase))) + { + document.Tags.Add(new NSwag.OpenApiTag { Name = tagName }); + } + } + + // Append count to tag descriptions + foreach (var tag in document.Tags) + { + if (tagOperationCount.TryGetValue(tag.Name, out var count)) + { + tag.Description ??= string.Empty; + tag.Description += string.Format(_messageTemplate, count); + } + } + } +} diff --git a/src/NSwag.AspNetCore.Themes/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs b/src/NSwag.AspNetCore.Themes/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs new file mode 100644 index 0000000..7142869 --- /dev/null +++ b/src/NSwag.AspNetCore.Themes/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs @@ -0,0 +1,36 @@ +using AspNetCore.Swagger.Themes.Processors; +using NSwag.Generation.AspNetCore; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Extension methods for configuring OpenAPI document generation with operation count feature. +/// +public static class AspNetCoreOpenApiDocumentGeneratorSettingsExtensions +{ + /// + /// Appends operation count to tag descriptions in Swagger UI. + /// + /// The OpenAPI document generator settings. + /// + /// Optional message template. Must contain {0} placeholder for the count. + /// Default: " (operations: {0})" + /// + /// + /// + /// services.AddOpenApiDocument(c => + /// { + /// c.AppendOperationCountToTags(); + /// // Or with custom template: + /// c.AppendOperationCountToTags(" [{0} endpoints]"); + /// }); + /// + /// + public static void AppendOperationCountToTags( + this AspNetCoreOpenApiDocumentGeneratorSettings settings, + string messageTemplate = " (operations: {0})") + { + settings.DocumentProcessors.Add( + new AppendOperationCountToTagDescriptionProcessor(messageTemplate)); + } +} diff --git a/tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionFilterTests.cs b/tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionFilterTests.cs new file mode 100644 index 0000000..5758846 --- /dev/null +++ b/tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionFilterTests.cs @@ -0,0 +1,190 @@ +using AspNetCore.Swagger.Extensions.Filters; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.OpenApi.Models; +using Shouldly; +using Swashbuckle.AspNetCore.SwaggerGen; +using System.Text.Json; +using Xunit; + +namespace AspNetCore.Swagger.Extensions.Tests; + +public class AppendOperationCountToTagDescriptionFilterTests +{ + [Fact] + public void Constructor_WithValidTemplate_Succeeds() + { + // Act + var filter = new AppendOperationCountToTagDescriptionFilter(" (operations: {0})"); + + // Assert + filter.ShouldNotBeNull(); + } + + [Fact] + public void Constructor_WithNullTemplate_ThrowsArgumentException() + { + // Act & Assert + Should.Throw(() => + new AppendOperationCountToTagDescriptionFilter(null!)); + } + + [Fact] + public void Constructor_WithEmptyTemplate_ThrowsArgumentException() + { + // Act & Assert + Should.Throw(() => + new AppendOperationCountToTagDescriptionFilter("")); + } + + [Fact] + public void Constructor_WithoutPlaceholder_ThrowsArgumentException() + { + // Act & Assert + var exception = Should.Throw(() => + new AppendOperationCountToTagDescriptionFilter(" operations")); + exception.Message.ShouldContain("{0}"); + } + + [Fact] + public void Apply_WithEmptyDocument_DoesNothing() + { + // Arrange + var filter = new AppendOperationCountToTagDescriptionFilter(); + var document = new OpenApiDocument + { + Paths = [] + }; + var context = CreateContext(); + + // Act + filter.Apply(document, context); + + // Assert + document.Tags.ShouldBeEmpty(); + } + + [Fact] + public void Apply_WithOperations_AppendsCount() + { + // Arrange + var filter = new AppendOperationCountToTagDescriptionFilter(); + var document = CreateDocumentWithTags(); + var context = CreateContext(); + + // Act + filter.Apply(document, context); + + // Assert + document.Tags.ShouldNotBeEmpty(); + var userTag = document.Tags.FirstOrDefault(t => t.Name == "Users"); + userTag.ShouldNotBeNull(); + userTag.Description.ShouldContain("(operations: 2)"); + } + + [Fact] + public void Apply_WithCustomTemplate_UsesTemplate() + { + // Arrange + var filter = new AppendOperationCountToTagDescriptionFilter(" [{0} endpoints]"); + var document = CreateDocumentWithTags(); + var context = CreateContext(); + + // Act + filter.Apply(document, context); + + // Assert + var userTag = document.Tags.FirstOrDefault(t => t.Name == "Users"); + userTag.ShouldNotBeNull(); + userTag.Description.ShouldContain("[2 endpoints]"); + } + + [Fact] + public void Apply_WithExistingDescription_AppendsCount() + { + // Arrange + var filter = new AppendOperationCountToTagDescriptionFilter(); + var document = CreateDocumentWithTags(); + document.Tags = new List + { + new() { Name = "Users", Description = "User management" } + }; + var context = CreateContext(); + + // Act + filter.Apply(document, context); + + // Assert + var userTag = document.Tags.FirstOrDefault(t => t.Name == "Users"); + userTag.ShouldNotBeNull(); + userTag.Description.ShouldBe("User management (operations: 2)"); + } + + [Fact] + public void Apply_WithMultipleTags_CountsCorrectly() + { + // Arrange + var filter = new AppendOperationCountToTagDescriptionFilter(); + var document = new OpenApiDocument + { + Paths = new OpenApiPaths + { + ["/users"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [OperationType.Get] = new() { Tags = new List { new() { Name = "Users" } } }, + [OperationType.Post] = new() { Tags = new List { new() { Name = "Users" } } } + } + }, + ["/products"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [OperationType.Get] = new() { Tags = new List { new() { Name = "Products" } } } + } + } + } + }; + var context = CreateContext(); + + // Act + filter.Apply(document, context); + + // Assert + document.Tags.Count.ShouldBe(2); + document.Tags.First(t => t.Name == "Users").Description.ShouldContain("(operations: 2)"); + document.Tags.First(t => t.Name == "Products").Description.ShouldContain("(operations: 1)"); + } + + private static DocumentFilterContext CreateContext() + { + return new DocumentFilterContext( + new List(), + new SchemaGenerator(new SchemaGeneratorOptions(), new JsonSerializerDataContractResolver(new JsonSerializerOptions())), + new SchemaRepository()); + } + + private static OpenApiDocument CreateDocumentWithTags() + { + return new OpenApiDocument + { + Paths = new OpenApiPaths + { + ["/users"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [OperationType.Get] = new() + { + Tags = new List { new() { Name = "Users" } } + }, + [OperationType.Post] = new() + { + Tags = new List { new() { Name = "Users" } } + } + } + } + } + }; + } +} \ No newline at end of file diff --git a/tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionProcessorTests.cs b/tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionProcessorTests.cs new file mode 100644 index 0000000..3f4b3d5 --- /dev/null +++ b/tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionProcessorTests.cs @@ -0,0 +1,182 @@ +using AspNetCore.Swagger.Extensions.Processors; +using NSwag; +using NSwag.Generation.Processors.Contexts; +using Shouldly; +using Xunit; + +namespace AspNetCore.Swagger.Extensions.Tests; + +public class AppendOperationCountToTagDescriptionProcessorTests +{ + [Fact] + public void Constructor_WithValidTemplate_Succeeds() + { + // Act + var processor = new AppendOperationCountToTagDescriptionProcessor(" (operations: {0})"); + + // Assert + processor.ShouldNotBeNull(); + } + + [Fact] + public void Constructor_WithNullTemplate_ThrowsArgumentException() + { + // Act & Assert + Should.Throw(() => + new AppendOperationCountToTagDescriptionProcessor(null!)); + } + + [Fact] + public void Constructor_WithEmptyTemplate_ThrowsArgumentException() + { + // Act & Assert + Should.Throw(() => + new AppendOperationCountToTagDescriptionProcessor("")); + } + + [Fact] + public void Constructor_WithoutPlaceholder_ThrowsArgumentException() + { + // Act & Assert + var exception = Should.Throw(() => + new AppendOperationCountToTagDescriptionProcessor(" operations")); + exception.Message.ShouldContain("{0}"); + } + + [Fact] + public void Process_WithEmptyDocument_DoesNothing() + { + // Arrange + var processor = new AppendOperationCountToTagDescriptionProcessor(); + var document = new OpenApiDocument + { + Tags = [] + }; + var context = CreateContext(document); + + // Act + processor.Process(context); + + // Assert + document.Tags.ShouldBeEmpty(); + } + + [Fact] + public void Process_WithOperations_AppendsCount() + { + // Arrange + var processor = new AppendOperationCountToTagDescriptionProcessor(); + var document = CreateDocumentWithTags(); + var context = CreateContext(document); + + // Act + processor.Process(context); + + // Assert + document.Tags.ShouldNotBeEmpty(); + var userTag = document.Tags.FirstOrDefault(t => t.Name == "Users"); + userTag.ShouldNotBeNull(); + userTag.Description.ShouldContain("(operations: 2)"); + } + + [Fact] + public void Process_WithCustomTemplate_UsesTemplate() + { + // Arrange + var processor = new AppendOperationCountToTagDescriptionProcessor(" [{0} endpoints]"); + var document = CreateDocumentWithTags(); + var context = CreateContext(document); + + // Act + processor.Process(context); + + // Assert + var userTag = document.Tags.FirstOrDefault(t => t.Name == "Users"); + userTag.ShouldNotBeNull(); + userTag.Description.ShouldContain("[2 endpoints]"); + } + + [Fact] + public void Process_WithExistingDescription_AppendsCount() + { + // Arrange + var processor = new AppendOperationCountToTagDescriptionProcessor(); + var document = CreateDocumentWithTags(); + document.Tags = new List + { + new() { Name = "Users", Description = "User management" } + }; + var context = CreateContext(document); + + // Act + processor.Process(context); + + // Assert + var userTag = document.Tags.FirstOrDefault(t => t.Name == "Users"); + userTag.ShouldNotBeNull(); + userTag.Description.ShouldBe("User management (operations: 2)"); + } + + [Fact] + public void Process_WithMultipleTags_CountsCorrectly() + { + // Arrange + var processor = new AppendOperationCountToTagDescriptionProcessor(); + var document = new OpenApiDocument(); + document.Paths.Add("/users", new OpenApiPathItem + { + { + OpenApiOperationMethod.Get, + new OpenApiOperation { Tags = new List { "Users" } } + }, + { + OpenApiOperationMethod.Post, + new OpenApiOperation { Tags = new List { "Users" } } + } + }); + document.Paths.Add("/products", new OpenApiPathItem + { + { + OpenApiOperationMethod.Get, + new OpenApiOperation { Tags = new List { "Products" } } + } + }); + var context = CreateContext(document); + + // Act + processor.Process(context); + + // Assert + document.Tags.Count.ShouldBe(2); + document.Tags.First(t => t.Name == "Users").Description.ShouldContain("(operations: 2)"); + document.Tags.First(t => t.Name == "Products").Description.ShouldContain("(operations: 1)"); + } + + private static DocumentProcessorContext CreateContext(OpenApiDocument document) + { + return new DocumentProcessorContext( + document, + new List(), + new List(), + null!, + null!, + null!); + } + + private static OpenApiDocument CreateDocumentWithTags() + { + var document = new OpenApiDocument(); + document.Paths.Add("/users", new OpenApiPathItem + { + { + OpenApiOperationMethod.Get, + new OpenApiOperation { Tags = new List { "Users" } } + }, + { + OpenApiOperationMethod.Post, + new OpenApiOperation { Tags = new List { "Users" } } + } + }); + return document; + } +} \ No newline at end of file diff --git a/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCore.Swagger.Extensions.Tests.csproj b/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCore.Swagger.Extensions.Tests.csproj new file mode 100644 index 0000000..f16b453 --- /dev/null +++ b/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCore.Swagger.Extensions.Tests.csproj @@ -0,0 +1,40 @@ +ο»Ώ + + + + + false + true + $(MSBuildProjectName.Replace(" ", "_")) + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + diff --git a/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCoreOpenApiDocumentGeneratorSettingsExtensionsTests.cs b/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCoreOpenApiDocumentGeneratorSettingsExtensionsTests.cs new file mode 100644 index 0000000..6fdd55a --- /dev/null +++ b/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCoreOpenApiDocumentGeneratorSettingsExtensionsTests.cs @@ -0,0 +1,42 @@ +using AspNetCore.Swagger.Extensions.Processors; +using Microsoft.Extensions.DependencyInjection; +using NSwag.Generation.AspNetCore; +using Shouldly; +using Xunit; + +namespace AspNetCore.Swagger.Extensions.Tests; + +public class AspNetCoreOpenApiDocumentGeneratorSettingsExtensionsTests +{ + [Fact] + public void AppendOperationCountToTags_AddsProcessor() + { + // Arrange + var settings = new AspNetCoreOpenApiDocumentGeneratorSettings(); + + // Act + settings.AppendOperationCountToTags(); + + // Assert + settings.DocumentProcessors.ShouldNotBeEmpty(); + settings.DocumentProcessors.ShouldContain(p => + p.GetType() == typeof(AppendOperationCountToTagDescriptionProcessor)); + } + + [Fact] + public void AppendOperationCountToTags_WithCustomTemplate_AddsProcessorWithTemplate() + { + // Arrange + var settings = new AspNetCoreOpenApiDocumentGeneratorSettings(); + var customTemplate = " [{0} endpoints]"; + + // Act + settings.AppendOperationCountToTags(customTemplate); + + // Assert + settings.DocumentProcessors.ShouldNotBeEmpty(); + var processor = settings.DocumentProcessors + .FirstOrDefault(p => p.GetType() == typeof(AppendOperationCountToTagDescriptionProcessor)); + processor.ShouldNotBeNull(); + } +} \ No newline at end of file diff --git a/tests/AspNetCore.Swagger.Extensions.Tests/SwaggerGenOptionsExtensionsTests.cs b/tests/AspNetCore.Swagger.Extensions.Tests/SwaggerGenOptionsExtensionsTests.cs new file mode 100644 index 0000000..610b7e6 --- /dev/null +++ b/tests/AspNetCore.Swagger.Extensions.Tests/SwaggerGenOptionsExtensionsTests.cs @@ -0,0 +1,43 @@ +using AspNetCore.Swagger.Extensions.Filters; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Swashbuckle.AspNetCore.SwaggerGen; +using Xunit; + +namespace AspNetCore.Swagger.Extensions.Tests; + +public class SwaggerGenOptionsExtensionsTests +{ + [Fact] + public void AppendOperationCountToTags_AddsFilter() + { + // Arrange + var options = new SwaggerGenOptions(); + + // Act + options.AppendOperationCountToTags(); + + // Assert + options.DocumentFilterDescriptors.ShouldNotBeEmpty(); + options.DocumentFilterDescriptors.ShouldContain(d => + d.Type == typeof(AppendOperationCountToTagDescriptionFilter)); + } + + [Fact] + public void AppendOperationCountToTags_WithCustomTemplate_AddsFilterWithTemplate() + { + // Arrange + var options = new SwaggerGenOptions(); + var customTemplate = " [{0} endpoints]"; + + // Act + options.AppendOperationCountToTags(customTemplate); + + // Assert + options.DocumentFilterDescriptors.ShouldNotBeEmpty(); + var descriptor = options.DocumentFilterDescriptors + .FirstOrDefault(d => d.Type == typeof(AppendOperationCountToTagDescriptionFilter)); + descriptor.ShouldNotBeNull(); + descriptor.Arguments.ShouldContain(customTemplate); + } +} \ No newline at end of file From b51f7a3debb6e1f415cb70cb5cee0a4168f563cb Mon Sep 17 00:00:00 2001 From: Teuz Date: Mon, 10 Aug 2026 22:36:39 +0200 Subject: [PATCH 03/35] fix(core): make theme and endpoint registries thread-safe ThemeSwitcher.s_registeredThemes/s_scannedTypes and FileProvider.s_registeredEndpoints are process-wide static fields mutated during UseSwaggerUI() setup without synchronization, so concurrent host startups in the same process (e.g. parallel test collections spinning up WebApplicationFactory instances) could corrupt state or throw. Switch them to ConcurrentDictionary-backed storage and replace every ContainsKey-then-indexer or HashSet.Add check-then-act with atomic TryAdd. --- .../AspNetCore/Swagger/Themes/FileProvider.cs | 9 +++--- .../Swagger/Themes/ThemeSwitcher.cs | 21 ++++++-------- .../FileProviderMiddlewareTests.cs | 27 ++++++++++++++++++ .../ThemeSwitcherTests.cs | 28 +++++++++++++++++++ 4 files changed, 68 insertions(+), 17 deletions(-) diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs index 9f5915c..b7adb32 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs @@ -1,5 +1,6 @@ ο»Ώusing Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +using System.Collections.Concurrent; using System.Collections.Frozen; using System.Reflection; using System.Text.Json; @@ -22,7 +23,7 @@ internal static class FileProvider internal const string JsFilename = "ui.min.js"; // Track registered endpoints to prevent duplicates - private static readonly HashSet s_registeredEndpoints = new(StringComparer.OrdinalIgnoreCase); + private static readonly ConcurrentDictionary s_registeredEndpoints = new(StringComparer.OrdinalIgnoreCase); private static FrozenSet s_frozenEndpoints; @@ -115,7 +116,7 @@ internal static string GetResourceText(string fileName, Assembly assembly, out s /// internal static void AddGetEndpoint(IApplicationBuilder app, string path, string content, string contentType = MimeTypes.Text.Css) { - if (!s_registeredEndpoints.Add(path)) + if (!s_registeredEndpoints.TryAdd(path, 0)) return; if (app is WebApplication webApp) @@ -161,7 +162,7 @@ internal static void ExposeThemeMetadata( string currentThemeName, string displayFormat) { - if (s_registeredEndpoints.Contains(ThemeMetadataPath)) + if (s_registeredEndpoints.ContainsKey(ThemeMetadataPath)) return; var themeList = themes.Select(rt => new @@ -199,7 +200,7 @@ internal static void ExposeThemeMetadata( /// Freezes collections after startup for better read performance. /// internal static void FreezeCollections() => - s_frozenEndpoints ??= s_registeredEndpoints.ToFrozenSet(StringComparer.OrdinalIgnoreCase); + s_frozenEndpoints ??= s_registeredEndpoints.Keys.ToFrozenSet(StringComparer.OrdinalIgnoreCase); /// /// Checks if a theme name is predefined. diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeSwitcher.cs b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeSwitcher.cs index 8bfa66c..cd5528c 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeSwitcher.cs +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeSwitcher.cs @@ -1,4 +1,5 @@ ο»Ώusing Microsoft.AspNetCore.Builder; +using System.Collections.Concurrent; using System.Collections.Frozen; using System.Reflection; @@ -15,9 +16,9 @@ internal static class ThemeSwitcher Theme.Dark, Theme.Light, Theme.Forest, Theme.DeepSea, Theme.Desert, Theme.Futuristic }.ToFrozenSet(); - private static readonly Dictionary s_registeredThemes = new(StringComparer.OrdinalIgnoreCase); + private static readonly ConcurrentDictionary s_registeredThemes = new(StringComparer.OrdinalIgnoreCase); private static FrozenDictionary s_frozenThemes; - private static readonly HashSet s_scannedTypes = []; + private static readonly ConcurrentDictionary s_scannedTypes = new(); /// /// Configures theme switcher for a given theme and options. @@ -64,14 +65,11 @@ internal static void RegisterTheme(BaseTheme theme, string cssPath, bool isStand { var themeName = theme.GetThemeName(); - if (s_registeredThemes.ContainsKey(themeName)) - return; - - s_registeredThemes[themeName] = new RegisteredTheme( + s_registeredThemes.TryAdd(themeName, new RegisteredTheme( theme, FileProvider.StylesPath + cssPath, isStandalone - ); + )); } /// @@ -82,14 +80,11 @@ internal static void RegisterCustomTheme(string cssFilename, bool isStandalone) var themeName = ExtractThemeName(cssFilename); var cssPath = FileProvider.StylesPath + cssFilename; - if (s_registeredThemes.ContainsKey(themeName)) - return; - - s_registeredThemes[themeName] = new RegisteredTheme( + s_registeredThemes.TryAdd(themeName, new RegisteredTheme( null, cssPath, isStandalone - ); + )); } /// @@ -188,7 +183,7 @@ private static void ScanAssemblyForThemes(Assembly assembly) /// private static void ScanTypeForThemes(Type type) { - if (!s_scannedTypes.Add(type)) + if (!s_scannedTypes.TryAdd(type, 0)) return; var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Static) diff --git a/tests/AspNetCore.SwaggerUI.Themes.Tests/FileProviderMiddlewareTests.cs b/tests/AspNetCore.SwaggerUI.Themes.Tests/FileProviderMiddlewareTests.cs index 89f1aac..09c1c6c 100644 --- a/tests/AspNetCore.SwaggerUI.Themes.Tests/FileProviderMiddlewareTests.cs +++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/FileProviderMiddlewareTests.cs @@ -114,4 +114,31 @@ public async Task AddGetEndpoint_ShouldHandleCustomContentType() var responseBody = await reader.ReadToEndAsync(); responseBody.ShouldBe(content); } + + [Fact] + public async Task AddGetEndpoint_WhenCalledConcurrently_RegistersEveryEndpointExactlyOnce() + { + // Arrange + const int EndpointCount = 50; + var prefix = $"/test-middleware-concurrent-{Guid.NewGuid():N}"; + var registrations = Enumerable.Range(0, EndpointCount) + .Select(i => (Path: $"{prefix}/{i}.css", AppBuilder: new MockApplicationBuilder())) + .ToList(); + + // Act - each registration uses its own builder so the middleware pipeline itself + // isn't mutated concurrently; only the shared static endpoint registry is stressed. + await Should.NotThrowAsync(() => Task.WhenAll( + registrations.Select(r => Task.Run(() => + AddGetEndpoint(r.AppBuilder, r.Path, $"body {{ /* {r.Path} */ }}"))))); + + // Assert + foreach (var (path, appBuilder) in registrations) + { + var app = appBuilder.Build(); + var context = MockApplicationBuilder.CreateHttpContext(path); + await app.Invoke(context); + + context.Response.StatusCode.ShouldBe(200); + } + } } \ No newline at end of file diff --git a/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeSwitcherTests.cs b/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeSwitcherTests.cs index 2832b76..f6319b2 100644 --- a/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeSwitcherTests.cs +++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeSwitcherTests.cs @@ -484,5 +484,33 @@ public void Scenario_CustomThemeModes_WorkCorrectly() #endregion Integration Scenarios + #region Concurrency Tests + + [Fact] + public async Task RegisterTheme_WhenCalledConcurrently_RegistersEveryThemeExactlyOnce() + { + // Arrange + const int ThemeCount = 50; + var prefix = $"concurrent-{Guid.NewGuid():N}-"; + var themes = Enumerable.Range(0, ThemeCount) + .Select(i => new TestCustomTheme($"{prefix}{i}.css")) + .ToList(); + + // Act + await Should.NotThrowAsync(() => Task.WhenAll( + themes.Select(theme => Task.Run(() => ThemeSwitcher.RegisterTheme(theme, theme.FileName, isStandalone: false))))); + + // Assert + var registeredNames = ThemeSwitcher.GetFilteredThemes(ThemeSwitcherOptions.CustomOnly()) + .Select(rt => rt.Name) + .Where(name => name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + registeredNames.Count.ShouldBe(ThemeCount); + registeredNames.Distinct(StringComparer.OrdinalIgnoreCase).Count().ShouldBe(ThemeCount); + } + + #endregion Concurrency Tests + private class TestCustomTheme(string fileName) : Theme(fileName); } \ No newline at end of file From 3270f5f665c818a5f3767200485ddd752e86c414 Mon Sep 17 00:00:00 2001 From: Teuz Date: Mon, 10 Aug 2026 22:49:27 +0200 Subject: [PATCH 04/35] perf(core): replace per-asset middleware chain with single dispatch table AddGetEndpoint used to register one MapGet (WebApplication) or one app.Use middleware (classic pipelines) per theme asset, so every unrelated request walked an ever-growing chain as themes were added. Now a single middleware is registered once per app instance and does an O(1) lookup against a shared dispatch table, for both WebApplication and IApplicationBuilder hosts alike. --- .../AspNetCore/Swagger/Themes/FileProvider.cs | 36 ++++----- .../FileProviderMiddlewareTests.cs | 80 ++++++++++++++++++- 2 files changed, 96 insertions(+), 20 deletions(-) diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs index b7adb32..b0c8d31 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Collections.Frozen; using System.Reflection; +using System.Runtime.CompilerServices; using System.Text.Json; namespace AspNetCore.Swagger.Themes; @@ -22,8 +23,11 @@ internal static class FileProvider internal const string ThemeMetadataPath = "/themes/metadata.json"; internal const string JsFilename = "ui.min.js"; - // Track registered endpoints to prevent duplicates - private static readonly ConcurrentDictionary s_registeredEndpoints = new(StringComparer.OrdinalIgnoreCase); + // Track registered endpoints to prevent duplicates and act as the shared dispatch table for the single middleware + private static readonly ConcurrentDictionary s_registeredEndpoints = new(StringComparer.OrdinalIgnoreCase); + + // Tracks which IApplicationBuilder instances already have the dispatch middleware registered + private static readonly ConditionalWeakTable s_dispatchRegisteredApps = new(); private static FrozenSet s_frozenEndpoints; @@ -116,35 +120,29 @@ internal static string GetResourceText(string fileName, Assembly assembly, out s /// internal static void AddGetEndpoint(IApplicationBuilder app, string path, string content, string contentType = MimeTypes.Text.Css) { - if (!s_registeredEndpoints.TryAdd(path, 0)) + if (!s_registeredEndpoints.TryAdd(path, (content, contentType))) return; - if (app is WebApplication webApp) + // Register the single dispatch middleware only the first time this app instance is seen. + // Every subsequent call just adds an entry to the shared dispatch table above. + s_dispatchRegisteredApps.GetValue(app, static application => { - webApp.MapGet(path, (HttpContext context) => + application.Use(async (context, next) => { - SetCacheHeaders(context); - return Results.Content(content, contentType); - }) - .ExcludeFromDescription() - .AllowAnonymous(); - } - else - { - app.Use(async (context, next) => - { - if (context.Request.Path.Equals(path, StringComparison.OrdinalIgnoreCase)) + if (s_registeredEndpoints.TryGetValue(context.Request.Path.Value ?? string.Empty, out var endpoint)) { SetCacheHeaders(context); - context.Response.ContentType = contentType; - await context.Response.WriteAsync(content); + context.Response.ContentType = endpoint.ContentType; + await context.Response.WriteAsync(endpoint.Content); } else { await next(); } }); - } + + return new object(); + }); static void SetCacheHeaders(HttpContext context) { diff --git a/tests/AspNetCore.SwaggerUI.Themes.Tests/FileProviderMiddlewareTests.cs b/tests/AspNetCore.SwaggerUI.Themes.Tests/FileProviderMiddlewareTests.cs index 09c1c6c..0ab5973 100644 --- a/tests/AspNetCore.SwaggerUI.Themes.Tests/FileProviderMiddlewareTests.cs +++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/FileProviderMiddlewareTests.cs @@ -1,5 +1,6 @@ using AspNetCore.Swagger.Themes.Tests.Utilities; using Shouldly; +using System.Net; using static AspNetCore.Swagger.Themes.FileProvider; namespace AspNetCore.Swagger.Themes.Tests; @@ -8,8 +9,16 @@ namespace AspNetCore.Swagger.Themes.Tests; /// Tests for FileProvider with non-WebApplication scenarios (middleware-based). /// These tests are isolated to avoid conflicts with WebApplication endpoint registrations. /// -public class FileProviderMiddlewareTests +public class FileProviderMiddlewareTests : IClassFixture> { + private readonly ThemeProviderWebApplicationFactory _themeProviderWebApplicationFactory; + + public FileProviderMiddlewareTests(ThemeProviderWebApplicationFactory themeProviderWebApplicationFactory) + { + _themeProviderWebApplicationFactory = themeProviderWebApplicationFactory; + _themeProviderWebApplicationFactory.CreateClient(); + } + private readonly Dictionary _advancedOptions = new() { { AdvancedOptions.PinnableTopbar, true }, @@ -115,6 +124,75 @@ public async Task AddGetEndpoint_ShouldHandleCustomContentType() responseBody.ShouldBe(content); } + [Fact] + public async Task AddGetEndpoint_ShouldResolveEachPathIndependently_WhenMultiplePathsRegisteredOnSameApp() + { + // Arrange + var mockAppBuilder = new MockApplicationBuilder(); + var prefix = $"/test-middleware-multi-{Guid.NewGuid():N}"; + var registrations = Enumerable.Range(0, 6) + .Select(i => ( + Path: $"{prefix}/{i}.css", + Content: $"body {{ /* asset {i} */ }}", + ContentType: i % 2 == 0 ? MimeTypes.Text.Css : MimeTypes.Text.Javascript)) + .ToList(); + + foreach (var (path, content, contentType) in registrations) + { + AddGetEndpoint(mockAppBuilder, path, content, contentType); + } + + var app = mockAppBuilder.Build(); + + // Act & Assert - each registered path resolves to its own content and content-type + foreach (var (path, content, contentType) in registrations) + { + var context = MockApplicationBuilder.CreateHttpContext(path); + await app.Invoke(context); + await context.Response.Body.FlushAsync(); + + context.Response.StatusCode.ShouldBe(200); + context.Response.ContentType.ShouldBe(contentType); + + context.Response.Body.Seek(0, SeekOrigin.Begin); + using var reader = new StreamReader(context.Response.Body); + (await reader.ReadToEndAsync()).ShouldBe(content); + } + + // An unregistered path still falls through to the terminal 404 + var missingContext = MockApplicationBuilder.CreateHttpContext($"{prefix}/unregistered.css"); + await app.Invoke(missingContext); + + missingContext.Response.StatusCode.ShouldBe(404); + } + + [Fact] + public async Task AddGetEndpoint_ShouldResolveEachPathIndependently_WhenWebApplication() + { + // Arrange - the shared test WebApplication (see Program.cs) registers one path per + // theme via AddGetEndpoint at startup, all served through the same dispatch middleware. + var registeredThemes = new ThemeTestData(); + + // Act & Assert - each theme's style path resolves through the same WebApplication instance + foreach (var theme in registeredThemes) + { + var fullPath = StylesPath + theme.FileName; + var expectedContent = GetResourceText(theme.FileName, theme.GetType()); + + var response = await _themeProviderWebApplicationFactory.Client.GetAsync(fullPath); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + response.Content.Headers.ContentType.MediaType.ShouldBe(MimeTypes.Text.Css); + (await response.Content.ReadAsStringAsync()).ShouldBeEquivalentTo(expectedContent); + } + + // An unregistered path still falls through to the app's default 404 handling + var missingResponse = await _themeProviderWebApplicationFactory.Client.GetAsync( + $"{StylesPath}unregistered-{Guid.NewGuid():N}.css"); + + missingResponse.StatusCode.ShouldBe(HttpStatusCode.NotFound); + } + [Fact] public async Task AddGetEndpoint_WhenCalledConcurrently_RegistersEveryEndpointExactlyOnce() { From 3b92082315e719f6396dec631b7b12d8b529b69c Mon Sep 17 00:00:00 2001 From: Teuz Date: Mon, 10 Aug 2026 23:16:33 +0200 Subject: [PATCH 05/35] fix(core): scope theme dispatch table per app and restore WebApplication auth bypass The single-middleware dispatch table introduced for classic IApplicationBuilder pipelines was keyed off a single process-wide dictionary, so any app instance with the dispatch middleware installed could serve paths registered by a completely different app instance in the same process. The dispatch content is now stored per app instance (via ConditionalWeakTable), while the existing path registry stays as the process-wide duplicate-registration guard only. Also reinstates the WebApplication-specific MapGet(...).AllowAnonymous() registration path, since routing already gives WebApplication O(1)-ish lookup and only MapGet's endpoint metadata can bypass a global fallback authorization policy - a bare middleware has no equivalent, so collapsing it into the shared IApplicationBuilder dispatch middleware silently broke themes for any consumer with a "secure everything by default" auth setup. --- .../AspNetCore/Swagger/Themes/FileProvider.cs | 40 ++++++-- .../FileProviderMiddlewareTests.cs | 45 +++------ .../ThemeProviderTests.cs | 98 ++++++++++++++++++- 3 files changed, 143 insertions(+), 40 deletions(-) diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs index b0c8d31..db6d12b 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs @@ -23,11 +23,12 @@ internal static class FileProvider internal const string ThemeMetadataPath = "/themes/metadata.json"; internal const string JsFilename = "ui.min.js"; - // Track registered endpoints to prevent duplicates and act as the shared dispatch table for the single middleware - private static readonly ConcurrentDictionary s_registeredEndpoints = new(StringComparer.OrdinalIgnoreCase); + // Track registered endpoint paths to prevent duplicate registration across the process + private static readonly ConcurrentDictionary s_registeredEndpoints = new(StringComparer.OrdinalIgnoreCase); - // Tracks which IApplicationBuilder instances already have the dispatch middleware registered - private static readonly ConditionalWeakTable s_dispatchRegisteredApps = new(); + // Per-app dispatch table backing the single middleware registered for classic IApplicationBuilder hosts. + // WebApplication hosts are served through routing (MapGet) instead, so they never need an entry here. + private static readonly ConditionalWeakTable> s_appDispatchTables = new(); private static FrozenSet s_frozenEndpoints; @@ -120,16 +121,35 @@ internal static string GetResourceText(string fileName, Assembly assembly, out s /// internal static void AddGetEndpoint(IApplicationBuilder app, string path, string content, string contentType = MimeTypes.Text.Css) { - if (!s_registeredEndpoints.TryAdd(path, (content, contentType))) + if (!s_registeredEndpoints.TryAdd(path, 0)) return; + if (app is WebApplication webApp) + { + // WebApplication hosts are served through ASP.NET Core's routing tree, which already + // gives O(1)-ish lookup regardless of how many themes are registered, and MapGet lets + // us bypass any global fallback authorization policy via AllowAnonymous. + webApp.MapGet(path, (HttpContext context) => + { + SetCacheHeaders(context); + return Results.Content(content, contentType); + }) + .ExcludeFromDescription() + .AllowAnonymous(); + + return; + } + // Register the single dispatch middleware only the first time this app instance is seen. - // Every subsequent call just adds an entry to the shared dispatch table above. - s_dispatchRegisteredApps.GetValue(app, static application => + // Every subsequent call just adds an entry to this app's own dispatch table, so requests + // are only ever served for paths that this specific app instance registered. + var dispatchTable = s_appDispatchTables.GetValue(app, static application => { + var table = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + application.Use(async (context, next) => { - if (s_registeredEndpoints.TryGetValue(context.Request.Path.Value ?? string.Empty, out var endpoint)) + if (table.TryGetValue(context.Request.Path.Value ?? string.Empty, out var endpoint)) { SetCacheHeaders(context); context.Response.ContentType = endpoint.ContentType; @@ -141,9 +161,11 @@ internal static void AddGetEndpoint(IApplicationBuilder app, string path, string } }); - return new object(); + return table; }); + dispatchTable[path] = (content, contentType); + static void SetCacheHeaders(HttpContext context) { context.Response.Headers.CacheControl = "max-age=3600"; diff --git a/tests/AspNetCore.SwaggerUI.Themes.Tests/FileProviderMiddlewareTests.cs b/tests/AspNetCore.SwaggerUI.Themes.Tests/FileProviderMiddlewareTests.cs index 0ab5973..c76c1f6 100644 --- a/tests/AspNetCore.SwaggerUI.Themes.Tests/FileProviderMiddlewareTests.cs +++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/FileProviderMiddlewareTests.cs @@ -1,6 +1,5 @@ using AspNetCore.Swagger.Themes.Tests.Utilities; using Shouldly; -using System.Net; using static AspNetCore.Swagger.Themes.FileProvider; namespace AspNetCore.Swagger.Themes.Tests; @@ -9,16 +8,8 @@ namespace AspNetCore.Swagger.Themes.Tests; /// Tests for FileProvider with non-WebApplication scenarios (middleware-based). /// These tests are isolated to avoid conflicts with WebApplication endpoint registrations. /// -public class FileProviderMiddlewareTests : IClassFixture> +public class FileProviderMiddlewareTests { - private readonly ThemeProviderWebApplicationFactory _themeProviderWebApplicationFactory; - - public FileProviderMiddlewareTests(ThemeProviderWebApplicationFactory themeProviderWebApplicationFactory) - { - _themeProviderWebApplicationFactory = themeProviderWebApplicationFactory; - _themeProviderWebApplicationFactory.CreateClient(); - } - private readonly Dictionary _advancedOptions = new() { { AdvancedOptions.PinnableTopbar, true }, @@ -167,30 +158,26 @@ public async Task AddGetEndpoint_ShouldResolveEachPathIndependently_WhenMultiple } [Fact] - public async Task AddGetEndpoint_ShouldResolveEachPathIndependently_WhenWebApplication() + public async Task AddGetEndpoint_ShouldNotServePathRegisteredByAnotherAppInstance() { - // Arrange - the shared test WebApplication (see Program.cs) registers one path per - // theme via AddGetEndpoint at startup, all served through the same dispatch middleware. - var registeredThemes = new ThemeTestData(); + // Arrange - two independent apps, each registering its own, distinct path + var appA = new MockApplicationBuilder(); + var appB = new MockApplicationBuilder(); + var prefix = $"/test-middleware-isolation-{Guid.NewGuid():N}"; + var pathOnlyRegisteredByA = $"{prefix}/a.css"; + var pathOnlyRegisteredByB = $"{prefix}/b.css"; - // Act & Assert - each theme's style path resolves through the same WebApplication instance - foreach (var theme in registeredThemes) - { - var fullPath = StylesPath + theme.FileName; - var expectedContent = GetResourceText(theme.FileName, theme.GetType()); - - var response = await _themeProviderWebApplicationFactory.Client.GetAsync(fullPath); + AddGetEndpoint(appA, pathOnlyRegisteredByA, "content-A"); + AddGetEndpoint(appB, pathOnlyRegisteredByB, "content-B"); - response.StatusCode.ShouldBe(HttpStatusCode.OK); - response.Content.Headers.ContentType.MediaType.ShouldBe(MimeTypes.Text.Css); - (await response.Content.ReadAsStringAsync()).ShouldBeEquivalentTo(expectedContent); - } + var builtA = appA.Build(); - // An unregistered path still falls through to the app's default 404 handling - var missingResponse = await _themeProviderWebApplicationFactory.Client.GetAsync( - $"{StylesPath}unregistered-{Guid.NewGuid():N}.css"); + // Act - request appA for a path only appB ever registered + var context = MockApplicationBuilder.CreateHttpContext(pathOnlyRegisteredByB); + await builtA.Invoke(context); - missingResponse.StatusCode.ShouldBe(HttpStatusCode.NotFound); + // Assert - appA never registered this path, so it must not serve appB's content + context.Response.StatusCode.ShouldBe(404); } [Fact] diff --git a/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeProviderTests.cs b/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeProviderTests.cs index 91edd37..d00ef80 100644 --- a/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeProviderTests.cs +++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeProviderTests.cs @@ -1,13 +1,22 @@ using AspNetCore.Swagger.Themes.Tests.Utilities; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Shouldly; +using System.Net; using System.Reflection; +using System.Text.Encodings.Web; using static AspNetCore.Swagger.Themes.FileProvider; namespace AspNetCore.Swagger.Themes.Tests; /// -/// Tests for ThemeProvider functionality using WebApplicationFactory. -/// Tests for non-WebApplication scenarios are in FileProviderMiddlewareTests. +/// Tests for ThemeProvider functionality using a WebApplication host (via WebApplicationFactory +/// or a locally hosted TestServer). Tests for non-WebApplication scenarios are in FileProviderMiddlewareTests. /// public class ThemeProviderTests : IClassFixture> { @@ -177,6 +186,91 @@ public async Task AddGetEndpoint_ShouldReturnStyleContent_WhenWebApplication(Bas (await response.Content.ReadAsStringAsync()).ShouldBeEquivalentTo(styleText); } + [Fact] + public async Task AddGetEndpoint_ShouldResolveEachRegisteredPath_WhenWebApplication() + { + // Arrange - the shared test WebApplication (see Program.cs) registers one path per + // theme via AddGetEndpoint at startup, all served through the app's routing table. + var registeredThemes = new ThemeTestData(); + + // Act & Assert - each theme's style path resolves independently through the same WebApplication instance + foreach (var theme in registeredThemes) + { + var fullPath = StylesPath + theme.FileName; + var expectedContent = GetResourceText(theme.FileName, theme.GetType()); + + var response = await _themeProviderWebApplicationFactory.Client.GetAsync(fullPath); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + response.Content.Headers.ContentType.MediaType.ShouldBe(MimeTypes.Text.Css); + (await response.Content.ReadAsStringAsync()).ShouldBeEquivalentTo(expectedContent); + } + + // An unregistered path still falls through to the app's default 404 handling + var missingResponse = await _themeProviderWebApplicationFactory.Client.GetAsync( + $"{StylesPath}unregistered-{Guid.NewGuid():N}.css"); + + missingResponse.StatusCode.ShouldBe(HttpStatusCode.NotFound); + } + + [Fact] + public async Task AddGetEndpoint_ShouldBypassGlobalFallbackAuthorizationPolicy_WhenWebApplication() + { + // Arrange - a WebApplication secured with a global fallback policy requiring an + // authenticated user, mirroring a consumer who secures everything by default + // before calling UseSwaggerUI(). + var builder = WebApplication.CreateBuilder(); + builder.WebHost.UseTestServer(); + builder.Services + .AddAuthentication(DenyAllAuthenticationHandler.SchemeName) + .AddScheme(DenyAllAuthenticationHandler.SchemeName, null); + builder.Services.AddAuthorization(options => + options.FallbackPolicy = new AuthorizationPolicyBuilder(DenyAllAuthenticationHandler.SchemeName) + .RequireAuthenticatedUser() + .Build()); + + await using var app = builder.Build(); + app.UseAuthentication(); + app.UseAuthorization(); + + const string ThemePath = "/styles/auth-bypass-theme.css"; + const string ThemeContent = "body { }"; + AddGetEndpoint(app, ThemePath, ThemeContent); + + // A control endpoint mapped without AllowAnonymous proves the fallback policy is actually enforced + app.MapGet("/secured-control", () => "secured"); + + await app.StartAsync(); + var client = app.GetTestClient(); + + // Act + var themeResponse = await client.GetAsync(ThemePath); + var controlResponse = await client.GetAsync("/secured-control"); + + // Assert - the theme asset bypasses the fallback policy via AllowAnonymous; the control endpoint doesn't + themeResponse.StatusCode.ShouldBe(HttpStatusCode.OK); + (await themeResponse.Content.ReadAsStringAsync()).ShouldBe(ThemeContent); + + controlResponse.StatusCode.ShouldNotBe(HttpStatusCode.OK); + + await app.StopAsync(); + } + + /// + /// Authentication handler that never authenticates the current request, used to + /// exercise a global fallback authorization policy without depending on a real scheme. + /// + private sealed class DenyAllAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder) : AuthenticationHandler(options, logger, encoder) + { + public const string SchemeName = "DenyAll"; + + protected override Task HandleAuthenticateAsync() => + Task.FromResult(AuthenticateResult.NoResult()); + } + [Fact] public void GetResourceText_ShouldFindThemeInSubfolder() { From 01e7da5a4d3106eceede7f3b0b9729bbaef291ff Mon Sep 17 00:00:00 2001 From: Teuz Date: Mon, 10 Aug 2026 23:16:54 +0200 Subject: [PATCH 06/35] feat(themes): persist pinnable topbar state to localStorage Reload previously reset the topbar to its default pinned state; the pin button's preference is now saved and restored on load, applying the class directly at init to avoid a flash of the wrong state. --- .../AspNetCore/Swagger/Themes/Scripts/ui.js | 47 ++++++++++++++----- .../Swagger/Themes/Scripts/ui.min.js | 6 +-- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js index 1055e75..b5392bb 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js @@ -28,30 +28,55 @@ function setUpPinnableTopbar(enabled) { if (enabled === false) return; + const PINNABLE_TOPBAR_STORAGE_KEY = 'swaggerui-pinnable-topbar-preference'; + const topbarWrapper = document.querySelector('.topbar-wrapper'); const pinTopbarBtn = document.createElement('button'); pinTopbarBtn.setAttribute('id', 'pin-topbar-btn'); - pinTopbarBtn.addEventListener('click', () => pinOrUnpinTopbar(pinTopbarBtn)) + pinTopbarBtn.addEventListener('click', () => pinOrUnpinTopbar(pinTopbarBtn, true)) topbarWrapper.appendChild(pinTopbarBtn); - pinOrUnpinTopbar(pinTopbarBtn); -} + // Pre-apply the saved (or default) pinned state directly, so there's no flash + // of unpinned state on page load. No saved preference defaults to pinned, matching + // the topbar's original always-pinned-on-load behavior. + const savedState = localStorage.getItem(PINNABLE_TOPBAR_STORAGE_KEY); + const isPinned = savedState !== null ? savedState === 'pinned' : true; -function pinOrUnpinTopbar(pinTopbarBtn) { - if (pinTopbarBtn.parentNode.parentNode.parentNode.classList.contains('pinned')) { - pinTopbarBtn.parentNode.parentNode.parentNode.classList.remove('pinned'); - setUnpinnedIconTo(pinTopbarBtn); - pinTopbarBtn.setAttribute('title', 'Pin topbar'); - } - else { + if (isPinned) { pinTopbarBtn.parentNode.parentNode.parentNode.classList.add('pinned'); setPinnedIconTo(pinTopbarBtn); pinTopbarBtn.setAttribute('title', 'Unpin topbar'); } + else { + pinTopbarBtn.parentNode.parentNode.parentNode.classList.remove('pinned'); + setUnpinnedIconTo(pinTopbarBtn); + pinTopbarBtn.setAttribute('title', 'Pin topbar'); + } + + function pinOrUnpinTopbar(pinTopbarBtn, saveToStorage) { + let pinned; + + if (pinTopbarBtn.parentNode.parentNode.parentNode.classList.contains('pinned')) { + pinTopbarBtn.parentNode.parentNode.parentNode.classList.remove('pinned'); + setUnpinnedIconTo(pinTopbarBtn); + pinTopbarBtn.setAttribute('title', 'Pin topbar'); + pinned = false; + } + else { + pinTopbarBtn.parentNode.parentNode.parentNode.classList.add('pinned'); + setPinnedIconTo(pinTopbarBtn); + pinTopbarBtn.setAttribute('title', 'Unpin topbar'); + pinned = true; + } - pinTopbarBtn?.blur(); + if (saveToStorage) { + localStorage.setItem(PINNABLE_TOPBAR_STORAGE_KEY, pinned ? 'pinned' : 'unpinned'); + } + + pinTopbarBtn?.blur(); + } } function setPinnedIconTo(element) { diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js index fd477c8..01ad950 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js @@ -1,5 +1 @@ -ο»Ώ/*Swagger UI https://github.com/teociaps/SwaggerUI.Themes */const rootElement=document.documentElement;function setUpPinnableTopbar(e){if(!1===e)return;let t=document.querySelector(".topbar-wrapper"),n=document.createElement("button");n.setAttribute("id","pin-topbar-btn"),n.addEventListener("click",()=>pinOrUnpinTopbar(n)),t.appendChild(n),pinOrUnpinTopbar(n)}function pinOrUnpinTopbar(e){e.parentNode.parentNode.parentNode.classList.contains("pinned")?(e.parentNode.parentNode.parentNode.classList.remove("pinned"),setUnpinnedIconTo(e),e.setAttribute("title","Pin topbar")):(e.parentNode.parentNode.parentNode.classList.add("pinned"),setPinnedIconTo(e),e.setAttribute("title","Unpin topbar")),e?.blur()}function setPinnedIconTo(e){e.innerHTML=` - - `}function setUnpinnedIconTo(e){e.innerHTML=` - - `}function setUpScrollToTopButton(e){if(!1===e)return;let t=document.createElement("div");t.classList.add("scroll-to-top-wrapper");let n=document.createElement("button");n.setAttribute("id","scroll-to-top-btn"),n.setAttribute("title","Back to top"),n.addEventListener("click",()=>{scrollToTop(),n?.blur()}),t.appendChild(n);let l=document.getElementById("swagger-ui");l.appendChild(t);let r=()=>{window.scrollY>=200?n.classList.add("showBtn"):n.classList.remove("showBtn")};window.addEventListener("scroll",r),window.addEventListener("resize",r)}function scrollToTop(){rootElement.scrollTo({top:0,behavior:"smooth"})}function setUpExpandAndCollapseOperationsButtons(e){if(!1===e)return;let t=0,n=document.getElementById("swagger-ui")||document.querySelector(".swagger-ui");function l(){let e=document.querySelectorAll(".opblock-tag-section");return!!e&&0!==e.length&&(e.forEach(e=>(function e(t){if(!t||"true"===t.dataset.expandCollapseAttached)return;let n=t.querySelector("h3"),l=n?.querySelector("button.expand-operation");if(n&&!n.querySelector(".expand-collapse-all-btn")){let r=document.createElement("button");r.setAttribute("title","Expand/Collapse all the operations"),r.classList.add("expand-collapse-all-btn"),r.innerHTML="Expand/Collapse All",l?l.before(r):n.appendChild(r),r.addEventListener("click",e=>{e.preventDefault(),e.stopPropagation();let n=t.querySelectorAll(".opblock .opblock-control-arrow");if(!n||0===n.length)return;let l=Array.from(n).every(e=>"true"===e.getAttribute("aria-expanded"));l?n.forEach(e=>{"true"===e.getAttribute("aria-expanded")&&e.click()}):n.forEach(e=>{"false"===e.getAttribute("aria-expanded")&&e.click()})})}t.dataset.expandCollapseAttached="true"})(e)),!0)}!function e(){t++;let r=l();if(r){try{let a=n||document.body,o=new MutationObserver(e=>{for(let t of e)t.addedNodes&&t.addedNodes.length>0&&l()});o.observe(a,{childList:!0,subtree:!0})}catch(i){}return}t<60&&setTimeout(e,200)}()}function setUpThemeSwitcher(e){if(!1===e)return;let t="swaggerui-theme-preference",n=null,l=null;function r(e,r){let a=n?.themes.find(t=>t.name===e);if(!a){console.warn(`[ThemeSwitcher] Theme not found: ${e}`);return}let o=document.querySelectorAll('link[rel="stylesheet"]'),i=n.themes.map(e=>e.cssPath),s=!1;o.forEach(t=>{let n=t.dataset.theme||i.some(e=>t.href?.endsWith(e));if(n){let l=t.dataset.theme===e||t.href?.endsWith(a.cssPath);l?(t.disabled=!1,t.dataset.theme=e,s=!0):t.disabled=!0}}),s||console.warn(`[ThemeSwitcher] Could not activate theme: ${e}`),l=e,r&&localStorage.setItem(t,e);let $=document.getElementById("theme-switcher-select");$&&$.value!==e&&($.value=e)}fetch("/themes/metadata.json").then(e=>e.ok?e.json():(console.warn("[ThemeSwitcher] Failed to load theme metadata"),null)).then(e=>{if(!e?.themes||e.themes.length<2){console.warn("[ThemeSwitcher] Not enough themes available for switcher");return}n=e,l=detectCurrentTheme(e.themes),function e(n){let a=localStorage.getItem(t);a&&n.some(e=>e.name===a)&&a!==l&&r(a,!1)}(e.themes),function e(t){let n=document.querySelector(".topbar-wrapper");if(!n){console.warn("[ThemeSwitcher] Topbar not found");return}let a=document.createElement("select");a.id="theme-switcher-select",a.setAttribute("aria-label","Select theme"),a.title="Switch theme",t.themes.forEach(e=>{let n=document.createElement("option");n.value=e.name,n.textContent=formatThemeName(e.name,t.config?.displayFormat||"{name}"),e.name===l&&(n.selected=!0),a.appendChild(n)}),a.addEventListener("change",e=>{r(e.target.value,!0)});let o=document.getElementById("pin-topbar-btn");o?o.before(a):n.appendChild(a)}(e)}).catch(e=>{console.error("[ThemeSwitcher] Error loading themes:",e)})}function detectCurrentTheme(e){let t=document.querySelector('link[rel="stylesheet"]:not([disabled])[data-theme]');if(t?.dataset.theme)return t.dataset.theme;let n=document.querySelectorAll('link[rel="stylesheet"]:not([disabled])');for(let l of n){let r=e.find(e=>!!l.href&&(l.href.includes(e.cssPath)||l.href.endsWith(e.cssPath)||l.href.includes(e.name.toLowerCase())));if(r)return r.name}return e.length>0?e[0].name:null}function formatThemeName(e,t){let n=e.replaceAll(/([A-Z])/g," $1").trim().split(/[\s_-]+/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(" ");return t.replace("{name}",n)}window.onpageshow=function(){let e=setInterval(function(){null!=document.getElementById("swagger-ui")&&(clearInterval(e),console.log("Hello Swagger UI!"),setUpPinnableTopbar({$PINNABLE_TOPBAR}),setUpScrollToTopButton({$BACK_TO_TOP}),setUpExpandAndCollapseOperationsButtons({$EXPAND_COLLAPSE_ALL_OPERATIONS}),setUpThemeSwitcher({$THEME_SWITCHER}))},100)}; \ No newline at end of file +/*Swagger UI https://github.com/teociaps/SwaggerUI.Themes */const rootElement=document.documentElement;function setUpPinnableTopbar(e){if(!1===e)return;const t="swaggerui-pinnable-topbar-preference",n=document.querySelector(".topbar-wrapper"),o=document.createElement("button");o.setAttribute("id","pin-topbar-btn"),o.addEventListener("click",()=>function(e,n){let o;e.parentNode.parentNode.parentNode.classList.contains("pinned")?(e.parentNode.parentNode.parentNode.classList.remove("pinned"),setUnpinnedIconTo(e),e.setAttribute("title","Pin topbar"),o=!1):(e.parentNode.parentNode.parentNode.classList.add("pinned"),setPinnedIconTo(e),e.setAttribute("title","Unpin topbar"),o=!0);n&&localStorage.setItem(t,o?"pinned":"unpinned");e?.blur()}(o,!0)),n.appendChild(o);const a=localStorage.getItem(t);null===a||"pinned"===a?(o.parentNode.parentNode.parentNode.classList.add("pinned"),setPinnedIconTo(o),o.setAttribute("title","Unpin topbar")):(o.parentNode.parentNode.parentNode.classList.remove("pinned"),setUnpinnedIconTo(o),o.setAttribute("title","Pin topbar"))}function setPinnedIconTo(e){e.innerHTML='\n \n '}function setUnpinnedIconTo(e){e.innerHTML='\n \n '}function setUpScrollToTopButton(e){if(!1===e)return;const t=document.createElement("div");t.classList.add("scroll-to-top-wrapper");const n=document.createElement("button");n.setAttribute("id","scroll-to-top-btn"),n.setAttribute("title","Back to top"),n.addEventListener("click",()=>{scrollToTop(),n?.blur()}),t.appendChild(n);document.getElementById("swagger-ui").appendChild(t);const o=()=>{window.scrollY>=200?n.classList.add("showBtn"):n.classList.remove("showBtn")};window.addEventListener("scroll",o),window.addEventListener("resize",o)}function scrollToTop(){rootElement.scrollTo({top:0,behavior:"smooth"})}function setUpExpandAndCollapseOperationsButtons(e){if(!1===e)return;let t=0;const n=document.getElementById("swagger-ui")||document.querySelector(".swagger-ui");function o(){const e=document.querySelectorAll(".opblock-tag-section");return!(!e||0===e.length)&&(e.forEach(e=>function(e){if(!e||"true"===e.dataset.expandCollapseAttached)return;const t=e.querySelector("h3"),n=t?.querySelector("button.expand-operation");if(t&&!t.querySelector(".expand-collapse-all-btn")){const o=document.createElement("button");o.setAttribute("title","Expand/Collapse all the operations"),o.classList.add("expand-collapse-all-btn"),o.innerHTML="Expand/Collapse All",n?n.before(o):t.appendChild(o),o.addEventListener("click",t=>{t.preventDefault(),t.stopPropagation();const n=e.querySelectorAll(".opblock .opblock-control-arrow");n&&0!==n.length&&(Array.from(n).every(e=>"true"===e.getAttribute("aria-expanded"))?n.forEach(e=>{"true"===e.getAttribute("aria-expanded")&&e.click()}):n.forEach(e=>{"false"===e.getAttribute("aria-expanded")&&e.click()}))})}e.dataset.expandCollapseAttached="true"}(e)),!0)}!function e(){if(t++,o())try{const e=n||document.body;new MutationObserver(e=>{for(const t of e)t.addedNodes&&t.addedNodes.length>0&&o()}).observe(e,{childList:!0,subtree:!0})}catch(e){}else t<60&&setTimeout(e,200)}()}function setUpThemeSwitcher(e){if(!1===e)return;const t="swaggerui-theme-preference";let n=null,o=null;function a(e,a){const r=n?.themes.find(t=>t.name===e);if(!r)return void console.warn(`[ThemeSwitcher] Theme not found: ${e}`);const l=document.querySelectorAll('link[rel="stylesheet"]'),s=n.themes.map(e=>e.cssPath);let c=!1;l.forEach(t=>{if(t.dataset.theme||s.some(e=>t.href?.endsWith(e))){t.dataset.theme===e||t.href?.endsWith(r.cssPath)?(t.disabled=!1,t.dataset.theme=e,c=!0):t.disabled=!0}}),c||console.warn(`[ThemeSwitcher] Could not activate theme: ${e}`),o=e,a&&localStorage.setItem(t,e);const i=document.getElementById("theme-switcher-select");i&&i.value!==e&&(i.value=e)}fetch("/themes/metadata.json").then(e=>e.ok?e.json():(console.warn("[ThemeSwitcher] Failed to load theme metadata"),null)).then(e=>{!e?.themes||e.themes.length<2?console.warn("[ThemeSwitcher] Not enough themes available for switcher"):(n=e,o=detectCurrentTheme(e.themes),function(e){const n=localStorage.getItem(t);n&&e.some(e=>e.name===n)&&n!==o&&a(n,!1)}(e.themes),function(e){const t=document.querySelector(".topbar-wrapper");if(!t)return void console.warn("[ThemeSwitcher] Topbar not found");const n=document.createElement("select");n.id="theme-switcher-select",n.setAttribute("aria-label","Select theme"),n.title="Switch theme",e.themes.forEach(t=>{const a=document.createElement("option");a.value=t.name,a.textContent=formatThemeName(t.name,e.config?.displayFormat||"{name}"),t.name===o&&(a.selected=!0),n.appendChild(a)}),n.addEventListener("change",e=>{a(e.target.value,!0)});const r=document.getElementById("pin-topbar-btn");r?r.before(n):t.appendChild(n)}(e))}).catch(e=>{console.error("[ThemeSwitcher] Error loading themes:",e)})}function detectCurrentTheme(e){const t=document.querySelector('link[rel="stylesheet"]:not([disabled])[data-theme]');if(t?.dataset.theme)return t.dataset.theme;const n=document.querySelectorAll('link[rel="stylesheet"]:not([disabled])');for(const t of n){const n=e.find(e=>!!t.href&&(t.href.includes(e.cssPath)||t.href.endsWith(e.cssPath)||t.href.includes(e.name.toLowerCase())));if(n)return n.name}return e.length>0?e[0].name:null}function formatThemeName(e,t){const n=e.replaceAll(/([A-Z])/g," $1").trim().split(/[\s_-]+/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(" ");return t.replace("{name}",n)}window.onpageshow=function(){let e=setInterval(function(){null!=document.getElementById("swagger-ui")&&(clearInterval(e),console.log("Hello Swagger UI!"),setUpPinnableTopbar({$PINNABLE_TOPBAR}),setUpScrollToTopButton({$BACK_TO_TOP}),setUpExpandAndCollapseOperationsButtons({$EXPAND_COLLAPSE_ALL_OPERATIONS}),setUpThemeSwitcher({$THEME_SWITCHER}))},100)}; \ No newline at end of file From 04ae3182079a6984a899f3b4869f50694ca0f91b Mon Sep 17 00:00:00 2001 From: Teuz Date: Mon, 10 Aug 2026 23:35:31 +0200 Subject: [PATCH 07/35] ci: parameterize deploy.ps1 by package family, switch to themes-v*/extensions-v* tags deploy.ps1 was hardcoded to only pack *.Themes projects, so the newly added *.Extensions packages were never packed or pushed by the release pipeline. Add a mandatory -Filter param (Themes|Extensions) that fails fast on an invalid or missing value, and have the workflow derive the filter from the release tag prefix (themes-v*/extensions-v*), failing the step explicitly for any other tag. --- .github/workflows/pack_and_deploy.yml | 16 +++++++++++++++- deploy.ps1 | 10 ++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pack_and_deploy.yml b/.github/workflows/pack_and_deploy.yml index 497f5ef..066cbe8 100644 --- a/.github/workflows/pack_and_deploy.yml +++ b/.github/workflows/pack_and_deploy.yml @@ -31,6 +31,20 @@ jobs: 9.0.x 10.0.x + - name: Determine package family from release tag + id: family + shell: bash + run: | + TAG="${{ github.event.release.tag_name }}" + if [[ "$TAG" == themes-v* ]]; then + echo "filter=Themes" >> "$GITHUB_OUTPUT" + elif [[ "$TAG" == extensions-v* ]]; then + echo "filter=Extensions" >> "$GITHUB_OUTPUT" + else + echo "::error::Release tag '$TAG' does not match themes-v* or extensions-v* β€” cannot determine package family to deploy." >&2 + exit 1 + fi + - name: Pack & Push to NuGet shell: pwsh - run: ./deploy.ps1 -Configuration ${{ env.CONFIGURATION }} -PackageOutput ${{ env.PACKAGE_OUTPUT }} -NuGetSourceUrl ${{ env.NUGET_SOURCE_URL }} -NuGetApiKey ${{ secrets.NUGET_API_KEY }} \ No newline at end of file + run: ./deploy.ps1 -Configuration ${{ env.CONFIGURATION }} -PackageOutput ${{ env.PACKAGE_OUTPUT }} -NuGetSourceUrl ${{ env.NUGET_SOURCE_URL }} -NuGetApiKey ${{ secrets.NUGET_API_KEY }} -Filter ${{ steps.family.outputs.filter }} \ No newline at end of file diff --git a/deploy.ps1 b/deploy.ps1 index 001711a..c283417 100644 --- a/deploy.ps1 +++ b/deploy.ps1 @@ -3,16 +3,22 @@ param ( [string]$Configuration = "Release", [string]$PackageOutput = "$(pwd)/artifacts", [string]$NuGetSourceUrl = "https://api.nuget.org/v3/index.json", - [string]$NuGetApiKey + [string]$NuGetApiKey, + [string]$Filter ) +# Validate the package family filter +if ($Filter -ne "Themes" -and $Filter -ne "Extensions") { + throw "Invalid -Filter value '${Filter}'. Must be 'Themes' or 'Extensions'." +} + # Ensure the output directory exists if (-not (Test-Path -Path $PackageOutput)) { New-Item -ItemType Directory -Path $PackageOutput | Out-Null } # Iterate over each project to pack -Get-ChildItem -Path src -Filter *.Themes -Directory | ForEach-Object { +Get-ChildItem -Path src -Filter "*.$Filter" -Directory | ForEach-Object { $projectPath = $_.FullName $projectName = $_.Name $csprojPath = Join-Path -Path $projectPath -ChildPath "${projectName}.csproj" From a88eba395d81f8ffba00a9eb1e428d1e703f152b Mon Sep 17 00:00:00 2001 From: Teuz Date: Mon, 10 Aug 2026 23:36:05 +0200 Subject: [PATCH 08/35] refactor(themes): unify pinnable topbar state application into one function Extract the pinned/unpinned class-icon-title logic into a single applyPinnedState helper shared by the initial-load restore and the click handler, instead of duplicating it, mirroring the theme switcher's switchTheme pattern. Also restores the ui.min.js BOM lost in the previous regeneration to keep the file's encoding consistent. --- .../AspNetCore/Swagger/Themes/Scripts/ui.js | 40 +++++++------------ .../Swagger/Themes/Scripts/ui.min.js | 2 +- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js index b5392bb..7704d04 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js @@ -34,48 +34,36 @@ function setUpPinnableTopbar(enabled) { const pinTopbarBtn = document.createElement('button'); pinTopbarBtn.setAttribute('id', 'pin-topbar-btn'); - pinTopbarBtn.addEventListener('click', () => pinOrUnpinTopbar(pinTopbarBtn, true)) + pinTopbarBtn.addEventListener('click', () => { + const currentlyPinned = pinTopbarBtn.parentNode.parentNode.parentNode.classList.contains('pinned'); + applyPinnedState(pinTopbarBtn, !currentlyPinned, true); + pinTopbarBtn?.blur(); + }) topbarWrapper.appendChild(pinTopbarBtn); - // Pre-apply the saved (or default) pinned state directly, so there's no flash + // Apply the saved (or default) pinned state directly, so there's no flash // of unpinned state on page load. No saved preference defaults to pinned, matching // the topbar's original always-pinned-on-load behavior. const savedState = localStorage.getItem(PINNABLE_TOPBAR_STORAGE_KEY); const isPinned = savedState !== null ? savedState === 'pinned' : true; + applyPinnedState(pinTopbarBtn, isPinned, false); - if (isPinned) { - pinTopbarBtn.parentNode.parentNode.parentNode.classList.add('pinned'); - setPinnedIconTo(pinTopbarBtn); - pinTopbarBtn.setAttribute('title', 'Unpin topbar'); - } - else { - pinTopbarBtn.parentNode.parentNode.parentNode.classList.remove('pinned'); - setUnpinnedIconTo(pinTopbarBtn); - pinTopbarBtn.setAttribute('title', 'Pin topbar'); - } - - function pinOrUnpinTopbar(pinTopbarBtn, saveToStorage) { - let pinned; - - if (pinTopbarBtn.parentNode.parentNode.parentNode.classList.contains('pinned')) { - pinTopbarBtn.parentNode.parentNode.parentNode.classList.remove('pinned'); - setUnpinnedIconTo(pinTopbarBtn); - pinTopbarBtn.setAttribute('title', 'Pin topbar'); - pinned = false; - } - else { + function applyPinnedState(pinTopbarBtn, pinned, saveToStorage) { + if (pinned) { pinTopbarBtn.parentNode.parentNode.parentNode.classList.add('pinned'); setPinnedIconTo(pinTopbarBtn); pinTopbarBtn.setAttribute('title', 'Unpin topbar'); - pinned = true; + } + else { + pinTopbarBtn.parentNode.parentNode.parentNode.classList.remove('pinned'); + setUnpinnedIconTo(pinTopbarBtn); + pinTopbarBtn.setAttribute('title', 'Pin topbar'); } if (saveToStorage) { localStorage.setItem(PINNABLE_TOPBAR_STORAGE_KEY, pinned ? 'pinned' : 'unpinned'); } - - pinTopbarBtn?.blur(); } } diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js index 01ad950..29decac 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js @@ -1 +1 @@ -/*Swagger UI https://github.com/teociaps/SwaggerUI.Themes */const rootElement=document.documentElement;function setUpPinnableTopbar(e){if(!1===e)return;const t="swaggerui-pinnable-topbar-preference",n=document.querySelector(".topbar-wrapper"),o=document.createElement("button");o.setAttribute("id","pin-topbar-btn"),o.addEventListener("click",()=>function(e,n){let o;e.parentNode.parentNode.parentNode.classList.contains("pinned")?(e.parentNode.parentNode.parentNode.classList.remove("pinned"),setUnpinnedIconTo(e),e.setAttribute("title","Pin topbar"),o=!1):(e.parentNode.parentNode.parentNode.classList.add("pinned"),setPinnedIconTo(e),e.setAttribute("title","Unpin topbar"),o=!0);n&&localStorage.setItem(t,o?"pinned":"unpinned");e?.blur()}(o,!0)),n.appendChild(o);const a=localStorage.getItem(t);null===a||"pinned"===a?(o.parentNode.parentNode.parentNode.classList.add("pinned"),setPinnedIconTo(o),o.setAttribute("title","Unpin topbar")):(o.parentNode.parentNode.parentNode.classList.remove("pinned"),setUnpinnedIconTo(o),o.setAttribute("title","Pin topbar"))}function setPinnedIconTo(e){e.innerHTML='\n \n '}function setUnpinnedIconTo(e){e.innerHTML='\n \n '}function setUpScrollToTopButton(e){if(!1===e)return;const t=document.createElement("div");t.classList.add("scroll-to-top-wrapper");const n=document.createElement("button");n.setAttribute("id","scroll-to-top-btn"),n.setAttribute("title","Back to top"),n.addEventListener("click",()=>{scrollToTop(),n?.blur()}),t.appendChild(n);document.getElementById("swagger-ui").appendChild(t);const o=()=>{window.scrollY>=200?n.classList.add("showBtn"):n.classList.remove("showBtn")};window.addEventListener("scroll",o),window.addEventListener("resize",o)}function scrollToTop(){rootElement.scrollTo({top:0,behavior:"smooth"})}function setUpExpandAndCollapseOperationsButtons(e){if(!1===e)return;let t=0;const n=document.getElementById("swagger-ui")||document.querySelector(".swagger-ui");function o(){const e=document.querySelectorAll(".opblock-tag-section");return!(!e||0===e.length)&&(e.forEach(e=>function(e){if(!e||"true"===e.dataset.expandCollapseAttached)return;const t=e.querySelector("h3"),n=t?.querySelector("button.expand-operation");if(t&&!t.querySelector(".expand-collapse-all-btn")){const o=document.createElement("button");o.setAttribute("title","Expand/Collapse all the operations"),o.classList.add("expand-collapse-all-btn"),o.innerHTML="Expand/Collapse All",n?n.before(o):t.appendChild(o),o.addEventListener("click",t=>{t.preventDefault(),t.stopPropagation();const n=e.querySelectorAll(".opblock .opblock-control-arrow");n&&0!==n.length&&(Array.from(n).every(e=>"true"===e.getAttribute("aria-expanded"))?n.forEach(e=>{"true"===e.getAttribute("aria-expanded")&&e.click()}):n.forEach(e=>{"false"===e.getAttribute("aria-expanded")&&e.click()}))})}e.dataset.expandCollapseAttached="true"}(e)),!0)}!function e(){if(t++,o())try{const e=n||document.body;new MutationObserver(e=>{for(const t of e)t.addedNodes&&t.addedNodes.length>0&&o()}).observe(e,{childList:!0,subtree:!0})}catch(e){}else t<60&&setTimeout(e,200)}()}function setUpThemeSwitcher(e){if(!1===e)return;const t="swaggerui-theme-preference";let n=null,o=null;function a(e,a){const r=n?.themes.find(t=>t.name===e);if(!r)return void console.warn(`[ThemeSwitcher] Theme not found: ${e}`);const l=document.querySelectorAll('link[rel="stylesheet"]'),s=n.themes.map(e=>e.cssPath);let c=!1;l.forEach(t=>{if(t.dataset.theme||s.some(e=>t.href?.endsWith(e))){t.dataset.theme===e||t.href?.endsWith(r.cssPath)?(t.disabled=!1,t.dataset.theme=e,c=!0):t.disabled=!0}}),c||console.warn(`[ThemeSwitcher] Could not activate theme: ${e}`),o=e,a&&localStorage.setItem(t,e);const i=document.getElementById("theme-switcher-select");i&&i.value!==e&&(i.value=e)}fetch("/themes/metadata.json").then(e=>e.ok?e.json():(console.warn("[ThemeSwitcher] Failed to load theme metadata"),null)).then(e=>{!e?.themes||e.themes.length<2?console.warn("[ThemeSwitcher] Not enough themes available for switcher"):(n=e,o=detectCurrentTheme(e.themes),function(e){const n=localStorage.getItem(t);n&&e.some(e=>e.name===n)&&n!==o&&a(n,!1)}(e.themes),function(e){const t=document.querySelector(".topbar-wrapper");if(!t)return void console.warn("[ThemeSwitcher] Topbar not found");const n=document.createElement("select");n.id="theme-switcher-select",n.setAttribute("aria-label","Select theme"),n.title="Switch theme",e.themes.forEach(t=>{const a=document.createElement("option");a.value=t.name,a.textContent=formatThemeName(t.name,e.config?.displayFormat||"{name}"),t.name===o&&(a.selected=!0),n.appendChild(a)}),n.addEventListener("change",e=>{a(e.target.value,!0)});const r=document.getElementById("pin-topbar-btn");r?r.before(n):t.appendChild(n)}(e))}).catch(e=>{console.error("[ThemeSwitcher] Error loading themes:",e)})}function detectCurrentTheme(e){const t=document.querySelector('link[rel="stylesheet"]:not([disabled])[data-theme]');if(t?.dataset.theme)return t.dataset.theme;const n=document.querySelectorAll('link[rel="stylesheet"]:not([disabled])');for(const t of n){const n=e.find(e=>!!t.href&&(t.href.includes(e.cssPath)||t.href.endsWith(e.cssPath)||t.href.includes(e.name.toLowerCase())));if(n)return n.name}return e.length>0?e[0].name:null}function formatThemeName(e,t){const n=e.replaceAll(/([A-Z])/g," $1").trim().split(/[\s_-]+/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(" ");return t.replace("{name}",n)}window.onpageshow=function(){let e=setInterval(function(){null!=document.getElementById("swagger-ui")&&(clearInterval(e),console.log("Hello Swagger UI!"),setUpPinnableTopbar({$PINNABLE_TOPBAR}),setUpScrollToTopButton({$BACK_TO_TOP}),setUpExpandAndCollapseOperationsButtons({$EXPAND_COLLAPSE_ALL_OPERATIONS}),setUpThemeSwitcher({$THEME_SWITCHER}))},100)}; \ No newline at end of file +ο»Ώ/*Swagger UI https://github.com/teociaps/SwaggerUI.Themes */const rootElement=document.documentElement;function setUpPinnableTopbar(e){if(!1===e)return;const t="swaggerui-pinnable-topbar-preference",n=document.querySelector(".topbar-wrapper"),o=document.createElement("button");o.setAttribute("id","pin-topbar-btn"),o.addEventListener("click",()=>{const e=o.parentNode.parentNode.parentNode.classList.contains("pinned");r(o,!e,!0),o?.blur()}),n.appendChild(o);const a=localStorage.getItem(t);function r(e,n,o){n?(e.parentNode.parentNode.parentNode.classList.add("pinned"),setPinnedIconTo(e),e.setAttribute("title","Unpin topbar")):(e.parentNode.parentNode.parentNode.classList.remove("pinned"),setUnpinnedIconTo(e),e.setAttribute("title","Pin topbar")),o&&localStorage.setItem(t,n?"pinned":"unpinned")}r(o,null===a||"pinned"===a,!1)}function setPinnedIconTo(e){e.innerHTML='\n \n '}function setUnpinnedIconTo(e){e.innerHTML='\n \n '}function setUpScrollToTopButton(e){if(!1===e)return;const t=document.createElement("div");t.classList.add("scroll-to-top-wrapper");const n=document.createElement("button");n.setAttribute("id","scroll-to-top-btn"),n.setAttribute("title","Back to top"),n.addEventListener("click",()=>{scrollToTop(),n?.blur()}),t.appendChild(n);document.getElementById("swagger-ui").appendChild(t);const o=()=>{window.scrollY>=200?n.classList.add("showBtn"):n.classList.remove("showBtn")};window.addEventListener("scroll",o),window.addEventListener("resize",o)}function scrollToTop(){rootElement.scrollTo({top:0,behavior:"smooth"})}function setUpExpandAndCollapseOperationsButtons(e){if(!1===e)return;let t=0;const n=document.getElementById("swagger-ui")||document.querySelector(".swagger-ui");function o(){const e=document.querySelectorAll(".opblock-tag-section");return!(!e||0===e.length)&&(e.forEach(e=>function(e){if(!e||"true"===e.dataset.expandCollapseAttached)return;const t=e.querySelector("h3"),n=t?.querySelector("button.expand-operation");if(t&&!t.querySelector(".expand-collapse-all-btn")){const o=document.createElement("button");o.setAttribute("title","Expand/Collapse all the operations"),o.classList.add("expand-collapse-all-btn"),o.innerHTML="Expand/Collapse All",n?n.before(o):t.appendChild(o),o.addEventListener("click",t=>{t.preventDefault(),t.stopPropagation();const n=e.querySelectorAll(".opblock .opblock-control-arrow");n&&0!==n.length&&(Array.from(n).every(e=>"true"===e.getAttribute("aria-expanded"))?n.forEach(e=>{"true"===e.getAttribute("aria-expanded")&&e.click()}):n.forEach(e=>{"false"===e.getAttribute("aria-expanded")&&e.click()}))})}e.dataset.expandCollapseAttached="true"}(e)),!0)}!function e(){if(t++,o())try{const e=n||document.body;new MutationObserver(e=>{for(const t of e)t.addedNodes&&t.addedNodes.length>0&&o()}).observe(e,{childList:!0,subtree:!0})}catch(e){}else t<60&&setTimeout(e,200)}()}function setUpThemeSwitcher(e){if(!1===e)return;const t="swaggerui-theme-preference";let n=null,o=null;function a(e,a){const r=n?.themes.find(t=>t.name===e);if(!r)return void console.warn(`[ThemeSwitcher] Theme not found: ${e}`);const l=document.querySelectorAll('link[rel="stylesheet"]'),s=n.themes.map(e=>e.cssPath);let c=!1;l.forEach(t=>{if(t.dataset.theme||s.some(e=>t.href?.endsWith(e))){t.dataset.theme===e||t.href?.endsWith(r.cssPath)?(t.disabled=!1,t.dataset.theme=e,c=!0):t.disabled=!0}}),c||console.warn(`[ThemeSwitcher] Could not activate theme: ${e}`),o=e,a&&localStorage.setItem(t,e);const i=document.getElementById("theme-switcher-select");i&&i.value!==e&&(i.value=e)}fetch("/themes/metadata.json").then(e=>e.ok?e.json():(console.warn("[ThemeSwitcher] Failed to load theme metadata"),null)).then(e=>{!e?.themes||e.themes.length<2?console.warn("[ThemeSwitcher] Not enough themes available for switcher"):(n=e,o=detectCurrentTheme(e.themes),function(e){const n=localStorage.getItem(t);n&&e.some(e=>e.name===n)&&n!==o&&a(n,!1)}(e.themes),function(e){const t=document.querySelector(".topbar-wrapper");if(!t)return void console.warn("[ThemeSwitcher] Topbar not found");const n=document.createElement("select");n.id="theme-switcher-select",n.setAttribute("aria-label","Select theme"),n.title="Switch theme",e.themes.forEach(t=>{const a=document.createElement("option");a.value=t.name,a.textContent=formatThemeName(t.name,e.config?.displayFormat||"{name}"),t.name===o&&(a.selected=!0),n.appendChild(a)}),n.addEventListener("change",e=>{a(e.target.value,!0)});const r=document.getElementById("pin-topbar-btn");r?r.before(n):t.appendChild(n)}(e))}).catch(e=>{console.error("[ThemeSwitcher] Error loading themes:",e)})}function detectCurrentTheme(e){const t=document.querySelector('link[rel="stylesheet"]:not([disabled])[data-theme]');if(t?.dataset.theme)return t.dataset.theme;const n=document.querySelectorAll('link[rel="stylesheet"]:not([disabled])');for(const t of n){const n=e.find(e=>!!t.href&&(t.href.includes(e.cssPath)||t.href.endsWith(e.cssPath)||t.href.includes(e.name.toLowerCase())));if(n)return n.name}return e.length>0?e[0].name:null}function formatThemeName(e,t){const n=e.replaceAll(/([A-Z])/g," $1").trim().split(/[\s_-]+/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(" ");return t.replace("{name}",n)}window.onpageshow=function(){let e=setInterval(function(){null!=document.getElementById("swagger-ui")&&(clearInterval(e),console.log("Hello Swagger UI!"),setUpPinnableTopbar({$PINNABLE_TOPBAR}),setUpScrollToTopButton({$BACK_TO_TOP}),setUpExpandAndCollapseOperationsButtons({$EXPAND_COLLAPSE_ALL_OPERATIONS}),setUpThemeSwitcher({$THEME_SWITCHER}))},100)}; \ No newline at end of file From 0dae4fed3cde9c34bec1b935ea822031c4b99782 Mon Sep 17 00:00:00 2001 From: Teuz Date: Mon, 10 Aug 2026 23:42:04 +0200 Subject: [PATCH 09/35] ci: harden tag parsing and update Build trigger for the new tag convention Pass the release tag through env: instead of splicing it straight into the bash run: body, avoiding the classic Actions script-injection pattern. Update build.yml's tag trigger from the old v*.*.* pattern to themes-v*.*.*/extensions-v*.*.*, since it would otherwise never match a release tag again once the new convention lands and silently stop running Build/Test on releases. Also make deploy.ps1's -Filter check case-sensitive so a wrong-case value fails loudly instead of producing a glob that silently matches no projects. --- .github/workflows/build.yml | 2 +- .github/workflows/pack_and_deploy.yml | 3 ++- deploy.ps1 | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3ca5a09..6c074ad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: Build on: push: branches: [main] - tags: ['v*.*.*'] + tags: ['themes-v*.*.*', 'extensions-v*.*.*'] paths-ignore: ['**/*.md', '**/*.png', '**/*.txt'] pull_request: branches: [main] diff --git a/.github/workflows/pack_and_deploy.yml b/.github/workflows/pack_and_deploy.yml index 066cbe8..acee17d 100644 --- a/.github/workflows/pack_and_deploy.yml +++ b/.github/workflows/pack_and_deploy.yml @@ -34,8 +34,9 @@ jobs: - name: Determine package family from release tag id: family shell: bash + env: + TAG: ${{ github.event.release.tag_name }} run: | - TAG="${{ github.event.release.tag_name }}" if [[ "$TAG" == themes-v* ]]; then echo "filter=Themes" >> "$GITHUB_OUTPUT" elif [[ "$TAG" == extensions-v* ]]; then diff --git a/deploy.ps1 b/deploy.ps1 index c283417..43d19a2 100644 --- a/deploy.ps1 +++ b/deploy.ps1 @@ -7,8 +7,9 @@ param ( [string]$Filter ) -# Validate the package family filter -if ($Filter -ne "Themes" -and $Filter -ne "Extensions") { +# Validate the package family filter (case-sensitive: "themes" must not silently +# slip through and produce a glob that matches nothing on case-sensitive filesystems) +if ($Filter -cne "Themes" -and $Filter -cne "Extensions") { throw "Invalid -Filter value '${Filter}'. Must be 'Themes' or 'Extensions'." } From d22e545addcf62da922a7e9026e294fc7457aca9 Mon Sep 17 00:00:00 2001 From: Teuz Date: Tue, 11 Aug 2026 09:59:33 +0200 Subject: [PATCH 10/35] feat(themes): add pinnable filter bar with offset-stacking CSS refactor Adds a PinnableFilterBar advanced option that lets consumers keep Swagger UI's operation filter box visible while scrolling, mirroring the existing pinnable-topbar feature (same JS pin-button/localStorage-persistence shape, defaulting to unpinned since this is a new opt-in feature). The filter bar's own DOM wrapper is exactly as tall as the bar itself, so naively making it position:sticky has no visible effect (a sticky box is bounded by its containing block, which needs spare height to actually "stick" through a scroll). The nearest ancestor with real height doesn't carry a stable class in Swagger UI's markup, so it's targeted structurally via :has(). Offsets contributed by the pinned topbar and pinned filter bar are now exposed as CSS custom properties and summed via calc() to correctly position the sticky operation tag header under any combination of pinned topbar/filter bar. --- .../Swagger/Themes/AdvancedOptions.cs | 5 ++ .../AspNetCore/Swagger/Themes/Scripts/ui.js | 47 +++++++++++++++++++ .../Swagger/Themes/Scripts/ui.min.js | 2 +- .../Swagger/Themes/Styles/common.css | 36 +++++++++++++- .../Swagger/Themes/Styles/common.min.css | 2 +- .../AdvancedOptionsTests.cs | 18 +++++++ 6 files changed, 106 insertions(+), 4 deletions(-) diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/AdvancedOptions.cs b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/AdvancedOptions.cs index e7bf728..6514ac1 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/AdvancedOptions.cs +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/AdvancedOptions.cs @@ -6,6 +6,7 @@ internal static class AdvancedOptions { internal const string PinnableTopbar = nameof(PinnableTopbar); + internal const string PinnableFilterBar = nameof(PinnableFilterBar); internal const string BackToTop = nameof(BackToTop); internal const string StickyOperations = nameof(StickyOperations); internal const string ExpandOrCollapseAllOperations = nameof(ExpandOrCollapseAllOperations); @@ -18,12 +19,14 @@ internal static class AdvancedOptions // JS placeholders internal const string PinnableTopbarJsPH = "{$PINNABLE_TOPBAR}"; + internal const string PinnableFilterBarJsPH = "{$PINNABLE_FILTER_BAR}"; internal const string BackToTopJsPH = "{$BACK_TO_TOP}"; internal const string ExpandOrCollapseAllOperationsJsPH = "{$EXPAND_COLLAPSE_ALL_OPERATIONS}"; internal const string ThemeSwitcherJsPH = "{$THEME_SWITCHER}"; private static readonly string[][] s_jsDependentFeatures = [ [PinnableTopbar, PinnableTopbarJsPH], + [PinnableFilterBar, PinnableFilterBarJsPH], [BackToTop, BackToTopJsPH], [ExpandOrCollapseAllOperations, ExpandOrCollapseAllOperationsJsPH], [ThemeSwitcher, ThemeSwitcherJsPH] @@ -34,6 +37,8 @@ internal static void EnablePinnableTopbar(this IDictionary optio options.TryAdd(PinnableTopbar, true); } + internal static void EnablePinnableFilterBar(this IDictionary options) => options.TryAdd(PinnableFilterBar, true); + internal static void EnableBackToTop(this IDictionary options) { options.TryAdd(BackToTop, true); diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js index 7704d04..9fee511 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js @@ -15,6 +15,8 @@ window.onpageshow = function () { setUpPinnableTopbar({$PINNABLE_TOPBAR}); + setUpPinnableFilterBar({$PINNABLE_FILTER_BAR}); + setUpScrollToTopButton({$BACK_TO_TOP}); setUpExpandAndCollapseOperationsButtons({$EXPAND_COLLAPSE_ALL_OPERATIONS}); @@ -67,6 +69,51 @@ function setUpPinnableTopbar(enabled) { } } +function setUpPinnableFilterBar(enabled) { + if (enabled === false) + return; + + const PINNABLE_FILTER_BAR_STORAGE_KEY = 'swaggerui-pinnable-filterbar-preference'; + + const filterWrapper = document.querySelector('.filter'); + if (!filterWrapper) + return; + + const pinFilterBarBtn = document.createElement('button'); + pinFilterBarBtn.setAttribute('id', 'pin-filterbar-btn'); + pinFilterBarBtn.addEventListener('click', () => { + const currentlyPinned = pinFilterBarBtn.parentNode.classList.contains('pinned'); + applyPinnedState(pinFilterBarBtn, !currentlyPinned, true); + pinFilterBarBtn?.blur(); + }) + + filterWrapper.appendChild(pinFilterBarBtn); + + // Apply the saved (or default) pinned state directly, so there's no flash + // of unpinned state on page load. No saved preference defaults to unpinned, + // since this is a new opt-in feature with no prior shipped behavior to preserve. + const savedState = localStorage.getItem(PINNABLE_FILTER_BAR_STORAGE_KEY); + const isPinned = savedState === 'pinned'; + applyPinnedState(pinFilterBarBtn, isPinned, false); + + function applyPinnedState(pinFilterBarBtn, pinned, saveToStorage) { + if (pinned) { + pinFilterBarBtn.parentNode.classList.add('pinned'); + setPinnedIconTo(pinFilterBarBtn); + pinFilterBarBtn.setAttribute('title', 'Unpin filter bar'); + } + else { + pinFilterBarBtn.parentNode.classList.remove('pinned'); + setUnpinnedIconTo(pinFilterBarBtn); + pinFilterBarBtn.setAttribute('title', 'Pin filter bar'); + } + + if (saveToStorage) { + localStorage.setItem(PINNABLE_FILTER_BAR_STORAGE_KEY, pinned ? 'pinned' : 'unpinned'); + } + } +} + function setPinnedIconTo(element) { element.innerHTML = ` diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js index 29decac..b11f7c5 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js @@ -1 +1 @@ -ο»Ώ/*Swagger UI https://github.com/teociaps/SwaggerUI.Themes */const rootElement=document.documentElement;function setUpPinnableTopbar(e){if(!1===e)return;const t="swaggerui-pinnable-topbar-preference",n=document.querySelector(".topbar-wrapper"),o=document.createElement("button");o.setAttribute("id","pin-topbar-btn"),o.addEventListener("click",()=>{const e=o.parentNode.parentNode.parentNode.classList.contains("pinned");r(o,!e,!0),o?.blur()}),n.appendChild(o);const a=localStorage.getItem(t);function r(e,n,o){n?(e.parentNode.parentNode.parentNode.classList.add("pinned"),setPinnedIconTo(e),e.setAttribute("title","Unpin topbar")):(e.parentNode.parentNode.parentNode.classList.remove("pinned"),setUnpinnedIconTo(e),e.setAttribute("title","Pin topbar")),o&&localStorage.setItem(t,n?"pinned":"unpinned")}r(o,null===a||"pinned"===a,!1)}function setPinnedIconTo(e){e.innerHTML='\n \n '}function setUnpinnedIconTo(e){e.innerHTML='\n \n '}function setUpScrollToTopButton(e){if(!1===e)return;const t=document.createElement("div");t.classList.add("scroll-to-top-wrapper");const n=document.createElement("button");n.setAttribute("id","scroll-to-top-btn"),n.setAttribute("title","Back to top"),n.addEventListener("click",()=>{scrollToTop(),n?.blur()}),t.appendChild(n);document.getElementById("swagger-ui").appendChild(t);const o=()=>{window.scrollY>=200?n.classList.add("showBtn"):n.classList.remove("showBtn")};window.addEventListener("scroll",o),window.addEventListener("resize",o)}function scrollToTop(){rootElement.scrollTo({top:0,behavior:"smooth"})}function setUpExpandAndCollapseOperationsButtons(e){if(!1===e)return;let t=0;const n=document.getElementById("swagger-ui")||document.querySelector(".swagger-ui");function o(){const e=document.querySelectorAll(".opblock-tag-section");return!(!e||0===e.length)&&(e.forEach(e=>function(e){if(!e||"true"===e.dataset.expandCollapseAttached)return;const t=e.querySelector("h3"),n=t?.querySelector("button.expand-operation");if(t&&!t.querySelector(".expand-collapse-all-btn")){const o=document.createElement("button");o.setAttribute("title","Expand/Collapse all the operations"),o.classList.add("expand-collapse-all-btn"),o.innerHTML="Expand/Collapse All",n?n.before(o):t.appendChild(o),o.addEventListener("click",t=>{t.preventDefault(),t.stopPropagation();const n=e.querySelectorAll(".opblock .opblock-control-arrow");n&&0!==n.length&&(Array.from(n).every(e=>"true"===e.getAttribute("aria-expanded"))?n.forEach(e=>{"true"===e.getAttribute("aria-expanded")&&e.click()}):n.forEach(e=>{"false"===e.getAttribute("aria-expanded")&&e.click()}))})}e.dataset.expandCollapseAttached="true"}(e)),!0)}!function e(){if(t++,o())try{const e=n||document.body;new MutationObserver(e=>{for(const t of e)t.addedNodes&&t.addedNodes.length>0&&o()}).observe(e,{childList:!0,subtree:!0})}catch(e){}else t<60&&setTimeout(e,200)}()}function setUpThemeSwitcher(e){if(!1===e)return;const t="swaggerui-theme-preference";let n=null,o=null;function a(e,a){const r=n?.themes.find(t=>t.name===e);if(!r)return void console.warn(`[ThemeSwitcher] Theme not found: ${e}`);const l=document.querySelectorAll('link[rel="stylesheet"]'),s=n.themes.map(e=>e.cssPath);let c=!1;l.forEach(t=>{if(t.dataset.theme||s.some(e=>t.href?.endsWith(e))){t.dataset.theme===e||t.href?.endsWith(r.cssPath)?(t.disabled=!1,t.dataset.theme=e,c=!0):t.disabled=!0}}),c||console.warn(`[ThemeSwitcher] Could not activate theme: ${e}`),o=e,a&&localStorage.setItem(t,e);const i=document.getElementById("theme-switcher-select");i&&i.value!==e&&(i.value=e)}fetch("/themes/metadata.json").then(e=>e.ok?e.json():(console.warn("[ThemeSwitcher] Failed to load theme metadata"),null)).then(e=>{!e?.themes||e.themes.length<2?console.warn("[ThemeSwitcher] Not enough themes available for switcher"):(n=e,o=detectCurrentTheme(e.themes),function(e){const n=localStorage.getItem(t);n&&e.some(e=>e.name===n)&&n!==o&&a(n,!1)}(e.themes),function(e){const t=document.querySelector(".topbar-wrapper");if(!t)return void console.warn("[ThemeSwitcher] Topbar not found");const n=document.createElement("select");n.id="theme-switcher-select",n.setAttribute("aria-label","Select theme"),n.title="Switch theme",e.themes.forEach(t=>{const a=document.createElement("option");a.value=t.name,a.textContent=formatThemeName(t.name,e.config?.displayFormat||"{name}"),t.name===o&&(a.selected=!0),n.appendChild(a)}),n.addEventListener("change",e=>{a(e.target.value,!0)});const r=document.getElementById("pin-topbar-btn");r?r.before(n):t.appendChild(n)}(e))}).catch(e=>{console.error("[ThemeSwitcher] Error loading themes:",e)})}function detectCurrentTheme(e){const t=document.querySelector('link[rel="stylesheet"]:not([disabled])[data-theme]');if(t?.dataset.theme)return t.dataset.theme;const n=document.querySelectorAll('link[rel="stylesheet"]:not([disabled])');for(const t of n){const n=e.find(e=>!!t.href&&(t.href.includes(e.cssPath)||t.href.endsWith(e.cssPath)||t.href.includes(e.name.toLowerCase())));if(n)return n.name}return e.length>0?e[0].name:null}function formatThemeName(e,t){const n=e.replaceAll(/([A-Z])/g," $1").trim().split(/[\s_-]+/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(" ");return t.replace("{name}",n)}window.onpageshow=function(){let e=setInterval(function(){null!=document.getElementById("swagger-ui")&&(clearInterval(e),console.log("Hello Swagger UI!"),setUpPinnableTopbar({$PINNABLE_TOPBAR}),setUpScrollToTopButton({$BACK_TO_TOP}),setUpExpandAndCollapseOperationsButtons({$EXPAND_COLLAPSE_ALL_OPERATIONS}),setUpThemeSwitcher({$THEME_SWITCHER}))},100)}; \ No newline at end of file +ο»Ώ/*Swagger UI https://github.com/teociaps/SwaggerUI.Themes */const rootElement=document.documentElement;function setUpPinnableTopbar(e){if(!1===e)return;const t="swaggerui-pinnable-topbar-preference",n=document.querySelector(".topbar-wrapper"),o=document.createElement("button");o.setAttribute("id","pin-topbar-btn"),o.addEventListener("click",()=>{const e=o.parentNode.parentNode.parentNode.classList.contains("pinned");a(o,!e,!0),o?.blur()}),n.appendChild(o);const r=localStorage.getItem(t);function a(e,n,o){n?(e.parentNode.parentNode.parentNode.classList.add("pinned"),setPinnedIconTo(e),e.setAttribute("title","Unpin topbar")):(e.parentNode.parentNode.parentNode.classList.remove("pinned"),setUnpinnedIconTo(e),e.setAttribute("title","Pin topbar")),o&&localStorage.setItem(t,n?"pinned":"unpinned")}a(o,null===r||"pinned"===r,!1)}function setUpPinnableFilterBar(e){if(!1===e)return;const t="swaggerui-pinnable-filterbar-preference",n=document.querySelector(".filter");if(!n)return;const o=document.createElement("button");o.setAttribute("id","pin-filterbar-btn"),o.addEventListener("click",()=>{const e=o.parentNode.classList.contains("pinned");a(o,!e,!0),o?.blur()}),n.appendChild(o);const r=localStorage.getItem(t);function a(e,n,o){n?(e.parentNode.classList.add("pinned"),setPinnedIconTo(e),e.setAttribute("title","Unpin filter bar")):(e.parentNode.classList.remove("pinned"),setUnpinnedIconTo(e),e.setAttribute("title","Pin filter bar")),o&&localStorage.setItem(t,n?"pinned":"unpinned")}a(o,"pinned"===r,!1)}function setPinnedIconTo(e){e.innerHTML='\n \n '}function setUnpinnedIconTo(e){e.innerHTML='\n \n '}function setUpScrollToTopButton(e){if(!1===e)return;const t=document.createElement("div");t.classList.add("scroll-to-top-wrapper");const n=document.createElement("button");n.setAttribute("id","scroll-to-top-btn"),n.setAttribute("title","Back to top"),n.addEventListener("click",()=>{scrollToTop(),n?.blur()}),t.appendChild(n);document.getElementById("swagger-ui").appendChild(t);const o=()=>{window.scrollY>=200?n.classList.add("showBtn"):n.classList.remove("showBtn")};window.addEventListener("scroll",o),window.addEventListener("resize",o)}function scrollToTop(){rootElement.scrollTo({top:0,behavior:"smooth"})}function setUpExpandAndCollapseOperationsButtons(e){if(!1===e)return;let t=0;const n=document.getElementById("swagger-ui")||document.querySelector(".swagger-ui");function o(){const e=document.querySelectorAll(".opblock-tag-section");return!(!e||0===e.length)&&(e.forEach(e=>function(e){if(!e||"true"===e.dataset.expandCollapseAttached)return;const t=e.querySelector("h3"),n=t?.querySelector("button.expand-operation");if(t&&!t.querySelector(".expand-collapse-all-btn")){const o=document.createElement("button");o.setAttribute("title","Expand/Collapse all the operations"),o.classList.add("expand-collapse-all-btn"),o.innerHTML="Expand/Collapse All",n?n.before(o):t.appendChild(o),o.addEventListener("click",t=>{t.preventDefault(),t.stopPropagation();const n=e.querySelectorAll(".opblock .opblock-control-arrow");n&&0!==n.length&&(Array.from(n).every(e=>"true"===e.getAttribute("aria-expanded"))?n.forEach(e=>{"true"===e.getAttribute("aria-expanded")&&e.click()}):n.forEach(e=>{"false"===e.getAttribute("aria-expanded")&&e.click()}))})}e.dataset.expandCollapseAttached="true"}(e)),!0)}!function e(){if(t++,o())try{const e=n||document.body;new MutationObserver(e=>{for(const t of e)t.addedNodes&&t.addedNodes.length>0&&o()}).observe(e,{childList:!0,subtree:!0})}catch(e){}else t<60&&setTimeout(e,200)}()}function setUpThemeSwitcher(e){if(!1===e)return;const t="swaggerui-theme-preference";let n=null,o=null;function r(e,r){const a=n?.themes.find(t=>t.name===e);if(!a)return void console.warn(`[ThemeSwitcher] Theme not found: ${e}`);const l=document.querySelectorAll('link[rel="stylesheet"]'),s=n.themes.map(e=>e.cssPath);let c=!1;l.forEach(t=>{if(t.dataset.theme||s.some(e=>t.href?.endsWith(e))){t.dataset.theme===e||t.href?.endsWith(a.cssPath)?(t.disabled=!1,t.dataset.theme=e,c=!0):t.disabled=!0}}),c||console.warn(`[ThemeSwitcher] Could not activate theme: ${e}`),o=e,r&&localStorage.setItem(t,e);const i=document.getElementById("theme-switcher-select");i&&i.value!==e&&(i.value=e)}fetch("/themes/metadata.json").then(e=>e.ok?e.json():(console.warn("[ThemeSwitcher] Failed to load theme metadata"),null)).then(e=>{!e?.themes||e.themes.length<2?console.warn("[ThemeSwitcher] Not enough themes available for switcher"):(n=e,o=detectCurrentTheme(e.themes),function(e){const n=localStorage.getItem(t);n&&e.some(e=>e.name===n)&&n!==o&&r(n,!1)}(e.themes),function(e){const t=document.querySelector(".topbar-wrapper");if(!t)return void console.warn("[ThemeSwitcher] Topbar not found");const n=document.createElement("select");n.id="theme-switcher-select",n.setAttribute("aria-label","Select theme"),n.title="Switch theme",e.themes.forEach(t=>{const r=document.createElement("option");r.value=t.name,r.textContent=formatThemeName(t.name,e.config?.displayFormat||"{name}"),t.name===o&&(r.selected=!0),n.appendChild(r)}),n.addEventListener("change",e=>{r(e.target.value,!0)});const a=document.getElementById("pin-topbar-btn");a?a.before(n):t.appendChild(n)}(e))}).catch(e=>{console.error("[ThemeSwitcher] Error loading themes:",e)})}function detectCurrentTheme(e){const t=document.querySelector('link[rel="stylesheet"]:not([disabled])[data-theme]');if(t?.dataset.theme)return t.dataset.theme;const n=document.querySelectorAll('link[rel="stylesheet"]:not([disabled])');for(const t of n){const n=e.find(e=>!!t.href&&(t.href.includes(e.cssPath)||t.href.endsWith(e.cssPath)||t.href.includes(e.name.toLowerCase())));if(n)return n.name}return e.length>0?e[0].name:null}function formatThemeName(e,t){const n=e.replaceAll(/([A-Z])/g," $1").trim().split(/[\s_-]+/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(" ");return t.replace("{name}",n)}window.onpageshow=function(){let e=setInterval(function(){null!=document.getElementById("swagger-ui")&&(clearInterval(e),console.log("Hello Swagger UI!"),setUpPinnableTopbar({$PINNABLE_TOPBAR}),setUpPinnableFilterBar({$PINNABLE_FILTER_BAR}),setUpScrollToTopButton({$BACK_TO_TOP}),setUpExpandAndCollapseOperationsButtons({$EXPAND_COLLAPSE_ALL_OPERATIONS}),setUpThemeSwitcher({$THEME_SWITCHER}))},100)}; \ No newline at end of file diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css index 7a42fe6..8c65b64 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css @@ -95,6 +95,15 @@ body { .swagger-ui .topbar.pinned { position: sticky; top: 10px; + --pinned-topbar-offset: 4.2rem; + } + + /* The filter bar and the sticky operation tag headers live in a different branch of the + DOM than the topbar, so they can't inherit --pinned-topbar-offset directly from + .topbar.pinned. Re-declare it here, on their closest common ancestor, so it only + carries a nonzero value while the topbar is actually pinned. */ + .swagger-ui:has(> .topbar.pinned) { + --pinned-topbar-offset: 4.2rem; } .swagger-ui .topbar.pinned::before { @@ -402,8 +411,8 @@ body { background: var(--opblock-tag-pinned-background-color-hover, #f1f1f1); } -.swagger-ui:has(> .topbar.pinned) .opblock-tag-section.is-open .opblock-tag { - top: 4.2rem; +.swagger-ui .opblock-tag-section.is-open .opblock-tag { + top: calc(var(--pinned-topbar-offset, 0px) + var(--pinned-filter-offset, 0px)); } .swagger-ui .opblock { @@ -650,6 +659,29 @@ body { border: 2px solid var(--filter-operation-filter-input-border-color, #d8dde7); } + /* .filter's own wrapper elements (.filter-container and its parent) are exactly as tall as + the filter bar itself, so making .filter position:sticky would have no visible effect: + a sticky box is bounded by its containing block, and that block would offer no extra room + for it to remain "stuck" while the page scrolls. The next ancestor up spans the full page + content (info box, filter, operations list, models, ...) as siblings, so that's the element + that actually needs position: sticky. It has no stable class of its own in Swagger UI's + markup, so it's targeted structurally via its known descendants instead. */ + .swagger-ui div:has(> .filter-container > .filter.pinned) { + position: sticky; + top: var(--pinned-topbar-offset, 0px); + z-index: 5; + background: var(--body-background-color, #fafafa); + --pinned-filter-offset: 6.8rem; + } + +/* The sticky operation tag headers live in a different branch of the DOM than the filter bar, + so they can't inherit --pinned-filter-offset directly from the rule above. Re-declare it here, + on their closest common ancestor, so it only carries a nonzero value while the filter bar is + actually pinned. */ +.swagger-ui:has(.filter.pinned) { + --pinned-filter-offset: 6.8rem; +} + .swagger-ui .download-url-wrapper .failed, .swagger-ui .filter .failed { color: var(--filter-download-failed-color, red) diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css index bd03f59..6275525 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css @@ -1 +1 @@ -ο»Ώ/*Common Theme https://github.com/teociaps/SwaggerUI.Themes */@charset "UTF-8";*,:focus{font-family:system-ui,sans-serif}.swagger-ui,.swagger-ui .checkbox p,.swagger-ui .dialog-ux .modal-ux-content h4,.swagger-ui .dialog-ux .modal-ux-content p,.swagger-ui .dialog-ux .modal-ux-header h3,.swagger-ui .errors-wrapper .errors h4,.swagger-ui .errors-wrapper hgroup h4,.swagger-ui .info .base-url,.swagger-ui .info .title,.swagger-ui .info h1,.swagger-ui .info h2,.swagger-ui .info h3,.swagger-ui .info h4,.swagger-ui .info h5,.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info table,.swagger-ui .loading-container .loading:after,.swagger-ui .model,.swagger-ui .opblock .opblock-section-header h4,.swagger-ui .opblock .opblock-section-header>label,.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-external-docs-wrapper,.swagger-ui .opblock-external-docs-wrapper h4,.swagger-ui .opblock-external-docs-wrapper p,.swagger-ui .opblock-tag,.swagger-ui .opblock-tag small,.swagger-ui .opblock-title_normal,.swagger-ui .opblock-title_normal h4,.swagger-ui .opblock-title_normal p,.swagger-ui .parameter__name,.swagger-ui .parameter__type,.swagger-ui .response-col_links,.swagger-ui .response-col_status,.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5,.swagger-ui .scheme-container .schemes>label,.swagger-ui .scopes h2,.swagger-ui .servers>label,.swagger-ui .tab li,.swagger-ui label,.swagger-ui section h3,.swagger-ui table.headers td{color:var(--swagger-main-color,#3b4151)}::-webkit-scrollbar{width:16px}::-webkit-scrollbar-thumb{height:56px;border-radius:8px;border:4px solid transparent;background-clip:content-box;background-color:var(--scrollbar-thumb-color,#a5a5a5);transition:background-color .3s}::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover-color,#727272)}body::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-color,#a5a5a5)}#theme-switcher-select,.swagger-ui select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat)}:focus{outline:0!important}:focus-visible{outline:auto!important;outline-offset:1px}body{background-color:var(--body-background-color,#fafafa);padding-bottom:1rem}*,::after,::before{box-sizing:border-box}.swagger-ui{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.15;margin:0}.swagger-ui .loading-container .loading:before{border:2px solid var(--loading-container-border-color,rgba(85,85,85,.1));border-top-color:var(--loading-container-border-top-color,rgba(0,0,0,.6))}.swagger-ui .errors-wrapper{background:var(--errors-wrapper-background-color,rgba(249,62,62,.1));border:2px solid var(--errors-wrapper-border-color,#f93e3e)}.swagger-ui .errors-wrapper .errors small{color:var(--errors-wrapper-errors-color,#606060)}.swagger-ui .topbar{background-color:transparent;z-index:1000;padding:0}.swagger-ui .topbar.pinned{position:sticky;top:10px}.swagger-ui .topbar.pinned::before{content:'';position:absolute;inset:-10px 0 0;pointer-events:none;user-select:none;backdrop-filter:blur(20px);-webkit-mask-image:linear-gradient(to bottom,black 40%,transparent);mask-image:linear-gradient(to bottom,black 40%,transparent);background:linear-gradient(90deg,rgba(0,0,0,0) 5%,rgba(8,8,8,.4) 50%,rgba(0,0,0,0) 95%)}.swagger-ui .topbar .topbar-wrapper{background-color:var(--topbar-background-color,#000);border:1px solid var(--topbar-border-color,gray);border-top:0;border-radius:0 0 .5rem .5rem;padding:.5rem;box-shadow:0 5px 15px 0 var(--topbar-pinned-shadow-color,rgba(0,0,0,.5))}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn svg{fill:var(--topbar-pin-icon-color,white)}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn{cursor:pointer;background:0 0;border:none;margin:.2rem;padding:.2rem;transition:.2s;rotate:z 359deg}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:hover{scale:1.05;animation:1.2s infinite pintobbar}#scroll-to-top-btn:active,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:active{scale:0.95}.swagger-ui .topbar.pinned .topbar-wrapper{background-color:var(--topbar-pinned-background-color,rgba(0,0,0,.7));border:1px solid var(--topbar-border-color,gray);border-radius:.5rem;backdrop-filter:blur(20px)}#theme-switcher-select,.swagger-ui .topbar .download-url-wrapper .select-label select,.swagger-ui .topbar .download-url-wrapper input[type=text]{border:2px solid var(--topbar-select-border-color,#62a03f)}.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn{translate:0 5px;rotate:z 0deg}.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:hover{animation:1.2s infinite unpintobbar}@keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-moz-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-webkit-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@keyframes unpintobbar{10%{translate:0 1px}70%{rotate:z 50deg;translate:-3px 1px}}@-moz-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}@-webkit-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}.swagger-ui .topbar .download-url-wrapper .select-label{color:var(--topbar-select-label-color,#f0f0f0)}.swagger-ui .topbar .download-url-wrapper .download-url-button{background:var(--topbar-download-url-button-background-color,#62a03f);color:var(--topbar-download-url-button-color,#fff)}#theme-switcher-select{min-width:120px;box-shadow:none}.swagger-ui .info a,.swagger-ui .scopes h2 a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .info a:hover{color:var(--swagger-info-link-hover,#1f69c0)}.swagger-ui .info .title small{background:var(--api-version-background-color,#7d8492)}.swagger-ui .info .title small.version-stamp{background-color:var(--api-version-stamp-background-color,#89bf04)}.swagger-ui .info .title small pre{color:var(--api-version-color,#fff)}.swagger-ui .scheme-container{background:0 0;box-shadow:none;margin:0;padding:0}.swagger-ui .auth-wrapper{display:flex;flex:1;justify-content:flex-end;border:1px solid var(--auth-wrapper-border-color,#d5d5d5);padding:0;border-radius:5px;box-shadow:0 3px 5px -5px var(--auth-wrapper-box-shadow-color,#000);background:var(--auth-wrapper-background-color,#fff)}.swagger-ui .auth-container{border-bottom:1px solid var(--auth-container-border-bottom-color,#ebebeb);color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container .errors{background-color:var(--auth-container-background-color,#fee);color:var(--auth-container-errors-color,red)}.swagger-ui .btn.authorize{background-color:var(--btn-authorize-background-color,transparent);border-color:var(--btn-authorize-border-color,#49cc90);color:var(--btn-authorize-font-color,#49cc90)}.swagger-ui .btn.authorize svg{fill:var(--btn-authorize-svg-fill-color,#49cc90)}.swagger-ui .opblock-tag-section{display:flex;flex-direction:column;border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;margin-block:1rem;overflow:clip}.swagger-ui .opblock-tag-section .expand-collapse-all-btn{display:none;user-select:none}.swagger-ui .opblock-tag-section.is-open .expand-collapse-all-btn{background:var(--opblock-tag-section-expandcollapse-background-color,rgb(220 220 220 / 45%));color:var(--swagger-main-color,#3b4151);border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;padding:5px 10px;font-size:medium;margin-inline:10px;display:block}.swagger-ui .opblock-tag{align-items:center;cursor:pointer;display:flex;padding:10px 20px 10px 10px;color:var(--swagger-main-color,#3b4151);font-size:24px;border-bottom:0 dashed var(--opblock-tag-border-bottom-color,#e1e1e1);margin:0;transition:.2s}.swagger-ui .opblock-tag:focus-within,.swagger-ui .opblock-tag:hover{background:var(--opblock-tag-background-color-hover,rgba(0,0,0,.02))}.swagger-ui .opblock-tag+div .operation-tag-content{margin-inline:5px}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag{position:sticky;top:0;z-index:4;margin-bottom:1rem;border-bottom:1px dashed var(--opblock-tag-border-bottom-color,#e1e1e1);background:var(--body-background-color,#fafafa)}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:focus-within,#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:hover{background:var(--opblock-tag-pinned-background-color-hover,#f1f1f1)}.swagger-ui:has(> .topbar.pinned) .opblock-tag-section.is-open .opblock-tag{top:4.2rem}.swagger-ui .opblock{border:1px solid var(--opblock-border-color,#000);box-shadow:0 0 3px var(--opblock-shadow-color,rgba(0,0,0,.19))}.swagger-ui .opblock .tab-header .tab-item.active h4 span:after{background:var(--opblock-tabheader-underline-color,gray)}.swagger-ui .opblock .opblock-summary button svg{fill:var(--opblock-summary-svg-icons-color,#000)}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid var(--opblock-summary-border-bottom-color,#000)}.swagger-ui .opblock.opblock-post,.swagger-ui .opblock.opblock-post .opblock-summary{border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock .opblock-section-header{background:var(--opblock-section-header-background-color,hsla(0,0%,100%,.8));box-shadow:0 1px 2px var(--opblock-section-header-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-summary-method{background:var(--opblock-summary-method-background-color,#000);color:var(--opblock-summary-method-color,#fff);text-shadow:0 1px 0 var(--opblock-summary-method-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{color:var(--swagger-main-color,#3b4151);word-break:normal}.swagger-ui .opblock.opblock-post{background:var(--opblock-post-background-color,rgba(73,204,144,.1))}.swagger-ui .opblock.opblock-post .opblock-summary-method{background:var(--opblock-post-method-color,#49cc90)}.swagger-ui .opblock.opblock-put,.swagger-ui .opblock.opblock-put .opblock-summary{border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span:after{background:var(--opblock-post-method-color,#49cc90);border-radius:1rem}.swagger-ui .opblock.opblock-put{background:var(--opblock-put-background-color,rgba(252,161,48,.1))}.swagger-ui .opblock.opblock-put .opblock-summary-method,.swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span:after{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-delete,.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete{background:var(--opblock-delete-background-color,rgba(249,62,62,.1))}.swagger-ui .opblock.opblock-delete .opblock-summary-method,.swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span:after{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-get,.swagger-ui .opblock.opblock-get .opblock-summary{border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get{background:var(--opblock-get-background-color,rgba(97,175,254,.1))}.swagger-ui .opblock.opblock-get .opblock-summary-method,.swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span:after{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-patch,.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch{background:var(--opblock-patch-background-color,rgba(80,227,194,.1))}.swagger-ui .opblock.opblock-patch .opblock-summary-method,.swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span:after{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-head,.swagger-ui .opblock.opblock-head .opblock-summary{border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head{background:var(--opblock-head-background-color,rgba(144,18,254,.1))}.swagger-ui .opblock.opblock-head .opblock-summary-method,.swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span:after{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-options,.swagger-ui .opblock.opblock-options .opblock-summary{border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options{background:var(--opblock-options-background-color,rgba(13,90,167,.1))}.swagger-ui .opblock.opblock-options .opblock-summary-method,.swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span:after{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-deprecated,.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated{background:var(--opblock-deprecated-background-color,hsla(0,0%,92%,.1))}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method,.swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span:after{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .tab li:first-of-type:after{background:var(--swagger-tabs-divider-color,rgba(0,0,0,.2))}.swagger-ui .response-col_links .response-undocumented,.swagger-ui .response-col_status .response-undocumented{color:var(--response-undocumented-color,#909090)}.swagger-ui .response-control-media-type--accept-controller select{border-color:var(--response-control-media-type-color,green)}.swagger-ui .response-control-media-type__accept-message{color:var(--response-control-media-type-color,green)}.swagger-ui .opblock-body pre.microlight{background:var(--opblock-pre-microlight-background-color,#333)!important;color:var(--opblock-pre-microlight-color,#fff)!important}.swagger-ui .download-contents{background:var(--download-contents-background-color,#7d8293);color:var(--download-contents-color,#fff)}.swagger-ui .copy-to-clipboard{background:var(--copy-to-clipboard-background-color,#7d8293)}.swagger-ui .copy-to-clipboard button{background:var(--copy-to-clipboard-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .filter .operation-filter-input{border:2px solid var(--filter-operation-filter-input-border-color,#d8dde7)}.swagger-ui .download-url-wrapper .failed,.swagger-ui .filter .failed{color:var(--filter-download-failed-color,red)}.swagger-ui .download-url-wrapper .loading,.swagger-ui .filter .loading{color:var(--filter-download-loading-color,#aaa)}.swagger-ui table.headers .header-example{color:var(--table-headers-example-color,#999)}.swagger-ui table thead tr td,.swagger-ui table thead tr th{border-bottom:1px solid var(--table-thead-border-bottom-color,rgba(59,65,81,.2));color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name.required span{color:var(--parameter-name-required-symbol-color,red)}.swagger-ui .parameter__name.required::after{color:var(--parameter-name-required-color,rgba(255,0,0,.6))}.swagger-ui .parameter__extension,.swagger-ui .parameter__in,.swagger-ui .response__extension{color:var(--parameter-in-extension-color,gray)}.swagger-ui .parameter__deprecated{color:var(--parameter-deprecated-color,red)}.swagger-ui .btn,.swagger-ui select,.swagger-ui textarea{color:var(--swagger-main-color,#3b4151)}.swagger-ui .btn{background:var(--button-background-color,transparent);border:2px solid var(--button-border-color,gray);box-shadow:0 1px 2px var(--button-shadow-color,rgba(0,0,0,.1))}.swagger-ui .btn:hover{box-shadow:0 0 5px var(--button-shadow-hover-color,rgba(0,0,0,.3))}.swagger-ui .btn.cancel{background-color:var(--button-cancel-background-color,transparent);border-color:var(--button-cancel-border-color,#ff6060);color:var(--button-cancel-color,#ff6060)}.swagger-ui .btn.execute{background-color:var(--button-execute-background-color,#4990e2);border-color:var(--button-execute-border-color,#4990e2);color:var(--button-execute-color,#fff)}.swagger-ui button.invalid{background:var(--button-invalid-background-color,#feebeb);border-color:var(--button-invalid-border-color,#f93e3e)}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{fill:var(--expand-operation-svg-arrow-color,#707070)}.swagger-ui .expand-methods:hover svg,.swagger-ui .expand-operation:hover svg{fill:var(--expand-operation-svg-arrow-hover-color,#404040)}.swagger-ui select{border:2px solid var(--select-border-color,#41444e);box-shadow:0 1px 2px 0 var(--select-shadow-color,rgba(0,0,0,.25));cursor:pointer}.swagger-ui select[multiple]{background:var(--select-background-color,#f7f7f7)}.swagger-ui select:disabled{opacity:.5;cursor:not-allowed}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text],.swagger-ui textarea{background:var(--input-background-color,#fff);border:1px solid var(--input-border-color,#d9d9d9);color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email].invalid,.swagger-ui input[type=file].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid,.swagger-ui select.invalid,.swagger-ui textarea.invalid{background:var(--input-invalid-background-color,#feebeb);border-color:var(--input-invalid-border-color,#f93e3e);color:var(--input-invalid-color,#3b4151)}.swagger-ui input[disabled],.swagger-ui select[disabled]{background-color:var(--input-disabled-background-color,#fafafa);color:var(--input-disabled-color,#888)}.swagger-ui select[disabled]{border-color:var(--select-disabled-border-color,#888)}.swagger-ui textarea[disabled]{background-color:var(--textarea-disabled-background-color,#41444e);color:var(--textarea-disabled-color,#fff)}.swagger-ui textarea{background:var(--textarea-background-color,hsla(0,0%,100%,.8))}.swagger-ui textarea:focus{border:2px solid var(--textarea-border-focus-color,#61affe)}.swagger-ui textarea.curl{background:var(--textarea-curl-background-color,#41444e);color:var(--textarea-curl-color,#fff)}.swagger-ui .checkbox{color:var(--checkbox-color,#303030)}.swagger-ui .checkbox input[type=checkbox]+label>.item{background:var(--checkbox-label-item-background-color,#e8e8e8);box-shadow:0 0 0 2px var(--checkbox-label-item-shadow-color,#e8e8e8)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:var(--checkbox-label-item-icon, #e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .dialog-ux .backdrop-ux{background:var(--dialog-backdrop-color,rgba(0,0,0,.8))}.swagger-ui .dialog-ux .modal-ux{background:var(--dialog-background-color,#fff);border:1px solid var(--dialog-border-color,#ebebeb);box-shadow:0 10px 30px 0 var(--dialog-shadow-color,rgba(0,0,0,.2))}.swagger-ui .dialog-ux .modal-ux-header{border-bottom:1px solid var(--dialog-border-color,#ebebeb)}.swagger-ui .dialog-ux .modal-ux-header button svg{fill:var(--dialog-close-button-icon-color,black)}.swagger-ui .model .deprecated span,.swagger-ui .model .deprecated td{color:var(--model-deprecated-color,#a0a0a0)!important}.swagger-ui .model-toggle{cursor:pointer;-webkit-transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}.swagger-ui .model-toggle.collapsed{-webkit-transform:rotate(0);transform:rotate(0)}.swagger-ui .model-toggle::after{background:var(--model-arrow-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .model-hint{background:var(--model-hint-background-color,rgba(0,0,0,.7));color:var(--model-hint-color,#ebebeb)}.swagger-ui .model .property{color:var(--model-property-color,#999)}.swagger-ui .model .property.primitive{color:var(--model-property-primitive-color,#6b6b6b)}.swagger-ui table.model tr.property-row .star{color:var(--model-property-required-symbol-color,red)}.swagger-ui .model .external-docs,.swagger-ui table.model tr.description{color:var(--model-property-description,#666)}.swagger-ui table.model tr.extension{color:var(--model-property-extension,#777)}.swagger-ui section.models{border:1px solid var(--model-section-border-color,rgba(59,65,81,.3));border-radius:.5rem;padding:0;margin:0}.swagger-ui section.models.is-open h4{border-bottom:1px solid var(--model-section-border-color,rgba(59,65,81,.3))}.swagger-ui section.models h4{color:var(--model-section-header-color,#606060);padding:0}.swagger-ui section.models h4:hover{background:var(--model-section-header-hover-background-color,rgba(0,0,0,.02))}.swagger-ui section.models h4 svg{fill:var(--expand-model-svg-arrow-color,#707070)}.swagger-ui section.models h4 button.models-control{color:var(--model-section-header-color,#606060);padding:20px}.swagger-ui section.models h5{color:var(--model-section-little-header-color,#707070)}.swagger-ui section.models .model-container{background:var(--model-container-background-color,rgba(0,0,0,.05))}.swagger-ui section.models .model-container:hover{background:var(--model-container-hover-background-color,rgba(0,0,0,.07))}.swagger-ui .model-box{background:var(--model-box-background-color,rgba(0,0,0,.1))}.swagger-ui .model-title{color:var(--model-title-color,#505050)}.swagger-ui .model-deprecated-warning{color:var(--model-deprecated-warning-color,#f93e3e)}.swagger-ui .prop-type{color:var(--model-prop-type-color,#55a)}.swagger-ui .prop-format{color:var(--model-prop-format-color,#606060)}.swagger-ui .markdown pre,.swagger-ui .renderedMarkdown pre{color:var(--rendered-markdown-pre-color,#000)}.swagger-ui .markdown code,.swagger-ui .renderedMarkdown code{background:var(--rendered-markdown-code-background-color,rgba(0,0,0,.05));color:var(--rendered-markdown-code-color,#9012fe)}.scroll-to-top-wrapper{position:fixed;bottom:0;left:0;right:0;text-align:end;box-sizing:border-box;margin:0 auto;max-width:1460px;padding:0 20px;width:100%;pointer-events:none;z-index:1000}#scroll-to-top-btn{background-color:var(--scroll-to-top-button-background-color,#000000d1);border:1px solid var(--scroll-to-top-button-border-color,#d1c9c9);border-radius:50%;color:#fff;cursor:pointer;width:2.5rem;aspect-ratio:1;backdrop-filter:blur(20px);margin:10px;position:relative;transition:background-color .5s,opacity .6s .2s,translate .8s,scale .2s;opacity:0;translate:0 100px;box-shadow:0 0 10px 0 var(--scroll-to-top-button-shadow-color,rgba(0,0,0,.4))}#scroll-to-top-btn.showBtn{opacity:1;translate:0 0;pointer-events:auto}#scroll-to-top-btn::before{position:absolute;content:'';top:10px;left:12px;right:12px;background-color:var(--scroll-to-top-button-icon-color,#fff);border-radius:1rem;height:2px}#scroll-to-top-btn::after{position:absolute;content:'';top:15px;left:50%;border:solid var(--scroll-to-top-button-icon-color,#fff);border-width:0 2px 2px 0;padding:3px;transform:rotate(-135deg);translate:-50% 4px}#scroll-to-top-btn:focus-within,#scroll-to-top-btn:hover{background-color:var(--scroll-to-top-button-hover-background-color,#0d0d0dba)}#scroll-to-top-btn:focus-within::after,#scroll-to-top-btn:hover::after{animation:1.2s ease-in infinite bounceUp}@keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-moz-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-webkit-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@media only screen and (max-width:680px){.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{word-break:break-all}} \ No newline at end of file +ο»Ώ/*Common Theme https://github.com/teociaps/SwaggerUI.Themes */@charset "UTF-8";::-webkit-scrollbar{width:16px}::-webkit-scrollbar-thumb{height:56px;border-radius:8px;border:4px solid transparent;background-clip:content-box;background-color:var(--scrollbar-thumb-color,#a5a5a5);transition:background-color .3s}::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover-color,#727272)}body::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-color,#a5a5a5)}*{font-family:system-ui,sans-serif}:focus{font-family:system-ui,sans-serif;outline:0!important}:focus-visible{outline:auto!important;outline-offset:1px}body{background-color:var(--body-background-color,#fafafa);padding-bottom:1rem}*,::after,::before{box-sizing:border-box}.swagger-ui{color:var(--swagger-main-color,#3b4151);-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.15;margin:0}.swagger-ui .loading-container .loading:after{color:var(--swagger-main-color,#3b4151)}.swagger-ui .loading-container .loading:before{border:2px solid var(--loading-container-border-color,rgba(85,85,85,.1));border-top-color:var(--loading-container-border-top-color,rgba(0,0,0,.6))}.swagger-ui .errors-wrapper{background:var(--errors-wrapper-background-color,rgba(249,62,62,.1));border:2px solid var(--errors-wrapper-border-color,#f93e3e)}.swagger-ui .errors-wrapper .errors h4,.swagger-ui .errors-wrapper hgroup h4{color:var(--swagger-main-color,#3b4151)}.swagger-ui .errors-wrapper .errors small{color:var(--errors-wrapper-errors-color,#606060)}.swagger-ui .topbar{background-color:transparent;z-index:1000;padding:0}.swagger-ui .topbar.pinned{position:sticky;top:10px;--pinned-topbar-offset:4.2rem}.swagger-ui:has(> .topbar.pinned){--pinned-topbar-offset:4.2rem}.swagger-ui .topbar.pinned::before{content:'';position:absolute;inset:-10px 0 0;pointer-events:none;user-select:none;backdrop-filter:blur(20px);-webkit-mask-image:linear-gradient(to bottom,black 40%,transparent);mask-image:linear-gradient(to bottom,black 40%,transparent);background:linear-gradient(90deg,rgba(0,0,0,0) 5%,rgba(8,8,8,.4) 50%,rgba(0,0,0,0) 95%)}.swagger-ui .topbar .topbar-wrapper{background-color:var(--topbar-background-color,#000);border:1px solid var(--topbar-border-color,gray);border-top:0;border-radius:0 0 .5rem .5rem;padding:.5rem;box-shadow:0 5px 15px 0 var(--topbar-pinned-shadow-color,rgba(0,0,0,.5))}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn svg{fill:var(--topbar-pin-icon-color,white)}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn{cursor:pointer;background:0 0;border:none;margin:.2rem;padding:.2rem;transition:all .2s;rotate:z 359deg}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:hover{scale:1.05;animation:pintobbar 1.2s infinite}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:active{scale:0.95}.swagger-ui .topbar.pinned .topbar-wrapper{background-color:var(--topbar-pinned-background-color,rgba(0,0,0,.7));border:1px solid var(--topbar-border-color,gray);border-radius:.5rem;backdrop-filter:blur(20px)}.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn{translate:0 5px;rotate:z 0deg}.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:hover{animation:unpintobbar 1.2s infinite}@keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-moz-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-webkit-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@keyframes unpintobbar{10%{translate:0 1px}70%{rotate:z 50deg;translate:-3px 1px}}@-moz-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}@-webkit-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}.swagger-ui .topbar .download-url-wrapper .select-label select,.swagger-ui .topbar .download-url-wrapper input[type=text]{border:2px solid var(--topbar-select-border-color,#62a03f)}.swagger-ui .topbar .download-url-wrapper .select-label{color:var(--topbar-select-label-color,#f0f0f0)}.swagger-ui .topbar .download-url-wrapper .download-url-button{background:var(--topbar-download-url-button-background-color,#62a03f);color:var(--topbar-download-url-button-color,#fff)}#theme-switcher-select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);border:2px solid var(--topbar-select-border-color,#62a03f);min-width:120px;box-shadow:none}.swagger-ui .info .base-url,.swagger-ui .info .title,.swagger-ui .info h1,.swagger-ui .info h2,.swagger-ui .info h3,.swagger-ui .info h4,.swagger-ui .info h5,.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info table{color:var(--swagger-main-color,#3b4151)}.swagger-ui .info a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .info a:hover{color:var(--swagger-info-link-hover,#1f69c0)}.swagger-ui .info .title small{background:var(--api-version-background-color,#7d8492)}.swagger-ui .info .title small.version-stamp{background-color:var(--api-version-stamp-background-color,#89bf04)}.swagger-ui .info .title small pre{color:var(--api-version-color,#fff)}.swagger-ui .scheme-container{background:0 0;box-shadow:none;margin:0;padding:0}.swagger-ui .auth-wrapper{display:flex;flex:1;justify-content:flex-end;border:1px solid var(--auth-wrapper-border-color,#d5d5d5);padding:0;border-radius:5px;box-shadow:0 3px 5px -5px var(--auth-wrapper-box-shadow-color,#000);background:var(--auth-wrapper-background-color,#fff)}.swagger-ui .scheme-container .schemes>label{color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container{border-bottom:1px solid var(--auth-container-border-bottom-color,#ebebeb);color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container .errors{background-color:var(--auth-container-background-color,#fee);color:var(--auth-container-errors-color,red)}.swagger-ui .btn.authorize{background-color:var(--btn-authorize-background-color,transparent);border-color:var(--btn-authorize-border-color,#49cc90);color:var(--btn-authorize-font-color,#49cc90)}.swagger-ui .btn.authorize svg{fill:var(--btn-authorize-svg-fill-color,#49cc90)}.swagger-ui .opblock-tag-section{display:flex;flex-direction:column;border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;margin-block:1rem;overflow:clip}.swagger-ui .opblock-tag-section .expand-collapse-all-btn{display:none;user-select:none}.swagger-ui .opblock-tag-section.is-open .expand-collapse-all-btn{background:var(--opblock-tag-section-expandcollapse-background-color,rgb(220 220 220 / 45%));color:var(--swagger-main-color,#3b4151);border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;padding:5px 10px;font-size:medium;margin-inline:10px;display:block}.swagger-ui .opblock-tag,.swagger-ui .opblock-tag small,.swagger-ui .parameter__type{color:var(--swagger-main-color,#3b4151)}.swagger-ui .opblock-tag{align-items:center;cursor:pointer;display:flex;padding:10px 20px 10px 10px;color:var(--swagger-main-color,#3b4151);font-size:24px;border-bottom:0 dashed var(--opblock-tag-border-bottom-color,#e1e1e1);margin:0;transition:all .2s}.swagger-ui .opblock-tag:focus-within,.swagger-ui .opblock-tag:hover{background:var(--opblock-tag-background-color-hover,rgba(0,0,0,.02))}.swagger-ui .opblock-tag+div .operation-tag-content{margin-inline:5px}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag{position:sticky;top:0;z-index:4;margin-bottom:1rem;border-bottom:1px dashed var(--opblock-tag-border-bottom-color,#e1e1e1);background:var(--body-background-color,#fafafa)}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:focus-within,#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:hover{background:var(--opblock-tag-pinned-background-color-hover,#f1f1f1)}.swagger-ui .opblock-tag-section.is-open .opblock-tag{top:calc(var(--pinned-topbar-offset,0px) + var(--pinned-filter-offset,0px))}.swagger-ui .opblock{border:1px solid var(--opblock-border-color,#000);box-shadow:0 0 3px var(--opblock-shadow-color,rgba(0,0,0,.19))}.swagger-ui .opblock .tab-header .tab-item.active h4 span:after{background:var(--opblock-tabheader-underline-color,gray)}.swagger-ui .opblock .opblock-summary button svg{fill:var(--opblock-summary-svg-icons-color,#000)}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid var(--opblock-summary-border-bottom-color,#000)}.swagger-ui .opblock .opblock-section-header{background:var(--opblock-section-header-background-color,hsla(0,0%,100%,.8));box-shadow:0 1px 2px var(--opblock-section-header-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-section-header h4,.swagger-ui .opblock .opblock-section-header>label{color:var(--swagger-main-color,#3b4151)}.swagger-ui .opblock .opblock-summary-method{background:var(--opblock-summary-method-background-color,#000);color:var(--opblock-summary-method-color,#fff);text-shadow:0 1px 0 var(--opblock-summary-method-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{color:var(--swagger-main-color,#3b4151);word-break:normal}.swagger-ui .opblock.opblock-post{background:var(--opblock-post-background-color,rgba(73,204,144,.1));border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock.opblock-post .opblock-summary-method{background:var(--opblock-post-method-color,#49cc90)}.swagger-ui .opblock.opblock-post .opblock-summary{border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span:after{background:var(--opblock-post-method-color,#49cc90);border-radius:1rem}.swagger-ui .opblock.opblock-put{background:var(--opblock-put-background-color,rgba(252,161,48,.1));border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-put .opblock-summary-method{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-put .opblock-summary{border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span:after{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-delete{background:var(--opblock-delete-background-color,rgba(249,62,62,.1));border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .opblock-summary-method{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span:after{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-get{background:var(--opblock-get-background-color,rgba(97,175,254,.1));border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get .opblock-summary-method{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-get .opblock-summary{border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span:after{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-patch{background:var(--opblock-patch-background-color,rgba(80,227,194,.1));border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .opblock-summary-method{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span:after{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-head{background:var(--opblock-head-background-color,rgba(144,18,254,.1));border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head .opblock-summary-method{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-head .opblock-summary{border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span:after{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-options{background:var(--opblock-options-background-color,rgba(13,90,167,.1));border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .opblock-summary-method{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .opblock-summary{border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span:after{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-deprecated{background:var(--opblock-deprecated-background-color,hsla(0,0%,92%,.1));border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span:after{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .tab li{color:var(--swagger-main-color,#3b4151)}.swagger-ui .tab li:first-of-type:after{background:var(--swagger-tabs-divider-color,rgba(0,0,0,.2))}.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-external-docs-wrapper,.swagger-ui .opblock-external-docs-wrapper h4,.swagger-ui .opblock-external-docs-wrapper p,.swagger-ui .opblock-title_normal,.swagger-ui .opblock-title_normal h4,.swagger-ui .opblock-title_normal p,.swagger-ui .response-col_links,.swagger-ui .response-col_status,.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5{color:var(--swagger-main-color,#3b4151)}.swagger-ui .response-col_links .response-undocumented,.swagger-ui .response-col_status .response-undocumented{color:var(--response-undocumented-color,#909090)}.swagger-ui .response-control-media-type--accept-controller select{border-color:var(--response-control-media-type-color,green)}.swagger-ui .response-control-media-type__accept-message{color:var(--response-control-media-type-color,green)}.swagger-ui .opblock-body pre.microlight{background:var(--opblock-pre-microlight-background-color,#333)!important;color:var(--opblock-pre-microlight-color,#fff)!important}.swagger-ui .download-contents{background:var(--download-contents-background-color,#7d8293);color:var(--download-contents-color,#fff)}.swagger-ui .copy-to-clipboard{background:var(--copy-to-clipboard-background-color,#7d8293)}.swagger-ui .copy-to-clipboard button{background:var(--copy-to-clipboard-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .filter .operation-filter-input{border:2px solid var(--filter-operation-filter-input-border-color,#d8dde7)}.swagger-ui div:has(> .filter-container > .filter.pinned){position:sticky;top:var(--pinned-topbar-offset,0);z-index:5;background:var(--body-background-color,#fafafa);--pinned-filter-offset:6.8rem}.swagger-ui:has(.filter.pinned){--pinned-filter-offset:6.8rem}.swagger-ui .download-url-wrapper .failed,.swagger-ui .filter .failed{color:var(--filter-download-failed-color,red)}.swagger-ui .download-url-wrapper .loading,.swagger-ui .filter .loading{color:var(--filter-download-loading-color,#aaa)}.swagger-ui section h3{color:var(--swagger-main-color,#3b4151)}.swagger-ui .servers>label,.swagger-ui table.headers td{color:var(--swagger-main-color,#3b4151)}.swagger-ui table.headers .header-example{color:var(--table-headers-example-color,#999)}.swagger-ui table thead tr td,.swagger-ui table thead tr th{border-bottom:1px solid var(--table-thead-border-bottom-color,rgba(59,65,81,.2));color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name{color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name.required span{color:var(--parameter-name-required-symbol-color,red)}.swagger-ui .parameter__name.required::after{color:var(--parameter-name-required-color,rgba(255,0,0,.6))}.swagger-ui .parameter__extension,.swagger-ui .parameter__in,.swagger-ui .response__extension{color:var(--parameter-in-extension-color,gray)}.swagger-ui .parameter__deprecated{color:var(--parameter-deprecated-color,red)}.swagger-ui .btn{background:var(--button-background-color,transparent);border:2px solid var(--button-border-color,gray);box-shadow:0 1px 2px var(--button-shadow-color,rgba(0,0,0,.1));color:var(--swagger-main-color,#3b4151)}.swagger-ui .btn:hover{box-shadow:0 0 5px var(--button-shadow-hover-color,rgba(0,0,0,.3))}.swagger-ui .btn.cancel{background-color:var(--button-cancel-background-color,transparent);border-color:var(--button-cancel-border-color,#ff6060);color:var(--button-cancel-color,#ff6060)}.swagger-ui .btn.execute{background-color:var(--button-execute-background-color,#4990e2);border-color:var(--button-execute-border-color,#4990e2);color:var(--button-execute-color,#fff)}.swagger-ui button.invalid{background:var(--button-invalid-background-color,#feebeb);border-color:var(--button-invalid-border-color,#f93e3e)}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{fill:var(--expand-operation-svg-arrow-color,#707070)}.swagger-ui .expand-methods:hover svg,.swagger-ui .expand-operation:hover svg{fill:var(--expand-operation-svg-arrow-hover-color,#404040)}.swagger-ui select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);border:2px solid var(--select-border-color,#41444e);box-shadow:0 1px 2px 0 var(--select-shadow-color,rgba(0,0,0,.25));color:var(--swagger-main-color,#3b4151);cursor:pointer}.swagger-ui select[multiple]{background:var(--select-background-color,#f7f7f7)}.swagger-ui select:disabled{opacity:.5;cursor:not-allowed}.swagger-ui label{color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text],.swagger-ui textarea{background:var(--input-background-color,#fff);border:1px solid var(--input-border-color,#d9d9d9);color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email].invalid,.swagger-ui input[type=file].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid,.swagger-ui select.invalid,.swagger-ui textarea.invalid{background:var(--input-invalid-background-color,#feebeb);border-color:var(--input-invalid-border-color,#f93e3e);color:var(--input-invalid-color,#3b4151)}.swagger-ui input[disabled],.swagger-ui select[disabled]{background-color:var(--input-disabled-background-color,#fafafa);color:var(--input-disabled-color,#888)}.swagger-ui select[disabled]{border-color:var(--select-disabled-border-color,#888)}.swagger-ui textarea[disabled]{background-color:var(--textarea-disabled-background-color,#41444e);color:var(--textarea-disabled-color,#fff)}.swagger-ui textarea{background:var(--textarea-background-color,hsla(0,0%,100%,.8));color:var(--swagger-main-color,#3b4151)}.swagger-ui textarea:focus{border:2px solid var(--textarea-border-focus-color,#61affe)}.swagger-ui textarea.curl{background:var(--textarea-curl-background-color,#41444e);color:var(--textarea-curl-color,#fff)}.swagger-ui .checkbox{color:var(--checkbox-color,#303030)}.swagger-ui .checkbox p{color:var(--swagger-main-color,#3b4151)}.swagger-ui .checkbox input[type=checkbox]+label>.item{background:var(--checkbox-label-item-background-color,#e8e8e8);box-shadow:0 0 0 2px var(--checkbox-label-item-shadow-color,#e8e8e8)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:var(--checkbox-label-item-icon, #e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .dialog-ux .backdrop-ux{background:var(--dialog-backdrop-color,rgba(0,0,0,.8))}.swagger-ui .dialog-ux .modal-ux{background:var(--dialog-background-color,#fff);border:1px solid var(--dialog-border-color,#ebebeb);box-shadow:0 10px 30px 0 var(--dialog-shadow-color,rgba(0,0,0,.2))}.swagger-ui .dialog-ux .modal-ux-content h4,.swagger-ui .dialog-ux .modal-ux-content p,.swagger-ui .dialog-ux .modal-ux-header h3{color:var(--swagger-main-color,#3b4151)}.swagger-ui .dialog-ux .modal-ux-header{border-bottom:1px solid var(--dialog-border-color,#ebebeb)}.swagger-ui .dialog-ux .modal-ux-header button svg{fill:var(--dialog-close-button-icon-color,black)}.swagger-ui .model{color:var(--swagger-main-color,#3b4151)}.swagger-ui .model .deprecated span,.swagger-ui .model .deprecated td{color:var(--model-deprecated-color,#a0a0a0)!important}.swagger-ui .model-toggle{cursor:pointer;-webkit-transition:-webkit-transform .15s ease-in;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}.swagger-ui .model-toggle.collapsed{-webkit-transform:rotate(0);transform:rotate(0)}.swagger-ui .model-toggle::after{background:var(--model-arrow-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .model-hint{background:var(--model-hint-background-color,rgba(0,0,0,.7));color:var(--model-hint-color,#ebebeb)}.swagger-ui .model .property{color:var(--model-property-color,#999)}.swagger-ui .model .property.primitive{color:var(--model-property-primitive-color,#6b6b6b)}.swagger-ui table.model tr.property-row .star{color:var(--model-property-required-symbol-color,red)}.swagger-ui .model .external-docs,.swagger-ui table.model tr.description{color:var(--model-property-description,#666)}.swagger-ui table.model tr.extension{color:var(--model-property-extension,#777)}.swagger-ui section.models{border:1px solid var(--model-section-border-color,rgba(59,65,81,.3));border-radius:.5rem;padding:0;margin:0}.swagger-ui section.models.is-open h4{border-bottom:1px solid var(--model-section-border-color,rgba(59,65,81,.3))}.swagger-ui section.models h4{color:var(--model-section-header-color,#606060);padding:0}.swagger-ui section.models h4:hover{background:var(--model-section-header-hover-background-color,rgba(0,0,0,.02))}.swagger-ui section.models h4 svg{fill:var(--expand-model-svg-arrow-color,#707070)}.swagger-ui section.models h4 button.models-control{color:var(--model-section-header-color,#606060);padding:20px}.swagger-ui section.models h5{color:var(--model-section-little-header-color,#707070)}.swagger-ui section.models .model-container{background:var(--model-container-background-color,rgba(0,0,0,.05))}.swagger-ui section.models .model-container:hover{background:var(--model-container-hover-background-color,rgba(0,0,0,.07))}.swagger-ui .model-box{background:var(--model-box-background-color,rgba(0,0,0,.1))}.swagger-ui .model-title{color:var(--model-title-color,#505050)}.swagger-ui .model-deprecated-warning{color:var(--model-deprecated-warning-color,#f93e3e)}.swagger-ui .prop-type{color:var(--model-prop-type-color,#55a)}.swagger-ui .prop-format{color:var(--model-prop-format-color,#606060)}.swagger-ui .scopes h2{color:var(--swagger-main-color,#3b4151)}.swagger-ui .scopes h2 a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .markdown pre,.swagger-ui .renderedMarkdown pre{color:var(--rendered-markdown-pre-color,#000)}.swagger-ui .markdown code,.swagger-ui .renderedMarkdown code{background:var(--rendered-markdown-code-background-color,rgba(0,0,0,.05));color:var(--rendered-markdown-code-color,#9012fe)}.scroll-to-top-wrapper{position:fixed;bottom:0;left:0;right:0;text-align:end;box-sizing:border-box;margin:0 auto;max-width:1460px;padding:0 20px;width:100%;pointer-events:none;z-index:1000}#scroll-to-top-btn{background-color:var(--scroll-to-top-button-background-color,#000000d1);border:1px solid var(--scroll-to-top-button-border-color,#d1c9c9);border-radius:50%;color:#fff;cursor:pointer;width:2.5rem;aspect-ratio:1;backdrop-filter:blur(20px);margin:10px;position:relative;transition:background-color .5s,opacity .6s .2s,translate .8s,scale .2s;opacity:0;translate:0 100px;box-shadow:0 0 10px 0 var(--scroll-to-top-button-shadow-color,rgba(0,0,0,.4))}#scroll-to-top-btn.showBtn{opacity:1;translate:0 0;pointer-events:auto}#scroll-to-top-btn::before{position:absolute;content:'';top:10px;left:12px;right:12px;background-color:var(--scroll-to-top-button-icon-color,#fff);border-radius:1rem;height:2px}#scroll-to-top-btn::after{position:absolute;content:'';top:15px;left:50%;border:solid var(--scroll-to-top-button-icon-color,#fff);border-width:0 2px 2px 0;padding:3px;transform:rotate(-135deg);translate:-50% 4px}#scroll-to-top-btn:focus-within,#scroll-to-top-btn:hover{background-color:var(--scroll-to-top-button-hover-background-color,#0d0d0dba)}#scroll-to-top-btn:focus-within::after,#scroll-to-top-btn:hover::after{animation:bounceUp 1.2s ease-in infinite}#scroll-to-top-btn:active{scale:0.95}@keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-moz-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-webkit-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@media only screen and (max-width:680px){.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{word-break:break-all}} \ No newline at end of file diff --git a/tests/AspNetCore.SwaggerUI.Themes.Tests/AdvancedOptionsTests.cs b/tests/AspNetCore.SwaggerUI.Themes.Tests/AdvancedOptionsTests.cs index 49ad878..8e001f7 100644 --- a/tests/AspNetCore.SwaggerUI.Themes.Tests/AdvancedOptionsTests.cs +++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/AdvancedOptionsTests.cs @@ -20,6 +20,20 @@ public void EnablePinnableTopbar_AddsCorrectOption() options[AdvancedOptions.PinnableTopbar].ShouldBe(true); } + [Fact] + public void EnablePinnableFilterBar_AddsCorrectOption() + { + // Arrange + var options = new Dictionary(); + + // Act + options.EnablePinnableFilterBar(); + + // Assert + options.ShouldContainKey(AdvancedOptions.PinnableFilterBar); + options[AdvancedOptions.PinnableFilterBar].ShouldBe(true); + } + [Fact] public void EnableBackToTop_AddsCorrectOption() { @@ -114,12 +128,14 @@ public void Apply_AppliesJsFeaturesCorrectly() // Arrange const string JsContent = $""" const pinnableTopbapFeature = {AdvancedOptions.PinnableTopbarJsPH}; + const pinnableFilterBarFeature = {AdvancedOptions.PinnableFilterBarJsPH}; const backToTopFeature = {AdvancedOptions.BackToTopJsPH}; const expandOrCollapseOpsFeature = {AdvancedOptions.ExpandOrCollapseAllOperationsJsPH}; """; var options = new Dictionary { { AdvancedOptions.PinnableTopbar, true }, + { AdvancedOptions.PinnableFilterBar, true }, { AdvancedOptions.BackToTop, true }, { AdvancedOptions.ExpandOrCollapseAllOperations, true } }; @@ -129,6 +145,7 @@ public void Apply_AppliesJsFeaturesCorrectly() const string ExpectedJsConent = """ const pinnableTopbapFeature = true; + const pinnableFilterBarFeature = true; const backToTopFeature = true; const expandOrCollapseOpsFeature = true; """; @@ -136,6 +153,7 @@ public void Apply_AppliesJsFeaturesCorrectly() // Assert result.ShouldBe(ExpectedJsConent); result.ShouldNotContain(AdvancedOptions.PinnableTopbarJsPH); + result.ShouldNotContain(AdvancedOptions.PinnableFilterBarJsPH); } [Fact] From 0cc2b8ec441761e5f8ec110d63ba61949bbdb425 Mon Sep 17 00:00:00 2001 From: Teuz Date: Tue, 11 Aug 2026 10:31:51 +0200 Subject: [PATCH 11/35] fix(themes): correct sticky-offset comment, style the filter pin button, and DRY up pin-toggle JS Corrects an inaccurate comment about which element the filter-bar sticky :has() selector actually targets (the div's own containing block is one level further up than the comment claimed), adds the CSS treatment the new filter-bar pin button was missing (cursor, hover/active feedback, icon color, pinned-state animation, matching the topbar's pin button), and extracts the topbar/filter-bar pin-toggle logic (button creation, click handling, localStorage persistence) into one shared helper instead of two near-identical copies. Also centralizes the pinned-topbar/filter-bar height constants into single :root declarations so the DOM-branch-specific copies required by the :has() offset-propagation trick can't drift apart, and recalibrates the filter bar's height now that its pin button has real padding/margin affecting layout. --- .../Swagger/Themes/AdvancedOptions.cs | 5 +- .../AspNetCore/Swagger/Themes/Scripts/ui.js | 118 +++++++++--------- .../Swagger/Themes/Scripts/ui.min.js | 2 +- .../Swagger/Themes/Styles/common.css | 67 ++++++++-- .../Swagger/Themes/Styles/common.min.css | 2 +- 5 files changed, 119 insertions(+), 75 deletions(-) diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/AdvancedOptions.cs b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/AdvancedOptions.cs index 6514ac1..dbd37ed 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/AdvancedOptions.cs +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/AdvancedOptions.cs @@ -37,7 +37,10 @@ internal static void EnablePinnableTopbar(this IDictionary optio options.TryAdd(PinnableTopbar, true); } - internal static void EnablePinnableFilterBar(this IDictionary options) => options.TryAdd(PinnableFilterBar, true); + internal static void EnablePinnableFilterBar(this IDictionary options) + { + options.TryAdd(PinnableFilterBar, true); + } internal static void EnableBackToTop(this IDictionary options) { diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js index 9fee511..a611cdb 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js @@ -30,86 +30,82 @@ function setUpPinnableTopbar(enabled) { if (enabled === false) return; - const PINNABLE_TOPBAR_STORAGE_KEY = 'swaggerui-pinnable-topbar-preference'; - - const topbarWrapper = document.querySelector('.topbar-wrapper'); - - const pinTopbarBtn = document.createElement('button'); - pinTopbarBtn.setAttribute('id', 'pin-topbar-btn'); - pinTopbarBtn.addEventListener('click', () => { - const currentlyPinned = pinTopbarBtn.parentNode.parentNode.parentNode.classList.contains('pinned'); - applyPinnedState(pinTopbarBtn, !currentlyPinned, true); - pinTopbarBtn?.blur(); - }) - - topbarWrapper.appendChild(pinTopbarBtn); - - // Apply the saved (or default) pinned state directly, so there's no flash - // of unpinned state on page load. No saved preference defaults to pinned, matching - // the topbar's original always-pinned-on-load behavior. - const savedState = localStorage.getItem(PINNABLE_TOPBAR_STORAGE_KEY); - const isPinned = savedState !== null ? savedState === 'pinned' : true; - applyPinnedState(pinTopbarBtn, isPinned, false); - - function applyPinnedState(pinTopbarBtn, pinned, saveToStorage) { - if (pinned) { - pinTopbarBtn.parentNode.parentNode.parentNode.classList.add('pinned'); - setPinnedIconTo(pinTopbarBtn); - pinTopbarBtn.setAttribute('title', 'Unpin topbar'); - } - else { - pinTopbarBtn.parentNode.parentNode.parentNode.classList.remove('pinned'); - setUnpinnedIconTo(pinTopbarBtn); - pinTopbarBtn.setAttribute('title', 'Pin topbar'); - } - - if (saveToStorage) { - localStorage.setItem(PINNABLE_TOPBAR_STORAGE_KEY, pinned ? 'pinned' : 'unpinned'); - } - } + setUpPinnableToggle({ + buttonId: 'pin-topbar-btn', + container: document.querySelector('.topbar-wrapper'), + // 3 parentNode hops up from the button (appended to .topbar-wrapper) reaches .topbar. + pinnedAncestorDepth: 3, + storageKey: 'swaggerui-pinnable-topbar-preference', + pinTitle: 'Pin topbar', + unpinTitle: 'Unpin topbar', + // No saved preference defaults to pinned, matching the topbar's original + // always-pinned-on-load behavior. + defaultPinned: true + }); } function setUpPinnableFilterBar(enabled) { if (enabled === false) return; - const PINNABLE_FILTER_BAR_STORAGE_KEY = 'swaggerui-pinnable-filterbar-preference'; + setUpPinnableToggle({ + buttonId: 'pin-filterbar-btn', + container: document.querySelector('.filter'), + // 1 parentNode hop up from the button (appended directly to .filter) reaches .filter itself. + pinnedAncestorDepth: 1, + storageKey: 'swaggerui-pinnable-filterbar-preference', + pinTitle: 'Pin filter bar', + unpinTitle: 'Unpin filter bar', + // This is a new opt-in feature with no prior shipped behavior to preserve, so it + // defaults to unpinned when there's no saved preference. + defaultPinned: false + }); +} - const filterWrapper = document.querySelector('.filter'); - if (!filterWrapper) +// Shared behavior behind every pinnable element (topbar, filter bar, ...): injects a pin +// button into `container`, toggles a 'pinned' class on the ancestor `pinnedAncestorDepth` +// parentNodes up from that button, and persists the choice to localStorage. +function setUpPinnableToggle({ buttonId, container, pinnedAncestorDepth, storageKey, pinTitle, unpinTitle, defaultPinned }) { + if (!container) return; - const pinFilterBarBtn = document.createElement('button'); - pinFilterBarBtn.setAttribute('id', 'pin-filterbar-btn'); - pinFilterBarBtn.addEventListener('click', () => { - const currentlyPinned = pinFilterBarBtn.parentNode.classList.contains('pinned'); - applyPinnedState(pinFilterBarBtn, !currentlyPinned, true); - pinFilterBarBtn?.blur(); + const pinBtn = document.createElement('button'); + pinBtn.setAttribute('id', buttonId); + pinBtn.addEventListener('click', () => { + const currentlyPinned = pinnedAncestorOf(pinBtn).classList.contains('pinned'); + applyPinnedState(!currentlyPinned, true); + pinBtn?.blur(); }) - filterWrapper.appendChild(pinFilterBarBtn); + container.appendChild(pinBtn); - // Apply the saved (or default) pinned state directly, so there's no flash - // of unpinned state on page load. No saved preference defaults to unpinned, - // since this is a new opt-in feature with no prior shipped behavior to preserve. - const savedState = localStorage.getItem(PINNABLE_FILTER_BAR_STORAGE_KEY); - const isPinned = savedState === 'pinned'; - applyPinnedState(pinFilterBarBtn, isPinned, false); + // Apply the saved (or default) pinned state directly, so there's no flash of the wrong + // state on page load. + const savedState = localStorage.getItem(storageKey); + const isPinned = savedState !== null ? savedState === 'pinned' : defaultPinned; + applyPinnedState(isPinned, false); + + function pinnedAncestorOf(button) { + let ancestor = button; + for (let i = 0; i < pinnedAncestorDepth; i++) + ancestor = ancestor.parentNode; + return ancestor; + } - function applyPinnedState(pinFilterBarBtn, pinned, saveToStorage) { + function applyPinnedState(pinned, saveToStorage) { if (pinned) { - pinFilterBarBtn.parentNode.classList.add('pinned'); - setPinnedIconTo(pinFilterBarBtn); - pinFilterBarBtn.setAttribute('title', 'Unpin filter bar'); + pinnedAncestorOf(pinBtn).classList.add('pinned'); + setPinnedIconTo(pinBtn); + pinBtn.setAttribute('title', unpinTitle); } else { - pinFilterBarBtn.parentNode.classList.remove('pinned'); - setUnpinnedIconTo(pinFilterBarBtn); - pinFilterBarBtn.setAttribute('title', 'Pin filter bar'); + pinnedAncestorOf(pinBtn).classList.remove('pinned'); + setUnpinnedIconTo(pinBtn); + pinBtn.setAttribute('title', pinTitle); } if (saveToStorage) { - localStorage.setItem(PINNABLE_FILTER_BAR_STORAGE_KEY, pinned ? 'pinned' : 'unpinned'); + localStorage.setItem(storageKey, pinned ? 'pinned' : 'unpinned'); } } } diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js index b11f7c5..66a015a 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js @@ -1 +1 @@ -ο»Ώ/*Swagger UI https://github.com/teociaps/SwaggerUI.Themes */const rootElement=document.documentElement;function setUpPinnableTopbar(e){if(!1===e)return;const t="swaggerui-pinnable-topbar-preference",n=document.querySelector(".topbar-wrapper"),o=document.createElement("button");o.setAttribute("id","pin-topbar-btn"),o.addEventListener("click",()=>{const e=o.parentNode.parentNode.parentNode.classList.contains("pinned");a(o,!e,!0),o?.blur()}),n.appendChild(o);const r=localStorage.getItem(t);function a(e,n,o){n?(e.parentNode.parentNode.parentNode.classList.add("pinned"),setPinnedIconTo(e),e.setAttribute("title","Unpin topbar")):(e.parentNode.parentNode.parentNode.classList.remove("pinned"),setUnpinnedIconTo(e),e.setAttribute("title","Pin topbar")),o&&localStorage.setItem(t,n?"pinned":"unpinned")}a(o,null===r||"pinned"===r,!1)}function setUpPinnableFilterBar(e){if(!1===e)return;const t="swaggerui-pinnable-filterbar-preference",n=document.querySelector(".filter");if(!n)return;const o=document.createElement("button");o.setAttribute("id","pin-filterbar-btn"),o.addEventListener("click",()=>{const e=o.parentNode.classList.contains("pinned");a(o,!e,!0),o?.blur()}),n.appendChild(o);const r=localStorage.getItem(t);function a(e,n,o){n?(e.parentNode.classList.add("pinned"),setPinnedIconTo(e),e.setAttribute("title","Unpin filter bar")):(e.parentNode.classList.remove("pinned"),setUnpinnedIconTo(e),e.setAttribute("title","Pin filter bar")),o&&localStorage.setItem(t,n?"pinned":"unpinned")}a(o,"pinned"===r,!1)}function setPinnedIconTo(e){e.innerHTML='\n \n '}function setUnpinnedIconTo(e){e.innerHTML='\n \n '}function setUpScrollToTopButton(e){if(!1===e)return;const t=document.createElement("div");t.classList.add("scroll-to-top-wrapper");const n=document.createElement("button");n.setAttribute("id","scroll-to-top-btn"),n.setAttribute("title","Back to top"),n.addEventListener("click",()=>{scrollToTop(),n?.blur()}),t.appendChild(n);document.getElementById("swagger-ui").appendChild(t);const o=()=>{window.scrollY>=200?n.classList.add("showBtn"):n.classList.remove("showBtn")};window.addEventListener("scroll",o),window.addEventListener("resize",o)}function scrollToTop(){rootElement.scrollTo({top:0,behavior:"smooth"})}function setUpExpandAndCollapseOperationsButtons(e){if(!1===e)return;let t=0;const n=document.getElementById("swagger-ui")||document.querySelector(".swagger-ui");function o(){const e=document.querySelectorAll(".opblock-tag-section");return!(!e||0===e.length)&&(e.forEach(e=>function(e){if(!e||"true"===e.dataset.expandCollapseAttached)return;const t=e.querySelector("h3"),n=t?.querySelector("button.expand-operation");if(t&&!t.querySelector(".expand-collapse-all-btn")){const o=document.createElement("button");o.setAttribute("title","Expand/Collapse all the operations"),o.classList.add("expand-collapse-all-btn"),o.innerHTML="Expand/Collapse All",n?n.before(o):t.appendChild(o),o.addEventListener("click",t=>{t.preventDefault(),t.stopPropagation();const n=e.querySelectorAll(".opblock .opblock-control-arrow");n&&0!==n.length&&(Array.from(n).every(e=>"true"===e.getAttribute("aria-expanded"))?n.forEach(e=>{"true"===e.getAttribute("aria-expanded")&&e.click()}):n.forEach(e=>{"false"===e.getAttribute("aria-expanded")&&e.click()}))})}e.dataset.expandCollapseAttached="true"}(e)),!0)}!function e(){if(t++,o())try{const e=n||document.body;new MutationObserver(e=>{for(const t of e)t.addedNodes&&t.addedNodes.length>0&&o()}).observe(e,{childList:!0,subtree:!0})}catch(e){}else t<60&&setTimeout(e,200)}()}function setUpThemeSwitcher(e){if(!1===e)return;const t="swaggerui-theme-preference";let n=null,o=null;function r(e,r){const a=n?.themes.find(t=>t.name===e);if(!a)return void console.warn(`[ThemeSwitcher] Theme not found: ${e}`);const l=document.querySelectorAll('link[rel="stylesheet"]'),s=n.themes.map(e=>e.cssPath);let c=!1;l.forEach(t=>{if(t.dataset.theme||s.some(e=>t.href?.endsWith(e))){t.dataset.theme===e||t.href?.endsWith(a.cssPath)?(t.disabled=!1,t.dataset.theme=e,c=!0):t.disabled=!0}}),c||console.warn(`[ThemeSwitcher] Could not activate theme: ${e}`),o=e,r&&localStorage.setItem(t,e);const i=document.getElementById("theme-switcher-select");i&&i.value!==e&&(i.value=e)}fetch("/themes/metadata.json").then(e=>e.ok?e.json():(console.warn("[ThemeSwitcher] Failed to load theme metadata"),null)).then(e=>{!e?.themes||e.themes.length<2?console.warn("[ThemeSwitcher] Not enough themes available for switcher"):(n=e,o=detectCurrentTheme(e.themes),function(e){const n=localStorage.getItem(t);n&&e.some(e=>e.name===n)&&n!==o&&r(n,!1)}(e.themes),function(e){const t=document.querySelector(".topbar-wrapper");if(!t)return void console.warn("[ThemeSwitcher] Topbar not found");const n=document.createElement("select");n.id="theme-switcher-select",n.setAttribute("aria-label","Select theme"),n.title="Switch theme",e.themes.forEach(t=>{const r=document.createElement("option");r.value=t.name,r.textContent=formatThemeName(t.name,e.config?.displayFormat||"{name}"),t.name===o&&(r.selected=!0),n.appendChild(r)}),n.addEventListener("change",e=>{r(e.target.value,!0)});const a=document.getElementById("pin-topbar-btn");a?a.before(n):t.appendChild(n)}(e))}).catch(e=>{console.error("[ThemeSwitcher] Error loading themes:",e)})}function detectCurrentTheme(e){const t=document.querySelector('link[rel="stylesheet"]:not([disabled])[data-theme]');if(t?.dataset.theme)return t.dataset.theme;const n=document.querySelectorAll('link[rel="stylesheet"]:not([disabled])');for(const t of n){const n=e.find(e=>!!t.href&&(t.href.includes(e.cssPath)||t.href.endsWith(e.cssPath)||t.href.includes(e.name.toLowerCase())));if(n)return n.name}return e.length>0?e[0].name:null}function formatThemeName(e,t){const n=e.replaceAll(/([A-Z])/g," $1").trim().split(/[\s_-]+/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(" ");return t.replace("{name}",n)}window.onpageshow=function(){let e=setInterval(function(){null!=document.getElementById("swagger-ui")&&(clearInterval(e),console.log("Hello Swagger UI!"),setUpPinnableTopbar({$PINNABLE_TOPBAR}),setUpPinnableFilterBar({$PINNABLE_FILTER_BAR}),setUpScrollToTopButton({$BACK_TO_TOP}),setUpExpandAndCollapseOperationsButtons({$EXPAND_COLLAPSE_ALL_OPERATIONS}),setUpThemeSwitcher({$THEME_SWITCHER}))},100)}; \ No newline at end of file +ο»Ώ/*Swagger UI https://github.com/teociaps/SwaggerUI.Themes */const rootElement=document.documentElement;function setUpPinnableTopbar(e){!1!==e&&setUpPinnableToggle({buttonId:"pin-topbar-btn",container:document.querySelector(".topbar-wrapper"),pinnedAncestorDepth:3,storageKey:"swaggerui-pinnable-topbar-preference",pinTitle:"Pin topbar",unpinTitle:"Unpin topbar",defaultPinned:!0})}function setUpPinnableFilterBar(e){!1!==e&&setUpPinnableToggle({buttonId:"pin-filterbar-btn",container:document.querySelector(".filter"),pinnedAncestorDepth:1,storageKey:"swaggerui-pinnable-filterbar-preference",pinTitle:"Pin filter bar",unpinTitle:"Unpin filter bar",defaultPinned:!1})}function setUpPinnableToggle({buttonId:e,container:t,pinnedAncestorDepth:n,storageKey:o,pinTitle:l,unpinTitle:a,defaultPinned:r}){if(!t)return;const i=document.createElement("button");i.setAttribute("id",e),i.addEventListener("click",()=>{d(!c(i).classList.contains("pinned"),!0),i?.blur()}),t.appendChild(i);const s=localStorage.getItem(o);function c(e){let t=e;for(let e=0;e{scrollToTop(),n?.blur()}),t.appendChild(n);document.getElementById("swagger-ui").appendChild(t);const o=()=>{window.scrollY>=200?n.classList.add("showBtn"):n.classList.remove("showBtn")};window.addEventListener("scroll",o),window.addEventListener("resize",o)}function scrollToTop(){rootElement.scrollTo({top:0,behavior:"smooth"})}function setUpExpandAndCollapseOperationsButtons(e){if(!1===e)return;let t=0;const n=document.getElementById("swagger-ui")||document.querySelector(".swagger-ui");function o(){const e=document.querySelectorAll(".opblock-tag-section");return!(!e||0===e.length)&&(e.forEach(e=>function(e){if(!e||"true"===e.dataset.expandCollapseAttached)return;const t=e.querySelector("h3"),n=t?.querySelector("button.expand-operation");if(t&&!t.querySelector(".expand-collapse-all-btn")){const o=document.createElement("button");o.setAttribute("title","Expand/Collapse all the operations"),o.classList.add("expand-collapse-all-btn"),o.innerHTML="Expand/Collapse All",n?n.before(o):t.appendChild(o),o.addEventListener("click",t=>{t.preventDefault(),t.stopPropagation();const n=e.querySelectorAll(".opblock .opblock-control-arrow");n&&0!==n.length&&(Array.from(n).every(e=>"true"===e.getAttribute("aria-expanded"))?n.forEach(e=>{"true"===e.getAttribute("aria-expanded")&&e.click()}):n.forEach(e=>{"false"===e.getAttribute("aria-expanded")&&e.click()}))})}e.dataset.expandCollapseAttached="true"}(e)),!0)}!function e(){if(t++,o())try{const e=n||document.body;new MutationObserver(e=>{for(const t of e)t.addedNodes&&t.addedNodes.length>0&&o()}).observe(e,{childList:!0,subtree:!0})}catch(e){}else t<60&&setTimeout(e,200)}()}function setUpThemeSwitcher(e){if(!1===e)return;const t="swaggerui-theme-preference";let n=null,o=null;function l(e,l){const a=n?.themes.find(t=>t.name===e);if(!a)return void console.warn(`[ThemeSwitcher] Theme not found: ${e}`);const r=document.querySelectorAll('link[rel="stylesheet"]'),i=n.themes.map(e=>e.cssPath);let s=!1;r.forEach(t=>{if(t.dataset.theme||i.some(e=>t.href?.endsWith(e))){t.dataset.theme===e||t.href?.endsWith(a.cssPath)?(t.disabled=!1,t.dataset.theme=e,s=!0):t.disabled=!0}}),s||console.warn(`[ThemeSwitcher] Could not activate theme: ${e}`),o=e,l&&localStorage.setItem(t,e);const c=document.getElementById("theme-switcher-select");c&&c.value!==e&&(c.value=e)}fetch("/themes/metadata.json").then(e=>e.ok?e.json():(console.warn("[ThemeSwitcher] Failed to load theme metadata"),null)).then(e=>{!e?.themes||e.themes.length<2?console.warn("[ThemeSwitcher] Not enough themes available for switcher"):(n=e,o=detectCurrentTheme(e.themes),function(e){const n=localStorage.getItem(t);n&&e.some(e=>e.name===n)&&n!==o&&l(n,!1)}(e.themes),function(e){const t=document.querySelector(".topbar-wrapper");if(!t)return void console.warn("[ThemeSwitcher] Topbar not found");const n=document.createElement("select");n.id="theme-switcher-select",n.setAttribute("aria-label","Select theme"),n.title="Switch theme",e.themes.forEach(t=>{const l=document.createElement("option");l.value=t.name,l.textContent=formatThemeName(t.name,e.config?.displayFormat||"{name}"),t.name===o&&(l.selected=!0),n.appendChild(l)}),n.addEventListener("change",e=>{l(e.target.value,!0)});const a=document.getElementById("pin-topbar-btn");a?a.before(n):t.appendChild(n)}(e))}).catch(e=>{console.error("[ThemeSwitcher] Error loading themes:",e)})}function detectCurrentTheme(e){const t=document.querySelector('link[rel="stylesheet"]:not([disabled])[data-theme]');if(t?.dataset.theme)return t.dataset.theme;const n=document.querySelectorAll('link[rel="stylesheet"]:not([disabled])');for(const t of n){const n=e.find(e=>!!t.href&&(t.href.includes(e.cssPath)||t.href.endsWith(e.cssPath)||t.href.includes(e.name.toLowerCase())));if(n)return n.name}return e.length>0?e[0].name:null}function formatThemeName(e,t){const n=e.replaceAll(/([A-Z])/g," $1").trim().split(/[\s_-]+/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(" ");return t.replace("{name}",n)}window.onpageshow=function(){let e=setInterval(function(){null!=document.getElementById("swagger-ui")&&(clearInterval(e),console.log("Hello Swagger UI!"),setUpPinnableTopbar({$PINNABLE_TOPBAR}),setUpPinnableFilterBar({$PINNABLE_FILTER_BAR}),setUpScrollToTopButton({$BACK_TO_TOP}),setUpExpandAndCollapseOperationsButtons({$EXPAND_COLLAPSE_ALL_OPERATIONS}),setUpThemeSwitcher({$THEME_SWITCHER}))},100)}; \ No newline at end of file diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css index 8c65b64..0d49975 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css @@ -6,6 +6,15 @@ @charset "UTF-8"; +:root { + /* Rendered heights of the pinned topbar/filter bar, used to stack sticky elements below + them without overlap. Declared once here so the two DOM-branch-specific copies below + (needed because of how .topbar.pinned/.filter.pinned relate to the elements that read + them) can't drift out of sync. */ + --topbar-pinned-height: 4.2rem; + --filterbar-pinned-height: 7.2rem; +} + *::-webkit-scrollbar { width: 16px; } @@ -95,7 +104,7 @@ body { .swagger-ui .topbar.pinned { position: sticky; top: 10px; - --pinned-topbar-offset: 4.2rem; + --pinned-topbar-offset: var(--topbar-pinned-height); } /* The filter bar and the sticky operation tag headers live in a different branch of the @@ -103,7 +112,7 @@ body { .topbar.pinned. Re-declare it here, on their closest common ancestor, so it only carries a nonzero value while the topbar is actually pinned. */ .swagger-ui:has(> .topbar.pinned) { - --pinned-topbar-offset: 4.2rem; + --pinned-topbar-offset: var(--topbar-pinned-height); } .swagger-ui .topbar.pinned::before { @@ -659,19 +668,22 @@ body { border: 2px solid var(--filter-operation-filter-input-border-color, #d8dde7); } - /* .filter's own wrapper elements (.filter-container and its parent) are exactly as tall as - the filter bar itself, so making .filter position:sticky would have no visible effect: - a sticky box is bounded by its containing block, and that block would offer no extra room - for it to remain "stuck" while the page scrolls. The next ancestor up spans the full page - content (info box, filter, operations list, models, ...) as siblings, so that's the element - that actually needs position: sticky. It has no stable class of its own in Swagger UI's - markup, so it's targeted structurally via its known descendants instead. */ + /* .filter's own wrapper (.filter-container) is exactly as tall as the filter bar itself + (its only child), so making .filter, or .filter-container, position:sticky would have no + visible effect: a sticky box is bounded by its own containing block, and that block would + offer no extra room for it to remain "stuck" while the page scrolls. What actually needs + position: sticky is .filter-container's own parent, matched here by this selector (the + ">" combinators pin it down to exactly that element): unlike .filter-container, that div's + *own* parent, one level further up, spans the full page content (info box, filter, + operations list, models, ...) as siblings, which gives this element room to stick. It has + no stable class of its own in Swagger UI's markup, so it's targeted structurally via its + known descendants instead. */ .swagger-ui div:has(> .filter-container > .filter.pinned) { position: sticky; top: var(--pinned-topbar-offset, 0px); z-index: 5; background: var(--body-background-color, #fafafa); - --pinned-filter-offset: 6.8rem; + --pinned-filter-offset: var(--filterbar-pinned-height); } /* The sticky operation tag headers live in a different branch of the DOM than the filter bar, @@ -679,9 +691,42 @@ body { on their closest common ancestor, so it only carries a nonzero value while the filter bar is actually pinned. */ .swagger-ui:has(.filter.pinned) { - --pinned-filter-offset: 6.8rem; + --pinned-filter-offset: var(--filterbar-pinned-height); } + .swagger-ui .filter #pin-filterbar-btn svg { + fill: var(--filterbar-pin-icon-color, #707070); + } + + .swagger-ui .filter #pin-filterbar-btn { + cursor: pointer; + background: transparent; + border: none; + margin: .2rem; + padding: .2rem; + transition: all .2s; + rotate: z 359deg; + } + + .swagger-ui .filter #pin-filterbar-btn:hover, + .swagger-ui .filter #pin-filterbar-btn:focus-within { + scale: 1.05; + animation: pintobbar 1.2s infinite; + } + + .swagger-ui .filter #pin-filterbar-btn:active { + scale: 0.95; + } + + .swagger-ui .filter.pinned #pin-filterbar-btn { + rotate: z 0deg; + } + + .swagger-ui .filter.pinned #pin-filterbar-btn:hover, + .swagger-ui .filter.pinned #pin-filterbar-btn:focus-within { + animation: unpintobbar 1.2s infinite; + } + .swagger-ui .download-url-wrapper .failed, .swagger-ui .filter .failed { color: var(--filter-download-failed-color, red) diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css index 6275525..0b160f2 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css @@ -1 +1 @@ -ο»Ώ/*Common Theme https://github.com/teociaps/SwaggerUI.Themes */@charset "UTF-8";::-webkit-scrollbar{width:16px}::-webkit-scrollbar-thumb{height:56px;border-radius:8px;border:4px solid transparent;background-clip:content-box;background-color:var(--scrollbar-thumb-color,#a5a5a5);transition:background-color .3s}::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover-color,#727272)}body::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-color,#a5a5a5)}*{font-family:system-ui,sans-serif}:focus{font-family:system-ui,sans-serif;outline:0!important}:focus-visible{outline:auto!important;outline-offset:1px}body{background-color:var(--body-background-color,#fafafa);padding-bottom:1rem}*,::after,::before{box-sizing:border-box}.swagger-ui{color:var(--swagger-main-color,#3b4151);-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.15;margin:0}.swagger-ui .loading-container .loading:after{color:var(--swagger-main-color,#3b4151)}.swagger-ui .loading-container .loading:before{border:2px solid var(--loading-container-border-color,rgba(85,85,85,.1));border-top-color:var(--loading-container-border-top-color,rgba(0,0,0,.6))}.swagger-ui .errors-wrapper{background:var(--errors-wrapper-background-color,rgba(249,62,62,.1));border:2px solid var(--errors-wrapper-border-color,#f93e3e)}.swagger-ui .errors-wrapper .errors h4,.swagger-ui .errors-wrapper hgroup h4{color:var(--swagger-main-color,#3b4151)}.swagger-ui .errors-wrapper .errors small{color:var(--errors-wrapper-errors-color,#606060)}.swagger-ui .topbar{background-color:transparent;z-index:1000;padding:0}.swagger-ui .topbar.pinned{position:sticky;top:10px;--pinned-topbar-offset:4.2rem}.swagger-ui:has(> .topbar.pinned){--pinned-topbar-offset:4.2rem}.swagger-ui .topbar.pinned::before{content:'';position:absolute;inset:-10px 0 0;pointer-events:none;user-select:none;backdrop-filter:blur(20px);-webkit-mask-image:linear-gradient(to bottom,black 40%,transparent);mask-image:linear-gradient(to bottom,black 40%,transparent);background:linear-gradient(90deg,rgba(0,0,0,0) 5%,rgba(8,8,8,.4) 50%,rgba(0,0,0,0) 95%)}.swagger-ui .topbar .topbar-wrapper{background-color:var(--topbar-background-color,#000);border:1px solid var(--topbar-border-color,gray);border-top:0;border-radius:0 0 .5rem .5rem;padding:.5rem;box-shadow:0 5px 15px 0 var(--topbar-pinned-shadow-color,rgba(0,0,0,.5))}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn svg{fill:var(--topbar-pin-icon-color,white)}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn{cursor:pointer;background:0 0;border:none;margin:.2rem;padding:.2rem;transition:all .2s;rotate:z 359deg}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:hover{scale:1.05;animation:pintobbar 1.2s infinite}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:active{scale:0.95}.swagger-ui .topbar.pinned .topbar-wrapper{background-color:var(--topbar-pinned-background-color,rgba(0,0,0,.7));border:1px solid var(--topbar-border-color,gray);border-radius:.5rem;backdrop-filter:blur(20px)}.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn{translate:0 5px;rotate:z 0deg}.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:hover{animation:unpintobbar 1.2s infinite}@keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-moz-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-webkit-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@keyframes unpintobbar{10%{translate:0 1px}70%{rotate:z 50deg;translate:-3px 1px}}@-moz-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}@-webkit-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}.swagger-ui .topbar .download-url-wrapper .select-label select,.swagger-ui .topbar .download-url-wrapper input[type=text]{border:2px solid var(--topbar-select-border-color,#62a03f)}.swagger-ui .topbar .download-url-wrapper .select-label{color:var(--topbar-select-label-color,#f0f0f0)}.swagger-ui .topbar .download-url-wrapper .download-url-button{background:var(--topbar-download-url-button-background-color,#62a03f);color:var(--topbar-download-url-button-color,#fff)}#theme-switcher-select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);border:2px solid var(--topbar-select-border-color,#62a03f);min-width:120px;box-shadow:none}.swagger-ui .info .base-url,.swagger-ui .info .title,.swagger-ui .info h1,.swagger-ui .info h2,.swagger-ui .info h3,.swagger-ui .info h4,.swagger-ui .info h5,.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info table{color:var(--swagger-main-color,#3b4151)}.swagger-ui .info a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .info a:hover{color:var(--swagger-info-link-hover,#1f69c0)}.swagger-ui .info .title small{background:var(--api-version-background-color,#7d8492)}.swagger-ui .info .title small.version-stamp{background-color:var(--api-version-stamp-background-color,#89bf04)}.swagger-ui .info .title small pre{color:var(--api-version-color,#fff)}.swagger-ui .scheme-container{background:0 0;box-shadow:none;margin:0;padding:0}.swagger-ui .auth-wrapper{display:flex;flex:1;justify-content:flex-end;border:1px solid var(--auth-wrapper-border-color,#d5d5d5);padding:0;border-radius:5px;box-shadow:0 3px 5px -5px var(--auth-wrapper-box-shadow-color,#000);background:var(--auth-wrapper-background-color,#fff)}.swagger-ui .scheme-container .schemes>label{color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container{border-bottom:1px solid var(--auth-container-border-bottom-color,#ebebeb);color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container .errors{background-color:var(--auth-container-background-color,#fee);color:var(--auth-container-errors-color,red)}.swagger-ui .btn.authorize{background-color:var(--btn-authorize-background-color,transparent);border-color:var(--btn-authorize-border-color,#49cc90);color:var(--btn-authorize-font-color,#49cc90)}.swagger-ui .btn.authorize svg{fill:var(--btn-authorize-svg-fill-color,#49cc90)}.swagger-ui .opblock-tag-section{display:flex;flex-direction:column;border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;margin-block:1rem;overflow:clip}.swagger-ui .opblock-tag-section .expand-collapse-all-btn{display:none;user-select:none}.swagger-ui .opblock-tag-section.is-open .expand-collapse-all-btn{background:var(--opblock-tag-section-expandcollapse-background-color,rgb(220 220 220 / 45%));color:var(--swagger-main-color,#3b4151);border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;padding:5px 10px;font-size:medium;margin-inline:10px;display:block}.swagger-ui .opblock-tag,.swagger-ui .opblock-tag small,.swagger-ui .parameter__type{color:var(--swagger-main-color,#3b4151)}.swagger-ui .opblock-tag{align-items:center;cursor:pointer;display:flex;padding:10px 20px 10px 10px;color:var(--swagger-main-color,#3b4151);font-size:24px;border-bottom:0 dashed var(--opblock-tag-border-bottom-color,#e1e1e1);margin:0;transition:all .2s}.swagger-ui .opblock-tag:focus-within,.swagger-ui .opblock-tag:hover{background:var(--opblock-tag-background-color-hover,rgba(0,0,0,.02))}.swagger-ui .opblock-tag+div .operation-tag-content{margin-inline:5px}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag{position:sticky;top:0;z-index:4;margin-bottom:1rem;border-bottom:1px dashed var(--opblock-tag-border-bottom-color,#e1e1e1);background:var(--body-background-color,#fafafa)}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:focus-within,#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:hover{background:var(--opblock-tag-pinned-background-color-hover,#f1f1f1)}.swagger-ui .opblock-tag-section.is-open .opblock-tag{top:calc(var(--pinned-topbar-offset,0px) + var(--pinned-filter-offset,0px))}.swagger-ui .opblock{border:1px solid var(--opblock-border-color,#000);box-shadow:0 0 3px var(--opblock-shadow-color,rgba(0,0,0,.19))}.swagger-ui .opblock .tab-header .tab-item.active h4 span:after{background:var(--opblock-tabheader-underline-color,gray)}.swagger-ui .opblock .opblock-summary button svg{fill:var(--opblock-summary-svg-icons-color,#000)}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid var(--opblock-summary-border-bottom-color,#000)}.swagger-ui .opblock .opblock-section-header{background:var(--opblock-section-header-background-color,hsla(0,0%,100%,.8));box-shadow:0 1px 2px var(--opblock-section-header-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-section-header h4,.swagger-ui .opblock .opblock-section-header>label{color:var(--swagger-main-color,#3b4151)}.swagger-ui .opblock .opblock-summary-method{background:var(--opblock-summary-method-background-color,#000);color:var(--opblock-summary-method-color,#fff);text-shadow:0 1px 0 var(--opblock-summary-method-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{color:var(--swagger-main-color,#3b4151);word-break:normal}.swagger-ui .opblock.opblock-post{background:var(--opblock-post-background-color,rgba(73,204,144,.1));border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock.opblock-post .opblock-summary-method{background:var(--opblock-post-method-color,#49cc90)}.swagger-ui .opblock.opblock-post .opblock-summary{border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span:after{background:var(--opblock-post-method-color,#49cc90);border-radius:1rem}.swagger-ui .opblock.opblock-put{background:var(--opblock-put-background-color,rgba(252,161,48,.1));border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-put .opblock-summary-method{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-put .opblock-summary{border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span:after{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-delete{background:var(--opblock-delete-background-color,rgba(249,62,62,.1));border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .opblock-summary-method{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span:after{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-get{background:var(--opblock-get-background-color,rgba(97,175,254,.1));border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get .opblock-summary-method{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-get .opblock-summary{border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span:after{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-patch{background:var(--opblock-patch-background-color,rgba(80,227,194,.1));border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .opblock-summary-method{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span:after{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-head{background:var(--opblock-head-background-color,rgba(144,18,254,.1));border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head .opblock-summary-method{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-head .opblock-summary{border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span:after{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-options{background:var(--opblock-options-background-color,rgba(13,90,167,.1));border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .opblock-summary-method{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .opblock-summary{border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span:after{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-deprecated{background:var(--opblock-deprecated-background-color,hsla(0,0%,92%,.1));border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span:after{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .tab li{color:var(--swagger-main-color,#3b4151)}.swagger-ui .tab li:first-of-type:after{background:var(--swagger-tabs-divider-color,rgba(0,0,0,.2))}.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-external-docs-wrapper,.swagger-ui .opblock-external-docs-wrapper h4,.swagger-ui .opblock-external-docs-wrapper p,.swagger-ui .opblock-title_normal,.swagger-ui .opblock-title_normal h4,.swagger-ui .opblock-title_normal p,.swagger-ui .response-col_links,.swagger-ui .response-col_status,.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5{color:var(--swagger-main-color,#3b4151)}.swagger-ui .response-col_links .response-undocumented,.swagger-ui .response-col_status .response-undocumented{color:var(--response-undocumented-color,#909090)}.swagger-ui .response-control-media-type--accept-controller select{border-color:var(--response-control-media-type-color,green)}.swagger-ui .response-control-media-type__accept-message{color:var(--response-control-media-type-color,green)}.swagger-ui .opblock-body pre.microlight{background:var(--opblock-pre-microlight-background-color,#333)!important;color:var(--opblock-pre-microlight-color,#fff)!important}.swagger-ui .download-contents{background:var(--download-contents-background-color,#7d8293);color:var(--download-contents-color,#fff)}.swagger-ui .copy-to-clipboard{background:var(--copy-to-clipboard-background-color,#7d8293)}.swagger-ui .copy-to-clipboard button{background:var(--copy-to-clipboard-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .filter .operation-filter-input{border:2px solid var(--filter-operation-filter-input-border-color,#d8dde7)}.swagger-ui div:has(> .filter-container > .filter.pinned){position:sticky;top:var(--pinned-topbar-offset,0);z-index:5;background:var(--body-background-color,#fafafa);--pinned-filter-offset:6.8rem}.swagger-ui:has(.filter.pinned){--pinned-filter-offset:6.8rem}.swagger-ui .download-url-wrapper .failed,.swagger-ui .filter .failed{color:var(--filter-download-failed-color,red)}.swagger-ui .download-url-wrapper .loading,.swagger-ui .filter .loading{color:var(--filter-download-loading-color,#aaa)}.swagger-ui section h3{color:var(--swagger-main-color,#3b4151)}.swagger-ui .servers>label,.swagger-ui table.headers td{color:var(--swagger-main-color,#3b4151)}.swagger-ui table.headers .header-example{color:var(--table-headers-example-color,#999)}.swagger-ui table thead tr td,.swagger-ui table thead tr th{border-bottom:1px solid var(--table-thead-border-bottom-color,rgba(59,65,81,.2));color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name{color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name.required span{color:var(--parameter-name-required-symbol-color,red)}.swagger-ui .parameter__name.required::after{color:var(--parameter-name-required-color,rgba(255,0,0,.6))}.swagger-ui .parameter__extension,.swagger-ui .parameter__in,.swagger-ui .response__extension{color:var(--parameter-in-extension-color,gray)}.swagger-ui .parameter__deprecated{color:var(--parameter-deprecated-color,red)}.swagger-ui .btn{background:var(--button-background-color,transparent);border:2px solid var(--button-border-color,gray);box-shadow:0 1px 2px var(--button-shadow-color,rgba(0,0,0,.1));color:var(--swagger-main-color,#3b4151)}.swagger-ui .btn:hover{box-shadow:0 0 5px var(--button-shadow-hover-color,rgba(0,0,0,.3))}.swagger-ui .btn.cancel{background-color:var(--button-cancel-background-color,transparent);border-color:var(--button-cancel-border-color,#ff6060);color:var(--button-cancel-color,#ff6060)}.swagger-ui .btn.execute{background-color:var(--button-execute-background-color,#4990e2);border-color:var(--button-execute-border-color,#4990e2);color:var(--button-execute-color,#fff)}.swagger-ui button.invalid{background:var(--button-invalid-background-color,#feebeb);border-color:var(--button-invalid-border-color,#f93e3e)}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{fill:var(--expand-operation-svg-arrow-color,#707070)}.swagger-ui .expand-methods:hover svg,.swagger-ui .expand-operation:hover svg{fill:var(--expand-operation-svg-arrow-hover-color,#404040)}.swagger-ui select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);border:2px solid var(--select-border-color,#41444e);box-shadow:0 1px 2px 0 var(--select-shadow-color,rgba(0,0,0,.25));color:var(--swagger-main-color,#3b4151);cursor:pointer}.swagger-ui select[multiple]{background:var(--select-background-color,#f7f7f7)}.swagger-ui select:disabled{opacity:.5;cursor:not-allowed}.swagger-ui label{color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text],.swagger-ui textarea{background:var(--input-background-color,#fff);border:1px solid var(--input-border-color,#d9d9d9);color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email].invalid,.swagger-ui input[type=file].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid,.swagger-ui select.invalid,.swagger-ui textarea.invalid{background:var(--input-invalid-background-color,#feebeb);border-color:var(--input-invalid-border-color,#f93e3e);color:var(--input-invalid-color,#3b4151)}.swagger-ui input[disabled],.swagger-ui select[disabled]{background-color:var(--input-disabled-background-color,#fafafa);color:var(--input-disabled-color,#888)}.swagger-ui select[disabled]{border-color:var(--select-disabled-border-color,#888)}.swagger-ui textarea[disabled]{background-color:var(--textarea-disabled-background-color,#41444e);color:var(--textarea-disabled-color,#fff)}.swagger-ui textarea{background:var(--textarea-background-color,hsla(0,0%,100%,.8));color:var(--swagger-main-color,#3b4151)}.swagger-ui textarea:focus{border:2px solid var(--textarea-border-focus-color,#61affe)}.swagger-ui textarea.curl{background:var(--textarea-curl-background-color,#41444e);color:var(--textarea-curl-color,#fff)}.swagger-ui .checkbox{color:var(--checkbox-color,#303030)}.swagger-ui .checkbox p{color:var(--swagger-main-color,#3b4151)}.swagger-ui .checkbox input[type=checkbox]+label>.item{background:var(--checkbox-label-item-background-color,#e8e8e8);box-shadow:0 0 0 2px var(--checkbox-label-item-shadow-color,#e8e8e8)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:var(--checkbox-label-item-icon, #e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .dialog-ux .backdrop-ux{background:var(--dialog-backdrop-color,rgba(0,0,0,.8))}.swagger-ui .dialog-ux .modal-ux{background:var(--dialog-background-color,#fff);border:1px solid var(--dialog-border-color,#ebebeb);box-shadow:0 10px 30px 0 var(--dialog-shadow-color,rgba(0,0,0,.2))}.swagger-ui .dialog-ux .modal-ux-content h4,.swagger-ui .dialog-ux .modal-ux-content p,.swagger-ui .dialog-ux .modal-ux-header h3{color:var(--swagger-main-color,#3b4151)}.swagger-ui .dialog-ux .modal-ux-header{border-bottom:1px solid var(--dialog-border-color,#ebebeb)}.swagger-ui .dialog-ux .modal-ux-header button svg{fill:var(--dialog-close-button-icon-color,black)}.swagger-ui .model{color:var(--swagger-main-color,#3b4151)}.swagger-ui .model .deprecated span,.swagger-ui .model .deprecated td{color:var(--model-deprecated-color,#a0a0a0)!important}.swagger-ui .model-toggle{cursor:pointer;-webkit-transition:-webkit-transform .15s ease-in;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}.swagger-ui .model-toggle.collapsed{-webkit-transform:rotate(0);transform:rotate(0)}.swagger-ui .model-toggle::after{background:var(--model-arrow-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .model-hint{background:var(--model-hint-background-color,rgba(0,0,0,.7));color:var(--model-hint-color,#ebebeb)}.swagger-ui .model .property{color:var(--model-property-color,#999)}.swagger-ui .model .property.primitive{color:var(--model-property-primitive-color,#6b6b6b)}.swagger-ui table.model tr.property-row .star{color:var(--model-property-required-symbol-color,red)}.swagger-ui .model .external-docs,.swagger-ui table.model tr.description{color:var(--model-property-description,#666)}.swagger-ui table.model tr.extension{color:var(--model-property-extension,#777)}.swagger-ui section.models{border:1px solid var(--model-section-border-color,rgba(59,65,81,.3));border-radius:.5rem;padding:0;margin:0}.swagger-ui section.models.is-open h4{border-bottom:1px solid var(--model-section-border-color,rgba(59,65,81,.3))}.swagger-ui section.models h4{color:var(--model-section-header-color,#606060);padding:0}.swagger-ui section.models h4:hover{background:var(--model-section-header-hover-background-color,rgba(0,0,0,.02))}.swagger-ui section.models h4 svg{fill:var(--expand-model-svg-arrow-color,#707070)}.swagger-ui section.models h4 button.models-control{color:var(--model-section-header-color,#606060);padding:20px}.swagger-ui section.models h5{color:var(--model-section-little-header-color,#707070)}.swagger-ui section.models .model-container{background:var(--model-container-background-color,rgba(0,0,0,.05))}.swagger-ui section.models .model-container:hover{background:var(--model-container-hover-background-color,rgba(0,0,0,.07))}.swagger-ui .model-box{background:var(--model-box-background-color,rgba(0,0,0,.1))}.swagger-ui .model-title{color:var(--model-title-color,#505050)}.swagger-ui .model-deprecated-warning{color:var(--model-deprecated-warning-color,#f93e3e)}.swagger-ui .prop-type{color:var(--model-prop-type-color,#55a)}.swagger-ui .prop-format{color:var(--model-prop-format-color,#606060)}.swagger-ui .scopes h2{color:var(--swagger-main-color,#3b4151)}.swagger-ui .scopes h2 a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .markdown pre,.swagger-ui .renderedMarkdown pre{color:var(--rendered-markdown-pre-color,#000)}.swagger-ui .markdown code,.swagger-ui .renderedMarkdown code{background:var(--rendered-markdown-code-background-color,rgba(0,0,0,.05));color:var(--rendered-markdown-code-color,#9012fe)}.scroll-to-top-wrapper{position:fixed;bottom:0;left:0;right:0;text-align:end;box-sizing:border-box;margin:0 auto;max-width:1460px;padding:0 20px;width:100%;pointer-events:none;z-index:1000}#scroll-to-top-btn{background-color:var(--scroll-to-top-button-background-color,#000000d1);border:1px solid var(--scroll-to-top-button-border-color,#d1c9c9);border-radius:50%;color:#fff;cursor:pointer;width:2.5rem;aspect-ratio:1;backdrop-filter:blur(20px);margin:10px;position:relative;transition:background-color .5s,opacity .6s .2s,translate .8s,scale .2s;opacity:0;translate:0 100px;box-shadow:0 0 10px 0 var(--scroll-to-top-button-shadow-color,rgba(0,0,0,.4))}#scroll-to-top-btn.showBtn{opacity:1;translate:0 0;pointer-events:auto}#scroll-to-top-btn::before{position:absolute;content:'';top:10px;left:12px;right:12px;background-color:var(--scroll-to-top-button-icon-color,#fff);border-radius:1rem;height:2px}#scroll-to-top-btn::after{position:absolute;content:'';top:15px;left:50%;border:solid var(--scroll-to-top-button-icon-color,#fff);border-width:0 2px 2px 0;padding:3px;transform:rotate(-135deg);translate:-50% 4px}#scroll-to-top-btn:focus-within,#scroll-to-top-btn:hover{background-color:var(--scroll-to-top-button-hover-background-color,#0d0d0dba)}#scroll-to-top-btn:focus-within::after,#scroll-to-top-btn:hover::after{animation:bounceUp 1.2s ease-in infinite}#scroll-to-top-btn:active{scale:0.95}@keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-moz-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-webkit-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@media only screen and (max-width:680px){.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{word-break:break-all}} \ No newline at end of file +ο»Ώ/*Common Theme https://github.com/teociaps/SwaggerUI.Themes */@charset "UTF-8";:root{--topbar-pinned-height:4.2rem;--filterbar-pinned-height:7.2rem}::-webkit-scrollbar{width:16px}::-webkit-scrollbar-thumb{height:56px;border-radius:8px;border:4px solid transparent;background-clip:content-box;background-color:var(--scrollbar-thumb-color,#a5a5a5);transition:background-color .3s}::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover-color,#727272)}body::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-color,#a5a5a5)}*{font-family:system-ui,sans-serif}:focus{font-family:system-ui,sans-serif;outline:0!important}:focus-visible{outline:auto!important;outline-offset:1px}body{background-color:var(--body-background-color,#fafafa);padding-bottom:1rem}*,::after,::before{box-sizing:border-box}.swagger-ui{color:var(--swagger-main-color,#3b4151);-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.15;margin:0}.swagger-ui .loading-container .loading:after{color:var(--swagger-main-color,#3b4151)}.swagger-ui .loading-container .loading:before{border:2px solid var(--loading-container-border-color,rgba(85,85,85,.1));border-top-color:var(--loading-container-border-top-color,rgba(0,0,0,.6))}.swagger-ui .errors-wrapper{background:var(--errors-wrapper-background-color,rgba(249,62,62,.1));border:2px solid var(--errors-wrapper-border-color,#f93e3e)}.swagger-ui .errors-wrapper .errors h4,.swagger-ui .errors-wrapper hgroup h4{color:var(--swagger-main-color,#3b4151)}.swagger-ui .errors-wrapper .errors small{color:var(--errors-wrapper-errors-color,#606060)}.swagger-ui .topbar{background-color:transparent;z-index:1000;padding:0}.swagger-ui .topbar.pinned{position:sticky;top:10px;--pinned-topbar-offset:var(--topbar-pinned-height)}.swagger-ui:has(> .topbar.pinned){--pinned-topbar-offset:var(--topbar-pinned-height)}.swagger-ui .topbar.pinned::before{content:'';position:absolute;inset:-10px 0 0;pointer-events:none;user-select:none;backdrop-filter:blur(20px);-webkit-mask-image:linear-gradient(to bottom,black 40%,transparent);mask-image:linear-gradient(to bottom,black 40%,transparent);background:linear-gradient(90deg,rgba(0,0,0,0) 5%,rgba(8,8,8,.4) 50%,rgba(0,0,0,0) 95%)}.swagger-ui .topbar .topbar-wrapper{background-color:var(--topbar-background-color,#000);border:1px solid var(--topbar-border-color,gray);border-top:0;border-radius:0 0 .5rem .5rem;padding:.5rem;box-shadow:0 5px 15px 0 var(--topbar-pinned-shadow-color,rgba(0,0,0,.5))}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn svg{fill:var(--topbar-pin-icon-color,white)}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn{cursor:pointer;background:0 0;border:none;margin:.2rem;padding:.2rem;transition:all .2s;rotate:z 359deg}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:hover{scale:1.05;animation:pintobbar 1.2s infinite}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:active{scale:0.95}.swagger-ui .topbar.pinned .topbar-wrapper{background-color:var(--topbar-pinned-background-color,rgba(0,0,0,.7));border:1px solid var(--topbar-border-color,gray);border-radius:.5rem;backdrop-filter:blur(20px)}.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn{translate:0 5px;rotate:z 0deg}.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:hover{animation:unpintobbar 1.2s infinite}@keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-moz-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-webkit-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@keyframes unpintobbar{10%{translate:0 1px}70%{rotate:z 50deg;translate:-3px 1px}}@-moz-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}@-webkit-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}.swagger-ui .topbar .download-url-wrapper .select-label select,.swagger-ui .topbar .download-url-wrapper input[type=text]{border:2px solid var(--topbar-select-border-color,#62a03f)}.swagger-ui .topbar .download-url-wrapper .select-label{color:var(--topbar-select-label-color,#f0f0f0)}.swagger-ui .topbar .download-url-wrapper .download-url-button{background:var(--topbar-download-url-button-background-color,#62a03f);color:var(--topbar-download-url-button-color,#fff)}#theme-switcher-select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);border:2px solid var(--topbar-select-border-color,#62a03f);min-width:120px;box-shadow:none}.swagger-ui .info .base-url,.swagger-ui .info .title,.swagger-ui .info h1,.swagger-ui .info h2,.swagger-ui .info h3,.swagger-ui .info h4,.swagger-ui .info h5,.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info table{color:var(--swagger-main-color,#3b4151)}.swagger-ui .info a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .info a:hover{color:var(--swagger-info-link-hover,#1f69c0)}.swagger-ui .info .title small{background:var(--api-version-background-color,#7d8492)}.swagger-ui .info .title small.version-stamp{background-color:var(--api-version-stamp-background-color,#89bf04)}.swagger-ui .info .title small pre{color:var(--api-version-color,#fff)}.swagger-ui .scheme-container{background:0 0;box-shadow:none;margin:0;padding:0}.swagger-ui .auth-wrapper{display:flex;flex:1;justify-content:flex-end;border:1px solid var(--auth-wrapper-border-color,#d5d5d5);padding:0;border-radius:5px;box-shadow:0 3px 5px -5px var(--auth-wrapper-box-shadow-color,#000);background:var(--auth-wrapper-background-color,#fff)}.swagger-ui .scheme-container .schemes>label{color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container{border-bottom:1px solid var(--auth-container-border-bottom-color,#ebebeb);color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container .errors{background-color:var(--auth-container-background-color,#fee);color:var(--auth-container-errors-color,red)}.swagger-ui .btn.authorize{background-color:var(--btn-authorize-background-color,transparent);border-color:var(--btn-authorize-border-color,#49cc90);color:var(--btn-authorize-font-color,#49cc90)}.swagger-ui .btn.authorize svg{fill:var(--btn-authorize-svg-fill-color,#49cc90)}.swagger-ui .opblock-tag-section{display:flex;flex-direction:column;border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;margin-block:1rem;overflow:clip}.swagger-ui .opblock-tag-section .expand-collapse-all-btn{display:none;user-select:none}.swagger-ui .opblock-tag-section.is-open .expand-collapse-all-btn{background:var(--opblock-tag-section-expandcollapse-background-color,rgb(220 220 220 / 45%));color:var(--swagger-main-color,#3b4151);border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;padding:5px 10px;font-size:medium;margin-inline:10px;display:block}.swagger-ui .opblock-tag,.swagger-ui .opblock-tag small,.swagger-ui .parameter__type{color:var(--swagger-main-color,#3b4151)}.swagger-ui .opblock-tag{align-items:center;cursor:pointer;display:flex;padding:10px 20px 10px 10px;color:var(--swagger-main-color,#3b4151);font-size:24px;border-bottom:0 dashed var(--opblock-tag-border-bottom-color,#e1e1e1);margin:0;transition:all .2s}.swagger-ui .opblock-tag:focus-within,.swagger-ui .opblock-tag:hover{background:var(--opblock-tag-background-color-hover,rgba(0,0,0,.02))}.swagger-ui .opblock-tag+div .operation-tag-content{margin-inline:5px}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag{position:sticky;top:0;z-index:4;margin-bottom:1rem;border-bottom:1px dashed var(--opblock-tag-border-bottom-color,#e1e1e1);background:var(--body-background-color,#fafafa)}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:focus-within,#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:hover{background:var(--opblock-tag-pinned-background-color-hover,#f1f1f1)}.swagger-ui .opblock-tag-section.is-open .opblock-tag{top:calc(var(--pinned-topbar-offset,0px) + var(--pinned-filter-offset,0px))}.swagger-ui .opblock{border:1px solid var(--opblock-border-color,#000);box-shadow:0 0 3px var(--opblock-shadow-color,rgba(0,0,0,.19))}.swagger-ui .opblock .tab-header .tab-item.active h4 span:after{background:var(--opblock-tabheader-underline-color,gray)}.swagger-ui .opblock .opblock-summary button svg{fill:var(--opblock-summary-svg-icons-color,#000)}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid var(--opblock-summary-border-bottom-color,#000)}.swagger-ui .opblock .opblock-section-header{background:var(--opblock-section-header-background-color,hsla(0,0%,100%,.8));box-shadow:0 1px 2px var(--opblock-section-header-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-section-header h4,.swagger-ui .opblock .opblock-section-header>label{color:var(--swagger-main-color,#3b4151)}.swagger-ui .opblock .opblock-summary-method{background:var(--opblock-summary-method-background-color,#000);color:var(--opblock-summary-method-color,#fff);text-shadow:0 1px 0 var(--opblock-summary-method-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{color:var(--swagger-main-color,#3b4151);word-break:normal}.swagger-ui .opblock.opblock-post{background:var(--opblock-post-background-color,rgba(73,204,144,.1));border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock.opblock-post .opblock-summary-method{background:var(--opblock-post-method-color,#49cc90)}.swagger-ui .opblock.opblock-post .opblock-summary{border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span:after{background:var(--opblock-post-method-color,#49cc90);border-radius:1rem}.swagger-ui .opblock.opblock-put{background:var(--opblock-put-background-color,rgba(252,161,48,.1));border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-put .opblock-summary-method{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-put .opblock-summary{border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span:after{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-delete{background:var(--opblock-delete-background-color,rgba(249,62,62,.1));border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .opblock-summary-method{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span:after{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-get{background:var(--opblock-get-background-color,rgba(97,175,254,.1));border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get .opblock-summary-method{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-get .opblock-summary{border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span:after{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-patch{background:var(--opblock-patch-background-color,rgba(80,227,194,.1));border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .opblock-summary-method{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span:after{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-head{background:var(--opblock-head-background-color,rgba(144,18,254,.1));border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head .opblock-summary-method{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-head .opblock-summary{border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span:after{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-options{background:var(--opblock-options-background-color,rgba(13,90,167,.1));border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .opblock-summary-method{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .opblock-summary{border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span:after{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-deprecated{background:var(--opblock-deprecated-background-color,hsla(0,0%,92%,.1));border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span:after{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .tab li{color:var(--swagger-main-color,#3b4151)}.swagger-ui .tab li:first-of-type:after{background:var(--swagger-tabs-divider-color,rgba(0,0,0,.2))}.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-external-docs-wrapper,.swagger-ui .opblock-external-docs-wrapper h4,.swagger-ui .opblock-external-docs-wrapper p,.swagger-ui .opblock-title_normal,.swagger-ui .opblock-title_normal h4,.swagger-ui .opblock-title_normal p,.swagger-ui .response-col_links,.swagger-ui .response-col_status,.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5{color:var(--swagger-main-color,#3b4151)}.swagger-ui .response-col_links .response-undocumented,.swagger-ui .response-col_status .response-undocumented{color:var(--response-undocumented-color,#909090)}.swagger-ui .response-control-media-type--accept-controller select{border-color:var(--response-control-media-type-color,green)}.swagger-ui .response-control-media-type__accept-message{color:var(--response-control-media-type-color,green)}.swagger-ui .opblock-body pre.microlight{background:var(--opblock-pre-microlight-background-color,#333)!important;color:var(--opblock-pre-microlight-color,#fff)!important}.swagger-ui .download-contents{background:var(--download-contents-background-color,#7d8293);color:var(--download-contents-color,#fff)}.swagger-ui .copy-to-clipboard{background:var(--copy-to-clipboard-background-color,#7d8293)}.swagger-ui .copy-to-clipboard button{background:var(--copy-to-clipboard-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .filter .operation-filter-input{border:2px solid var(--filter-operation-filter-input-border-color,#d8dde7)}.swagger-ui div:has(> .filter-container > .filter.pinned){position:sticky;top:var(--pinned-topbar-offset,0);z-index:5;background:var(--body-background-color,#fafafa);--pinned-filter-offset:var(--filterbar-pinned-height)}.swagger-ui:has(.filter.pinned){--pinned-filter-offset:var(--filterbar-pinned-height)}.swagger-ui .filter #pin-filterbar-btn svg{fill:var(--filterbar-pin-icon-color,#707070)}.swagger-ui .filter #pin-filterbar-btn{cursor:pointer;background:0 0;border:none;margin:.2rem;padding:.2rem;transition:all .2s;rotate:z 359deg}.swagger-ui .filter #pin-filterbar-btn:focus-within,.swagger-ui .filter #pin-filterbar-btn:hover{scale:1.05;animation:pintobbar 1.2s infinite}.swagger-ui .filter #pin-filterbar-btn:active{scale:0.95}.swagger-ui .filter.pinned #pin-filterbar-btn{rotate:z 0deg}.swagger-ui .filter.pinned #pin-filterbar-btn:focus-within,.swagger-ui .filter.pinned #pin-filterbar-btn:hover{animation:unpintobbar 1.2s infinite}.swagger-ui .download-url-wrapper .failed,.swagger-ui .filter .failed{color:var(--filter-download-failed-color,red)}.swagger-ui .download-url-wrapper .loading,.swagger-ui .filter .loading{color:var(--filter-download-loading-color,#aaa)}.swagger-ui section h3{color:var(--swagger-main-color,#3b4151)}.swagger-ui .servers>label,.swagger-ui table.headers td{color:var(--swagger-main-color,#3b4151)}.swagger-ui table.headers .header-example{color:var(--table-headers-example-color,#999)}.swagger-ui table thead tr td,.swagger-ui table thead tr th{border-bottom:1px solid var(--table-thead-border-bottom-color,rgba(59,65,81,.2));color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name{color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name.required span{color:var(--parameter-name-required-symbol-color,red)}.swagger-ui .parameter__name.required::after{color:var(--parameter-name-required-color,rgba(255,0,0,.6))}.swagger-ui .parameter__extension,.swagger-ui .parameter__in,.swagger-ui .response__extension{color:var(--parameter-in-extension-color,gray)}.swagger-ui .parameter__deprecated{color:var(--parameter-deprecated-color,red)}.swagger-ui .btn{background:var(--button-background-color,transparent);border:2px solid var(--button-border-color,gray);box-shadow:0 1px 2px var(--button-shadow-color,rgba(0,0,0,.1));color:var(--swagger-main-color,#3b4151)}.swagger-ui .btn:hover{box-shadow:0 0 5px var(--button-shadow-hover-color,rgba(0,0,0,.3))}.swagger-ui .btn.cancel{background-color:var(--button-cancel-background-color,transparent);border-color:var(--button-cancel-border-color,#ff6060);color:var(--button-cancel-color,#ff6060)}.swagger-ui .btn.execute{background-color:var(--button-execute-background-color,#4990e2);border-color:var(--button-execute-border-color,#4990e2);color:var(--button-execute-color,#fff)}.swagger-ui button.invalid{background:var(--button-invalid-background-color,#feebeb);border-color:var(--button-invalid-border-color,#f93e3e)}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{fill:var(--expand-operation-svg-arrow-color,#707070)}.swagger-ui .expand-methods:hover svg,.swagger-ui .expand-operation:hover svg{fill:var(--expand-operation-svg-arrow-hover-color,#404040)}.swagger-ui select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);border:2px solid var(--select-border-color,#41444e);box-shadow:0 1px 2px 0 var(--select-shadow-color,rgba(0,0,0,.25));color:var(--swagger-main-color,#3b4151);cursor:pointer}.swagger-ui select[multiple]{background:var(--select-background-color,#f7f7f7)}.swagger-ui select:disabled{opacity:.5;cursor:not-allowed}.swagger-ui label{color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text],.swagger-ui textarea{background:var(--input-background-color,#fff);border:1px solid var(--input-border-color,#d9d9d9);color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email].invalid,.swagger-ui input[type=file].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid,.swagger-ui select.invalid,.swagger-ui textarea.invalid{background:var(--input-invalid-background-color,#feebeb);border-color:var(--input-invalid-border-color,#f93e3e);color:var(--input-invalid-color,#3b4151)}.swagger-ui input[disabled],.swagger-ui select[disabled]{background-color:var(--input-disabled-background-color,#fafafa);color:var(--input-disabled-color,#888)}.swagger-ui select[disabled]{border-color:var(--select-disabled-border-color,#888)}.swagger-ui textarea[disabled]{background-color:var(--textarea-disabled-background-color,#41444e);color:var(--textarea-disabled-color,#fff)}.swagger-ui textarea{background:var(--textarea-background-color,hsla(0,0%,100%,.8));color:var(--swagger-main-color,#3b4151)}.swagger-ui textarea:focus{border:2px solid var(--textarea-border-focus-color,#61affe)}.swagger-ui textarea.curl{background:var(--textarea-curl-background-color,#41444e);color:var(--textarea-curl-color,#fff)}.swagger-ui .checkbox{color:var(--checkbox-color,#303030)}.swagger-ui .checkbox p{color:var(--swagger-main-color,#3b4151)}.swagger-ui .checkbox input[type=checkbox]+label>.item{background:var(--checkbox-label-item-background-color,#e8e8e8);box-shadow:0 0 0 2px var(--checkbox-label-item-shadow-color,#e8e8e8)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:var(--checkbox-label-item-icon, #e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .dialog-ux .backdrop-ux{background:var(--dialog-backdrop-color,rgba(0,0,0,.8))}.swagger-ui .dialog-ux .modal-ux{background:var(--dialog-background-color,#fff);border:1px solid var(--dialog-border-color,#ebebeb);box-shadow:0 10px 30px 0 var(--dialog-shadow-color,rgba(0,0,0,.2))}.swagger-ui .dialog-ux .modal-ux-content h4,.swagger-ui .dialog-ux .modal-ux-content p,.swagger-ui .dialog-ux .modal-ux-header h3{color:var(--swagger-main-color,#3b4151)}.swagger-ui .dialog-ux .modal-ux-header{border-bottom:1px solid var(--dialog-border-color,#ebebeb)}.swagger-ui .dialog-ux .modal-ux-header button svg{fill:var(--dialog-close-button-icon-color,black)}.swagger-ui .model{color:var(--swagger-main-color,#3b4151)}.swagger-ui .model .deprecated span,.swagger-ui .model .deprecated td{color:var(--model-deprecated-color,#a0a0a0)!important}.swagger-ui .model-toggle{cursor:pointer;-webkit-transition:-webkit-transform .15s ease-in;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}.swagger-ui .model-toggle.collapsed{-webkit-transform:rotate(0);transform:rotate(0)}.swagger-ui .model-toggle::after{background:var(--model-arrow-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .model-hint{background:var(--model-hint-background-color,rgba(0,0,0,.7));color:var(--model-hint-color,#ebebeb)}.swagger-ui .model .property{color:var(--model-property-color,#999)}.swagger-ui .model .property.primitive{color:var(--model-property-primitive-color,#6b6b6b)}.swagger-ui table.model tr.property-row .star{color:var(--model-property-required-symbol-color,red)}.swagger-ui .model .external-docs,.swagger-ui table.model tr.description{color:var(--model-property-description,#666)}.swagger-ui table.model tr.extension{color:var(--model-property-extension,#777)}.swagger-ui section.models{border:1px solid var(--model-section-border-color,rgba(59,65,81,.3));border-radius:.5rem;padding:0;margin:0}.swagger-ui section.models.is-open h4{border-bottom:1px solid var(--model-section-border-color,rgba(59,65,81,.3))}.swagger-ui section.models h4{color:var(--model-section-header-color,#606060);padding:0}.swagger-ui section.models h4:hover{background:var(--model-section-header-hover-background-color,rgba(0,0,0,.02))}.swagger-ui section.models h4 svg{fill:var(--expand-model-svg-arrow-color,#707070)}.swagger-ui section.models h4 button.models-control{color:var(--model-section-header-color,#606060);padding:20px}.swagger-ui section.models h5{color:var(--model-section-little-header-color,#707070)}.swagger-ui section.models .model-container{background:var(--model-container-background-color,rgba(0,0,0,.05))}.swagger-ui section.models .model-container:hover{background:var(--model-container-hover-background-color,rgba(0,0,0,.07))}.swagger-ui .model-box{background:var(--model-box-background-color,rgba(0,0,0,.1))}.swagger-ui .model-title{color:var(--model-title-color,#505050)}.swagger-ui .model-deprecated-warning{color:var(--model-deprecated-warning-color,#f93e3e)}.swagger-ui .prop-type{color:var(--model-prop-type-color,#55a)}.swagger-ui .prop-format{color:var(--model-prop-format-color,#606060)}.swagger-ui .scopes h2{color:var(--swagger-main-color,#3b4151)}.swagger-ui .scopes h2 a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .markdown pre,.swagger-ui .renderedMarkdown pre{color:var(--rendered-markdown-pre-color,#000)}.swagger-ui .markdown code,.swagger-ui .renderedMarkdown code{background:var(--rendered-markdown-code-background-color,rgba(0,0,0,.05));color:var(--rendered-markdown-code-color,#9012fe)}.scroll-to-top-wrapper{position:fixed;bottom:0;left:0;right:0;text-align:end;box-sizing:border-box;margin:0 auto;max-width:1460px;padding:0 20px;width:100%;pointer-events:none;z-index:1000}#scroll-to-top-btn{background-color:var(--scroll-to-top-button-background-color,#000000d1);border:1px solid var(--scroll-to-top-button-border-color,#d1c9c9);border-radius:50%;color:#fff;cursor:pointer;width:2.5rem;aspect-ratio:1;backdrop-filter:blur(20px);margin:10px;position:relative;transition:background-color .5s,opacity .6s .2s,translate .8s,scale .2s;opacity:0;translate:0 100px;box-shadow:0 0 10px 0 var(--scroll-to-top-button-shadow-color,rgba(0,0,0,.4))}#scroll-to-top-btn.showBtn{opacity:1;translate:0 0;pointer-events:auto}#scroll-to-top-btn::before{position:absolute;content:'';top:10px;left:12px;right:12px;background-color:var(--scroll-to-top-button-icon-color,#fff);border-radius:1rem;height:2px}#scroll-to-top-btn::after{position:absolute;content:'';top:15px;left:50%;border:solid var(--scroll-to-top-button-icon-color,#fff);border-width:0 2px 2px 0;padding:3px;transform:rotate(-135deg);translate:-50% 4px}#scroll-to-top-btn:focus-within,#scroll-to-top-btn:hover{background-color:var(--scroll-to-top-button-hover-background-color,#0d0d0dba)}#scroll-to-top-btn:focus-within::after,#scroll-to-top-btn:hover::after{animation:bounceUp 1.2s ease-in infinite}#scroll-to-top-btn:active{scale:0.95}@keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-moz-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-webkit-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@media only screen and (max-width:680px){.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{word-break:break-all}} \ No newline at end of file From 880877b46000f04a9cedf143686b8df02e26d5d9 Mon Sep 17 00:00:00 2001 From: Teuz Date: Tue, 11 Aug 2026 10:56:47 +0200 Subject: [PATCH 12/35] feat(nswag): add EnableFilter() and EnableFilter(bool pinned) Give NSwag consumers a way to enable the swagger-ui bundle's native operation filter box, matching Swashbuckle's typed EnableFilter(). NSwag's AdditionalSettings dictionary is spliced as raw key: value text into the same SwaggerUIBundle({...}) config object the bundle itself reads, so the raw "filter" key works identically. The bool-overload also wires in the pinnable filter bar from EnablePinnableFilterBar(). --- .../Builder/SwaggerUiSettingsExtensions.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/SwaggerUiSettingsExtensions.cs b/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/SwaggerUiSettingsExtensions.cs index 066efbf..5adf309 100644 --- a/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/SwaggerUiSettingsExtensions.cs +++ b/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/SwaggerUiSettingsExtensions.cs @@ -57,6 +57,31 @@ public void EnableAllAdvancedOptions(ThemeSwitcherOptions switcherOptions = null public void EnablePinnableTopbar() => settings.AdditionalSettings.EnablePinnableTopbar(); + /// + /// Enables the operation filter box. + /// + /// + /// Sets the raw filter key consumed directly by the swagger-ui bundle's own + /// SwaggerUIBundle({...}) initializer, the same key Swashbuckle's typed + /// EnableFilter() produces. + /// + public void EnableFilter() => + settings.AdditionalSettings["filter"] = true; + + /// + /// Enables the operation filter box, optionally making it pinnable. + /// + /// + /// If , the filter box can be pinned in place. + /// + public void EnableFilter(bool pinned) + { + settings.EnableFilter(); + + if (pinned) + settings.AdditionalSettings.EnablePinnableFilterBar(); + } + /// /// Shows a button to scroll back to the top of the page. /// From 2482a5480446d6467761db2edc5d1c6a24aee4a1 Mon Sep 17 00:00:00 2001 From: Teuz Date: Tue, 11 Aug 2026 11:01:24 +0200 Subject: [PATCH 13/35] feat(swashbuckle): add EnableFilter(bool pinned) overload Lets Swashbuckle consumers enable the operation filter box and, in the same call, make the filter bar pinnable via the pin behavior built into the pinnable-filter-bar feature. --- .../Builder/SwaggerUIOptionsExtensions.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIOptionsExtensions.cs b/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIOptionsExtensions.cs index 92a0cf3..31a4ef3 100644 --- a/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIOptionsExtensions.cs +++ b/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIOptionsExtensions.cs @@ -57,6 +57,20 @@ public void EnableAllAdvancedOptions(ThemeSwitcherOptions switcherOptions = null public void EnablePinnableTopbar() => options.ConfigObject.AdditionalItems.EnablePinnableTopbar(); + /// + /// Enables the operation filter box, optionally making it pinnable. + /// + /// + /// If , the filter box can be pinned in place. + /// + public void EnableFilter(bool pinned) + { + options.EnableFilter(); + + if (pinned) + options.ConfigObject.AdditionalItems.EnablePinnableFilterBar(); + } + /// /// Shows a button to scroll back to the top of the page. /// From b64889c837375caa727d73a09c11faa304b1544e Mon Sep 17 00:00:00 2001 From: Teuz Date: Tue, 11 Aug 2026 11:09:35 +0200 Subject: [PATCH 14/35] fix(nswag): use TryAdd for the filter AdditionalSettings key Match the non-destructive idiom the rest of this dictionary relies on so a user-provided AdditionalSettings["filter"] value (e.g. a filter expression string) isn't silently clobbered by EnableFilter(). --- .../AspNetCore/Builder/SwaggerUiSettingsExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/SwaggerUiSettingsExtensions.cs b/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/SwaggerUiSettingsExtensions.cs index 5adf309..c33408d 100644 --- a/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/SwaggerUiSettingsExtensions.cs +++ b/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/SwaggerUiSettingsExtensions.cs @@ -62,11 +62,11 @@ public void EnablePinnableTopbar() => /// /// /// Sets the raw filter key consumed directly by the swagger-ui bundle's own - /// SwaggerUIBundle({...}) initializer, the same key Swashbuckle's typed - /// EnableFilter() produces. + /// SwaggerUIBundle({...}) initializer, as opposed to the other options here + /// which drive this library's own theme JS. /// public void EnableFilter() => - settings.AdditionalSettings["filter"] = true; + settings.AdditionalSettings.TryAdd("filter", true); /// /// Enables the operation filter box, optionally making it pinnable. From a94e432bc2ad236c23318239b35575d83977e1bd Mon Sep 17 00:00:00 2001 From: Teuz Date: Tue, 11 Aug 2026 11:19:32 +0200 Subject: [PATCH 15/35] test(swashbuckle): cover both branches of EnableFilter(bool pinned) Adds direct unit tests against SwaggerUIOptions to exercise the pin/no-pin branch that decides whether the filter bar's pinnable option gets registered, since the existing suite only covered the underlying dictionary extension it calls into. --- .../AspNetCore.Swagger.Themes.Tests.csproj | 2 ++ .../SwaggerUIOptionsExtensionsTests.cs | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerUIOptionsExtensionsTests.cs diff --git a/tests/AspNetCore.SwaggerUI.Themes.Tests/AspNetCore.Swagger.Themes.Tests.csproj b/tests/AspNetCore.SwaggerUI.Themes.Tests/AspNetCore.Swagger.Themes.Tests.csproj index 45ffea8..8a733cf 100644 --- a/tests/AspNetCore.SwaggerUI.Themes.Tests/AspNetCore.Swagger.Themes.Tests.csproj +++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/AspNetCore.Swagger.Themes.Tests.csproj @@ -39,10 +39,12 @@ all + + diff --git a/tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerUIOptionsExtensionsTests.cs b/tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerUIOptionsExtensionsTests.cs new file mode 100644 index 0000000..be0a705 --- /dev/null +++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerUIOptionsExtensionsTests.cs @@ -0,0 +1,35 @@ +using Microsoft.AspNetCore.Builder; +using Shouldly; +using Swashbuckle.AspNetCore.SwaggerUI; +using Xunit; + +namespace AspNetCore.Swagger.Themes.Tests; + +public class SwaggerUIOptionsExtensionsTests +{ + [Fact] + public void EnableFilter_Pinned_AddsPinnableFilterBarOption() + { + // Arrange + var options = new SwaggerUIOptions(); + + // Act + options.EnableFilter(pinned: true); + + // Assert + options.ConfigObject.AdditionalItems.ShouldContainKey(AdvancedOptions.PinnableFilterBar); + } + + [Fact] + public void EnableFilter_NotPinned_DoesNotAddPinnableFilterBarOption() + { + // Arrange + var options = new SwaggerUIOptions(); + + // Act + options.EnableFilter(pinned: false); + + // Assert + options.ConfigObject.AdditionalItems.ShouldNotContainKey(AdvancedOptions.PinnableFilterBar); + } +} From ab8568116ef86585078c817a9316f7889f07b500 Mon Sep 17 00:00:00 2001 From: Teuz Date: Tue, 11 Aug 2026 11:36:27 +0200 Subject: [PATCH 16/35] docs: document pin persistence, pinnable filter bar, and release notes for v3.1.0 --- README.md | 5 +++ .../package-readme.md | 5 ++- src/AspNetCore.SwaggerUI.Themes/readme.txt | 31 ++++++++++++++++ src/NSwag.AspNetCore.Themes/readme.txt | 37 +++++++++++++++++++ 4 files changed, 77 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 85070cd..25a9d78 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,11 @@ app.UseSwaggerUi(Theme.Dark); // NSwag // Enable runtime theme switcher! app.UseSwaggerUI(Theme.Dark, c => c.EnableThemeSwitcher()); + +// Enable a pinnable filter box +app.UseSwaggerUI(Theme.Dark, c => c.EnableFilter(pinned: true)); // Swashbuckle +// or +app.UseSwaggerUi(Theme.Dark, c => c.EnableFilter(pinned: true)); // NSwag ``` ### Extensions Quick Start diff --git a/src/AspNetCore.SwaggerUI.Themes/package-readme.md b/src/AspNetCore.SwaggerUI.Themes/package-readme.md index 6d78629..95138d6 100644 --- a/src/AspNetCore.SwaggerUI.Themes/package-readme.md +++ b/src/AspNetCore.SwaggerUI.Themes/package-readme.md @@ -21,6 +21,9 @@ app.UseSwaggerUI(Theme.Dark); // Or enable runtime theme switcher! app.UseSwaggerUI(Theme.Dark, c => c.EnableThemeSwitcher()); + +// Or enable a pinnable filter box +app.UseSwaggerUI(Theme.Dark, c => c.EnableFilter(pinned: true)); ``` > **Note**: The `UseSwaggerUI()` method is provided by Swashbuckle.AspNetCore. This package adds convenient overloads to apply themes seamlessly. @@ -33,7 +36,7 @@ app.UseSwaggerUI(Theme.Dark, c => c.EnableThemeSwitcher()); - **[Custom Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Custom-Themes)** - Build your own themes with full control, or create standalone themes with zero dependencies -- **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Enhance your documentation with powerful UI capabilities +- **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Enhance your documentation with powerful UI capabilities, including a [pinnable filter bar](https://github.com/teociaps/SwaggerUI.Themes/wiki/Feature-Pinnable-Filter-Bar) via `EnableFilter(pinned: true)` - _...discover more in the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki/Features)!_ diff --git a/src/AspNetCore.SwaggerUI.Themes/readme.txt b/src/AspNetCore.SwaggerUI.Themes/readme.txt index a406be4..520b96f 100644 --- a/src/AspNetCore.SwaggerUI.Themes/readme.txt +++ b/src/AspNetCore.SwaggerUI.Themes/readme.txt @@ -1,4 +1,35 @@ ο»Ώ+===================================================================================+ +| AspNetCore.SwaggerUI.Themes β€’ RELEASE NOTES - v3.1 | ++===================================================================================+ + +NEW FEATURES +------------------------------------------------------------------------------------- +- Pinnable Filter Bar: Pin the operation filter box in place, just like the topbar + Enable it with: c.EnableFilter(pinned: true); + +IMPROVEMENTS +------------------------------------------------------------------------------------- +- Pinnable topbar now remembers its pinned/unpinned state across page reloads +- Improved theme asset serving performance +- Fixed a thread-safety issue that could affect parallel test execution + + +QUICK START +------------------------------------------------------------------------------------- +Pinnable filter bar: + app.UseSwaggerUI(Theme.Dark, c => c.EnableFilter(pinned: true)); + + +DOCUMENTATION +------------------------------------------------------------------------------------- + +Full docs: https://github.com/teociaps/SwaggerUI.Themes/wiki +Repository: https://github.com/teociaps/SwaggerUI.Themes + +===================================================================================== + + ++===================================================================================+ | AspNetCore.SwaggerUI.Themes β€’ RELEASE NOTES - v3 | +===================================================================================+ diff --git a/src/NSwag.AspNetCore.Themes/readme.txt b/src/NSwag.AspNetCore.Themes/readme.txt index e087d7e..b34d7b0 100644 --- a/src/NSwag.AspNetCore.Themes/readme.txt +++ b/src/NSwag.AspNetCore.Themes/readme.txt @@ -1,4 +1,41 @@ ο»Ώ+===================================================================================+ +| NSwag.AspNetCore.Themes β€’ RELEASE NOTES - v3.1 | ++===================================================================================+ + +NEW FEATURES +------------------------------------------------------------------------------------- +- Filter Box Support: settings.EnableFilter() is now available for NSwag, enabling + Swagger UI's native operation filter box (previously only available through the + Swashbuckle package) +- Pinnable Filter Bar: Pin the operation filter box in place, just like the topbar + Enable it with: settings.EnableFilter(pinned: true); + +IMPROVEMENTS +------------------------------------------------------------------------------------- +- Pinnable topbar now remembers its pinned/unpinned state across page reloads +- Improved theme asset serving performance +- Fixed a thread-safety issue that could affect parallel test execution + + +QUICK START +------------------------------------------------------------------------------------- +Filter box: + app.UseSwaggerUi(Theme.Dark, settings => settings.EnableFilter()); + +Pinnable filter bar: + app.UseSwaggerUi(Theme.Dark, settings => settings.EnableFilter(pinned: true)); + + +DOCUMENTATION +------------------------------------------------------------------------------------- + +Full docs: https://github.com/teociaps/SwaggerUI.Themes/wiki +Repository: https://github.com/teociaps/SwaggerUI.Themes + +===================================================================================== + + ++===================================================================================+ | NSwag.AspNetCore.Themes β€’ RELEASE NOTES - v3 | +===================================================================================+ From 9c2791c48e031fc92cbfedcc423ab87b2e5b14fb Mon Sep 17 00:00:00 2001 From: Teuz Date: Tue, 11 Aug 2026 11:46:37 +0200 Subject: [PATCH 17/35] docs: fix v3.1 release notes formatting and cover NSwag filter support in package readmes --- README.md | 2 +- src/AspNetCore.SwaggerUI.Themes/readme.txt | 1 + src/NSwag.AspNetCore.Themes/package-readme.md | 5 ++++- src/NSwag.AspNetCore.Themes/readme.txt | 1 + 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 25a9d78..229e746 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ builder.Services.AddOpenApiDocument(c => - **[Custom Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Custom-Themes)** - Build your own themes with full control, or create standalone themes with zero dependencies -- **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Enhance your documentation with powerful UI capabilities +- **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Enhance your documentation with powerful UI capabilities, including a [pinnable filter bar](https://github.com/teociaps/SwaggerUI.Themes/wiki/Feature-Pinnable-Filter-Bar) via `EnableFilter(pinned: true)` ### Extensions - **API Counter** - Automatically display operation counts in tag descriptions for better API surface area visibility diff --git a/src/AspNetCore.SwaggerUI.Themes/readme.txt b/src/AspNetCore.SwaggerUI.Themes/readme.txt index 520b96f..66486e7 100644 --- a/src/AspNetCore.SwaggerUI.Themes/readme.txt +++ b/src/AspNetCore.SwaggerUI.Themes/readme.txt @@ -7,6 +7,7 @@ NEW FEATURES - Pinnable Filter Bar: Pin the operation filter box in place, just like the topbar Enable it with: c.EnableFilter(pinned: true); + IMPROVEMENTS ------------------------------------------------------------------------------------- - Pinnable topbar now remembers its pinned/unpinned state across page reloads diff --git a/src/NSwag.AspNetCore.Themes/package-readme.md b/src/NSwag.AspNetCore.Themes/package-readme.md index c59e707..c6facaf 100644 --- a/src/NSwag.AspNetCore.Themes/package-readme.md +++ b/src/NSwag.AspNetCore.Themes/package-readme.md @@ -21,6 +21,9 @@ app.UseSwaggerUi(Theme.Dark); // Or enable runtime theme switcher! app.UseSwaggerUi(Theme.Dark, c => c.EnableThemeSwitcher()); + +// Or enable a pinnable filter box (EnableFilter() is new for NSwag!) +app.UseSwaggerUi(Theme.Dark, c => c.EnableFilter(pinned: true)); ``` > **Note**: The `UseSwaggerUi()` method is provided by NSwag.AspNetCore. This package adds convenient overloads to apply themes seamlessly. @@ -33,7 +36,7 @@ app.UseSwaggerUi(Theme.Dark, c => c.EnableThemeSwitcher()); - **[Custom Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Custom-Themes)** - Build your own themes with full control, or create standalone themes with zero dependencies -- **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Enhance your documentation with powerful UI capabilities +- **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Enhance your documentation with powerful UI capabilities, including a [pinnable filter bar](https://github.com/teociaps/SwaggerUI.Themes/wiki/Feature-Pinnable-Filter-Bar) via `EnableFilter(pinned: true)` (the filter box itself, via `EnableFilter()`, is new for NSwag) - _...discover more in the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki/Features)!_ diff --git a/src/NSwag.AspNetCore.Themes/readme.txt b/src/NSwag.AspNetCore.Themes/readme.txt index b34d7b0..9328f0c 100644 --- a/src/NSwag.AspNetCore.Themes/readme.txt +++ b/src/NSwag.AspNetCore.Themes/readme.txt @@ -10,6 +10,7 @@ NEW FEATURES - Pinnable Filter Bar: Pin the operation filter box in place, just like the topbar Enable it with: settings.EnableFilter(pinned: true); + IMPROVEMENTS ------------------------------------------------------------------------------------- - Pinnable topbar now remembers its pinned/unpinned state across page reloads From 9183355aee311cd29095c87410a49fdc9e678494 Mon Sep 17 00:00:00 2001 From: Teuz Date: Tue, 11 Aug 2026 12:23:04 +0200 Subject: [PATCH 18/35] style(themes): deduplicate pin-button CSS shared between topbar and filter bar The topbar's and filter bar's pin buttons had byte-for-byte identical base/hover/active/pinned rules copy-pasted between them, differing only in the icon fill color and the topbar's extra translate on its pinned state. Combine the shared declarations into grouped selectors instead. --- .../Swagger/Themes/Styles/common.css | 56 ++++++++----------- .../Swagger/Themes/Styles/common.min.css | 2 +- 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css index 0d49975..4527eb8 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css @@ -140,7 +140,10 @@ body { fill: var(--topbar-pin-icon-color, white); } - .swagger-ui .topbar .topbar-wrapper #pin-topbar-btn { + /* Base/hover/active button styling is shared with the filter bar's pin button + (#pin-filterbar-btn below); only the icon fill color differs between the two. */ + .swagger-ui .topbar .topbar-wrapper #pin-topbar-btn, + .swagger-ui .filter #pin-filterbar-btn { cursor: pointer; background: transparent; border: none; @@ -151,12 +154,15 @@ body { } .swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:hover, - .swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:focus-within { + .swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:focus-within, + .swagger-ui .filter #pin-filterbar-btn:hover, + .swagger-ui .filter #pin-filterbar-btn:focus-within { scale: 1.05; animation: pintobbar 1.2s infinite; } - .swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:active { + .swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:active, + .swagger-ui .filter #pin-filterbar-btn:active { scale: 0.95; } @@ -167,13 +173,21 @@ body { backdrop-filter: blur(20px); } - .swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn { - translate: 0 5px; + /* rotate is shared with the filter bar's pinned pin button (#pin-filterbar-btn + below); translate is topbar-specific, nudging the button into the pinned layout. */ + .swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn, + .swagger-ui .filter.pinned #pin-filterbar-btn { rotate: z 0deg; } + .swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn { + translate: 0 5px; + } + .swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:hover, - .swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:focus-within { + .swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:focus-within, + .swagger-ui .filter.pinned #pin-filterbar-btn:hover, + .swagger-ui .filter.pinned #pin-filterbar-btn:focus-within { animation: unpintobbar 1.2s infinite; } @@ -698,34 +712,8 @@ body { fill: var(--filterbar-pin-icon-color, #707070); } - .swagger-ui .filter #pin-filterbar-btn { - cursor: pointer; - background: transparent; - border: none; - margin: .2rem; - padding: .2rem; - transition: all .2s; - rotate: z 359deg; - } - - .swagger-ui .filter #pin-filterbar-btn:hover, - .swagger-ui .filter #pin-filterbar-btn:focus-within { - scale: 1.05; - animation: pintobbar 1.2s infinite; - } - - .swagger-ui .filter #pin-filterbar-btn:active { - scale: 0.95; - } - - .swagger-ui .filter.pinned #pin-filterbar-btn { - rotate: z 0deg; - } - - .swagger-ui .filter.pinned #pin-filterbar-btn:hover, - .swagger-ui .filter.pinned #pin-filterbar-btn:focus-within { - animation: unpintobbar 1.2s infinite; - } + /* Base, hover/focus, active, and pinned button styling is shared with the topbar's pin + button (#pin-topbar-btn) β€” see the .topbar .topbar-wrapper #pin-topbar-btn rules above. */ .swagger-ui .download-url-wrapper .failed, .swagger-ui .filter .failed { diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css index 0b160f2..462d241 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css @@ -1 +1 @@ -ο»Ώ/*Common Theme https://github.com/teociaps/SwaggerUI.Themes */@charset "UTF-8";:root{--topbar-pinned-height:4.2rem;--filterbar-pinned-height:7.2rem}::-webkit-scrollbar{width:16px}::-webkit-scrollbar-thumb{height:56px;border-radius:8px;border:4px solid transparent;background-clip:content-box;background-color:var(--scrollbar-thumb-color,#a5a5a5);transition:background-color .3s}::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover-color,#727272)}body::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-color,#a5a5a5)}*{font-family:system-ui,sans-serif}:focus{font-family:system-ui,sans-serif;outline:0!important}:focus-visible{outline:auto!important;outline-offset:1px}body{background-color:var(--body-background-color,#fafafa);padding-bottom:1rem}*,::after,::before{box-sizing:border-box}.swagger-ui{color:var(--swagger-main-color,#3b4151);-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.15;margin:0}.swagger-ui .loading-container .loading:after{color:var(--swagger-main-color,#3b4151)}.swagger-ui .loading-container .loading:before{border:2px solid var(--loading-container-border-color,rgba(85,85,85,.1));border-top-color:var(--loading-container-border-top-color,rgba(0,0,0,.6))}.swagger-ui .errors-wrapper{background:var(--errors-wrapper-background-color,rgba(249,62,62,.1));border:2px solid var(--errors-wrapper-border-color,#f93e3e)}.swagger-ui .errors-wrapper .errors h4,.swagger-ui .errors-wrapper hgroup h4{color:var(--swagger-main-color,#3b4151)}.swagger-ui .errors-wrapper .errors small{color:var(--errors-wrapper-errors-color,#606060)}.swagger-ui .topbar{background-color:transparent;z-index:1000;padding:0}.swagger-ui .topbar.pinned{position:sticky;top:10px;--pinned-topbar-offset:var(--topbar-pinned-height)}.swagger-ui:has(> .topbar.pinned){--pinned-topbar-offset:var(--topbar-pinned-height)}.swagger-ui .topbar.pinned::before{content:'';position:absolute;inset:-10px 0 0;pointer-events:none;user-select:none;backdrop-filter:blur(20px);-webkit-mask-image:linear-gradient(to bottom,black 40%,transparent);mask-image:linear-gradient(to bottom,black 40%,transparent);background:linear-gradient(90deg,rgba(0,0,0,0) 5%,rgba(8,8,8,.4) 50%,rgba(0,0,0,0) 95%)}.swagger-ui .topbar .topbar-wrapper{background-color:var(--topbar-background-color,#000);border:1px solid var(--topbar-border-color,gray);border-top:0;border-radius:0 0 .5rem .5rem;padding:.5rem;box-shadow:0 5px 15px 0 var(--topbar-pinned-shadow-color,rgba(0,0,0,.5))}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn svg{fill:var(--topbar-pin-icon-color,white)}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn{cursor:pointer;background:0 0;border:none;margin:.2rem;padding:.2rem;transition:all .2s;rotate:z 359deg}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:hover{scale:1.05;animation:pintobbar 1.2s infinite}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:active{scale:0.95}.swagger-ui .topbar.pinned .topbar-wrapper{background-color:var(--topbar-pinned-background-color,rgba(0,0,0,.7));border:1px solid var(--topbar-border-color,gray);border-radius:.5rem;backdrop-filter:blur(20px)}.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn{translate:0 5px;rotate:z 0deg}.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:hover{animation:unpintobbar 1.2s infinite}@keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-moz-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-webkit-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@keyframes unpintobbar{10%{translate:0 1px}70%{rotate:z 50deg;translate:-3px 1px}}@-moz-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}@-webkit-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}.swagger-ui .topbar .download-url-wrapper .select-label select,.swagger-ui .topbar .download-url-wrapper input[type=text]{border:2px solid var(--topbar-select-border-color,#62a03f)}.swagger-ui .topbar .download-url-wrapper .select-label{color:var(--topbar-select-label-color,#f0f0f0)}.swagger-ui .topbar .download-url-wrapper .download-url-button{background:var(--topbar-download-url-button-background-color,#62a03f);color:var(--topbar-download-url-button-color,#fff)}#theme-switcher-select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);border:2px solid var(--topbar-select-border-color,#62a03f);min-width:120px;box-shadow:none}.swagger-ui .info .base-url,.swagger-ui .info .title,.swagger-ui .info h1,.swagger-ui .info h2,.swagger-ui .info h3,.swagger-ui .info h4,.swagger-ui .info h5,.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info table{color:var(--swagger-main-color,#3b4151)}.swagger-ui .info a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .info a:hover{color:var(--swagger-info-link-hover,#1f69c0)}.swagger-ui .info .title small{background:var(--api-version-background-color,#7d8492)}.swagger-ui .info .title small.version-stamp{background-color:var(--api-version-stamp-background-color,#89bf04)}.swagger-ui .info .title small pre{color:var(--api-version-color,#fff)}.swagger-ui .scheme-container{background:0 0;box-shadow:none;margin:0;padding:0}.swagger-ui .auth-wrapper{display:flex;flex:1;justify-content:flex-end;border:1px solid var(--auth-wrapper-border-color,#d5d5d5);padding:0;border-radius:5px;box-shadow:0 3px 5px -5px var(--auth-wrapper-box-shadow-color,#000);background:var(--auth-wrapper-background-color,#fff)}.swagger-ui .scheme-container .schemes>label{color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container{border-bottom:1px solid var(--auth-container-border-bottom-color,#ebebeb);color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container .errors{background-color:var(--auth-container-background-color,#fee);color:var(--auth-container-errors-color,red)}.swagger-ui .btn.authorize{background-color:var(--btn-authorize-background-color,transparent);border-color:var(--btn-authorize-border-color,#49cc90);color:var(--btn-authorize-font-color,#49cc90)}.swagger-ui .btn.authorize svg{fill:var(--btn-authorize-svg-fill-color,#49cc90)}.swagger-ui .opblock-tag-section{display:flex;flex-direction:column;border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;margin-block:1rem;overflow:clip}.swagger-ui .opblock-tag-section .expand-collapse-all-btn{display:none;user-select:none}.swagger-ui .opblock-tag-section.is-open .expand-collapse-all-btn{background:var(--opblock-tag-section-expandcollapse-background-color,rgb(220 220 220 / 45%));color:var(--swagger-main-color,#3b4151);border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;padding:5px 10px;font-size:medium;margin-inline:10px;display:block}.swagger-ui .opblock-tag,.swagger-ui .opblock-tag small,.swagger-ui .parameter__type{color:var(--swagger-main-color,#3b4151)}.swagger-ui .opblock-tag{align-items:center;cursor:pointer;display:flex;padding:10px 20px 10px 10px;color:var(--swagger-main-color,#3b4151);font-size:24px;border-bottom:0 dashed var(--opblock-tag-border-bottom-color,#e1e1e1);margin:0;transition:all .2s}.swagger-ui .opblock-tag:focus-within,.swagger-ui .opblock-tag:hover{background:var(--opblock-tag-background-color-hover,rgba(0,0,0,.02))}.swagger-ui .opblock-tag+div .operation-tag-content{margin-inline:5px}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag{position:sticky;top:0;z-index:4;margin-bottom:1rem;border-bottom:1px dashed var(--opblock-tag-border-bottom-color,#e1e1e1);background:var(--body-background-color,#fafafa)}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:focus-within,#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:hover{background:var(--opblock-tag-pinned-background-color-hover,#f1f1f1)}.swagger-ui .opblock-tag-section.is-open .opblock-tag{top:calc(var(--pinned-topbar-offset,0px) + var(--pinned-filter-offset,0px))}.swagger-ui .opblock{border:1px solid var(--opblock-border-color,#000);box-shadow:0 0 3px var(--opblock-shadow-color,rgba(0,0,0,.19))}.swagger-ui .opblock .tab-header .tab-item.active h4 span:after{background:var(--opblock-tabheader-underline-color,gray)}.swagger-ui .opblock .opblock-summary button svg{fill:var(--opblock-summary-svg-icons-color,#000)}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid var(--opblock-summary-border-bottom-color,#000)}.swagger-ui .opblock .opblock-section-header{background:var(--opblock-section-header-background-color,hsla(0,0%,100%,.8));box-shadow:0 1px 2px var(--opblock-section-header-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-section-header h4,.swagger-ui .opblock .opblock-section-header>label{color:var(--swagger-main-color,#3b4151)}.swagger-ui .opblock .opblock-summary-method{background:var(--opblock-summary-method-background-color,#000);color:var(--opblock-summary-method-color,#fff);text-shadow:0 1px 0 var(--opblock-summary-method-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{color:var(--swagger-main-color,#3b4151);word-break:normal}.swagger-ui .opblock.opblock-post{background:var(--opblock-post-background-color,rgba(73,204,144,.1));border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock.opblock-post .opblock-summary-method{background:var(--opblock-post-method-color,#49cc90)}.swagger-ui .opblock.opblock-post .opblock-summary{border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span:after{background:var(--opblock-post-method-color,#49cc90);border-radius:1rem}.swagger-ui .opblock.opblock-put{background:var(--opblock-put-background-color,rgba(252,161,48,.1));border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-put .opblock-summary-method{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-put .opblock-summary{border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span:after{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-delete{background:var(--opblock-delete-background-color,rgba(249,62,62,.1));border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .opblock-summary-method{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span:after{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-get{background:var(--opblock-get-background-color,rgba(97,175,254,.1));border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get .opblock-summary-method{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-get .opblock-summary{border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span:after{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-patch{background:var(--opblock-patch-background-color,rgba(80,227,194,.1));border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .opblock-summary-method{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span:after{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-head{background:var(--opblock-head-background-color,rgba(144,18,254,.1));border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head .opblock-summary-method{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-head .opblock-summary{border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span:after{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-options{background:var(--opblock-options-background-color,rgba(13,90,167,.1));border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .opblock-summary-method{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .opblock-summary{border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span:after{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-deprecated{background:var(--opblock-deprecated-background-color,hsla(0,0%,92%,.1));border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span:after{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .tab li{color:var(--swagger-main-color,#3b4151)}.swagger-ui .tab li:first-of-type:after{background:var(--swagger-tabs-divider-color,rgba(0,0,0,.2))}.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-external-docs-wrapper,.swagger-ui .opblock-external-docs-wrapper h4,.swagger-ui .opblock-external-docs-wrapper p,.swagger-ui .opblock-title_normal,.swagger-ui .opblock-title_normal h4,.swagger-ui .opblock-title_normal p,.swagger-ui .response-col_links,.swagger-ui .response-col_status,.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5{color:var(--swagger-main-color,#3b4151)}.swagger-ui .response-col_links .response-undocumented,.swagger-ui .response-col_status .response-undocumented{color:var(--response-undocumented-color,#909090)}.swagger-ui .response-control-media-type--accept-controller select{border-color:var(--response-control-media-type-color,green)}.swagger-ui .response-control-media-type__accept-message{color:var(--response-control-media-type-color,green)}.swagger-ui .opblock-body pre.microlight{background:var(--opblock-pre-microlight-background-color,#333)!important;color:var(--opblock-pre-microlight-color,#fff)!important}.swagger-ui .download-contents{background:var(--download-contents-background-color,#7d8293);color:var(--download-contents-color,#fff)}.swagger-ui .copy-to-clipboard{background:var(--copy-to-clipboard-background-color,#7d8293)}.swagger-ui .copy-to-clipboard button{background:var(--copy-to-clipboard-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .filter .operation-filter-input{border:2px solid var(--filter-operation-filter-input-border-color,#d8dde7)}.swagger-ui div:has(> .filter-container > .filter.pinned){position:sticky;top:var(--pinned-topbar-offset,0);z-index:5;background:var(--body-background-color,#fafafa);--pinned-filter-offset:var(--filterbar-pinned-height)}.swagger-ui:has(.filter.pinned){--pinned-filter-offset:var(--filterbar-pinned-height)}.swagger-ui .filter #pin-filterbar-btn svg{fill:var(--filterbar-pin-icon-color,#707070)}.swagger-ui .filter #pin-filterbar-btn{cursor:pointer;background:0 0;border:none;margin:.2rem;padding:.2rem;transition:all .2s;rotate:z 359deg}.swagger-ui .filter #pin-filterbar-btn:focus-within,.swagger-ui .filter #pin-filterbar-btn:hover{scale:1.05;animation:pintobbar 1.2s infinite}.swagger-ui .filter #pin-filterbar-btn:active{scale:0.95}.swagger-ui .filter.pinned #pin-filterbar-btn{rotate:z 0deg}.swagger-ui .filter.pinned #pin-filterbar-btn:focus-within,.swagger-ui .filter.pinned #pin-filterbar-btn:hover{animation:unpintobbar 1.2s infinite}.swagger-ui .download-url-wrapper .failed,.swagger-ui .filter .failed{color:var(--filter-download-failed-color,red)}.swagger-ui .download-url-wrapper .loading,.swagger-ui .filter .loading{color:var(--filter-download-loading-color,#aaa)}.swagger-ui section h3{color:var(--swagger-main-color,#3b4151)}.swagger-ui .servers>label,.swagger-ui table.headers td{color:var(--swagger-main-color,#3b4151)}.swagger-ui table.headers .header-example{color:var(--table-headers-example-color,#999)}.swagger-ui table thead tr td,.swagger-ui table thead tr th{border-bottom:1px solid var(--table-thead-border-bottom-color,rgba(59,65,81,.2));color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name{color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name.required span{color:var(--parameter-name-required-symbol-color,red)}.swagger-ui .parameter__name.required::after{color:var(--parameter-name-required-color,rgba(255,0,0,.6))}.swagger-ui .parameter__extension,.swagger-ui .parameter__in,.swagger-ui .response__extension{color:var(--parameter-in-extension-color,gray)}.swagger-ui .parameter__deprecated{color:var(--parameter-deprecated-color,red)}.swagger-ui .btn{background:var(--button-background-color,transparent);border:2px solid var(--button-border-color,gray);box-shadow:0 1px 2px var(--button-shadow-color,rgba(0,0,0,.1));color:var(--swagger-main-color,#3b4151)}.swagger-ui .btn:hover{box-shadow:0 0 5px var(--button-shadow-hover-color,rgba(0,0,0,.3))}.swagger-ui .btn.cancel{background-color:var(--button-cancel-background-color,transparent);border-color:var(--button-cancel-border-color,#ff6060);color:var(--button-cancel-color,#ff6060)}.swagger-ui .btn.execute{background-color:var(--button-execute-background-color,#4990e2);border-color:var(--button-execute-border-color,#4990e2);color:var(--button-execute-color,#fff)}.swagger-ui button.invalid{background:var(--button-invalid-background-color,#feebeb);border-color:var(--button-invalid-border-color,#f93e3e)}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{fill:var(--expand-operation-svg-arrow-color,#707070)}.swagger-ui .expand-methods:hover svg,.swagger-ui .expand-operation:hover svg{fill:var(--expand-operation-svg-arrow-hover-color,#404040)}.swagger-ui select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);border:2px solid var(--select-border-color,#41444e);box-shadow:0 1px 2px 0 var(--select-shadow-color,rgba(0,0,0,.25));color:var(--swagger-main-color,#3b4151);cursor:pointer}.swagger-ui select[multiple]{background:var(--select-background-color,#f7f7f7)}.swagger-ui select:disabled{opacity:.5;cursor:not-allowed}.swagger-ui label{color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text],.swagger-ui textarea{background:var(--input-background-color,#fff);border:1px solid var(--input-border-color,#d9d9d9);color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email].invalid,.swagger-ui input[type=file].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid,.swagger-ui select.invalid,.swagger-ui textarea.invalid{background:var(--input-invalid-background-color,#feebeb);border-color:var(--input-invalid-border-color,#f93e3e);color:var(--input-invalid-color,#3b4151)}.swagger-ui input[disabled],.swagger-ui select[disabled]{background-color:var(--input-disabled-background-color,#fafafa);color:var(--input-disabled-color,#888)}.swagger-ui select[disabled]{border-color:var(--select-disabled-border-color,#888)}.swagger-ui textarea[disabled]{background-color:var(--textarea-disabled-background-color,#41444e);color:var(--textarea-disabled-color,#fff)}.swagger-ui textarea{background:var(--textarea-background-color,hsla(0,0%,100%,.8));color:var(--swagger-main-color,#3b4151)}.swagger-ui textarea:focus{border:2px solid var(--textarea-border-focus-color,#61affe)}.swagger-ui textarea.curl{background:var(--textarea-curl-background-color,#41444e);color:var(--textarea-curl-color,#fff)}.swagger-ui .checkbox{color:var(--checkbox-color,#303030)}.swagger-ui .checkbox p{color:var(--swagger-main-color,#3b4151)}.swagger-ui .checkbox input[type=checkbox]+label>.item{background:var(--checkbox-label-item-background-color,#e8e8e8);box-shadow:0 0 0 2px var(--checkbox-label-item-shadow-color,#e8e8e8)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:var(--checkbox-label-item-icon, #e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .dialog-ux .backdrop-ux{background:var(--dialog-backdrop-color,rgba(0,0,0,.8))}.swagger-ui .dialog-ux .modal-ux{background:var(--dialog-background-color,#fff);border:1px solid var(--dialog-border-color,#ebebeb);box-shadow:0 10px 30px 0 var(--dialog-shadow-color,rgba(0,0,0,.2))}.swagger-ui .dialog-ux .modal-ux-content h4,.swagger-ui .dialog-ux .modal-ux-content p,.swagger-ui .dialog-ux .modal-ux-header h3{color:var(--swagger-main-color,#3b4151)}.swagger-ui .dialog-ux .modal-ux-header{border-bottom:1px solid var(--dialog-border-color,#ebebeb)}.swagger-ui .dialog-ux .modal-ux-header button svg{fill:var(--dialog-close-button-icon-color,black)}.swagger-ui .model{color:var(--swagger-main-color,#3b4151)}.swagger-ui .model .deprecated span,.swagger-ui .model .deprecated td{color:var(--model-deprecated-color,#a0a0a0)!important}.swagger-ui .model-toggle{cursor:pointer;-webkit-transition:-webkit-transform .15s ease-in;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}.swagger-ui .model-toggle.collapsed{-webkit-transform:rotate(0);transform:rotate(0)}.swagger-ui .model-toggle::after{background:var(--model-arrow-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .model-hint{background:var(--model-hint-background-color,rgba(0,0,0,.7));color:var(--model-hint-color,#ebebeb)}.swagger-ui .model .property{color:var(--model-property-color,#999)}.swagger-ui .model .property.primitive{color:var(--model-property-primitive-color,#6b6b6b)}.swagger-ui table.model tr.property-row .star{color:var(--model-property-required-symbol-color,red)}.swagger-ui .model .external-docs,.swagger-ui table.model tr.description{color:var(--model-property-description,#666)}.swagger-ui table.model tr.extension{color:var(--model-property-extension,#777)}.swagger-ui section.models{border:1px solid var(--model-section-border-color,rgba(59,65,81,.3));border-radius:.5rem;padding:0;margin:0}.swagger-ui section.models.is-open h4{border-bottom:1px solid var(--model-section-border-color,rgba(59,65,81,.3))}.swagger-ui section.models h4{color:var(--model-section-header-color,#606060);padding:0}.swagger-ui section.models h4:hover{background:var(--model-section-header-hover-background-color,rgba(0,0,0,.02))}.swagger-ui section.models h4 svg{fill:var(--expand-model-svg-arrow-color,#707070)}.swagger-ui section.models h4 button.models-control{color:var(--model-section-header-color,#606060);padding:20px}.swagger-ui section.models h5{color:var(--model-section-little-header-color,#707070)}.swagger-ui section.models .model-container{background:var(--model-container-background-color,rgba(0,0,0,.05))}.swagger-ui section.models .model-container:hover{background:var(--model-container-hover-background-color,rgba(0,0,0,.07))}.swagger-ui .model-box{background:var(--model-box-background-color,rgba(0,0,0,.1))}.swagger-ui .model-title{color:var(--model-title-color,#505050)}.swagger-ui .model-deprecated-warning{color:var(--model-deprecated-warning-color,#f93e3e)}.swagger-ui .prop-type{color:var(--model-prop-type-color,#55a)}.swagger-ui .prop-format{color:var(--model-prop-format-color,#606060)}.swagger-ui .scopes h2{color:var(--swagger-main-color,#3b4151)}.swagger-ui .scopes h2 a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .markdown pre,.swagger-ui .renderedMarkdown pre{color:var(--rendered-markdown-pre-color,#000)}.swagger-ui .markdown code,.swagger-ui .renderedMarkdown code{background:var(--rendered-markdown-code-background-color,rgba(0,0,0,.05));color:var(--rendered-markdown-code-color,#9012fe)}.scroll-to-top-wrapper{position:fixed;bottom:0;left:0;right:0;text-align:end;box-sizing:border-box;margin:0 auto;max-width:1460px;padding:0 20px;width:100%;pointer-events:none;z-index:1000}#scroll-to-top-btn{background-color:var(--scroll-to-top-button-background-color,#000000d1);border:1px solid var(--scroll-to-top-button-border-color,#d1c9c9);border-radius:50%;color:#fff;cursor:pointer;width:2.5rem;aspect-ratio:1;backdrop-filter:blur(20px);margin:10px;position:relative;transition:background-color .5s,opacity .6s .2s,translate .8s,scale .2s;opacity:0;translate:0 100px;box-shadow:0 0 10px 0 var(--scroll-to-top-button-shadow-color,rgba(0,0,0,.4))}#scroll-to-top-btn.showBtn{opacity:1;translate:0 0;pointer-events:auto}#scroll-to-top-btn::before{position:absolute;content:'';top:10px;left:12px;right:12px;background-color:var(--scroll-to-top-button-icon-color,#fff);border-radius:1rem;height:2px}#scroll-to-top-btn::after{position:absolute;content:'';top:15px;left:50%;border:solid var(--scroll-to-top-button-icon-color,#fff);border-width:0 2px 2px 0;padding:3px;transform:rotate(-135deg);translate:-50% 4px}#scroll-to-top-btn:focus-within,#scroll-to-top-btn:hover{background-color:var(--scroll-to-top-button-hover-background-color,#0d0d0dba)}#scroll-to-top-btn:focus-within::after,#scroll-to-top-btn:hover::after{animation:bounceUp 1.2s ease-in infinite}#scroll-to-top-btn:active{scale:0.95}@keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-moz-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-webkit-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@media only screen and (max-width:680px){.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{word-break:break-all}} \ No newline at end of file +/*Common Theme https://github.com/teociaps/SwaggerUI.Themes */@charset "UTF-8";:root{--topbar-pinned-height:4.2rem;--filterbar-pinned-height:7.2rem}::-webkit-scrollbar{width:16px}::-webkit-scrollbar-thumb{height:56px;border-radius:8px;border:4px solid transparent;background-clip:content-box;background-color:var(--scrollbar-thumb-color,#a5a5a5);transition:background-color .3s}::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover-color,#727272)}body::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-color,#a5a5a5)}*{font-family:system-ui,sans-serif}:focus{font-family:system-ui,sans-serif;outline:0!important}:focus-visible{outline:auto!important;outline-offset:1px}body{background-color:var(--body-background-color,#fafafa);padding-bottom:1rem}*,::after,::before{box-sizing:border-box}.swagger-ui{color:var(--swagger-main-color,#3b4151);-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.15;margin:0}.swagger-ui .loading-container .loading:after{color:var(--swagger-main-color,#3b4151)}.swagger-ui .loading-container .loading:before{border:2px solid var(--loading-container-border-color,rgba(85,85,85,.1));border-top-color:var(--loading-container-border-top-color,rgba(0,0,0,.6))}.swagger-ui .errors-wrapper{background:var(--errors-wrapper-background-color,rgba(249,62,62,.1));border:2px solid var(--errors-wrapper-border-color,#f93e3e)}.swagger-ui .errors-wrapper .errors h4,.swagger-ui .errors-wrapper hgroup h4{color:var(--swagger-main-color,#3b4151)}.swagger-ui .errors-wrapper .errors small{color:var(--errors-wrapper-errors-color,#606060)}.swagger-ui .topbar{background-color:transparent;z-index:1000;padding:0}.swagger-ui .topbar.pinned{position:sticky;top:10px;--pinned-topbar-offset:var(--topbar-pinned-height)}.swagger-ui:has(> .topbar.pinned){--pinned-topbar-offset:var(--topbar-pinned-height)}.swagger-ui .topbar.pinned::before{content:'';position:absolute;inset:-10px 0 0;pointer-events:none;user-select:none;backdrop-filter:blur(20px);-webkit-mask-image:linear-gradient(to bottom,black 40%,transparent);mask-image:linear-gradient(to bottom,black 40%,transparent);background:linear-gradient(90deg,rgba(0,0,0,0) 5%,rgba(8,8,8,.4) 50%,rgba(0,0,0,0) 95%)}.swagger-ui .topbar .topbar-wrapper{background-color:var(--topbar-background-color,#000);border:1px solid var(--topbar-border-color,gray);border-top:0;border-radius:0 0 .5rem .5rem;padding:.5rem;box-shadow:0 5px 15px 0 var(--topbar-pinned-shadow-color,rgba(0,0,0,.5))}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn svg{fill:var(--topbar-pin-icon-color,white)}.swagger-ui .filter #pin-filterbar-btn,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn{cursor:pointer;background:0 0;border:none;margin:.2rem;padding:.2rem;transition:all .2s;rotate:z 359deg}.swagger-ui .filter #pin-filterbar-btn:focus-within,.swagger-ui .filter #pin-filterbar-btn:hover,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:hover{scale:1.05;animation:pintobbar 1.2s infinite}.swagger-ui .filter #pin-filterbar-btn:active,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:active{scale:0.95}.swagger-ui .topbar.pinned .topbar-wrapper{background-color:var(--topbar-pinned-background-color,rgba(0,0,0,.7));border:1px solid var(--topbar-border-color,gray);border-radius:.5rem;backdrop-filter:blur(20px)}.swagger-ui .filter.pinned #pin-filterbar-btn,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn{rotate:z 0deg}.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn{translate:0 5px}.swagger-ui .filter.pinned #pin-filterbar-btn:focus-within,.swagger-ui .filter.pinned #pin-filterbar-btn:hover,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:hover{animation:unpintobbar 1.2s infinite}@keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-moz-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-webkit-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@keyframes unpintobbar{10%{translate:0 1px}70%{rotate:z 50deg;translate:-3px 1px}}@-moz-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}@-webkit-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}.swagger-ui .topbar .download-url-wrapper .select-label select,.swagger-ui .topbar .download-url-wrapper input[type=text]{border:2px solid var(--topbar-select-border-color,#62a03f)}.swagger-ui .topbar .download-url-wrapper .select-label{color:var(--topbar-select-label-color,#f0f0f0)}.swagger-ui .topbar .download-url-wrapper .download-url-button{background:var(--topbar-download-url-button-background-color,#62a03f);color:var(--topbar-download-url-button-color,#fff)}#theme-switcher-select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);border:2px solid var(--topbar-select-border-color,#62a03f);min-width:120px;box-shadow:none}.swagger-ui .info .base-url,.swagger-ui .info .title,.swagger-ui .info h1,.swagger-ui .info h2,.swagger-ui .info h3,.swagger-ui .info h4,.swagger-ui .info h5,.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info table{color:var(--swagger-main-color,#3b4151)}.swagger-ui .info a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .info a:hover{color:var(--swagger-info-link-hover,#1f69c0)}.swagger-ui .info .title small{background:var(--api-version-background-color,#7d8492)}.swagger-ui .info .title small.version-stamp{background-color:var(--api-version-stamp-background-color,#89bf04)}.swagger-ui .info .title small pre{color:var(--api-version-color,#fff)}.swagger-ui .scheme-container{background:0 0;box-shadow:none;margin:0;padding:0}.swagger-ui .auth-wrapper{display:flex;flex:1;justify-content:flex-end;border:1px solid var(--auth-wrapper-border-color,#d5d5d5);padding:0;border-radius:5px;box-shadow:0 3px 5px -5px var(--auth-wrapper-box-shadow-color,#000);background:var(--auth-wrapper-background-color,#fff)}.swagger-ui .scheme-container .schemes>label{color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container{border-bottom:1px solid var(--auth-container-border-bottom-color,#ebebeb);color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container .errors{background-color:var(--auth-container-background-color,#fee);color:var(--auth-container-errors-color,red)}.swagger-ui .btn.authorize{background-color:var(--btn-authorize-background-color,transparent);border-color:var(--btn-authorize-border-color,#49cc90);color:var(--btn-authorize-font-color,#49cc90)}.swagger-ui .btn.authorize svg{fill:var(--btn-authorize-svg-fill-color,#49cc90)}.swagger-ui .opblock-tag-section{display:flex;flex-direction:column;border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;margin-block:1rem;overflow:clip}.swagger-ui .opblock-tag-section .expand-collapse-all-btn{display:none;user-select:none}.swagger-ui .opblock-tag-section.is-open .expand-collapse-all-btn{background:var(--opblock-tag-section-expandcollapse-background-color,rgb(220 220 220 / 45%));color:var(--swagger-main-color,#3b4151);border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;padding:5px 10px;font-size:medium;margin-inline:10px;display:block}.swagger-ui .opblock-tag,.swagger-ui .opblock-tag small,.swagger-ui .parameter__type{color:var(--swagger-main-color,#3b4151)}.swagger-ui .opblock-tag{align-items:center;cursor:pointer;display:flex;padding:10px 20px 10px 10px;color:var(--swagger-main-color,#3b4151);font-size:24px;border-bottom:0 dashed var(--opblock-tag-border-bottom-color,#e1e1e1);margin:0;transition:all .2s}.swagger-ui .opblock-tag:focus-within,.swagger-ui .opblock-tag:hover{background:var(--opblock-tag-background-color-hover,rgba(0,0,0,.02))}.swagger-ui .opblock-tag+div .operation-tag-content{margin-inline:5px}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag{position:sticky;top:0;z-index:4;margin-bottom:1rem;border-bottom:1px dashed var(--opblock-tag-border-bottom-color,#e1e1e1);background:var(--body-background-color,#fafafa)}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:focus-within,#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:hover{background:var(--opblock-tag-pinned-background-color-hover,#f1f1f1)}.swagger-ui .opblock-tag-section.is-open .opblock-tag{top:calc(var(--pinned-topbar-offset,0px) + var(--pinned-filter-offset,0px))}.swagger-ui .opblock{border:1px solid var(--opblock-border-color,#000);box-shadow:0 0 3px var(--opblock-shadow-color,rgba(0,0,0,.19))}.swagger-ui .opblock .tab-header .tab-item.active h4 span:after{background:var(--opblock-tabheader-underline-color,gray)}.swagger-ui .opblock .opblock-summary button svg{fill:var(--opblock-summary-svg-icons-color,#000)}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid var(--opblock-summary-border-bottom-color,#000)}.swagger-ui .opblock .opblock-section-header{background:var(--opblock-section-header-background-color,hsla(0,0%,100%,.8));box-shadow:0 1px 2px var(--opblock-section-header-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-section-header h4,.swagger-ui .opblock .opblock-section-header>label{color:var(--swagger-main-color,#3b4151)}.swagger-ui .opblock .opblock-summary-method{background:var(--opblock-summary-method-background-color,#000);color:var(--opblock-summary-method-color,#fff);text-shadow:0 1px 0 var(--opblock-summary-method-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{color:var(--swagger-main-color,#3b4151);word-break:normal}.swagger-ui .opblock.opblock-post{background:var(--opblock-post-background-color,rgba(73,204,144,.1));border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock.opblock-post .opblock-summary-method{background:var(--opblock-post-method-color,#49cc90)}.swagger-ui .opblock.opblock-post .opblock-summary{border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span:after{background:var(--opblock-post-method-color,#49cc90);border-radius:1rem}.swagger-ui .opblock.opblock-put{background:var(--opblock-put-background-color,rgba(252,161,48,.1));border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-put .opblock-summary-method{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-put .opblock-summary{border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span:after{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-delete{background:var(--opblock-delete-background-color,rgba(249,62,62,.1));border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .opblock-summary-method{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span:after{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-get{background:var(--opblock-get-background-color,rgba(97,175,254,.1));border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get .opblock-summary-method{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-get .opblock-summary{border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span:after{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-patch{background:var(--opblock-patch-background-color,rgba(80,227,194,.1));border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .opblock-summary-method{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span:after{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-head{background:var(--opblock-head-background-color,rgba(144,18,254,.1));border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head .opblock-summary-method{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-head .opblock-summary{border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span:after{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-options{background:var(--opblock-options-background-color,rgba(13,90,167,.1));border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .opblock-summary-method{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .opblock-summary{border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span:after{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-deprecated{background:var(--opblock-deprecated-background-color,hsla(0,0%,92%,.1));border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span:after{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .tab li{color:var(--swagger-main-color,#3b4151)}.swagger-ui .tab li:first-of-type:after{background:var(--swagger-tabs-divider-color,rgba(0,0,0,.2))}.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-external-docs-wrapper,.swagger-ui .opblock-external-docs-wrapper h4,.swagger-ui .opblock-external-docs-wrapper p,.swagger-ui .opblock-title_normal,.swagger-ui .opblock-title_normal h4,.swagger-ui .opblock-title_normal p,.swagger-ui .response-col_links,.swagger-ui .response-col_status,.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5{color:var(--swagger-main-color,#3b4151)}.swagger-ui .response-col_links .response-undocumented,.swagger-ui .response-col_status .response-undocumented{color:var(--response-undocumented-color,#909090)}.swagger-ui .response-control-media-type--accept-controller select{border-color:var(--response-control-media-type-color,green)}.swagger-ui .response-control-media-type__accept-message{color:var(--response-control-media-type-color,green)}.swagger-ui .opblock-body pre.microlight{background:var(--opblock-pre-microlight-background-color,#333)!important;color:var(--opblock-pre-microlight-color,#fff)!important}.swagger-ui .download-contents{background:var(--download-contents-background-color,#7d8293);color:var(--download-contents-color,#fff)}.swagger-ui .copy-to-clipboard{background:var(--copy-to-clipboard-background-color,#7d8293)}.swagger-ui .copy-to-clipboard button{background:var(--copy-to-clipboard-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .filter .operation-filter-input{border:2px solid var(--filter-operation-filter-input-border-color,#d8dde7)}.swagger-ui div:has(> .filter-container > .filter.pinned){position:sticky;top:var(--pinned-topbar-offset,0);z-index:5;background:var(--body-background-color,#fafafa);--pinned-filter-offset:var(--filterbar-pinned-height)}.swagger-ui:has(.filter.pinned){--pinned-filter-offset:var(--filterbar-pinned-height)}.swagger-ui .filter #pin-filterbar-btn svg{fill:var(--filterbar-pin-icon-color,#707070)}.swagger-ui .download-url-wrapper .failed,.swagger-ui .filter .failed{color:var(--filter-download-failed-color,red)}.swagger-ui .download-url-wrapper .loading,.swagger-ui .filter .loading{color:var(--filter-download-loading-color,#aaa)}.swagger-ui section h3{color:var(--swagger-main-color,#3b4151)}.swagger-ui .servers>label,.swagger-ui table.headers td{color:var(--swagger-main-color,#3b4151)}.swagger-ui table.headers .header-example{color:var(--table-headers-example-color,#999)}.swagger-ui table thead tr td,.swagger-ui table thead tr th{border-bottom:1px solid var(--table-thead-border-bottom-color,rgba(59,65,81,.2));color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name{color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name.required span{color:var(--parameter-name-required-symbol-color,red)}.swagger-ui .parameter__name.required::after{color:var(--parameter-name-required-color,rgba(255,0,0,.6))}.swagger-ui .parameter__extension,.swagger-ui .parameter__in,.swagger-ui .response__extension{color:var(--parameter-in-extension-color,gray)}.swagger-ui .parameter__deprecated{color:var(--parameter-deprecated-color,red)}.swagger-ui .btn{background:var(--button-background-color,transparent);border:2px solid var(--button-border-color,gray);box-shadow:0 1px 2px var(--button-shadow-color,rgba(0,0,0,.1));color:var(--swagger-main-color,#3b4151)}.swagger-ui .btn:hover{box-shadow:0 0 5px var(--button-shadow-hover-color,rgba(0,0,0,.3))}.swagger-ui .btn.cancel{background-color:var(--button-cancel-background-color,transparent);border-color:var(--button-cancel-border-color,#ff6060);color:var(--button-cancel-color,#ff6060)}.swagger-ui .btn.execute{background-color:var(--button-execute-background-color,#4990e2);border-color:var(--button-execute-border-color,#4990e2);color:var(--button-execute-color,#fff)}.swagger-ui button.invalid{background:var(--button-invalid-background-color,#feebeb);border-color:var(--button-invalid-border-color,#f93e3e)}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{fill:var(--expand-operation-svg-arrow-color,#707070)}.swagger-ui .expand-methods:hover svg,.swagger-ui .expand-operation:hover svg{fill:var(--expand-operation-svg-arrow-hover-color,#404040)}.swagger-ui select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);border:2px solid var(--select-border-color,#41444e);box-shadow:0 1px 2px 0 var(--select-shadow-color,rgba(0,0,0,.25));color:var(--swagger-main-color,#3b4151);cursor:pointer}.swagger-ui select[multiple]{background:var(--select-background-color,#f7f7f7)}.swagger-ui select:disabled{opacity:.5;cursor:not-allowed}.swagger-ui label{color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text],.swagger-ui textarea{background:var(--input-background-color,#fff);border:1px solid var(--input-border-color,#d9d9d9);color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email].invalid,.swagger-ui input[type=file].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid,.swagger-ui select.invalid,.swagger-ui textarea.invalid{background:var(--input-invalid-background-color,#feebeb);border-color:var(--input-invalid-border-color,#f93e3e);color:var(--input-invalid-color,#3b4151)}.swagger-ui input[disabled],.swagger-ui select[disabled]{background-color:var(--input-disabled-background-color,#fafafa);color:var(--input-disabled-color,#888)}.swagger-ui select[disabled]{border-color:var(--select-disabled-border-color,#888)}.swagger-ui textarea[disabled]{background-color:var(--textarea-disabled-background-color,#41444e);color:var(--textarea-disabled-color,#fff)}.swagger-ui textarea{background:var(--textarea-background-color,hsla(0,0%,100%,.8));color:var(--swagger-main-color,#3b4151)}.swagger-ui textarea:focus{border:2px solid var(--textarea-border-focus-color,#61affe)}.swagger-ui textarea.curl{background:var(--textarea-curl-background-color,#41444e);color:var(--textarea-curl-color,#fff)}.swagger-ui .checkbox{color:var(--checkbox-color,#303030)}.swagger-ui .checkbox p{color:var(--swagger-main-color,#3b4151)}.swagger-ui .checkbox input[type=checkbox]+label>.item{background:var(--checkbox-label-item-background-color,#e8e8e8);box-shadow:0 0 0 2px var(--checkbox-label-item-shadow-color,#e8e8e8)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:var(--checkbox-label-item-icon, #e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .dialog-ux .backdrop-ux{background:var(--dialog-backdrop-color,rgba(0,0,0,.8))}.swagger-ui .dialog-ux .modal-ux{background:var(--dialog-background-color,#fff);border:1px solid var(--dialog-border-color,#ebebeb);box-shadow:0 10px 30px 0 var(--dialog-shadow-color,rgba(0,0,0,.2))}.swagger-ui .dialog-ux .modal-ux-content h4,.swagger-ui .dialog-ux .modal-ux-content p,.swagger-ui .dialog-ux .modal-ux-header h3{color:var(--swagger-main-color,#3b4151)}.swagger-ui .dialog-ux .modal-ux-header{border-bottom:1px solid var(--dialog-border-color,#ebebeb)}.swagger-ui .dialog-ux .modal-ux-header button svg{fill:var(--dialog-close-button-icon-color,black)}.swagger-ui .model{color:var(--swagger-main-color,#3b4151)}.swagger-ui .model .deprecated span,.swagger-ui .model .deprecated td{color:var(--model-deprecated-color,#a0a0a0)!important}.swagger-ui .model-toggle{cursor:pointer;-webkit-transition:-webkit-transform .15s ease-in;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}.swagger-ui .model-toggle.collapsed{-webkit-transform:rotate(0);transform:rotate(0)}.swagger-ui .model-toggle::after{background:var(--model-arrow-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .model-hint{background:var(--model-hint-background-color,rgba(0,0,0,.7));color:var(--model-hint-color,#ebebeb)}.swagger-ui .model .property{color:var(--model-property-color,#999)}.swagger-ui .model .property.primitive{color:var(--model-property-primitive-color,#6b6b6b)}.swagger-ui table.model tr.property-row .star{color:var(--model-property-required-symbol-color,red)}.swagger-ui .model .external-docs,.swagger-ui table.model tr.description{color:var(--model-property-description,#666)}.swagger-ui table.model tr.extension{color:var(--model-property-extension,#777)}.swagger-ui section.models{border:1px solid var(--model-section-border-color,rgba(59,65,81,.3));border-radius:.5rem;padding:0;margin:0}.swagger-ui section.models.is-open h4{border-bottom:1px solid var(--model-section-border-color,rgba(59,65,81,.3))}.swagger-ui section.models h4{color:var(--model-section-header-color,#606060);padding:0}.swagger-ui section.models h4:hover{background:var(--model-section-header-hover-background-color,rgba(0,0,0,.02))}.swagger-ui section.models h4 svg{fill:var(--expand-model-svg-arrow-color,#707070)}.swagger-ui section.models h4 button.models-control{color:var(--model-section-header-color,#606060);padding:20px}.swagger-ui section.models h5{color:var(--model-section-little-header-color,#707070)}.swagger-ui section.models .model-container{background:var(--model-container-background-color,rgba(0,0,0,.05))}.swagger-ui section.models .model-container:hover{background:var(--model-container-hover-background-color,rgba(0,0,0,.07))}.swagger-ui .model-box{background:var(--model-box-background-color,rgba(0,0,0,.1))}.swagger-ui .model-title{color:var(--model-title-color,#505050)}.swagger-ui .model-deprecated-warning{color:var(--model-deprecated-warning-color,#f93e3e)}.swagger-ui .prop-type{color:var(--model-prop-type-color,#55a)}.swagger-ui .prop-format{color:var(--model-prop-format-color,#606060)}.swagger-ui .scopes h2{color:var(--swagger-main-color,#3b4151)}.swagger-ui .scopes h2 a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .markdown pre,.swagger-ui .renderedMarkdown pre{color:var(--rendered-markdown-pre-color,#000)}.swagger-ui .markdown code,.swagger-ui .renderedMarkdown code{background:var(--rendered-markdown-code-background-color,rgba(0,0,0,.05));color:var(--rendered-markdown-code-color,#9012fe)}.scroll-to-top-wrapper{position:fixed;bottom:0;left:0;right:0;text-align:end;box-sizing:border-box;margin:0 auto;max-width:1460px;padding:0 20px;width:100%;pointer-events:none;z-index:1000}#scroll-to-top-btn{background-color:var(--scroll-to-top-button-background-color,#000000d1);border:1px solid var(--scroll-to-top-button-border-color,#d1c9c9);border-radius:50%;color:#fff;cursor:pointer;width:2.5rem;aspect-ratio:1;backdrop-filter:blur(20px);margin:10px;position:relative;transition:background-color .5s,opacity .6s .2s,translate .8s,scale .2s;opacity:0;translate:0 100px;box-shadow:0 0 10px 0 var(--scroll-to-top-button-shadow-color,rgba(0,0,0,.4))}#scroll-to-top-btn.showBtn{opacity:1;translate:0 0;pointer-events:auto}#scroll-to-top-btn::before{position:absolute;content:'';top:10px;left:12px;right:12px;background-color:var(--scroll-to-top-button-icon-color,#fff);border-radius:1rem;height:2px}#scroll-to-top-btn::after{position:absolute;content:'';top:15px;left:50%;border:solid var(--scroll-to-top-button-icon-color,#fff);border-width:0 2px 2px 0;padding:3px;transform:rotate(-135deg);translate:-50% 4px}#scroll-to-top-btn:focus-within,#scroll-to-top-btn:hover{background-color:var(--scroll-to-top-button-hover-background-color,#0d0d0dba)}#scroll-to-top-btn:focus-within::after,#scroll-to-top-btn:hover::after{animation:bounceUp 1.2s ease-in infinite}#scroll-to-top-btn:active{scale:0.95}@keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-moz-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-webkit-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@media only screen and (max-width:680px){.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{word-break:break-all}} \ No newline at end of file From 51b24d9894173abc277f0b46f64df7edec4f28f6 Mon Sep 17 00:00:00 2001 From: Teuz Date: Thu, 13 Aug 2026 15:59:41 +0200 Subject: [PATCH 19/35] Update NuGet package versions across projects --- .../Sample.AspNetCore.SwaggerUI.Extensions.csproj | 2 +- .../Sample.AspNetCore.SwaggerUI.NSwag.csproj | 2 +- .../Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj | 4 ++-- .../Sample.NSwag.AspNetCore.Extensions.csproj | 8 ++++---- .../NSwag.AspNetCore.Extensions.csproj | 4 ++-- .../NSwag.AspNetCore.Themes.csproj | 2 +- .../AspNetCore.Swagger.Extensions.Tests.csproj | 8 ++++---- .../AspNetCore.Swagger.Themes.Tests.csproj | 6 +++--- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/samples/Sample.AspNetCore.SwaggerUI.Extensions/Sample.AspNetCore.SwaggerUI.Extensions.csproj b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Sample.AspNetCore.SwaggerUI.Extensions.csproj index 2fbe57d..1a23206 100644 --- a/samples/Sample.AspNetCore.SwaggerUI.Extensions/Sample.AspNetCore.SwaggerUI.Extensions.csproj +++ b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Sample.AspNetCore.SwaggerUI.Extensions.csproj @@ -9,7 +9,7 @@ - + diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Sample.AspNetCore.SwaggerUI.NSwag.csproj b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Sample.AspNetCore.SwaggerUI.NSwag.csproj index ef4ed4a..3e3a122 100644 --- a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Sample.AspNetCore.SwaggerUI.NSwag.csproj +++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Sample.AspNetCore.SwaggerUI.NSwag.csproj @@ -9,7 +9,7 @@ - + diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj index 3b05d88..c825bf9 100644 --- a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj +++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj @@ -9,8 +9,8 @@ - - + + diff --git a/samples/Sample.NSwag.AspNetCore.Extensions/Sample.NSwag.AspNetCore.Extensions.csproj b/samples/Sample.NSwag.AspNetCore.Extensions/Sample.NSwag.AspNetCore.Extensions.csproj index 6bf3dd3..4980a36 100644 --- a/samples/Sample.NSwag.AspNetCore.Extensions/Sample.NSwag.AspNetCore.Extensions.csproj +++ b/samples/Sample.NSwag.AspNetCore.Extensions/Sample.NSwag.AspNetCore.Extensions.csproj @@ -1,4 +1,4 @@ - +ο»Ώ @@ -9,15 +9,15 @@ - + - + - + diff --git a/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj b/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj index e64715c..4f2afef 100644 --- a/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj +++ b/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj @@ -1,4 +1,4 @@ - +ο»Ώ @@ -24,7 +24,7 @@ - + diff --git a/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj b/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj index 19caafd..297d747 100644 --- a/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj +++ b/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj @@ -24,7 +24,7 @@ - + diff --git a/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCore.Swagger.Extensions.Tests.csproj b/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCore.Swagger.Extensions.Tests.csproj index f16b453..888b824 100644 --- a/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCore.Swagger.Extensions.Tests.csproj +++ b/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCore.Swagger.Extensions.Tests.csproj @@ -9,13 +9,13 @@ - + - + @@ -23,13 +23,13 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/tests/AspNetCore.SwaggerUI.Themes.Tests/AspNetCore.Swagger.Themes.Tests.csproj b/tests/AspNetCore.SwaggerUI.Themes.Tests/AspNetCore.Swagger.Themes.Tests.csproj index 8a733cf..992da6f 100644 --- a/tests/AspNetCore.SwaggerUI.Themes.Tests/AspNetCore.Swagger.Themes.Tests.csproj +++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/AspNetCore.Swagger.Themes.Tests.csproj @@ -20,13 +20,13 @@ - + - + @@ -34,7 +34,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all From 0cbe2bff0a97c77163fd98938375f05efa54d51c Mon Sep 17 00:00:00 2001 From: Teuz Date: Thu, 13 Aug 2026 16:21:02 +0200 Subject: [PATCH 20/35] fix(themes): rename 'pinned' param to 'pinnable' for filter bar, enhance styles and update docs --- README.md | 8 +-- .../Program.cs | 7 ++ .../Program.cs | 9 ++- .../AspNetCore/Swagger/Themes/Scripts/ui.js | 40 +++++++++-- .../Swagger/Themes/Scripts/ui.min.js | 2 +- .../Swagger/Themes/Styles/common.css | 33 +++++++++- .../Swagger/Themes/Styles/common.min.css | 2 +- .../AspNetCore/Swagger/Themes/Styles/dark.css | 2 +- .../Swagger/Themes/Styles/dark.min.css | 2 +- .../Builder/SwaggerUIOptionsExtensions.cs | 6 +- .../package-readme.md | 4 +- src/AspNetCore.SwaggerUI.Themes/readme.txt | 63 +----------------- .../Builder/SwaggerUiSettingsExtensions.cs | 6 +- src/NSwag.AspNetCore.Themes/package-readme.md | 6 +- src/NSwag.AspNetCore.Themes/readme.txt | 66 +------------------ .../SwaggerUIOptionsExtensionsTests.cs | 8 +-- 16 files changed, 106 insertions(+), 158 deletions(-) diff --git a/README.md b/README.md index 229e746..534954f 100644 --- a/README.md +++ b/README.md @@ -83,9 +83,7 @@ app.UseSwaggerUi(Theme.Dark); // NSwag app.UseSwaggerUI(Theme.Dark, c => c.EnableThemeSwitcher()); // Enable a pinnable filter box -app.UseSwaggerUI(Theme.Dark, c => c.EnableFilter(pinned: true)); // Swashbuckle -// or -app.UseSwaggerUi(Theme.Dark, c => c.EnableFilter(pinned: true)); // NSwag +app.UseSwaggerUI(Theme.Dark, c => c.EnableFilter(pinnable: true)); ``` ### Extensions Quick Start @@ -114,13 +112,11 @@ builder.Services.AddOpenApiDocument(c => - **[Custom Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Custom-Themes)** - Build your own themes with full control, or create standalone themes with zero dependencies -- **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Enhance your documentation with powerful UI capabilities, including a [pinnable filter bar](https://github.com/teociaps/SwaggerUI.Themes/wiki/Feature-Pinnable-Filter-Bar) via `EnableFilter(pinned: true)` +- **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Enhance your documentation with powerful UI capabilities ### Extensions - **API Counter** - Automatically display operation counts in tag descriptions for better API surface area visibility -- _More extensions coming soon!_ - _Discover more features in the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki/Features)!_ ## πŸ“š Basic Usage Examples diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs index e31f52c..6078b04 100644 --- a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs +++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs @@ -195,6 +195,13 @@ // .WithThemes(Theme.Dark, Theme.Light) // .WithCustomThemes(CustomThemeMode.None)); //}); + + // 19. Pinnable filter bar + //app.UseSwaggerUi(Theme.Dark, c => + //{ + // c.DocumentTitle = "Sample API - Pinnable Filter Bar"; + // c.EnableFilter(pinnable: true); + //}); } app.UseHttpsRedirection(); diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs index c85f618..c3a01a0 100644 --- a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs +++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs @@ -8,7 +8,7 @@ builder.Services.AddSwaggerGen(c => { SwaggerGenConfigurer.Configure(c); - + // πŸ“Š API Counter: Server-side operation count c.AppendOperationCountToTags(); // Or with custom template: @@ -196,6 +196,13 @@ // .WithThemes(Theme.Dark, Theme.Light) // .WithCustomThemes(CustomThemeMode.None)); //}); + + // 19. Pinnable filter bar + //app.UseSwaggerUI(Theme.Dark, c => + //{ + // c.DocumentTitle = "Sample API - Pinnable Filter Bar"; + // c.EnableFilter(pinnable: true); + //}); } app.UseHttpsRedirection(); diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js index a611cdb..900b0a9 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js @@ -48,24 +48,50 @@ function setUpPinnableFilterBar(enabled) { if (enabled === false) return; + const filterBar = document.querySelector('.filter'); + if (!filterBar) + return; + + // CSS alone can't tell whether a sticky element has actually reached its sticky top, so + // toggle a 'stuck' class here (styled in common.css) by comparing the sticky wrapper's + // viewport position against its computed sticky offset. The wrapper is 2 parentElement + // hops up from .filter (.filter -> .filter-container -> sticky div, see common.css). + const stickyWrapper = filterBar.parentElement?.parentElement; + + function updateStuckState() { + if (!stickyWrapper) + return; + + // getComputedStyle().top is 'auto' (NaN) while unpinned, so the comparison alone + // could never be true then; the explicit pinned check just makes that intent clear. + const stuck = filterBar.classList.contains('pinned') + && stickyWrapper.getBoundingClientRect().top <= parseFloat(getComputedStyle(stickyWrapper).top) + 1; + filterBar.classList.toggle('stuck', stuck); + } + + window.addEventListener('scroll', updateStuckState, { passive: true }); + window.addEventListener('resize', updateStuckState); + setUpPinnableToggle({ buttonId: 'pin-filterbar-btn', - container: document.querySelector('.filter'), + container: filterBar, // 1 parentNode hop up from the button (appended directly to .filter) reaches .filter itself. pinnedAncestorDepth: 1, storageKey: 'swaggerui-pinnable-filterbar-preference', pinTitle: 'Pin filter bar', unpinTitle: 'Unpin filter bar', - // This is a new opt-in feature with no prior shipped behavior to preserve, so it - // defaults to unpinned when there's no saved preference. - defaultPinned: false + defaultPinned: false, + // Re-evaluate on every pin/unpin so unpinning mid-scroll clears the 'stuck' class + // immediately instead of waiting for the next scroll event. + onPinnedStateApplied: updateStuckState }); } // Shared behavior behind every pinnable element (topbar, filter bar, ...): injects a pin // button into `container`, toggles a 'pinned' class on the ancestor `pinnedAncestorDepth` -// parentNodes up from that button, and persists the choice to localStorage. -function setUpPinnableToggle({ buttonId, container, pinnedAncestorDepth, storageKey, pinTitle, unpinTitle, defaultPinned }) { +// parentNodes up from that button, persists the choice to localStorage, and notifies the +// optional `onPinnedStateApplied` callback after every applied state (initial load included). +function setUpPinnableToggle({ buttonId, container, pinnedAncestorDepth, storageKey, pinTitle, unpinTitle, defaultPinned, onPinnedStateApplied }) { if (!container) return; @@ -107,6 +133,8 @@ function setUpPinnableToggle({ buttonId, container, pinnedAncestorDepth, storage if (saveToStorage) { localStorage.setItem(storageKey, pinned ? 'pinned' : 'unpinned'); } + + onPinnedStateApplied?.(); } } diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js index 66a015a..ef407d3 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js @@ -1 +1 @@ -ο»Ώ/*Swagger UI https://github.com/teociaps/SwaggerUI.Themes */const rootElement=document.documentElement;function setUpPinnableTopbar(e){!1!==e&&setUpPinnableToggle({buttonId:"pin-topbar-btn",container:document.querySelector(".topbar-wrapper"),pinnedAncestorDepth:3,storageKey:"swaggerui-pinnable-topbar-preference",pinTitle:"Pin topbar",unpinTitle:"Unpin topbar",defaultPinned:!0})}function setUpPinnableFilterBar(e){!1!==e&&setUpPinnableToggle({buttonId:"pin-filterbar-btn",container:document.querySelector(".filter"),pinnedAncestorDepth:1,storageKey:"swaggerui-pinnable-filterbar-preference",pinTitle:"Pin filter bar",unpinTitle:"Unpin filter bar",defaultPinned:!1})}function setUpPinnableToggle({buttonId:e,container:t,pinnedAncestorDepth:n,storageKey:o,pinTitle:l,unpinTitle:a,defaultPinned:r}){if(!t)return;const i=document.createElement("button");i.setAttribute("id",e),i.addEventListener("click",()=>{d(!c(i).classList.contains("pinned"),!0),i?.blur()}),t.appendChild(i);const s=localStorage.getItem(o);function c(e){let t=e;for(let e=0;e{scrollToTop(),n?.blur()}),t.appendChild(n);document.getElementById("swagger-ui").appendChild(t);const o=()=>{window.scrollY>=200?n.classList.add("showBtn"):n.classList.remove("showBtn")};window.addEventListener("scroll",o),window.addEventListener("resize",o)}function scrollToTop(){rootElement.scrollTo({top:0,behavior:"smooth"})}function setUpExpandAndCollapseOperationsButtons(e){if(!1===e)return;let t=0;const n=document.getElementById("swagger-ui")||document.querySelector(".swagger-ui");function o(){const e=document.querySelectorAll(".opblock-tag-section");return!(!e||0===e.length)&&(e.forEach(e=>function(e){if(!e||"true"===e.dataset.expandCollapseAttached)return;const t=e.querySelector("h3"),n=t?.querySelector("button.expand-operation");if(t&&!t.querySelector(".expand-collapse-all-btn")){const o=document.createElement("button");o.setAttribute("title","Expand/Collapse all the operations"),o.classList.add("expand-collapse-all-btn"),o.innerHTML="Expand/Collapse All",n?n.before(o):t.appendChild(o),o.addEventListener("click",t=>{t.preventDefault(),t.stopPropagation();const n=e.querySelectorAll(".opblock .opblock-control-arrow");n&&0!==n.length&&(Array.from(n).every(e=>"true"===e.getAttribute("aria-expanded"))?n.forEach(e=>{"true"===e.getAttribute("aria-expanded")&&e.click()}):n.forEach(e=>{"false"===e.getAttribute("aria-expanded")&&e.click()}))})}e.dataset.expandCollapseAttached="true"}(e)),!0)}!function e(){if(t++,o())try{const e=n||document.body;new MutationObserver(e=>{for(const t of e)t.addedNodes&&t.addedNodes.length>0&&o()}).observe(e,{childList:!0,subtree:!0})}catch(e){}else t<60&&setTimeout(e,200)}()}function setUpThemeSwitcher(e){if(!1===e)return;const t="swaggerui-theme-preference";let n=null,o=null;function l(e,l){const a=n?.themes.find(t=>t.name===e);if(!a)return void console.warn(`[ThemeSwitcher] Theme not found: ${e}`);const r=document.querySelectorAll('link[rel="stylesheet"]'),i=n.themes.map(e=>e.cssPath);let s=!1;r.forEach(t=>{if(t.dataset.theme||i.some(e=>t.href?.endsWith(e))){t.dataset.theme===e||t.href?.endsWith(a.cssPath)?(t.disabled=!1,t.dataset.theme=e,s=!0):t.disabled=!0}}),s||console.warn(`[ThemeSwitcher] Could not activate theme: ${e}`),o=e,l&&localStorage.setItem(t,e);const c=document.getElementById("theme-switcher-select");c&&c.value!==e&&(c.value=e)}fetch("/themes/metadata.json").then(e=>e.ok?e.json():(console.warn("[ThemeSwitcher] Failed to load theme metadata"),null)).then(e=>{!e?.themes||e.themes.length<2?console.warn("[ThemeSwitcher] Not enough themes available for switcher"):(n=e,o=detectCurrentTheme(e.themes),function(e){const n=localStorage.getItem(t);n&&e.some(e=>e.name===n)&&n!==o&&l(n,!1)}(e.themes),function(e){const t=document.querySelector(".topbar-wrapper");if(!t)return void console.warn("[ThemeSwitcher] Topbar not found");const n=document.createElement("select");n.id="theme-switcher-select",n.setAttribute("aria-label","Select theme"),n.title="Switch theme",e.themes.forEach(t=>{const l=document.createElement("option");l.value=t.name,l.textContent=formatThemeName(t.name,e.config?.displayFormat||"{name}"),t.name===o&&(l.selected=!0),n.appendChild(l)}),n.addEventListener("change",e=>{l(e.target.value,!0)});const a=document.getElementById("pin-topbar-btn");a?a.before(n):t.appendChild(n)}(e))}).catch(e=>{console.error("[ThemeSwitcher] Error loading themes:",e)})}function detectCurrentTheme(e){const t=document.querySelector('link[rel="stylesheet"]:not([disabled])[data-theme]');if(t?.dataset.theme)return t.dataset.theme;const n=document.querySelectorAll('link[rel="stylesheet"]:not([disabled])');for(const t of n){const n=e.find(e=>!!t.href&&(t.href.includes(e.cssPath)||t.href.endsWith(e.cssPath)||t.href.includes(e.name.toLowerCase())));if(n)return n.name}return e.length>0?e[0].name:null}function formatThemeName(e,t){const n=e.replaceAll(/([A-Z])/g," $1").trim().split(/[\s_-]+/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(" ");return t.replace("{name}",n)}window.onpageshow=function(){let e=setInterval(function(){null!=document.getElementById("swagger-ui")&&(clearInterval(e),console.log("Hello Swagger UI!"),setUpPinnableTopbar({$PINNABLE_TOPBAR}),setUpPinnableFilterBar({$PINNABLE_FILTER_BAR}),setUpScrollToTopButton({$BACK_TO_TOP}),setUpExpandAndCollapseOperationsButtons({$EXPAND_COLLAPSE_ALL_OPERATIONS}),setUpThemeSwitcher({$THEME_SWITCHER}))},100)}; \ No newline at end of file +ο»Ώ/*Swagger UI https://github.com/teociaps/SwaggerUI.Themes */const rootElement=document.documentElement;function setUpPinnableTopbar(e){!1!==e&&setUpPinnableToggle({buttonId:"pin-topbar-btn",container:document.querySelector(".topbar-wrapper"),pinnedAncestorDepth:3,storageKey:"swaggerui-pinnable-topbar-preference",pinTitle:"Pin topbar",unpinTitle:"Unpin topbar",defaultPinned:!0})}function setUpPinnableFilterBar(e){if(!1===e)return;const t=document.querySelector(".filter");if(!t)return;const n=t.parentElement?.parentElement;function o(){if(!n)return;const e=t.classList.contains("pinned")&&n.getBoundingClientRect().top<=parseFloat(getComputedStyle(n).top)+1;t.classList.toggle("stuck",e)}window.addEventListener("scroll",o,{passive:!0}),window.addEventListener("resize",o),setUpPinnableToggle({buttonId:"pin-filterbar-btn",container:t,pinnedAncestorDepth:1,storageKey:"swaggerui-pinnable-filterbar-preference",pinTitle:"Pin filter bar",unpinTitle:"Unpin filter bar",defaultPinned:!1,onPinnedStateApplied:o})}function setUpPinnableToggle({buttonId:e,container:t,pinnedAncestorDepth:n,storageKey:o,pinTitle:l,unpinTitle:a,defaultPinned:r,onPinnedStateApplied:p}){if(!t)return;const i=document.createElement("button");i.setAttribute("id",e),i.addEventListener("click",()=>{d(!c(i).classList.contains("pinned"),!0),i?.blur()}),t.appendChild(i);const s=localStorage.getItem(o);function c(e){let t=e;for(let e=0;e{scrollToTop(),n?.blur()}),t.appendChild(n);document.getElementById("swagger-ui").appendChild(t);const o=()=>{window.scrollY>=200?n.classList.add("showBtn"):n.classList.remove("showBtn")};window.addEventListener("scroll",o),window.addEventListener("resize",o)}function scrollToTop(){rootElement.scrollTo({top:0,behavior:"smooth"})}function setUpExpandAndCollapseOperationsButtons(e){if(!1===e)return;let t=0;const n=document.getElementById("swagger-ui")||document.querySelector(".swagger-ui");function o(){const e=document.querySelectorAll(".opblock-tag-section");return!(!e||0===e.length)&&(e.forEach(e=>function(e){if(!e||"true"===e.dataset.expandCollapseAttached)return;const t=e.querySelector("h3"),n=t?.querySelector("button.expand-operation");if(t&&!t.querySelector(".expand-collapse-all-btn")){const o=document.createElement("button");o.setAttribute("title","Expand/Collapse all the operations"),o.classList.add("expand-collapse-all-btn"),o.innerHTML="Expand/Collapse All",n?n.before(o):t.appendChild(o),o.addEventListener("click",t=>{t.preventDefault(),t.stopPropagation();const n=e.querySelectorAll(".opblock .opblock-control-arrow");n&&0!==n.length&&(Array.from(n).every(e=>"true"===e.getAttribute("aria-expanded"))?n.forEach(e=>{"true"===e.getAttribute("aria-expanded")&&e.click()}):n.forEach(e=>{"false"===e.getAttribute("aria-expanded")&&e.click()}))})}e.dataset.expandCollapseAttached="true"}(e)),!0)}!function e(){if(t++,o())try{const e=n||document.body;new MutationObserver(e=>{for(const t of e)t.addedNodes&&t.addedNodes.length>0&&o()}).observe(e,{childList:!0,subtree:!0})}catch(e){}else t<60&&setTimeout(e,200)}()}function setUpThemeSwitcher(e){if(!1===e)return;const t="swaggerui-theme-preference";let n=null,o=null;function l(e,l){const a=n?.themes.find(t=>t.name===e);if(!a)return void console.warn(`[ThemeSwitcher] Theme not found: ${e}`);const r=document.querySelectorAll('link[rel="stylesheet"]'),i=n.themes.map(e=>e.cssPath);let s=!1;r.forEach(t=>{if(t.dataset.theme||i.some(e=>t.href?.endsWith(e))){t.dataset.theme===e||t.href?.endsWith(a.cssPath)?(t.disabled=!1,t.dataset.theme=e,s=!0):t.disabled=!0}}),s||console.warn(`[ThemeSwitcher] Could not activate theme: ${e}`),o=e,l&&localStorage.setItem(t,e);const c=document.getElementById("theme-switcher-select");c&&c.value!==e&&(c.value=e)}fetch("/themes/metadata.json").then(e=>e.ok?e.json():(console.warn("[ThemeSwitcher] Failed to load theme metadata"),null)).then(e=>{!e?.themes||e.themes.length<2?console.warn("[ThemeSwitcher] Not enough themes available for switcher"):(n=e,o=detectCurrentTheme(e.themes),function(e){const n=localStorage.getItem(t);n&&e.some(e=>e.name===n)&&n!==o&&l(n,!1)}(e.themes),function(e){const t=document.querySelector(".topbar-wrapper");if(!t)return void console.warn("[ThemeSwitcher] Topbar not found");const n=document.createElement("select");n.id="theme-switcher-select",n.setAttribute("aria-label","Select theme"),n.title="Switch theme",e.themes.forEach(t=>{const l=document.createElement("option");l.value=t.name,l.textContent=formatThemeName(t.name,e.config?.displayFormat||"{name}"),t.name===o&&(l.selected=!0),n.appendChild(l)}),n.addEventListener("change",e=>{l(e.target.value,!0)});const a=document.getElementById("pin-topbar-btn");a?a.before(n):t.appendChild(n)}(e))}).catch(e=>{console.error("[ThemeSwitcher] Error loading themes:",e)})}function detectCurrentTheme(e){const t=document.querySelector('link[rel="stylesheet"]:not([disabled])[data-theme]');if(t?.dataset.theme)return t.dataset.theme;const n=document.querySelectorAll('link[rel="stylesheet"]:not([disabled])');for(const t of n){const n=e.find(e=>!!t.href&&(t.href.includes(e.cssPath)||t.href.endsWith(e.cssPath)||t.href.includes(e.name.toLowerCase())));if(n)return n.name}return e.length>0?e[0].name:null}function formatThemeName(e,t){const n=e.replaceAll(/([A-Z])/g," $1").trim().split(/[\s_-]+/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(" ");return t.replace("{name}",n)}window.onpageshow=function(){let e=setInterval(function(){null!=document.getElementById("swagger-ui")&&(clearInterval(e),console.log("Hello Swagger UI!"),setUpPinnableTopbar({$PINNABLE_TOPBAR}),setUpPinnableFilterBar({$PINNABLE_FILTER_BAR}),setUpScrollToTopButton({$BACK_TO_TOP}),setUpExpandAndCollapseOperationsButtons({$EXPAND_COLLAPSE_ALL_OPERATIONS}),setUpThemeSwitcher({$THEME_SWITCHER}))},100)}; \ No newline at end of file diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css index 4527eb8..5dc459a 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css @@ -11,8 +11,8 @@ them without overlap. Declared once here so the two DOM-branch-specific copies below (needed because of how .topbar.pinned/.filter.pinned relate to the elements that read them) can't drift out of sync. */ - --topbar-pinned-height: 4.2rem; - --filterbar-pinned-height: 7.2rem; + --topbar-pinned-height: 4rem; + --filterbar-pinned-height: 5rem; } *::-webkit-scrollbar { @@ -678,10 +678,39 @@ body { } /* Other of operations */ +/* The pin button is appended as a sibling of .filter-container (swagger-ui-dist's own, + unstyled, full-width wrapper around the filter input) directly inside .filter. It's + absolutely positioned over the input's right end (like an input-embedded icon), with + position: relative here making .filter its containing block; align-items keeps its + static position vertically centered. */ +.swagger-ui .filter { + position: relative; + display: flex; + align-items: center; +} + + .swagger-ui .filter .filter-container { + flex: 1; + min-width: 0; + } + + .swagger-ui .filter #pin-filterbar-btn { + position: absolute; + right: 1.5rem; + } + .swagger-ui .filter .operation-filter-input { border: 2px solid var(--filter-operation-filter-input-border-color, #d8dde7); } + /* Only while the pinned filter bar has actually reached its sticky top (the 'stuck' + class is toggled by ui.js β€” CSS can't detect that state): fake a bottom edge for the + sticky bar by stacking two hard shadows below the input β€” a background-colored fill, + then a 1px line in the operations' border color. */ + .swagger-ui .filter.pinned.stuck .operation-filter-input { + box-shadow: 0 19px 0 0 var(--body-background-color, #fafafa), 0 20px 0 0 var(--opblock-tag-section-border-color, #dddddd); + } + /* .filter's own wrapper (.filter-container) is exactly as tall as the filter bar itself (its only child), so making .filter, or .filter-container, position:sticky would have no visible effect: a sticky box is bounded by its own containing block, and that block would diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css index 462d241..c34d2eb 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css @@ -1 +1 @@ -/*Common Theme https://github.com/teociaps/SwaggerUI.Themes */@charset "UTF-8";:root{--topbar-pinned-height:4.2rem;--filterbar-pinned-height:7.2rem}::-webkit-scrollbar{width:16px}::-webkit-scrollbar-thumb{height:56px;border-radius:8px;border:4px solid transparent;background-clip:content-box;background-color:var(--scrollbar-thumb-color,#a5a5a5);transition:background-color .3s}::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover-color,#727272)}body::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-color,#a5a5a5)}*{font-family:system-ui,sans-serif}:focus{font-family:system-ui,sans-serif;outline:0!important}:focus-visible{outline:auto!important;outline-offset:1px}body{background-color:var(--body-background-color,#fafafa);padding-bottom:1rem}*,::after,::before{box-sizing:border-box}.swagger-ui{color:var(--swagger-main-color,#3b4151);-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.15;margin:0}.swagger-ui .loading-container .loading:after{color:var(--swagger-main-color,#3b4151)}.swagger-ui .loading-container .loading:before{border:2px solid var(--loading-container-border-color,rgba(85,85,85,.1));border-top-color:var(--loading-container-border-top-color,rgba(0,0,0,.6))}.swagger-ui .errors-wrapper{background:var(--errors-wrapper-background-color,rgba(249,62,62,.1));border:2px solid var(--errors-wrapper-border-color,#f93e3e)}.swagger-ui .errors-wrapper .errors h4,.swagger-ui .errors-wrapper hgroup h4{color:var(--swagger-main-color,#3b4151)}.swagger-ui .errors-wrapper .errors small{color:var(--errors-wrapper-errors-color,#606060)}.swagger-ui .topbar{background-color:transparent;z-index:1000;padding:0}.swagger-ui .topbar.pinned{position:sticky;top:10px;--pinned-topbar-offset:var(--topbar-pinned-height)}.swagger-ui:has(> .topbar.pinned){--pinned-topbar-offset:var(--topbar-pinned-height)}.swagger-ui .topbar.pinned::before{content:'';position:absolute;inset:-10px 0 0;pointer-events:none;user-select:none;backdrop-filter:blur(20px);-webkit-mask-image:linear-gradient(to bottom,black 40%,transparent);mask-image:linear-gradient(to bottom,black 40%,transparent);background:linear-gradient(90deg,rgba(0,0,0,0) 5%,rgba(8,8,8,.4) 50%,rgba(0,0,0,0) 95%)}.swagger-ui .topbar .topbar-wrapper{background-color:var(--topbar-background-color,#000);border:1px solid var(--topbar-border-color,gray);border-top:0;border-radius:0 0 .5rem .5rem;padding:.5rem;box-shadow:0 5px 15px 0 var(--topbar-pinned-shadow-color,rgba(0,0,0,.5))}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn svg{fill:var(--topbar-pin-icon-color,white)}.swagger-ui .filter #pin-filterbar-btn,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn{cursor:pointer;background:0 0;border:none;margin:.2rem;padding:.2rem;transition:all .2s;rotate:z 359deg}.swagger-ui .filter #pin-filterbar-btn:focus-within,.swagger-ui .filter #pin-filterbar-btn:hover,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:hover{scale:1.05;animation:pintobbar 1.2s infinite}.swagger-ui .filter #pin-filterbar-btn:active,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:active{scale:0.95}.swagger-ui .topbar.pinned .topbar-wrapper{background-color:var(--topbar-pinned-background-color,rgba(0,0,0,.7));border:1px solid var(--topbar-border-color,gray);border-radius:.5rem;backdrop-filter:blur(20px)}.swagger-ui .filter.pinned #pin-filterbar-btn,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn{rotate:z 0deg}.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn{translate:0 5px}.swagger-ui .filter.pinned #pin-filterbar-btn:focus-within,.swagger-ui .filter.pinned #pin-filterbar-btn:hover,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:hover{animation:unpintobbar 1.2s infinite}@keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-moz-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-webkit-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@keyframes unpintobbar{10%{translate:0 1px}70%{rotate:z 50deg;translate:-3px 1px}}@-moz-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}@-webkit-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}.swagger-ui .topbar .download-url-wrapper .select-label select,.swagger-ui .topbar .download-url-wrapper input[type=text]{border:2px solid var(--topbar-select-border-color,#62a03f)}.swagger-ui .topbar .download-url-wrapper .select-label{color:var(--topbar-select-label-color,#f0f0f0)}.swagger-ui .topbar .download-url-wrapper .download-url-button{background:var(--topbar-download-url-button-background-color,#62a03f);color:var(--topbar-download-url-button-color,#fff)}#theme-switcher-select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);border:2px solid var(--topbar-select-border-color,#62a03f);min-width:120px;box-shadow:none}.swagger-ui .info .base-url,.swagger-ui .info .title,.swagger-ui .info h1,.swagger-ui .info h2,.swagger-ui .info h3,.swagger-ui .info h4,.swagger-ui .info h5,.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info table{color:var(--swagger-main-color,#3b4151)}.swagger-ui .info a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .info a:hover{color:var(--swagger-info-link-hover,#1f69c0)}.swagger-ui .info .title small{background:var(--api-version-background-color,#7d8492)}.swagger-ui .info .title small.version-stamp{background-color:var(--api-version-stamp-background-color,#89bf04)}.swagger-ui .info .title small pre{color:var(--api-version-color,#fff)}.swagger-ui .scheme-container{background:0 0;box-shadow:none;margin:0;padding:0}.swagger-ui .auth-wrapper{display:flex;flex:1;justify-content:flex-end;border:1px solid var(--auth-wrapper-border-color,#d5d5d5);padding:0;border-radius:5px;box-shadow:0 3px 5px -5px var(--auth-wrapper-box-shadow-color,#000);background:var(--auth-wrapper-background-color,#fff)}.swagger-ui .scheme-container .schemes>label{color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container{border-bottom:1px solid var(--auth-container-border-bottom-color,#ebebeb);color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container .errors{background-color:var(--auth-container-background-color,#fee);color:var(--auth-container-errors-color,red)}.swagger-ui .btn.authorize{background-color:var(--btn-authorize-background-color,transparent);border-color:var(--btn-authorize-border-color,#49cc90);color:var(--btn-authorize-font-color,#49cc90)}.swagger-ui .btn.authorize svg{fill:var(--btn-authorize-svg-fill-color,#49cc90)}.swagger-ui .opblock-tag-section{display:flex;flex-direction:column;border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;margin-block:1rem;overflow:clip}.swagger-ui .opblock-tag-section .expand-collapse-all-btn{display:none;user-select:none}.swagger-ui .opblock-tag-section.is-open .expand-collapse-all-btn{background:var(--opblock-tag-section-expandcollapse-background-color,rgb(220 220 220 / 45%));color:var(--swagger-main-color,#3b4151);border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;padding:5px 10px;font-size:medium;margin-inline:10px;display:block}.swagger-ui .opblock-tag,.swagger-ui .opblock-tag small,.swagger-ui .parameter__type{color:var(--swagger-main-color,#3b4151)}.swagger-ui .opblock-tag{align-items:center;cursor:pointer;display:flex;padding:10px 20px 10px 10px;color:var(--swagger-main-color,#3b4151);font-size:24px;border-bottom:0 dashed var(--opblock-tag-border-bottom-color,#e1e1e1);margin:0;transition:all .2s}.swagger-ui .opblock-tag:focus-within,.swagger-ui .opblock-tag:hover{background:var(--opblock-tag-background-color-hover,rgba(0,0,0,.02))}.swagger-ui .opblock-tag+div .operation-tag-content{margin-inline:5px}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag{position:sticky;top:0;z-index:4;margin-bottom:1rem;border-bottom:1px dashed var(--opblock-tag-border-bottom-color,#e1e1e1);background:var(--body-background-color,#fafafa)}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:focus-within,#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:hover{background:var(--opblock-tag-pinned-background-color-hover,#f1f1f1)}.swagger-ui .opblock-tag-section.is-open .opblock-tag{top:calc(var(--pinned-topbar-offset,0px) + var(--pinned-filter-offset,0px))}.swagger-ui .opblock{border:1px solid var(--opblock-border-color,#000);box-shadow:0 0 3px var(--opblock-shadow-color,rgba(0,0,0,.19))}.swagger-ui .opblock .tab-header .tab-item.active h4 span:after{background:var(--opblock-tabheader-underline-color,gray)}.swagger-ui .opblock .opblock-summary button svg{fill:var(--opblock-summary-svg-icons-color,#000)}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid var(--opblock-summary-border-bottom-color,#000)}.swagger-ui .opblock .opblock-section-header{background:var(--opblock-section-header-background-color,hsla(0,0%,100%,.8));box-shadow:0 1px 2px var(--opblock-section-header-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-section-header h4,.swagger-ui .opblock .opblock-section-header>label{color:var(--swagger-main-color,#3b4151)}.swagger-ui .opblock .opblock-summary-method{background:var(--opblock-summary-method-background-color,#000);color:var(--opblock-summary-method-color,#fff);text-shadow:0 1px 0 var(--opblock-summary-method-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{color:var(--swagger-main-color,#3b4151);word-break:normal}.swagger-ui .opblock.opblock-post{background:var(--opblock-post-background-color,rgba(73,204,144,.1));border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock.opblock-post .opblock-summary-method{background:var(--opblock-post-method-color,#49cc90)}.swagger-ui .opblock.opblock-post .opblock-summary{border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span:after{background:var(--opblock-post-method-color,#49cc90);border-radius:1rem}.swagger-ui .opblock.opblock-put{background:var(--opblock-put-background-color,rgba(252,161,48,.1));border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-put .opblock-summary-method{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-put .opblock-summary{border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span:after{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-delete{background:var(--opblock-delete-background-color,rgba(249,62,62,.1));border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .opblock-summary-method{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span:after{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-get{background:var(--opblock-get-background-color,rgba(97,175,254,.1));border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get .opblock-summary-method{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-get .opblock-summary{border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span:after{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-patch{background:var(--opblock-patch-background-color,rgba(80,227,194,.1));border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .opblock-summary-method{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span:after{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-head{background:var(--opblock-head-background-color,rgba(144,18,254,.1));border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head .opblock-summary-method{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-head .opblock-summary{border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span:after{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-options{background:var(--opblock-options-background-color,rgba(13,90,167,.1));border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .opblock-summary-method{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .opblock-summary{border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span:after{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-deprecated{background:var(--opblock-deprecated-background-color,hsla(0,0%,92%,.1));border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span:after{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .tab li{color:var(--swagger-main-color,#3b4151)}.swagger-ui .tab li:first-of-type:after{background:var(--swagger-tabs-divider-color,rgba(0,0,0,.2))}.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-external-docs-wrapper,.swagger-ui .opblock-external-docs-wrapper h4,.swagger-ui .opblock-external-docs-wrapper p,.swagger-ui .opblock-title_normal,.swagger-ui .opblock-title_normal h4,.swagger-ui .opblock-title_normal p,.swagger-ui .response-col_links,.swagger-ui .response-col_status,.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5{color:var(--swagger-main-color,#3b4151)}.swagger-ui .response-col_links .response-undocumented,.swagger-ui .response-col_status .response-undocumented{color:var(--response-undocumented-color,#909090)}.swagger-ui .response-control-media-type--accept-controller select{border-color:var(--response-control-media-type-color,green)}.swagger-ui .response-control-media-type__accept-message{color:var(--response-control-media-type-color,green)}.swagger-ui .opblock-body pre.microlight{background:var(--opblock-pre-microlight-background-color,#333)!important;color:var(--opblock-pre-microlight-color,#fff)!important}.swagger-ui .download-contents{background:var(--download-contents-background-color,#7d8293);color:var(--download-contents-color,#fff)}.swagger-ui .copy-to-clipboard{background:var(--copy-to-clipboard-background-color,#7d8293)}.swagger-ui .copy-to-clipboard button{background:var(--copy-to-clipboard-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .filter .operation-filter-input{border:2px solid var(--filter-operation-filter-input-border-color,#d8dde7)}.swagger-ui div:has(> .filter-container > .filter.pinned){position:sticky;top:var(--pinned-topbar-offset,0);z-index:5;background:var(--body-background-color,#fafafa);--pinned-filter-offset:var(--filterbar-pinned-height)}.swagger-ui:has(.filter.pinned){--pinned-filter-offset:var(--filterbar-pinned-height)}.swagger-ui .filter #pin-filterbar-btn svg{fill:var(--filterbar-pin-icon-color,#707070)}.swagger-ui .download-url-wrapper .failed,.swagger-ui .filter .failed{color:var(--filter-download-failed-color,red)}.swagger-ui .download-url-wrapper .loading,.swagger-ui .filter .loading{color:var(--filter-download-loading-color,#aaa)}.swagger-ui section h3{color:var(--swagger-main-color,#3b4151)}.swagger-ui .servers>label,.swagger-ui table.headers td{color:var(--swagger-main-color,#3b4151)}.swagger-ui table.headers .header-example{color:var(--table-headers-example-color,#999)}.swagger-ui table thead tr td,.swagger-ui table thead tr th{border-bottom:1px solid var(--table-thead-border-bottom-color,rgba(59,65,81,.2));color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name{color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name.required span{color:var(--parameter-name-required-symbol-color,red)}.swagger-ui .parameter__name.required::after{color:var(--parameter-name-required-color,rgba(255,0,0,.6))}.swagger-ui .parameter__extension,.swagger-ui .parameter__in,.swagger-ui .response__extension{color:var(--parameter-in-extension-color,gray)}.swagger-ui .parameter__deprecated{color:var(--parameter-deprecated-color,red)}.swagger-ui .btn{background:var(--button-background-color,transparent);border:2px solid var(--button-border-color,gray);box-shadow:0 1px 2px var(--button-shadow-color,rgba(0,0,0,.1));color:var(--swagger-main-color,#3b4151)}.swagger-ui .btn:hover{box-shadow:0 0 5px var(--button-shadow-hover-color,rgba(0,0,0,.3))}.swagger-ui .btn.cancel{background-color:var(--button-cancel-background-color,transparent);border-color:var(--button-cancel-border-color,#ff6060);color:var(--button-cancel-color,#ff6060)}.swagger-ui .btn.execute{background-color:var(--button-execute-background-color,#4990e2);border-color:var(--button-execute-border-color,#4990e2);color:var(--button-execute-color,#fff)}.swagger-ui button.invalid{background:var(--button-invalid-background-color,#feebeb);border-color:var(--button-invalid-border-color,#f93e3e)}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{fill:var(--expand-operation-svg-arrow-color,#707070)}.swagger-ui .expand-methods:hover svg,.swagger-ui .expand-operation:hover svg{fill:var(--expand-operation-svg-arrow-hover-color,#404040)}.swagger-ui select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);border:2px solid var(--select-border-color,#41444e);box-shadow:0 1px 2px 0 var(--select-shadow-color,rgba(0,0,0,.25));color:var(--swagger-main-color,#3b4151);cursor:pointer}.swagger-ui select[multiple]{background:var(--select-background-color,#f7f7f7)}.swagger-ui select:disabled{opacity:.5;cursor:not-allowed}.swagger-ui label{color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text],.swagger-ui textarea{background:var(--input-background-color,#fff);border:1px solid var(--input-border-color,#d9d9d9);color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email].invalid,.swagger-ui input[type=file].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid,.swagger-ui select.invalid,.swagger-ui textarea.invalid{background:var(--input-invalid-background-color,#feebeb);border-color:var(--input-invalid-border-color,#f93e3e);color:var(--input-invalid-color,#3b4151)}.swagger-ui input[disabled],.swagger-ui select[disabled]{background-color:var(--input-disabled-background-color,#fafafa);color:var(--input-disabled-color,#888)}.swagger-ui select[disabled]{border-color:var(--select-disabled-border-color,#888)}.swagger-ui textarea[disabled]{background-color:var(--textarea-disabled-background-color,#41444e);color:var(--textarea-disabled-color,#fff)}.swagger-ui textarea{background:var(--textarea-background-color,hsla(0,0%,100%,.8));color:var(--swagger-main-color,#3b4151)}.swagger-ui textarea:focus{border:2px solid var(--textarea-border-focus-color,#61affe)}.swagger-ui textarea.curl{background:var(--textarea-curl-background-color,#41444e);color:var(--textarea-curl-color,#fff)}.swagger-ui .checkbox{color:var(--checkbox-color,#303030)}.swagger-ui .checkbox p{color:var(--swagger-main-color,#3b4151)}.swagger-ui .checkbox input[type=checkbox]+label>.item{background:var(--checkbox-label-item-background-color,#e8e8e8);box-shadow:0 0 0 2px var(--checkbox-label-item-shadow-color,#e8e8e8)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:var(--checkbox-label-item-icon, #e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .dialog-ux .backdrop-ux{background:var(--dialog-backdrop-color,rgba(0,0,0,.8))}.swagger-ui .dialog-ux .modal-ux{background:var(--dialog-background-color,#fff);border:1px solid var(--dialog-border-color,#ebebeb);box-shadow:0 10px 30px 0 var(--dialog-shadow-color,rgba(0,0,0,.2))}.swagger-ui .dialog-ux .modal-ux-content h4,.swagger-ui .dialog-ux .modal-ux-content p,.swagger-ui .dialog-ux .modal-ux-header h3{color:var(--swagger-main-color,#3b4151)}.swagger-ui .dialog-ux .modal-ux-header{border-bottom:1px solid var(--dialog-border-color,#ebebeb)}.swagger-ui .dialog-ux .modal-ux-header button svg{fill:var(--dialog-close-button-icon-color,black)}.swagger-ui .model{color:var(--swagger-main-color,#3b4151)}.swagger-ui .model .deprecated span,.swagger-ui .model .deprecated td{color:var(--model-deprecated-color,#a0a0a0)!important}.swagger-ui .model-toggle{cursor:pointer;-webkit-transition:-webkit-transform .15s ease-in;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}.swagger-ui .model-toggle.collapsed{-webkit-transform:rotate(0);transform:rotate(0)}.swagger-ui .model-toggle::after{background:var(--model-arrow-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .model-hint{background:var(--model-hint-background-color,rgba(0,0,0,.7));color:var(--model-hint-color,#ebebeb)}.swagger-ui .model .property{color:var(--model-property-color,#999)}.swagger-ui .model .property.primitive{color:var(--model-property-primitive-color,#6b6b6b)}.swagger-ui table.model tr.property-row .star{color:var(--model-property-required-symbol-color,red)}.swagger-ui .model .external-docs,.swagger-ui table.model tr.description{color:var(--model-property-description,#666)}.swagger-ui table.model tr.extension{color:var(--model-property-extension,#777)}.swagger-ui section.models{border:1px solid var(--model-section-border-color,rgba(59,65,81,.3));border-radius:.5rem;padding:0;margin:0}.swagger-ui section.models.is-open h4{border-bottom:1px solid var(--model-section-border-color,rgba(59,65,81,.3))}.swagger-ui section.models h4{color:var(--model-section-header-color,#606060);padding:0}.swagger-ui section.models h4:hover{background:var(--model-section-header-hover-background-color,rgba(0,0,0,.02))}.swagger-ui section.models h4 svg{fill:var(--expand-model-svg-arrow-color,#707070)}.swagger-ui section.models h4 button.models-control{color:var(--model-section-header-color,#606060);padding:20px}.swagger-ui section.models h5{color:var(--model-section-little-header-color,#707070)}.swagger-ui section.models .model-container{background:var(--model-container-background-color,rgba(0,0,0,.05))}.swagger-ui section.models .model-container:hover{background:var(--model-container-hover-background-color,rgba(0,0,0,.07))}.swagger-ui .model-box{background:var(--model-box-background-color,rgba(0,0,0,.1))}.swagger-ui .model-title{color:var(--model-title-color,#505050)}.swagger-ui .model-deprecated-warning{color:var(--model-deprecated-warning-color,#f93e3e)}.swagger-ui .prop-type{color:var(--model-prop-type-color,#55a)}.swagger-ui .prop-format{color:var(--model-prop-format-color,#606060)}.swagger-ui .scopes h2{color:var(--swagger-main-color,#3b4151)}.swagger-ui .scopes h2 a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .markdown pre,.swagger-ui .renderedMarkdown pre{color:var(--rendered-markdown-pre-color,#000)}.swagger-ui .markdown code,.swagger-ui .renderedMarkdown code{background:var(--rendered-markdown-code-background-color,rgba(0,0,0,.05));color:var(--rendered-markdown-code-color,#9012fe)}.scroll-to-top-wrapper{position:fixed;bottom:0;left:0;right:0;text-align:end;box-sizing:border-box;margin:0 auto;max-width:1460px;padding:0 20px;width:100%;pointer-events:none;z-index:1000}#scroll-to-top-btn{background-color:var(--scroll-to-top-button-background-color,#000000d1);border:1px solid var(--scroll-to-top-button-border-color,#d1c9c9);border-radius:50%;color:#fff;cursor:pointer;width:2.5rem;aspect-ratio:1;backdrop-filter:blur(20px);margin:10px;position:relative;transition:background-color .5s,opacity .6s .2s,translate .8s,scale .2s;opacity:0;translate:0 100px;box-shadow:0 0 10px 0 var(--scroll-to-top-button-shadow-color,rgba(0,0,0,.4))}#scroll-to-top-btn.showBtn{opacity:1;translate:0 0;pointer-events:auto}#scroll-to-top-btn::before{position:absolute;content:'';top:10px;left:12px;right:12px;background-color:var(--scroll-to-top-button-icon-color,#fff);border-radius:1rem;height:2px}#scroll-to-top-btn::after{position:absolute;content:'';top:15px;left:50%;border:solid var(--scroll-to-top-button-icon-color,#fff);border-width:0 2px 2px 0;padding:3px;transform:rotate(-135deg);translate:-50% 4px}#scroll-to-top-btn:focus-within,#scroll-to-top-btn:hover{background-color:var(--scroll-to-top-button-hover-background-color,#0d0d0dba)}#scroll-to-top-btn:focus-within::after,#scroll-to-top-btn:hover::after{animation:bounceUp 1.2s ease-in infinite}#scroll-to-top-btn:active{scale:0.95}@keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-moz-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-webkit-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@media only screen and (max-width:680px){.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{word-break:break-all}} \ No newline at end of file +/*Common Theme https://github.com/teociaps/SwaggerUI.Themes */@charset "UTF-8";:root{--topbar-pinned-height:4rem;--filterbar-pinned-height:5rem}::-webkit-scrollbar{width:16px}::-webkit-scrollbar-thumb{height:56px;border-radius:8px;border:4px solid transparent;background-clip:content-box;background-color:var(--scrollbar-thumb-color,#a5a5a5);transition:background-color .3s}::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover-color,#727272)}body::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-color,#a5a5a5)}*{font-family:system-ui,sans-serif}:focus{font-family:system-ui,sans-serif;outline:0!important}:focus-visible{outline:auto!important;outline-offset:1px}body{background-color:var(--body-background-color,#fafafa);padding-bottom:1rem}*,::after,::before{box-sizing:border-box}.swagger-ui{color:var(--swagger-main-color,#3b4151);-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.15;margin:0}.swagger-ui .loading-container .loading:after{color:var(--swagger-main-color,#3b4151)}.swagger-ui .loading-container .loading:before{border:2px solid var(--loading-container-border-color,rgba(85,85,85,.1));border-top-color:var(--loading-container-border-top-color,rgba(0,0,0,.6))}.swagger-ui .errors-wrapper{background:var(--errors-wrapper-background-color,rgba(249,62,62,.1));border:2px solid var(--errors-wrapper-border-color,#f93e3e)}.swagger-ui .errors-wrapper .errors h4,.swagger-ui .errors-wrapper hgroup h4{color:var(--swagger-main-color,#3b4151)}.swagger-ui .errors-wrapper .errors small{color:var(--errors-wrapper-errors-color,#606060)}.swagger-ui .topbar{background-color:transparent;z-index:1000;padding:0}.swagger-ui .topbar.pinned{position:sticky;top:10px;--pinned-topbar-offset:var(--topbar-pinned-height)}.swagger-ui:has(> .topbar.pinned){--pinned-topbar-offset:var(--topbar-pinned-height)}.swagger-ui .topbar.pinned::before{content:'';position:absolute;inset:-10px 0 0;pointer-events:none;user-select:none;backdrop-filter:blur(20px);-webkit-mask-image:linear-gradient(to bottom,black 40%,transparent);mask-image:linear-gradient(to bottom,black 40%,transparent);background:linear-gradient(90deg,rgba(0,0,0,0) 5%,rgba(8,8,8,.4) 50%,rgba(0,0,0,0) 95%)}.swagger-ui .topbar .topbar-wrapper{background-color:var(--topbar-background-color,#000);border:1px solid var(--topbar-border-color,gray);border-top:0;border-radius:0 0 .5rem .5rem;padding:.5rem;box-shadow:0 5px 15px 0 var(--topbar-pinned-shadow-color,rgba(0,0,0,.5))}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn svg{fill:var(--topbar-pin-icon-color,white)}.swagger-ui .filter #pin-filterbar-btn,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn{cursor:pointer;background:0 0;border:none;margin:.2rem;padding:.2rem;transition:all .2s;rotate:z 359deg}.swagger-ui .filter #pin-filterbar-btn:focus-within,.swagger-ui .filter #pin-filterbar-btn:hover,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:hover{scale:1.05;animation:pintobbar 1.2s infinite}.swagger-ui .filter #pin-filterbar-btn:active,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:active{scale:0.95}.swagger-ui .topbar.pinned .topbar-wrapper{background-color:var(--topbar-pinned-background-color,rgba(0,0,0,.7));border:1px solid var(--topbar-border-color,gray);border-radius:.5rem;backdrop-filter:blur(20px)}.swagger-ui .filter.pinned #pin-filterbar-btn,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn{rotate:z 0deg}.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn{translate:0 5px}.swagger-ui .filter.pinned #pin-filterbar-btn:focus-within,.swagger-ui .filter.pinned #pin-filterbar-btn:hover,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:hover{animation:unpintobbar 1.2s infinite}@keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-moz-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-webkit-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@keyframes unpintobbar{10%{translate:0 1px}70%{rotate:z 50deg;translate:-3px 1px}}@-moz-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}@-webkit-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}.swagger-ui .topbar .download-url-wrapper .select-label select,.swagger-ui .topbar .download-url-wrapper input[type=text]{border:2px solid var(--topbar-select-border-color,#62a03f)}.swagger-ui .topbar .download-url-wrapper .select-label{color:var(--topbar-select-label-color,#f0f0f0)}.swagger-ui .topbar .download-url-wrapper .download-url-button{background:var(--topbar-download-url-button-background-color,#62a03f);color:var(--topbar-download-url-button-color,#fff)}#theme-switcher-select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);border:2px solid var(--topbar-select-border-color,#62a03f);min-width:120px;box-shadow:none}.swagger-ui .info .base-url,.swagger-ui .info .title,.swagger-ui .info h1,.swagger-ui .info h2,.swagger-ui .info h3,.swagger-ui .info h4,.swagger-ui .info h5,.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info table{color:var(--swagger-main-color,#3b4151)}.swagger-ui .info a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .info a:hover{color:var(--swagger-info-link-hover,#1f69c0)}.swagger-ui .info .title small{background:var(--api-version-background-color,#7d8492)}.swagger-ui .info .title small.version-stamp{background-color:var(--api-version-stamp-background-color,#89bf04)}.swagger-ui .info .title small pre{color:var(--api-version-color,#fff)}.swagger-ui .scheme-container{background:0 0;box-shadow:none;margin:0;padding:0}.swagger-ui .auth-wrapper{display:flex;flex:1;justify-content:flex-end;border:1px solid var(--auth-wrapper-border-color,#d5d5d5);padding:0;border-radius:5px;box-shadow:0 3px 5px -5px var(--auth-wrapper-box-shadow-color,#000);background:var(--auth-wrapper-background-color,#fff)}.swagger-ui .scheme-container .schemes>label{color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container{border-bottom:1px solid var(--auth-container-border-bottom-color,#ebebeb);color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container .errors{background-color:var(--auth-container-background-color,#fee);color:var(--auth-container-errors-color,red)}.swagger-ui .btn.authorize{background-color:var(--btn-authorize-background-color,transparent);border-color:var(--btn-authorize-border-color,#49cc90);color:var(--btn-authorize-font-color,#49cc90)}.swagger-ui .btn.authorize svg{fill:var(--btn-authorize-svg-fill-color,#49cc90)}.swagger-ui .opblock-tag-section{display:flex;flex-direction:column;border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;margin-block:1rem;overflow:clip}.swagger-ui .opblock-tag-section .expand-collapse-all-btn{display:none;user-select:none}.swagger-ui .opblock-tag-section.is-open .expand-collapse-all-btn{background:var(--opblock-tag-section-expandcollapse-background-color,rgb(220 220 220 / 45%));color:var(--swagger-main-color,#3b4151);border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;padding:5px 10px;font-size:medium;margin-inline:10px;display:block}.swagger-ui .opblock-tag,.swagger-ui .opblock-tag small,.swagger-ui .parameter__type{color:var(--swagger-main-color,#3b4151)}.swagger-ui .opblock-tag{align-items:center;cursor:pointer;display:flex;padding:10px 20px 10px 10px;color:var(--swagger-main-color,#3b4151);font-size:24px;border-bottom:0 dashed var(--opblock-tag-border-bottom-color,#e1e1e1);margin:0;transition:all .2s}.swagger-ui .opblock-tag:focus-within,.swagger-ui .opblock-tag:hover{background:var(--opblock-tag-background-color-hover,rgba(0,0,0,.02))}.swagger-ui .opblock-tag+div .operation-tag-content{margin-inline:5px}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag{position:sticky;top:0;z-index:4;margin-bottom:1rem;border-bottom:1px dashed var(--opblock-tag-border-bottom-color,#e1e1e1);background:var(--body-background-color,#fafafa)}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:focus-within,#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:hover{background:var(--opblock-tag-pinned-background-color-hover,#f1f1f1)}.swagger-ui .opblock-tag-section.is-open .opblock-tag{top:calc(var(--pinned-topbar-offset,0px) + var(--pinned-filter-offset,0px))}.swagger-ui .opblock{border:1px solid var(--opblock-border-color,#000);box-shadow:0 0 3px var(--opblock-shadow-color,rgba(0,0,0,.19))}.swagger-ui .opblock .tab-header .tab-item.active h4 span:after{background:var(--opblock-tabheader-underline-color,gray)}.swagger-ui .opblock .opblock-summary button svg{fill:var(--opblock-summary-svg-icons-color,#000)}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid var(--opblock-summary-border-bottom-color,#000)}.swagger-ui .opblock .opblock-section-header{background:var(--opblock-section-header-background-color,hsla(0,0%,100%,.8));box-shadow:0 1px 2px var(--opblock-section-header-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-section-header h4,.swagger-ui .opblock .opblock-section-header>label{color:var(--swagger-main-color,#3b4151)}.swagger-ui .opblock .opblock-summary-method{background:var(--opblock-summary-method-background-color,#000);color:var(--opblock-summary-method-color,#fff);text-shadow:0 1px 0 var(--opblock-summary-method-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{color:var(--swagger-main-color,#3b4151);word-break:normal}.swagger-ui .opblock.opblock-post{background:var(--opblock-post-background-color,rgba(73,204,144,.1));border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock.opblock-post .opblock-summary-method{background:var(--opblock-post-method-color,#49cc90)}.swagger-ui .opblock.opblock-post .opblock-summary{border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span:after{background:var(--opblock-post-method-color,#49cc90);border-radius:1rem}.swagger-ui .opblock.opblock-put{background:var(--opblock-put-background-color,rgba(252,161,48,.1));border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-put .opblock-summary-method{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-put .opblock-summary{border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span:after{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-delete{background:var(--opblock-delete-background-color,rgba(249,62,62,.1));border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .opblock-summary-method{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span:after{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-get{background:var(--opblock-get-background-color,rgba(97,175,254,.1));border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get .opblock-summary-method{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-get .opblock-summary{border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span:after{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-patch{background:var(--opblock-patch-background-color,rgba(80,227,194,.1));border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .opblock-summary-method{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span:after{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-head{background:var(--opblock-head-background-color,rgba(144,18,254,.1));border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head .opblock-summary-method{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-head .opblock-summary{border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span:after{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-options{background:var(--opblock-options-background-color,rgba(13,90,167,.1));border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .opblock-summary-method{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .opblock-summary{border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span:after{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-deprecated{background:var(--opblock-deprecated-background-color,hsla(0,0%,92%,.1));border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span:after{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .tab li{color:var(--swagger-main-color,#3b4151)}.swagger-ui .tab li:first-of-type:after{background:var(--swagger-tabs-divider-color,rgba(0,0,0,.2))}.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-external-docs-wrapper,.swagger-ui .opblock-external-docs-wrapper h4,.swagger-ui .opblock-external-docs-wrapper p,.swagger-ui .opblock-title_normal,.swagger-ui .opblock-title_normal h4,.swagger-ui .opblock-title_normal p,.swagger-ui .response-col_links,.swagger-ui .response-col_status,.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5{color:var(--swagger-main-color,#3b4151)}.swagger-ui .response-col_links .response-undocumented,.swagger-ui .response-col_status .response-undocumented{color:var(--response-undocumented-color,#909090)}.swagger-ui .response-control-media-type--accept-controller select{border-color:var(--response-control-media-type-color,green)}.swagger-ui .response-control-media-type__accept-message{color:var(--response-control-media-type-color,green)}.swagger-ui .opblock-body pre.microlight{background:var(--opblock-pre-microlight-background-color,#333)!important;color:var(--opblock-pre-microlight-color,#fff)!important}.swagger-ui .download-contents{background:var(--download-contents-background-color,#7d8293);color:var(--download-contents-color,#fff)}.swagger-ui .copy-to-clipboard{background:var(--copy-to-clipboard-background-color,#7d8293)}.swagger-ui .copy-to-clipboard button{background:var(--copy-to-clipboard-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .filter{position:relative;display:flex;align-items:center}.swagger-ui .filter .filter-container{flex:1;min-width:0}.swagger-ui .filter #pin-filterbar-btn{position:absolute;right:1.5rem}.swagger-ui .filter .operation-filter-input{border:2px solid var(--filter-operation-filter-input-border-color,#d8dde7)}.swagger-ui .filter.pinned.stuck .operation-filter-input{box-shadow:0 19px 0 0 var(--body-background-color,#fafafa),0 20px 0 0 var(--opblock-tag-section-border-color,#ddd)}.swagger-ui div:has(> .filter-container > .filter.pinned){position:sticky;top:var(--pinned-topbar-offset,0);z-index:5;background:var(--body-background-color,#fafafa);--pinned-filter-offset:var(--filterbar-pinned-height)}.swagger-ui:has(.filter.pinned){--pinned-filter-offset:var(--filterbar-pinned-height)}.swagger-ui .filter #pin-filterbar-btn svg{fill:var(--filterbar-pin-icon-color,#707070)}.swagger-ui .download-url-wrapper .failed,.swagger-ui .filter .failed{color:var(--filter-download-failed-color,red)}.swagger-ui .download-url-wrapper .loading,.swagger-ui .filter .loading{color:var(--filter-download-loading-color,#aaa)}.swagger-ui section h3{color:var(--swagger-main-color,#3b4151)}.swagger-ui .servers>label,.swagger-ui table.headers td{color:var(--swagger-main-color,#3b4151)}.swagger-ui table.headers .header-example{color:var(--table-headers-example-color,#999)}.swagger-ui table thead tr td,.swagger-ui table thead tr th{border-bottom:1px solid var(--table-thead-border-bottom-color,rgba(59,65,81,.2));color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name{color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name.required span{color:var(--parameter-name-required-symbol-color,red)}.swagger-ui .parameter__name.required::after{color:var(--parameter-name-required-color,rgba(255,0,0,.6))}.swagger-ui .parameter__extension,.swagger-ui .parameter__in,.swagger-ui .response__extension{color:var(--parameter-in-extension-color,gray)}.swagger-ui .parameter__deprecated{color:var(--parameter-deprecated-color,red)}.swagger-ui .btn{background:var(--button-background-color,transparent);border:2px solid var(--button-border-color,gray);box-shadow:0 1px 2px var(--button-shadow-color,rgba(0,0,0,.1));color:var(--swagger-main-color,#3b4151)}.swagger-ui .btn:hover{box-shadow:0 0 5px var(--button-shadow-hover-color,rgba(0,0,0,.3))}.swagger-ui .btn.cancel{background-color:var(--button-cancel-background-color,transparent);border-color:var(--button-cancel-border-color,#ff6060);color:var(--button-cancel-color,#ff6060)}.swagger-ui .btn.execute{background-color:var(--button-execute-background-color,#4990e2);border-color:var(--button-execute-border-color,#4990e2);color:var(--button-execute-color,#fff)}.swagger-ui button.invalid{background:var(--button-invalid-background-color,#feebeb);border-color:var(--button-invalid-border-color,#f93e3e)}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{fill:var(--expand-operation-svg-arrow-color,#707070)}.swagger-ui .expand-methods:hover svg,.swagger-ui .expand-operation:hover svg{fill:var(--expand-operation-svg-arrow-hover-color,#404040)}.swagger-ui select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);border:2px solid var(--select-border-color,#41444e);box-shadow:0 1px 2px 0 var(--select-shadow-color,rgba(0,0,0,.25));color:var(--swagger-main-color,#3b4151);cursor:pointer}.swagger-ui select[multiple]{background:var(--select-background-color,#f7f7f7)}.swagger-ui select:disabled{opacity:.5;cursor:not-allowed}.swagger-ui label{color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text],.swagger-ui textarea{background:var(--input-background-color,#fff);border:1px solid var(--input-border-color,#d9d9d9);color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email].invalid,.swagger-ui input[type=file].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid,.swagger-ui select.invalid,.swagger-ui textarea.invalid{background:var(--input-invalid-background-color,#feebeb);border-color:var(--input-invalid-border-color,#f93e3e);color:var(--input-invalid-color,#3b4151)}.swagger-ui input[disabled],.swagger-ui select[disabled]{background-color:var(--input-disabled-background-color,#fafafa);color:var(--input-disabled-color,#888)}.swagger-ui select[disabled]{border-color:var(--select-disabled-border-color,#888)}.swagger-ui textarea[disabled]{background-color:var(--textarea-disabled-background-color,#41444e);color:var(--textarea-disabled-color,#fff)}.swagger-ui textarea{background:var(--textarea-background-color,hsla(0,0%,100%,.8));color:var(--swagger-main-color,#3b4151)}.swagger-ui textarea:focus{border:2px solid var(--textarea-border-focus-color,#61affe)}.swagger-ui textarea.curl{background:var(--textarea-curl-background-color,#41444e);color:var(--textarea-curl-color,#fff)}.swagger-ui .checkbox{color:var(--checkbox-color,#303030)}.swagger-ui .checkbox p{color:var(--swagger-main-color,#3b4151)}.swagger-ui .checkbox input[type=checkbox]+label>.item{background:var(--checkbox-label-item-background-color,#e8e8e8);box-shadow:0 0 0 2px var(--checkbox-label-item-shadow-color,#e8e8e8)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:var(--checkbox-label-item-icon, #e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .dialog-ux .backdrop-ux{background:var(--dialog-backdrop-color,rgba(0,0,0,.8))}.swagger-ui .dialog-ux .modal-ux{background:var(--dialog-background-color,#fff);border:1px solid var(--dialog-border-color,#ebebeb);box-shadow:0 10px 30px 0 var(--dialog-shadow-color,rgba(0,0,0,.2))}.swagger-ui .dialog-ux .modal-ux-content h4,.swagger-ui .dialog-ux .modal-ux-content p,.swagger-ui .dialog-ux .modal-ux-header h3{color:var(--swagger-main-color,#3b4151)}.swagger-ui .dialog-ux .modal-ux-header{border-bottom:1px solid var(--dialog-border-color,#ebebeb)}.swagger-ui .dialog-ux .modal-ux-header button svg{fill:var(--dialog-close-button-icon-color,black)}.swagger-ui .model{color:var(--swagger-main-color,#3b4151)}.swagger-ui .model .deprecated span,.swagger-ui .model .deprecated td{color:var(--model-deprecated-color,#a0a0a0)!important}.swagger-ui .model-toggle{cursor:pointer;-webkit-transition:-webkit-transform .15s ease-in;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}.swagger-ui .model-toggle.collapsed{-webkit-transform:rotate(0);transform:rotate(0)}.swagger-ui .model-toggle::after{background:var(--model-arrow-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .model-hint{background:var(--model-hint-background-color,rgba(0,0,0,.7));color:var(--model-hint-color,#ebebeb)}.swagger-ui .model .property{color:var(--model-property-color,#999)}.swagger-ui .model .property.primitive{color:var(--model-property-primitive-color,#6b6b6b)}.swagger-ui table.model tr.property-row .star{color:var(--model-property-required-symbol-color,red)}.swagger-ui .model .external-docs,.swagger-ui table.model tr.description{color:var(--model-property-description,#666)}.swagger-ui table.model tr.extension{color:var(--model-property-extension,#777)}.swagger-ui section.models{border:1px solid var(--model-section-border-color,rgba(59,65,81,.3));border-radius:.5rem;padding:0;margin:0}.swagger-ui section.models.is-open h4{border-bottom:1px solid var(--model-section-border-color,rgba(59,65,81,.3))}.swagger-ui section.models h4{color:var(--model-section-header-color,#606060);padding:0}.swagger-ui section.models h4:hover{background:var(--model-section-header-hover-background-color,rgba(0,0,0,.02))}.swagger-ui section.models h4 svg{fill:var(--expand-model-svg-arrow-color,#707070)}.swagger-ui section.models h4 button.models-control{color:var(--model-section-header-color,#606060);padding:20px}.swagger-ui section.models h5{color:var(--model-section-little-header-color,#707070)}.swagger-ui section.models .model-container{background:var(--model-container-background-color,rgba(0,0,0,.05))}.swagger-ui section.models .model-container:hover{background:var(--model-container-hover-background-color,rgba(0,0,0,.07))}.swagger-ui .model-box{background:var(--model-box-background-color,rgba(0,0,0,.1))}.swagger-ui .model-title{color:var(--model-title-color,#505050)}.swagger-ui .model-deprecated-warning{color:var(--model-deprecated-warning-color,#f93e3e)}.swagger-ui .prop-type{color:var(--model-prop-type-color,#55a)}.swagger-ui .prop-format{color:var(--model-prop-format-color,#606060)}.swagger-ui .scopes h2{color:var(--swagger-main-color,#3b4151)}.swagger-ui .scopes h2 a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .markdown pre,.swagger-ui .renderedMarkdown pre{color:var(--rendered-markdown-pre-color,#000)}.swagger-ui .markdown code,.swagger-ui .renderedMarkdown code{background:var(--rendered-markdown-code-background-color,rgba(0,0,0,.05));color:var(--rendered-markdown-code-color,#9012fe)}.scroll-to-top-wrapper{position:fixed;bottom:0;left:0;right:0;text-align:end;box-sizing:border-box;margin:0 auto;max-width:1460px;padding:0 20px;width:100%;pointer-events:none;z-index:1000}#scroll-to-top-btn{background-color:var(--scroll-to-top-button-background-color,#000000d1);border:1px solid var(--scroll-to-top-button-border-color,#d1c9c9);border-radius:50%;color:#fff;cursor:pointer;width:2.5rem;aspect-ratio:1;backdrop-filter:blur(20px);margin:10px;position:relative;transition:background-color .5s,opacity .6s .2s,translate .8s,scale .2s;opacity:0;translate:0 100px;box-shadow:0 0 10px 0 var(--scroll-to-top-button-shadow-color,rgba(0,0,0,.4))}#scroll-to-top-btn.showBtn{opacity:1;translate:0 0;pointer-events:auto}#scroll-to-top-btn::before{position:absolute;content:'';top:10px;left:12px;right:12px;background-color:var(--scroll-to-top-button-icon-color,#fff);border-radius:1rem;height:2px}#scroll-to-top-btn::after{position:absolute;content:'';top:15px;left:50%;border:solid var(--scroll-to-top-button-icon-color,#fff);border-width:0 2px 2px 0;padding:3px;transform:rotate(-135deg);translate:-50% 4px}#scroll-to-top-btn:focus-within,#scroll-to-top-btn:hover{background-color:var(--scroll-to-top-button-hover-background-color,#0d0d0dba)}#scroll-to-top-btn:focus-within::after,#scroll-to-top-btn:hover::after{animation:bounceUp 1.2s ease-in infinite}#scroll-to-top-btn:active{scale:0.95}@keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-moz-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-webkit-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@media only screen and (max-width:680px){.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{word-break:break-all}} \ No newline at end of file diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/dark.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/dark.css index ce542f8..4addccc 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/dark.css +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/dark.css @@ -103,7 +103,7 @@ --download-contents-color: white; --copy-to-clipboard-background-color: #7d8293; --copy-to-clipboard-icon: url('data:image/svg+xml;charset=utf-8,') 50% no-repeat; - --filter-operation-filter-input-border-color: #d8dde7; + --filter-operation-filter-input-border-color: #909090; --filter-download-failed-color: red; --filter-download-loading-color: #aaa; --table-headers-example-color: #999; diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/dark.min.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/dark.min.css index 9d48b52..d8fa929 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/dark.min.css +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/dark.min.css @@ -1 +1 @@ -ο»Ώ/*Dark Theme https://github.com/teociaps/SwaggerUI.Themes */@media (prefers-color-scheme:dark){:root{--scrollbar-thumb-color:hsl(0, 0%, 45%);--scrollbar-thumb-hover-color:hsl(0, 0%, 55%);--body-background-color:#101010;--swagger-main-color:#ecf0f1;--loading-container-border-color:rgba(200, 200, 200, .1);--loading-container-border-top-color:rgba(255, 255, 255, .6);--errors-wrapper-background-color:rgb(255, 0, 0, .1);--errors-wrapper-border-color:#f93e3e;--errors-wrapper-errors-color:#606060;--topbar-background-color:black;--topbar-pinned-background-color:rgba(0, 0, 0, .3);--topbar-pinned-shadow-color:rgba(0, 0, 0, .6);--topbar-pin-icon-color:white;--topbar-border-color:gray;--topbar-select-border-color:#5b5b5b;--topbar-select-label-color:#dfd9d9;--topbar-download-url-button-background-color:#5b5b5b;--topbar-download-url-button-color:#dfd9d9;--swagger-info-link:#4990e2;--swagger-info-link-hover:#1b78e5;--api-version-background-color:#616161;--api-version-stamp-background-color:#23891f;--api-version-color:#cdcdcd;--auth-wrapper-background-color:black;--auth-wrapper-border-color:#383636;--auth-wrapper-box-shadow-color:#c1c1c1;--auth-container-border-bottom-color:#ebebeb;--auth-container-background-color:#ddd;--auth-container-errors-color:red;--btn-authorize-background-color:transparent;--btn-authorize-border-color:#49cc90;--btn-authorize-font-color:#49cc90;--btn-authorize-svg-fill-color:#49cc90;--opblock-tag-section-border-color:#312c2c;--opblock-border-color:black;--opblock-tag-section-expandcollapse-background-color:rgb(0 0 0 / 45%);--opblock-tag-background-color-hover:rgba(0, 0, 0, .1);--opblock-tag-pinned-background-color-hover:#0e0e0e;--opblock-tag-border-bottom-color:rgba(109, 113, 121, .3);--opblock-shadow-color:rgba(59, 59, 59, .19);--opblock-tabheader-underline-color:rgba(255, 255, 255, .6);--opblock-summary-svg-icons-color:white;--opblock-summary-border-bottom-color:black;--opblock-section-header-background-color:#0000004a;--opblock-section-header-shadow-color:rgba(0, 0, 0, .1);--opblock-summary-method-background-color:black;--opblock-summary-method-color:white;--opblock-summary-method-shadow-color:rgba(0, 0, 0, .1);--opblock-post-background-color:rgba(56, 203, 82, .1);--opblock-post-method-color:#49cc90;--opblock-post-border-color:#49cc90;--opblock-put-background-color:rgba(255, 189, 63, .1);--opblock-put-method-color:#eb9532;--opblock-put-border-color:#eb9532;--opblock-delete-background-color:rgba(255, 4, 4, .1);--opblock-delete-method-color:#c0392b;--opblock-delete-border-color:#c0392b;--opblock-get-background-color:rgb(33, 161, 255, .1);--opblock-get-method-color:#61affe;--opblock-get-border-color:#61affe;--opblock-patch-background-color:rgba(255, 0, 165, .1);--opblock-patch-method-color:#9b59b6;--opblock-patch-border-color:#9b59b6;--opblock-head-background-color:rgba(62, 73, 114, .1);--opblock-head-method-color:#3e4972;--opblock-head-border-color:#3e4972;--opblock-options-background-color:rgba(37, 64, 211, .1);--opblock-options-method-color:#263795;--opblock-options-border-color:#263795;--opblock-deprecated-background-color:hsla(0, 0%, 87%, .1);--opblock-deprecated-method-color:#a59595;--opblock-deprecated-border-color:#a59595;--swagger-tabs-divider-color:gray;--response-undocumented-color:#999;--response-control-media-type-color:green;--opblock-pre-microlight-background-color:#333;--opblock-pre-microlight-color:#ebe3e3;--download-contents-background-color:#7d8293;--download-contents-color:white;--copy-to-clipboard-background-color:#7d8293;--copy-to-clipboard-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--filter-operation-filter-input-border-color:#d8dde7;--filter-download-failed-color:red;--filter-download-loading-color:#aaa;--table-headers-example-color:#999;--table-thead-border-bottom-color:rgba(59, 65, 81, .2);--parameter-name-required-symbol-color:red;--parameter-name-required-color:rgba(255, 0, 0, .8);--parameter-in-extension-color:#878585;--parameter-deprecated-color:red;--button-background-color:transparent;--button-border-color:#a1a1a1;--button-shadow-color:rgba(0, 0, 0, .1);--button-shadow-hover-color:rgba(0, 0, 0, .3);--button-cancel-background-color:transparent;--button-cancel-border-color:#ff6060;--button-cancel-color:#ff6060;--button-execute-background-color:#4990e2;--button-execute-border-color:#5fa9ff;--button-execute-color:white;--button-invalid-background-color:#feebeb;--button-invalid-border-color:#f93e3e;--expand-operation-svg-arrow-color:#aaa;--expand-operation-svg-arrow-hover-color:white;--select-background-icon:#0c0c0c url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat;--select-border-color:#41444e;--select-shadow-color:rgba(0, 0, 0, .25);--input-background-color:#0c0c0c;--input-border-color:#6d6d6d;--input-invalid-background-color:#210101;--input-invalid-border-color:#f93e3e;--input-invalid-color:#f3ebeb;--input-disabled-background-color:#333333;--input-disabled-color:#757575;--select-disabled-border-color:#888;--textarea-disabled-background-color:#2b2b2b;--textarea-disabled-color:#b3b3b3;--textarea-background-color:hsla(0, 0%, 0%, .8);--textarea-border-focus-color:#a5a5a5;--textarea-curl-background-color:#41444e;--textarea-curl-color:#fff;--checkbox-color:#303030;--checkbox-label-item-background-color:#e8e8e8;--checkbox-label-item-shadow-color:#e8e8e8;--checkbox-label-item-icon:#e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--dialog-backdrop-color:rgba(0, 0, 0, .8);--dialog-background-color:#161616;--dialog-border-color:#333436;--dialog-shadow-color:rgba(0, 0, 0, .2);--dialog-close-button-icon-color:#fff;--model-deprecated-color:#a0a0a0;--model-arrow-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--model-hint-background-color:rgba(0, 0, 0, .7);--model-hint-color:#ebebeb;--model-property-color:#999;--model-property-primitive-color:#676767;--model-property-required-symbol-color:red;--model-property-description:#b9b9b9;--model-property-extension:#8d8a8a;--model-section-border-color:rgba(96, 96, 96, .3);--model-section-header-color:white;--model-section-header-hover-background-color:rgba(0, 0, 0, .02);--expand-model-svg-arrow-color:white;--model-section-little-header-color:#707070;--model-container-background-color:rgba(20, 20, 20, .1);--model-container-hover-background-color:rgba(0, 0, 0, .1);--model-box-background-color:rgba(0, 0, 0, .15);--model-title-color:#bec6cf;--model-deprecated-warning-color:#f93e3e;--model-prop-type-color:#6464db;--model-prop-format-color:#b7b7b7;--rendered-markdown-pre-color:#000;--rendered-markdown-code-background-color:rgba(0, 0, 0, .05);--rendered-markdown-code-color:#9012fe;--scroll-to-top-button-background-color:#00000085;--scroll-to-top-button-hover-background-color:#0d0d0d85;--scroll-to-top-button-border-color:#232323;--scroll-to-top-button-icon-color:#d1cccc;--scroll-to-top-button-shadow-color:rgba(0, 0, 0, .3)}} \ No newline at end of file +ο»Ώ/*Dark Theme https://github.com/teociaps/SwaggerUI.Themes */@media (prefers-color-scheme:dark){:root{--scrollbar-thumb-color:hsl(0, 0%, 45%);--scrollbar-thumb-hover-color:hsl(0, 0%, 55%);--body-background-color:#101010;--swagger-main-color:#ecf0f1;--loading-container-border-color:rgba(200, 200, 200, .1);--loading-container-border-top-color:rgba(255, 255, 255, .6);--errors-wrapper-background-color:rgb(255, 0, 0, .1);--errors-wrapper-border-color:#f93e3e;--errors-wrapper-errors-color:#606060;--topbar-background-color:black;--topbar-pinned-background-color:rgba(0, 0, 0, .3);--topbar-pinned-shadow-color:rgba(0, 0, 0, .6);--topbar-pin-icon-color:white;--topbar-border-color:gray;--topbar-select-border-color:#5b5b5b;--topbar-select-label-color:#dfd9d9;--topbar-download-url-button-background-color:#5b5b5b;--topbar-download-url-button-color:#dfd9d9;--swagger-info-link:#4990e2;--swagger-info-link-hover:#1b78e5;--api-version-background-color:#616161;--api-version-stamp-background-color:#23891f;--api-version-color:#cdcdcd;--auth-wrapper-background-color:black;--auth-wrapper-border-color:#383636;--auth-wrapper-box-shadow-color:#c1c1c1;--auth-container-border-bottom-color:#ebebeb;--auth-container-background-color:#ddd;--auth-container-errors-color:red;--btn-authorize-background-color:transparent;--btn-authorize-border-color:#49cc90;--btn-authorize-font-color:#49cc90;--btn-authorize-svg-fill-color:#49cc90;--opblock-tag-section-border-color:#312c2c;--opblock-border-color:black;--opblock-tag-section-expandcollapse-background-color:rgb(0 0 0 / 45%);--opblock-tag-background-color-hover:rgba(0, 0, 0, .1);--opblock-tag-pinned-background-color-hover:#0e0e0e;--opblock-tag-border-bottom-color:rgba(109, 113, 121, .3);--opblock-shadow-color:rgba(59, 59, 59, .19);--opblock-tabheader-underline-color:rgba(255, 255, 255, .6);--opblock-summary-svg-icons-color:white;--opblock-summary-border-bottom-color:black;--opblock-section-header-background-color:#0000004a;--opblock-section-header-shadow-color:rgba(0, 0, 0, .1);--opblock-summary-method-background-color:black;--opblock-summary-method-color:white;--opblock-summary-method-shadow-color:rgba(0, 0, 0, .1);--opblock-post-background-color:rgba(56, 203, 82, .1);--opblock-post-method-color:#49cc90;--opblock-post-border-color:#49cc90;--opblock-put-background-color:rgba(255, 189, 63, .1);--opblock-put-method-color:#eb9532;--opblock-put-border-color:#eb9532;--opblock-delete-background-color:rgba(255, 4, 4, .1);--opblock-delete-method-color:#c0392b;--opblock-delete-border-color:#c0392b;--opblock-get-background-color:rgb(33, 161, 255, .1);--opblock-get-method-color:#61affe;--opblock-get-border-color:#61affe;--opblock-patch-background-color:rgba(255, 0, 165, .1);--opblock-patch-method-color:#9b59b6;--opblock-patch-border-color:#9b59b6;--opblock-head-background-color:rgba(62, 73, 114, .1);--opblock-head-method-color:#3e4972;--opblock-head-border-color:#3e4972;--opblock-options-background-color:rgba(37, 64, 211, .1);--opblock-options-method-color:#263795;--opblock-options-border-color:#263795;--opblock-deprecated-background-color:hsla(0, 0%, 87%, .1);--opblock-deprecated-method-color:#a59595;--opblock-deprecated-border-color:#a59595;--swagger-tabs-divider-color:gray;--response-undocumented-color:#999;--response-control-media-type-color:green;--opblock-pre-microlight-background-color:#333;--opblock-pre-microlight-color:#ebe3e3;--download-contents-background-color:#7d8293;--download-contents-color:white;--copy-to-clipboard-background-color:#7d8293;--copy-to-clipboard-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--filter-operation-filter-input-border-color:#909090;--filter-download-failed-color:red;--filter-download-loading-color:#aaa;--table-headers-example-color:#999;--table-thead-border-bottom-color:rgba(59, 65, 81, .2);--parameter-name-required-symbol-color:red;--parameter-name-required-color:rgba(255, 0, 0, .8);--parameter-in-extension-color:#878585;--parameter-deprecated-color:red;--button-background-color:transparent;--button-border-color:#a1a1a1;--button-shadow-color:rgba(0, 0, 0, .1);--button-shadow-hover-color:rgba(0, 0, 0, .3);--button-cancel-background-color:transparent;--button-cancel-border-color:#ff6060;--button-cancel-color:#ff6060;--button-execute-background-color:#4990e2;--button-execute-border-color:#5fa9ff;--button-execute-color:white;--button-invalid-background-color:#feebeb;--button-invalid-border-color:#f93e3e;--expand-operation-svg-arrow-color:#aaa;--expand-operation-svg-arrow-hover-color:white;--select-background-icon:#0c0c0c url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat;--select-border-color:#41444e;--select-shadow-color:rgba(0, 0, 0, .25);--input-background-color:#0c0c0c;--input-border-color:#6d6d6d;--input-invalid-background-color:#210101;--input-invalid-border-color:#f93e3e;--input-invalid-color:#f3ebeb;--input-disabled-background-color:#333333;--input-disabled-color:#757575;--select-disabled-border-color:#888;--textarea-disabled-background-color:#2b2b2b;--textarea-disabled-color:#b3b3b3;--textarea-background-color:hsla(0, 0%, 0%, .8);--textarea-border-focus-color:#a5a5a5;--textarea-curl-background-color:#41444e;--textarea-curl-color:#fff;--checkbox-color:#303030;--checkbox-label-item-background-color:#e8e8e8;--checkbox-label-item-shadow-color:#e8e8e8;--checkbox-label-item-icon:#e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--dialog-backdrop-color:rgba(0, 0, 0, .8);--dialog-background-color:#161616;--dialog-border-color:#333436;--dialog-shadow-color:rgba(0, 0, 0, .2);--dialog-close-button-icon-color:#fff;--model-deprecated-color:#a0a0a0;--model-arrow-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--model-hint-background-color:rgba(0, 0, 0, .7);--model-hint-color:#ebebeb;--model-property-color:#999;--model-property-primitive-color:#676767;--model-property-required-symbol-color:red;--model-property-description:#b9b9b9;--model-property-extension:#8d8a8a;--model-section-border-color:rgba(96, 96, 96, .3);--model-section-header-color:white;--model-section-header-hover-background-color:rgba(0, 0, 0, .02);--expand-model-svg-arrow-color:white;--model-section-little-header-color:#707070;--model-container-background-color:rgba(20, 20, 20, .1);--model-container-hover-background-color:rgba(0, 0, 0, .1);--model-box-background-color:rgba(0, 0, 0, .15);--model-title-color:#bec6cf;--model-deprecated-warning-color:#f93e3e;--model-prop-type-color:#6464db;--model-prop-format-color:#b7b7b7;--rendered-markdown-pre-color:#000;--rendered-markdown-code-background-color:rgba(0, 0, 0, .05);--rendered-markdown-code-color:#9012fe;--scroll-to-top-button-background-color:#00000085;--scroll-to-top-button-hover-background-color:#0d0d0d85;--scroll-to-top-button-border-color:#232323;--scroll-to-top-button-icon-color:#d1cccc;--scroll-to-top-button-shadow-color:rgba(0, 0, 0, .3)}} \ No newline at end of file diff --git a/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIOptionsExtensions.cs b/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIOptionsExtensions.cs index 31a4ef3..891b419 100644 --- a/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIOptionsExtensions.cs +++ b/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIOptionsExtensions.cs @@ -60,14 +60,14 @@ public void EnablePinnableTopbar() => /// /// Enables the operation filter box, optionally making it pinnable. /// - /// + /// /// If , the filter box can be pinned in place. /// - public void EnableFilter(bool pinned) + public void EnableFilter(bool pinnable) { options.EnableFilter(); - if (pinned) + if (pinnable) options.ConfigObject.AdditionalItems.EnablePinnableFilterBar(); } diff --git a/src/AspNetCore.SwaggerUI.Themes/package-readme.md b/src/AspNetCore.SwaggerUI.Themes/package-readme.md index 95138d6..7c7bc7f 100644 --- a/src/AspNetCore.SwaggerUI.Themes/package-readme.md +++ b/src/AspNetCore.SwaggerUI.Themes/package-readme.md @@ -23,7 +23,7 @@ app.UseSwaggerUI(Theme.Dark); app.UseSwaggerUI(Theme.Dark, c => c.EnableThemeSwitcher()); // Or enable a pinnable filter box -app.UseSwaggerUI(Theme.Dark, c => c.EnableFilter(pinned: true)); +app.UseSwaggerUI(Theme.Dark, c => c.EnableFilter(pinnable: true)); ``` > **Note**: The `UseSwaggerUI()` method is provided by Swashbuckle.AspNetCore. This package adds convenient overloads to apply themes seamlessly. @@ -36,7 +36,7 @@ app.UseSwaggerUI(Theme.Dark, c => c.EnableFilter(pinned: true)); - **[Custom Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Custom-Themes)** - Build your own themes with full control, or create standalone themes with zero dependencies -- **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Enhance your documentation with powerful UI capabilities, including a [pinnable filter bar](https://github.com/teociaps/SwaggerUI.Themes/wiki/Feature-Pinnable-Filter-Bar) via `EnableFilter(pinned: true)` +- **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Enhance your documentation with powerful UI capabilities - _...discover more in the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki/Features)!_ diff --git a/src/AspNetCore.SwaggerUI.Themes/readme.txt b/src/AspNetCore.SwaggerUI.Themes/readme.txt index 66486e7..0d29907 100644 --- a/src/AspNetCore.SwaggerUI.Themes/readme.txt +++ b/src/AspNetCore.SwaggerUI.Themes/readme.txt @@ -5,7 +5,7 @@ NEW FEATURES ------------------------------------------------------------------------------------- - Pinnable Filter Bar: Pin the operation filter box in place, just like the topbar - Enable it with: c.EnableFilter(pinned: true); + Enable it with: c.EnableFilter(pinnable: true); IMPROVEMENTS @@ -18,7 +18,7 @@ IMPROVEMENTS QUICK START ------------------------------------------------------------------------------------- Pinnable filter bar: - app.UseSwaggerUI(Theme.Dark, c => c.EnableFilter(pinned: true)); + app.UseSwaggerUI(Theme.Dark, c => c.EnableFilter(pinnable: true)); DOCUMENTATION @@ -27,63 +27,4 @@ DOCUMENTATION Full docs: https://github.com/teociaps/SwaggerUI.Themes/wiki Repository: https://github.com/teociaps/SwaggerUI.Themes -===================================================================================== - - -+===================================================================================+ -| AspNetCore.SwaggerUI.Themes β€’ RELEASE NOTES - v3 | -+===================================================================================+ - -BREAKING CHANGES -------------------------------------------------------------------------------------- -- API redesign: Style -> Theme (all classes renamed) -- Classic themes removed (modern themes only) -- .NET 6 & 7 support discontinued - -See migration guide: https://github.com/teociaps/SwaggerUI.Themes/wiki/Migration-v3 - - -NEW FEATURES -------------------------------------------------------------------------------------- -- πŸ”₯ Runtime Theme Switcher: Change themes on-the-fly without refreshing the page -- Auto-Discovery: Custom themes automatically available in the theme switcher -- Nested Folder Support: Organize your themes in subfolders -- Standalone Themes: Create CSS-only themes with zero dependencies -- Smart Filename Resolution: Support standard and standalone variants simultaneously -- Minified Stylesheet Support: Optimize load times with minified CSS - - -PLATFORM & DEPENDENCIES -------------------------------------------------------------------------------------- -- .NET 10 support added -- Swashbuckle.AspNetCore updated to v9.0.6 - - -IMPROVEMENTS -------------------------------------------------------------------------------------- -- Unified modern theme system with consistent defaults -- Advanced features now available on all themes -- Smaller package footprint (optimized assets) -- Improved performance and clarity throughout - - -QUICK START -------------------------------------------------------------------------------------- -Basic theme: - app.UseSwaggerUI(Theme.Dark); - -Enable theme switcher: - app.UseSwaggerUI(Theme.Dark, c => c.EnableThemeSwitcher()); - -All features: - app.UseSwaggerUI(Theme.Dark, c => c.EnableAllAdvancedOptions()); - - -DOCUMENTATION -------------------------------------------------------------------------------------- - -Full docs: https://github.com/teociaps/SwaggerUI.Themes/wiki -Migration: https://github.com/teociaps/SwaggerUI.Themes/wiki/Migration-v3 -Repository: https://github.com/teociaps/SwaggerUI.Themes - ===================================================================================== \ No newline at end of file diff --git a/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/SwaggerUiSettingsExtensions.cs b/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/SwaggerUiSettingsExtensions.cs index c33408d..b868c5d 100644 --- a/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/SwaggerUiSettingsExtensions.cs +++ b/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/SwaggerUiSettingsExtensions.cs @@ -71,14 +71,14 @@ public void EnableFilter() => /// /// Enables the operation filter box, optionally making it pinnable. /// - /// + /// /// If , the filter box can be pinned in place. /// - public void EnableFilter(bool pinned) + public void EnableFilter(bool pinnable) { settings.EnableFilter(); - if (pinned) + if (pinnable) settings.AdditionalSettings.EnablePinnableFilterBar(); } diff --git a/src/NSwag.AspNetCore.Themes/package-readme.md b/src/NSwag.AspNetCore.Themes/package-readme.md index c6facaf..c45ee78 100644 --- a/src/NSwag.AspNetCore.Themes/package-readme.md +++ b/src/NSwag.AspNetCore.Themes/package-readme.md @@ -22,8 +22,8 @@ app.UseSwaggerUi(Theme.Dark); // Or enable runtime theme switcher! app.UseSwaggerUi(Theme.Dark, c => c.EnableThemeSwitcher()); -// Or enable a pinnable filter box (EnableFilter() is new for NSwag!) -app.UseSwaggerUi(Theme.Dark, c => c.EnableFilter(pinned: true)); +// Or enable a pinnable filter box +app.UseSwaggerUi(Theme.Dark, c => c.EnableFilter(pinnable: true)); ``` > **Note**: The `UseSwaggerUi()` method is provided by NSwag.AspNetCore. This package adds convenient overloads to apply themes seamlessly. @@ -36,7 +36,7 @@ app.UseSwaggerUi(Theme.Dark, c => c.EnableFilter(pinned: true)); - **[Custom Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Custom-Themes)** - Build your own themes with full control, or create standalone themes with zero dependencies -- **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Enhance your documentation with powerful UI capabilities, including a [pinnable filter bar](https://github.com/teociaps/SwaggerUI.Themes/wiki/Feature-Pinnable-Filter-Bar) via `EnableFilter(pinned: true)` (the filter box itself, via `EnableFilter()`, is new for NSwag) +- **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Enhance your documentation with powerful UI capabilities - _...discover more in the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki/Features)!_ diff --git a/src/NSwag.AspNetCore.Themes/readme.txt b/src/NSwag.AspNetCore.Themes/readme.txt index 9328f0c..a62f21e 100644 --- a/src/NSwag.AspNetCore.Themes/readme.txt +++ b/src/NSwag.AspNetCore.Themes/readme.txt @@ -5,10 +5,9 @@ NEW FEATURES ------------------------------------------------------------------------------------- - Filter Box Support: settings.EnableFilter() is now available for NSwag, enabling - Swagger UI's native operation filter box (previously only available through the - Swashbuckle package) + Swagger UI's native operation filter box - Pinnable Filter Bar: Pin the operation filter box in place, just like the topbar - Enable it with: settings.EnableFilter(pinned: true); + Enable it with: settings.EnableFilter(pinnable: true); IMPROVEMENTS @@ -24,7 +23,7 @@ Filter box: app.UseSwaggerUi(Theme.Dark, settings => settings.EnableFilter()); Pinnable filter bar: - app.UseSwaggerUi(Theme.Dark, settings => settings.EnableFilter(pinned: true)); + app.UseSwaggerUi(Theme.Dark, settings => settings.EnableFilter(pinnable: true)); DOCUMENTATION @@ -33,63 +32,4 @@ DOCUMENTATION Full docs: https://github.com/teociaps/SwaggerUI.Themes/wiki Repository: https://github.com/teociaps/SwaggerUI.Themes -===================================================================================== - - -+===================================================================================+ -| NSwag.AspNetCore.Themes β€’ RELEASE NOTES - v3 | -+===================================================================================+ - -BREAKING CHANGES -------------------------------------------------------------------------------------- -- API redesign: Style -> Theme (all classes renamed) -- Classic themes removed (modern themes only) -- .NET 6 & 7 support discontinued - -See migration guide: https://github.com/teociaps/SwaggerUI.Themes/wiki/Migration-v3 - - -NEW FEATURES -------------------------------------------------------------------------------------- -- πŸ”₯ Runtime Theme Switcher: Change themes on-the-fly without refreshing the page -- Auto-Discovery: Custom themes automatically available in the theme switcher -- Nested Folder Support: Organize your themes in subfolders -- Standalone Themes: Create CSS-only themes with zero dependencies -- Smart Filename Resolution: Support standard and standalone variants simultaneously -- Minified Stylesheet Support: Optimize load times with minified CSS - - -PLATFORM & DEPENDENCIES -------------------------------------------------------------------------------------- -- .NET 10 support added -- NSwag.AspNetCore updated to v14.6.3 - - -IMPROVEMENTS -------------------------------------------------------------------------------------- -- Unified modern theme system with consistent defaults -- Advanced features now available on all themes -- Smaller package footprint (optimized assets) -- Improved performance and clarity throughout - - -QUICK START -------------------------------------------------------------------------------------- -Basic theme: - app.UseSwaggerUi(Theme.Dark); - -Enable theme switcher: - app.UseSwaggerUi(Theme.Dark, c => c.EnableThemeSwitcher()); - -All features: - app.UseSwaggerUi(Theme.Dark, c => c.EnableAllAdvancedOptions()); - - -DOCUMENTATION -------------------------------------------------------------------------------------- - -Full docs: https://github.com/teociaps/SwaggerUI.Themes/wiki -Migration: https://github.com/teociaps/SwaggerUI.Themes/wiki/Migration-v3 -Repository: https://github.com/teociaps/SwaggerUI.Themes - ===================================================================================== \ No newline at end of file diff --git a/tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerUIOptionsExtensionsTests.cs b/tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerUIOptionsExtensionsTests.cs index be0a705..0513074 100644 --- a/tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerUIOptionsExtensionsTests.cs +++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerUIOptionsExtensionsTests.cs @@ -8,26 +8,26 @@ namespace AspNetCore.Swagger.Themes.Tests; public class SwaggerUIOptionsExtensionsTests { [Fact] - public void EnableFilter_Pinned_AddsPinnableFilterBarOption() + public void EnableFilter_Pinnable_AddsPinnableFilterBarOption() { // Arrange var options = new SwaggerUIOptions(); // Act - options.EnableFilter(pinned: true); + options.EnableFilter(pinnable: true); // Assert options.ConfigObject.AdditionalItems.ShouldContainKey(AdvancedOptions.PinnableFilterBar); } [Fact] - public void EnableFilter_NotPinned_DoesNotAddPinnableFilterBarOption() + public void EnableFilter_NotPinnable_DoesNotAddPinnableFilterBarOption() { // Arrange var options = new SwaggerUIOptions(); // Act - options.EnableFilter(pinned: false); + options.EnableFilter(pinnable: false); // Assert options.ConfigObject.AdditionalItems.ShouldNotContainKey(AdvancedOptions.PinnableFilterBar); From 1135cdd3f99d1d05e89dcf9aa293b7005da48387 Mon Sep 17 00:00:00 2001 From: Teuz Date: Thu, 13 Aug 2026 16:46:02 +0200 Subject: [PATCH 21/35] docs(extensions): align package-readme/readme.txt style with Themes packages Restyles both Extensions packages' NuGet docs to match the Themes packages' format: emoji section headers, quick-start/features/usage layout in package-readme.md, and the boxed release-notes format in readme.txt (now v1.0, describing the initial API Counter feature). --- .../package-readme.md | 45 ++++++++++--------- .../readme.txt | 32 ++++++------- .../package-readme.md | 45 ++++++++++--------- src/NSwag.AspNetCore.Extensions/readme.txt | 32 ++++++------- 4 files changed, 82 insertions(+), 72 deletions(-) diff --git a/src/AspNetCore.SwaggerUI.Extensions/package-readme.md b/src/AspNetCore.SwaggerUI.Extensions/package-readme.md index 1f0dce6..8c8d7dc 100644 --- a/src/AspNetCore.SwaggerUI.Extensions/package-readme.md +++ b/src/AspNetCore.SwaggerUI.Extensions/package-readme.md @@ -1,44 +1,49 @@ -# AspNetCore.SwaggerUI.Extensions +ο»Ώ# AspNetCore.SwaggerUI.Extensions -Enhance your ASP.NET Core Swagger UI with functional extensions that go beyond theming. +### Enhance your ASP.NET Core Swagger UI with functional extensions that go beyond theming! -## Features +**Automatic operation counts**  β€’  **Zero-config setup** β€’ _**...and more coming soon!**_ -- **API Counter** - Automatically display operation counts in tag descriptions -- More extensions coming soon! +**[Get Started](https://github.com/teociaps/SwaggerUI.Themes/wiki/Getting-Started)** β€’ **[Full Documentation](https://github.com/teociaps/SwaggerUI.Themes/wiki)** -## Installation +## πŸš€ Quick Start ```bash dotnet add package AspNetCore.SwaggerUI.Extensions ``` -## Quick Start +```csharp +builder.Services.AddSwaggerGen(c => +{ + c.AppendOperationCountToTags(); +}); +``` + +> **Note**: `AddSwaggerGen()` is provided by Swashbuckle.AspNetCore. This package adds convenient extension methods on top of it. + +## ✨ Features -### API Counter +- **API Counter** - Automatically display operation counts in tag descriptions +- _...more extensions coming soon!_ -Add operation counts to your Swagger tags: +## πŸ“š Basic Usage Examples ```csharp builder.Services.AddSwaggerGen(c => { + // Append operation count to tag descriptions c.AppendOperationCountToTags(); - // Or with custom template: + + // Or with a custom template c.AppendOperationCountToTags(" [{0} endpoints]"); }); ``` -This will append the operation count to each tag's description, helping users understand the API surface area at a glance. - -## Documentation - -For more information and advanced usage, visit the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki). - ## Related Packages -- **AspNetCore.SwaggerUI.Themes** - Runtime theme switching and customization for Swashbuckle -- **NSwag.AspNetCore.Extensions** - Extensions for NSwag users +- **[AspNetCore.SwaggerUI.Themes](https://www.nuget.org/packages/AspNetCore.SwaggerUI.Themes/)** - Runtime theme switching and customization for Swashbuckle +- **[NSwag.AspNetCore.Extensions](https://www.nuget.org/packages/NSwag.AspNetCore.Extensions/)** - Extensions for NSwag users -## License +--- -MIT Licensed - see [LICENSE](https://github.com/teociaps/SwaggerUI.Themes/blob/main/LICENSE) for details. +#### Discover all the features and customization options in the [documentation](https://github.com/teociaps/SwaggerUI.Themes/wiki)! \ No newline at end of file diff --git a/src/AspNetCore.SwaggerUI.Extensions/readme.txt b/src/AspNetCore.SwaggerUI.Extensions/readme.txt index 84661bf..b0378d2 100644 --- a/src/AspNetCore.SwaggerUI.Extensions/readme.txt +++ b/src/AspNetCore.SwaggerUI.Extensions/readme.txt @@ -1,23 +1,23 @@ -Thank you for installing AspNetCore.SwaggerUI.Extensions! +ο»Ώ+===================================================================================+ +| AspNetCore.SwaggerUI.Extensions β€’ RELEASE NOTES - v1.0 | ++===================================================================================+ + +NEW FEATURES +------------------------------------------------------------------------------------- +- API Counter: Automatically append operation counts to tag descriptions + Enable it with: c.AppendOperationCountToTags(); + QUICK START ------------ -Add operation counts to your Swagger tags: +------------------------------------------------------------------------------------- +API Counter: + builder.Services.AddSwaggerGen(c => c.AppendOperationCountToTags()); - builder.Services.AddSwaggerGen(c => - { - c.AppendOperationCountToTags(); - }); DOCUMENTATION -------------- -Full documentation: https://github.com/teociaps/SwaggerUI.Themes/wiki +------------------------------------------------------------------------------------- -RELATED PACKAGES ----------------- -- AspNetCore.SwaggerUI.Themes - For runtime theme switching -- NSwag.AspNetCore.Extensions - For NSwag users +Full docs: https://github.com/teociaps/SwaggerUI.Themes/wiki +Repository: https://github.com/teociaps/SwaggerUI.Themes -SUPPORT -------- -Report issues: https://github.com/teociaps/SwaggerUI.Themes/issues +===================================================================================== \ No newline at end of file diff --git a/src/NSwag.AspNetCore.Extensions/package-readme.md b/src/NSwag.AspNetCore.Extensions/package-readme.md index 8161fe1..14815f7 100644 --- a/src/NSwag.AspNetCore.Extensions/package-readme.md +++ b/src/NSwag.AspNetCore.Extensions/package-readme.md @@ -1,44 +1,49 @@ -# NSwag.AspNetCore.Extensions +ο»Ώ# NSwag.AspNetCore.Extensions -Enhance your ASP.NET Core Swagger UI with functional extensions that go beyond theming. +### Enhance your ASP.NET Core Swagger UI with functional extensions that go beyond theming! -## Features +**Automatic operation counts**  β€’  **Zero-config setup** β€’ _**...and more coming soon!**_ -- **API Counter** - Automatically display operation counts in tag descriptions -- More extensions coming soon! +**[Get Started](https://github.com/teociaps/SwaggerUI.Themes/wiki/Getting-Started)** β€’ **[Full Documentation](https://github.com/teociaps/SwaggerUI.Themes/wiki)** -## Installation +## πŸš€ Quick Start ```bash dotnet add package NSwag.AspNetCore.Extensions ``` -## Quick Start +```csharp +builder.Services.AddOpenApiDocument(c => +{ + c.AppendOperationCountToTags(); +}); +``` + +> **Note**: `AddOpenApiDocument()` is provided by NSwag.AspNetCore. This package adds convenient extension methods on top of it. + +## ✨ Features -### API Counter +- **API Counter** - Automatically display operation counts in tag descriptions +- _...more extensions coming soon!_ -Add operation counts to your Swagger tags: +## πŸ“š Basic Usage Examples ```csharp builder.Services.AddOpenApiDocument(c => { + // Append operation count to tag descriptions c.AppendOperationCountToTags(); - // Or with custom template: + + // Or with a custom template c.AppendOperationCountToTags(" [{0} endpoints]"); }); ``` -This will append the operation count to each tag's description, helping users understand the API surface area at a glance. - -## Documentation - -For more information and advanced usage, visit the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki). - ## Related Packages -- **NSwag.AspNetCore.Themes** - Runtime theme switching and customization for NSwag -- **AspNetCore.SwaggerUI.Extensions** - Extensions for Swashbuckle users +- **[NSwag.AspNetCore.Themes](https://www.nuget.org/packages/NSwag.AspNetCore.Themes/)** - Runtime theme switching and customization for NSwag +- **[AspNetCore.SwaggerUI.Extensions](https://www.nuget.org/packages/AspNetCore.SwaggerUI.Extensions/)** - Extensions for Swashbuckle users -## License +--- -MIT Licensed - see [LICENSE](https://github.com/teociaps/SwaggerUI.Themes/blob/main/LICENSE) for details. +#### Discover all the features and customization options in the [documentation](https://github.com/teociaps/SwaggerUI.Themes/wiki)! \ No newline at end of file diff --git a/src/NSwag.AspNetCore.Extensions/readme.txt b/src/NSwag.AspNetCore.Extensions/readme.txt index 2c922f6..b73f740 100644 --- a/src/NSwag.AspNetCore.Extensions/readme.txt +++ b/src/NSwag.AspNetCore.Extensions/readme.txt @@ -1,23 +1,23 @@ -Thank you for installing NSwag.AspNetCore.Extensions! +ο»Ώ+===================================================================================+ +| NSwag.AspNetCore.Extensions β€’ RELEASE NOTES - v1.0 | ++===================================================================================+ + +NEW FEATURES +------------------------------------------------------------------------------------- +- API Counter: Automatically append operation counts to tag descriptions + Enable it with: c.AppendOperationCountToTags(); + QUICK START ------------ -Add operation counts to your Swagger tags: +------------------------------------------------------------------------------------- +API Counter: + builder.Services.AddOpenApiDocument(c => c.AppendOperationCountToTags()); - builder.Services.AddOpenApiDocument(c => - { - c.AppendOperationCountToTags(); - }); DOCUMENTATION -------------- -Full documentation: https://github.com/teociaps/SwaggerUI.Themes/wiki +------------------------------------------------------------------------------------- -RELATED PACKAGES ----------------- -- NSwag.AspNetCore.Themes - For runtime theme switching -- AspNetCore.SwaggerUI.Extensions - For Swashbuckle users +Full docs: https://github.com/teociaps/SwaggerUI.Themes/wiki +Repository: https://github.com/teociaps/SwaggerUI.Themes -SUPPORT -------- -Report issues: https://github.com/teociaps/SwaggerUI.Themes/issues +===================================================================================== \ No newline at end of file From 6a32bbd53b2152340762761b74a1b2dc10578ee3 Mon Sep 17 00:00:00 2001 From: Teuz Date: Thu, 13 Aug 2026 16:56:24 +0200 Subject: [PATCH 22/35] docs(extensions): drop cross-generator Extensions package from Related Packages A Swashbuckle user has no use for the NSwag-only Extensions package, and vice versa. Keep only the same-generator Themes companion link, which covers a genuinely different concern for the same audience. --- src/AspNetCore.SwaggerUI.Extensions/package-readme.md | 1 - src/NSwag.AspNetCore.Extensions/package-readme.md | 1 - 2 files changed, 2 deletions(-) diff --git a/src/AspNetCore.SwaggerUI.Extensions/package-readme.md b/src/AspNetCore.SwaggerUI.Extensions/package-readme.md index 8c8d7dc..cd78785 100644 --- a/src/AspNetCore.SwaggerUI.Extensions/package-readme.md +++ b/src/AspNetCore.SwaggerUI.Extensions/package-readme.md @@ -42,7 +42,6 @@ builder.Services.AddSwaggerGen(c => ## Related Packages - **[AspNetCore.SwaggerUI.Themes](https://www.nuget.org/packages/AspNetCore.SwaggerUI.Themes/)** - Runtime theme switching and customization for Swashbuckle -- **[NSwag.AspNetCore.Extensions](https://www.nuget.org/packages/NSwag.AspNetCore.Extensions/)** - Extensions for NSwag users --- diff --git a/src/NSwag.AspNetCore.Extensions/package-readme.md b/src/NSwag.AspNetCore.Extensions/package-readme.md index 14815f7..bc10a09 100644 --- a/src/NSwag.AspNetCore.Extensions/package-readme.md +++ b/src/NSwag.AspNetCore.Extensions/package-readme.md @@ -42,7 +42,6 @@ builder.Services.AddOpenApiDocument(c => ## Related Packages - **[NSwag.AspNetCore.Themes](https://www.nuget.org/packages/NSwag.AspNetCore.Themes/)** - Runtime theme switching and customization for NSwag -- **[AspNetCore.SwaggerUI.Extensions](https://www.nuget.org/packages/AspNetCore.SwaggerUI.Extensions/)** - Extensions for Swashbuckle users --- From 886893d5e20fba5ec11a286459ea908ffdf21dc1 Mon Sep 17 00:00:00 2001 From: Teuz Date: Thu, 13 Aug 2026 17:00:24 +0200 Subject: [PATCH 23/35] chore(themes): bump version to 3.1.0 Pinnable filter bar, persisted pinnable topbar state, and the other v3.1 changes documented in the readme.txt release notes ship as a minor bump. --- build/Versioning.props | 4 ++-- .../AspNetCore.SwaggerUI.Themes.csproj | 4 ++-- src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build/Versioning.props b/build/Versioning.props index d078017..8456379 100644 --- a/build/Versioning.props +++ b/build/Versioning.props @@ -11,8 +11,8 @@ $([System.DateTime]::Today.ToString("yyMMdd")) $(Today)-$(Floored) 3 - 0 - 2 + 1 + 0 -$(Revision) $(MajorVersion).$(MinorVersion).$(PatchVersion)$(VersionSuffix) diff --git a/src/AspNetCore.SwaggerUI.Themes/AspNetCore.SwaggerUI.Themes.csproj b/src/AspNetCore.SwaggerUI.Themes/AspNetCore.SwaggerUI.Themes.csproj index c297dd7..0ede010 100644 --- a/src/AspNetCore.SwaggerUI.Themes/AspNetCore.SwaggerUI.Themes.csproj +++ b/src/AspNetCore.SwaggerUI.Themes/AspNetCore.SwaggerUI.Themes.csproj @@ -6,8 +6,8 @@ 3 - 0 - 2 + 1 + 0 $(MajorVersion).$(MinorVersion).$(PatchVersion)$(VersionSuffix) diff --git a/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj b/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj index 297d747..8d5e107 100644 --- a/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj +++ b/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj @@ -6,8 +6,8 @@ 3 - 0 - 2 + 1 + 0 $(MajorVersion).$(MinorVersion).$(PatchVersion)$(VersionSuffix) From f54d36c456934bc695deaaea101b34a051215b7a Mon Sep 17 00:00:00 2001 From: Teuz Date: Thu, 13 Aug 2026 18:17:53 +0200 Subject: [PATCH 24/35] assets: add pinnable-filter-bar.gif for docs --- assets/pinnable-filter-bar.gif | Bin 0 -> 334763 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/pinnable-filter-bar.gif diff --git a/assets/pinnable-filter-bar.gif b/assets/pinnable-filter-bar.gif new file mode 100644 index 0000000000000000000000000000000000000000..2c1dadc4f996aaca269306d6a34fe2cbd0d5dbd1 GIT binary patch literal 334763 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ΞUCS@hak<53x%Eh1We6}Gn!lVp#kIFEP92n? zCdT5B#A+9mgsnu)PmN3CC<5XH9wC88oFF9JaB@|ahM8%dIsaw)Tg(+U0LsXRqO&Ix z>loDBLpc+~z$pzRY zN)mT6n4thT%^?ci)A|79g(7KSJ;4?CI|zRc?G&%N7mus~OhF(>AueYT0+Nxs**)gKUJW{21vX*1UeNk)Ad*+sjq{P@tM5O09{1BzQ2Y#pqG`O#;AvUz%m*~vi8Hgq^ zkR839d_|8C&>DA4JBq31nE`-2Lyo_^5a)me&_fVa07Z9GCOXlOM(Kde9ypQ+H1ET8 zX>l4!#0Dh92$Qhm5+6;D2V)svlHwe&iNnwPEf8bzgEDv`Ktt+c2`0%wMpQTUK~!Wm zNst!DFhTehdIf@oe�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

||d!Z29(GH_m@JZy(SJfiaU^C7q+Ro~%7U3J% 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

)0he;1IgTg%;B`m(dx`hbx=>^+eF3p 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 literal 0 HcmV?d00001 From a186a4fdcbcfae05377535b04d153c3a53942e61 Mon Sep 17 00:00:00 2001 From: Teuz Date: Fri, 14 Aug 2026 11:01:42 +0200 Subject: [PATCH 25/35] fix(themes): remove duplicated API Counter feature from Themes packages AppendOperationCountToTags() and its filter/processor were leftover in AspNetCore.SwaggerUI.Themes and NSwag.AspNetCore.Themes from before the Extensions packages existed as their own projects - an exact duplicate of what now lives in AspNetCore.SwaggerUI.Extensions and NSwag.AspNetCore.Extensions. Never shipped in a published Themes release, so removing it isn't a breaking change. Drops the corresponding calls from the two Themes sample projects, which don't reference the Extensions packages; the dedicated Sample.AspNetCore.SwaggerUI.Extensions / Sample.NSwag.AspNetCore.Extensions samples already demonstrate the feature. --- ...endOperationCountToTagDescriptionFilter.cs | 93 ------------------ .../SwaggerGenOptionsExtensions.cs | 35 ------- ...OperationCountToTagDescriptionProcessor.cs | 95 ------------------- ...nApiDocumentGeneratorSettingsExtensions.cs | 36 ------- 4 files changed, 259 deletions(-) delete mode 100644 src/AspNetCore.SwaggerUI.Themes/AspNetCore/Swagger/Themes/Filters/AppendOperationCountToTagDescriptionFilter.cs delete mode 100644 src/AspNetCore.SwaggerUI.Themes/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs delete mode 100644 src/NSwag.AspNetCore.Themes/AspNetCore/Swagger/Themes/Processors/AppendOperationCountToTagDescriptionProcessor.cs delete mode 100644 src/NSwag.AspNetCore.Themes/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs diff --git a/src/AspNetCore.SwaggerUI.Themes/AspNetCore/Swagger/Themes/Filters/AppendOperationCountToTagDescriptionFilter.cs b/src/AspNetCore.SwaggerUI.Themes/AspNetCore/Swagger/Themes/Filters/AppendOperationCountToTagDescriptionFilter.cs deleted file mode 100644 index 5c730cb..0000000 --- a/src/AspNetCore.SwaggerUI.Themes/AspNetCore/Swagger/Themes/Filters/AppendOperationCountToTagDescriptionFilter.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace AspNetCore.Swagger.Themes.Filters; - -///

-/// Document filter that appends operation count to tag descriptions in Swagger UI. -/// -/// -/// This filter modifies the OpenAPI document at generation time to include -/// the number of operations (endpoints) in each tag's description. -/// Inspired by: https://github.com/unchase/Unchase.Swashbuckle.AspNetCore.Extensions -/// -public sealed class AppendOperationCountToTagDescriptionFilter : IDocumentFilter -{ - private readonly string _messageTemplate; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The message template to use. Must contain {0} placeholder for the count. - /// Default: " (operations: {0})" - /// - /// - /// Thrown when does not contain {0} placeholder. - /// - public AppendOperationCountToTagDescriptionFilter(string messageTemplate = " (operations: {0})") - { - ArgumentException.ThrowIfNullOrWhiteSpace(messageTemplate); - - if (!messageTemplate.Contains("{0}")) - { - throw new ArgumentException("The message template must contain '{0}' placeholder.", nameof(messageTemplate)); - } - - _messageTemplate = messageTemplate; - } - - /// - /// Applies the filter to the OpenAPI document. - /// - /// The OpenAPI document to modify. - /// The document filter context. - public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) - { - // Count operations per tag - var tagOperationCount = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var path in swaggerDoc.Paths.Values) - { - foreach (var operation in path.Operations.Values) - { - if (operation?.Tags is null || operation.Tags.Count == 0) - continue; - - foreach (var tag in operation.Tags) - { - if (tagOperationCount.ContainsKey(tag.Name)) - tagOperationCount[tag.Name]++; - else - tagOperationCount[tag.Name] = 1; - } - } - } - - // If no counts found, nothing to do - if (tagOperationCount.Count == 0) - return; - - // Ensure Tags collection exists - swaggerDoc.Tags ??= new List(); - - // Create tags if they don't exist - foreach (var tagName in tagOperationCount.Keys) - { - if (!swaggerDoc.Tags.Any(t => t.Name.Equals(tagName, StringComparison.OrdinalIgnoreCase))) - { - swaggerDoc.Tags.Add(new OpenApiTag { Name = tagName }); - } - } - - // Append count to tag descriptions - foreach (var tag in swaggerDoc.Tags) - { - if (tagOperationCount.TryGetValue(tag.Name, out var count)) - { - tag.Description ??= string.Empty; - tag.Description += string.Format(_messageTemplate, count); - } - } - } -} diff --git a/src/AspNetCore.SwaggerUI.Themes/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs b/src/AspNetCore.SwaggerUI.Themes/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs deleted file mode 100644 index 42b6295..0000000 --- a/src/AspNetCore.SwaggerUI.Themes/Microsoft/Extensions/DependencyInjection/SwaggerGenOptionsExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using AspNetCore.Swagger.Themes.Filters; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace Microsoft.Extensions.DependencyInjection; - -/// -/// Extension methods for configuring Swagger generation with operation count feature. -/// -public static class SwaggerGenOptionsExtensions -{ - /// - /// Appends operation count to tag descriptions in Swagger UI. - /// - /// The Swagger generation options. - /// - /// Optional message template. Must contain {0} placeholder for the count. - /// Default: " (operations: {0})" - /// - /// - /// - /// services.AddSwaggerGen(c => - /// { - /// c.AppendOperationCountToTags(); - /// // Or with custom template: - /// c.AppendOperationCountToTags(" [{0} endpoints]"); - /// }); - /// - /// - public static void AppendOperationCountToTags( - this SwaggerGenOptions options, - string messageTemplate = " (operations: {0})") - { - options.DocumentFilter(messageTemplate); - } -} diff --git a/src/NSwag.AspNetCore.Themes/AspNetCore/Swagger/Themes/Processors/AppendOperationCountToTagDescriptionProcessor.cs b/src/NSwag.AspNetCore.Themes/AspNetCore/Swagger/Themes/Processors/AppendOperationCountToTagDescriptionProcessor.cs deleted file mode 100644 index e180725..0000000 --- a/src/NSwag.AspNetCore.Themes/AspNetCore/Swagger/Themes/Processors/AppendOperationCountToTagDescriptionProcessor.cs +++ /dev/null @@ -1,95 +0,0 @@ -using NSwag; -using NSwag.Generation.Processors; -using NSwag.Generation.Processors.Contexts; - -namespace AspNetCore.Swagger.Themes.Processors; - -/// -/// Document processor that appends operation count to tag descriptions in Swagger UI. -/// -/// -/// This processor modifies the OpenAPI document at generation time to include -/// the number of operations (endpoints) in each tag's description. -/// Inspired by: https://github.com/unchase/Unchase.Swashbuckle.AspNetCore.Extensions -/// -public sealed class AppendOperationCountToTagDescriptionProcessor : IDocumentProcessor -{ - private readonly string _messageTemplate; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The message template to use. Must contain {0} placeholder for the count. - /// Default: " (operations: {0})" - /// - /// - /// Thrown when does not contain {0} placeholder. - /// - public AppendOperationCountToTagDescriptionProcessor(string messageTemplate = " (operations: {0})") - { - ArgumentException.ThrowIfNullOrWhiteSpace(messageTemplate); - - if (!messageTemplate.Contains("{0}")) - { - throw new ArgumentException("The message template must contain '{0}' placeholder.", nameof(messageTemplate)); - } - - _messageTemplate = messageTemplate; - } - - /// - /// Processes the OpenAPI document. - /// - /// The document processor context. - public void Process(DocumentProcessorContext context) - { - var document = context.Document; - - // Count operations per tag - var tagOperationCount = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var path in document.Paths.Values) - { - foreach (var operation in path.Values) - { - if (operation?.Tags is null || operation.Tags.Count == 0) - continue; - - foreach (var tag in operation.Tags) - { - if (tagOperationCount.ContainsKey(tag)) - tagOperationCount[tag]++; - else - tagOperationCount[tag] = 1; - } - } - } - - // If no counts found, nothing to do - if (tagOperationCount.Count == 0) - return; - - // Ensure Tags collection exists - document.Tags ??= new List(); - - // Create tags if they don't exist - foreach (var tagName in tagOperationCount.Keys) - { - if (!document.Tags.Any(t => t.Name.Equals(tagName, StringComparison.OrdinalIgnoreCase))) - { - document.Tags.Add(new NSwag.OpenApiTag { Name = tagName }); - } - } - - // Append count to tag descriptions - foreach (var tag in document.Tags) - { - if (tagOperationCount.TryGetValue(tag.Name, out var count)) - { - tag.Description ??= string.Empty; - tag.Description += string.Format(_messageTemplate, count); - } - } - } -} diff --git a/src/NSwag.AspNetCore.Themes/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs b/src/NSwag.AspNetCore.Themes/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs deleted file mode 100644 index 7142869..0000000 --- a/src/NSwag.AspNetCore.Themes/Microsoft/Extensions/DependencyInjection/AspNetCoreOpenApiDocumentGeneratorSettingsExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -using AspNetCore.Swagger.Themes.Processors; -using NSwag.Generation.AspNetCore; - -namespace Microsoft.Extensions.DependencyInjection; - -/// -/// Extension methods for configuring OpenAPI document generation with operation count feature. -/// -public static class AspNetCoreOpenApiDocumentGeneratorSettingsExtensions -{ - /// - /// Appends operation count to tag descriptions in Swagger UI. - /// - /// The OpenAPI document generator settings. - /// - /// Optional message template. Must contain {0} placeholder for the count. - /// Default: " (operations: {0})" - /// - /// - /// - /// services.AddOpenApiDocument(c => - /// { - /// c.AppendOperationCountToTags(); - /// // Or with custom template: - /// c.AppendOperationCountToTags(" [{0} endpoints]"); - /// }); - /// - /// - public static void AppendOperationCountToTags( - this AspNetCoreOpenApiDocumentGeneratorSettings settings, - string messageTemplate = " (operations: {0})") - { - settings.DocumentProcessors.Add( - new AppendOperationCountToTagDescriptionProcessor(messageTemplate)); - } -} From 94903fd0a45c36fa251ae47ba3b2d276ca69d5eb Mon Sep 17 00:00:00 2001 From: Teuz Date: Fri, 14 Aug 2026 11:01:55 +0200 Subject: [PATCH 26/35] chore(samples): drop API Counter calls from Themes samples Follow-up to a186a4f: these two samples reference the Themes packages only (not Extensions), so the AppendOperationCountToTags() call no longer compiles once the duplicated feature is removed from Themes. The dedicated Extensions samples already demonstrate it. --- samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs | 5 ----- samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs | 5 ----- 2 files changed, 10 deletions(-) diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs index 6078b04..6ec243b 100644 --- a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs +++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs @@ -8,11 +8,6 @@ builder.Services.AddOpenApiDocument(c => { OpenApiDocGenConfigurer.Configure(c); - - // πŸ“Š API Counter: Server-side operation count - c.AppendOperationCountToTags(); - // Or with custom template: - // c.AppendOperationCountToTags(" [{0} endpoints]"); }); var app = builder.Build(); diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs index c3a01a0..14456d5 100644 --- a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs +++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs @@ -8,11 +8,6 @@ builder.Services.AddSwaggerGen(c => { SwaggerGenConfigurer.Configure(c); - - // πŸ“Š API Counter: Server-side operation count - c.AppendOperationCountToTags(); - // Or with custom template: - // c.AppendOperationCountToTags(" [{0} endpoints]"); }); var app = builder.Build(); From 5dd93c41d617b9e5c4f688f7281937b5f9489474 Mon Sep 17 00:00:00 2001 From: Teuz Date: Fri, 14 Aug 2026 11:18:47 +0200 Subject: [PATCH 27/35] fix(ci): loop dotnet test over each matched test project 'dotnet test tests/*' relied on there being exactly one directory under tests/ - now that AspNetCore.Swagger.Extensions.Tests sits alongside AspNetCore.SwaggerUI.Themes.Tests, the shell glob expands to two paths and dotnet test rejects multiple project arguments (MSB1008). Loop over the glob and test each project separately. --- .github/workflows/test.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cb9c05f..63a31b2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,4 +31,8 @@ jobs: 10.0.x - name: Test - run: dotnet test ${{ env.TEST_PROJECTS_PATH }} --configuration ${{ env.CONFIGURATION }} \ No newline at end of file + shell: bash + run: | + for project in ${{ env.TEST_PROJECTS_PATH }}; do + dotnet test "$project" --configuration ${{ env.CONFIGURATION }} + done \ No newline at end of file From a50845f014e0606f7cd8852bfd3dd08be9ecbed4 Mon Sep 17 00:00:00 2001 From: Teuz Date: Fri, 14 Aug 2026 11:20:09 +0200 Subject: [PATCH 28/35] fix(ci): re-run Build/Test on new commits pushed to an open PR Both pull_request triggers listed [opened, reopened, edited] but not synchronize, so pushing new commits to an already-open PR never re-ran CI - it only re-triggered on PR metadata edits. This is why the tests/* glob fix in the previous commit didn't get validated by its own PR. --- .github/workflows/build.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6c074ad..208032b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ on: paths-ignore: ['**/*.md', '**/*.png', '**/*.txt'] pull_request: branches: [main] - types: [opened, reopened, edited] + types: [opened, reopened, edited, synchronize] paths-ignore: ['**/*.md', '**/*.png', '**/*.txt'] env: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 63a31b2..e23841d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ on: - completed pull_request: branches: [main] - types: [opened, reopened, edited] + types: [opened, reopened, edited, synchronize] paths-ignore: ['**/*.md', '**/*.png', '**/*.txt'] env: From 45dee6f233a64ff95675b63086cb7d7f57f1f426 Mon Sep 17 00:00:00 2001 From: Teuz Date: Fri, 14 Aug 2026 11:43:54 +0200 Subject: [PATCH 29/35] ci(deploy): migrate NuGet push to Trusted Publishing (OIDC) Replaces the long-lived NUGET_API_KEY secret with NuGet/login@v1, which exchanges the job's OIDC token for a short-lived API key at publish time. Requires a Trusted Publishing policy configured on nuget.org for this repo/workflow, plus a NUGET_USER secret holding the nuget.org username (not email). deploy.ps1 needs no change - it already accepts the API key as a plain parameter. --- .github/workflows/pack_and_deploy.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pack_and_deploy.yml b/.github/workflows/pack_and_deploy.yml index acee17d..38d555f 100644 --- a/.github/workflows/pack_and_deploy.yml +++ b/.github/workflows/pack_and_deploy.yml @@ -13,6 +13,9 @@ env: jobs: deploy: runs-on: ubuntu-latest + permissions: + contents: read + id-token: write # required so GitHub can mint the OIDC token NuGet Trusted Publishing exchanges for a short-lived API key steps: - name: Checkout repository @@ -46,6 +49,12 @@ jobs: exit 1 fi + - name: NuGet login (OIDC β†’ temp API key) + uses: NuGet/login@v1 + id: nuget_login + with: + user: ${{ secrets.NUGET_USER }} # nuget.org username (profile name), NOT the email address + - name: Pack & Push to NuGet shell: pwsh - run: ./deploy.ps1 -Configuration ${{ env.CONFIGURATION }} -PackageOutput ${{ env.PACKAGE_OUTPUT }} -NuGetSourceUrl ${{ env.NUGET_SOURCE_URL }} -NuGetApiKey ${{ secrets.NUGET_API_KEY }} -Filter ${{ steps.family.outputs.filter }} \ No newline at end of file + run: ./deploy.ps1 -Configuration ${{ env.CONFIGURATION }} -PackageOutput ${{ env.PACKAGE_OUTPUT }} -NuGetSourceUrl ${{ env.NUGET_SOURCE_URL }} -NuGetApiKey ${{ steps.nuget_login.outputs.NUGET_API_KEY }} -Filter ${{ steps.family.outputs.filter }} \ No newline at end of file From c0a274f6bc45a8cdd72146eff4881c781f2613a5 Mon Sep 17 00:00:00 2001 From: Teuz Date: Fri, 14 Aug 2026 16:54:32 +0200 Subject: [PATCH 30/35] fix(extensions): stop Debug builds from inheriting the Themes packages' version Versioning.props' shared Debug-config fallback (Version + the implicit GeneratePackageOnBuild=true) tracks the Themes packages' version line (currently 3.x) and both Extensions csproj files only overrode their own 1.0.0 version for Release, so any plain 'dotnet build' (default Debug) silently auto-packed AspNetCore.SwaggerUI.Extensions / NSwag.AspNetCore.Extensions stamped with the Themes version instead of their own. Add an explicit Debug-config override to both, matching the pattern already used for Release. Verified: dotnet msbuild -t:GetVersion now reports 1.0.0- in Debug (was 3.1.0-) and 1.0.0 in Release for both packages, and a locally packed nupkg loads and executes AppendOperationCountToTagDescriptionFilter.Apply() correctly end to end. --- .../AspNetCore.SwaggerUI.Extensions.csproj | 10 ++++++++++ .../NSwag.AspNetCore.Extensions.csproj | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj b/src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj index 23f304c..eabca1c 100644 --- a/src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj +++ b/src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj @@ -12,6 +12,16 @@ $(MajorVersion).$(MinorVersion).$(PatchVersion)$(VersionSuffix) + + + 1 + 0 + 0 + -$(Revision) + $(MajorVersion).$(MinorVersion).$(PatchVersion)$(VersionSuffix) + + AspNetCore.SwaggerUI.Extensions diff --git a/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj b/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj index 4f2afef..41b2a56 100644 --- a/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj +++ b/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj @@ -12,6 +12,16 @@ $(MajorVersion).$(MinorVersion).$(PatchVersion)$(VersionSuffix) + + + 1 + 0 + 0 + -$(Revision) + $(MajorVersion).$(MinorVersion).$(PatchVersion)$(VersionSuffix) + + NSwag.AspNetCore.Extensions From 65f22cca378ea7ecd77410543df67a449b2a23f4 Mon Sep 17 00:00:00 2001 From: Teuz Date: Mon, 17 Aug 2026 13:14:12 +0200 Subject: [PATCH 31/35] fix(extensions): support Microsoft.OpenApi v2 (Swashbuckle.AspNetCore 10.x / net10.0) Microsoft.OpenApi 2.x is a breaking rewrite (Models namespace removed, OpenApiDocument.Tags and OpenApiOperation.Tags collection types changed, OpenApiTagReference replaces OpenApiTag on operations) that ships with Swashbuckle.AspNetCore 10.x. AppendOperationCountToTagDescriptionFilter is now multi-targeted via #if NET10_0_OR_GREATER to compile correctly against both API shapes, and AspNetCore.SwaggerUI.Extensions.csproj takes a TFM-conditional Swashbuckle.AspNetCore reference (9.x for net8.0/net9.0, 10.x for net10.0) with upper-bounded ranges so NuGet can't silently float a TFM onto the wrong major line again. Updates the filter's test suite to build the corresponding v1/v2 object graphs per TFM, and relaxes the empty-document assertion since OpenApiDocument.Tags defaults to null in v2 vs. an empty collection in v1 - both are correct given the filter never touches Tags when there's nothing to append. Verified: dotnet test passes 22/22 on net8.0, net9.0, and net10.0; a locally packed nupkg was smoke-tested end to end against real Swashbuckle.AspNetCore 10.2.3 on net10.0. --- ...ple.AspNetCore.SwaggerUI.Extensions.csproj | 2 +- .../AspNetCore.SwaggerUI.Extensions.csproj | 10 ++++- ...endOperationCountToTagDescriptionFilter.cs | 8 ++++ .../NSwag.AspNetCore.Extensions.csproj | 2 +- ...erationCountToTagDescriptionFilterTests.cs | 45 ++++++++++++++++++- ...AspNetCore.Swagger.Extensions.Tests.csproj | 3 +- 6 files changed, 64 insertions(+), 6 deletions(-) diff --git a/samples/Sample.AspNetCore.SwaggerUI.Extensions/Sample.AspNetCore.SwaggerUI.Extensions.csproj b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Sample.AspNetCore.SwaggerUI.Extensions.csproj index 1a23206..aec7bfb 100644 --- a/samples/Sample.AspNetCore.SwaggerUI.Extensions/Sample.AspNetCore.SwaggerUI.Extensions.csproj +++ b/samples/Sample.AspNetCore.SwaggerUI.Extensions/Sample.AspNetCore.SwaggerUI.Extensions.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj b/src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj index eabca1c..b5b52c2 100644 --- a/src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj +++ b/src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj @@ -25,7 +25,7 @@ AspNetCore.SwaggerUI.Extensions - Enhance your ASP.NET Core Swagger UI with functional extensions like API counter and more. Extends Swashbuckle.AspNetCore with additional capabilities. + Enhance your ASP.NET Core Swagger UI with additional capabilities. swagger;swagger-ui;swaggerui;swashbuckle;aspnetcore;webapi;openapi;extension;api-counter;operation-count;tag-extensions @@ -33,8 +33,14 @@ true + - + + diff --git a/src/AspNetCore.SwaggerUI.Extensions/AspNetCore/Swagger/Extensions/Filters/AppendOperationCountToTagDescriptionFilter.cs b/src/AspNetCore.SwaggerUI.Extensions/AspNetCore/Swagger/Extensions/Filters/AppendOperationCountToTagDescriptionFilter.cs index 02aa737..ce60eb9 100644 --- a/src/AspNetCore.SwaggerUI.Extensions/AspNetCore/Swagger/Extensions/Filters/AppendOperationCountToTagDescriptionFilter.cs +++ b/src/AspNetCore.SwaggerUI.Extensions/AspNetCore/Swagger/Extensions/Filters/AppendOperationCountToTagDescriptionFilter.cs @@ -1,4 +1,8 @@ +#if NET10_0_OR_GREATER +using Microsoft.OpenApi; +#else using Microsoft.OpenApi.Models; +#endif using Swashbuckle.AspNetCore.SwaggerGen; namespace AspNetCore.Swagger.Extensions.Filters; @@ -68,7 +72,11 @@ public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) return; // Ensure Tags collection exists +#if NET10_0_OR_GREATER + swaggerDoc.Tags ??= new HashSet(); +#else swaggerDoc.Tags ??= []; +#endif // Create tags if they don't exist foreach (var tagName in tagOperationCount.Keys) diff --git a/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj b/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj index 41b2a56..1e81c1d 100644 --- a/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj +++ b/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj @@ -25,7 +25,7 @@ NSwag.AspNetCore.Extensions - Enhance your ASP.NET Core Swagger UI with functional extensions like API counter and more. Extends NSwag.AspNetCore with additional capabilities. + Enhance your ASP.NET Core Swagger UI with additional capabilities. swagger;swagger-ui;swaggerui;nswag;aspnetcore;webapi;openapi;extension;api-counter;operation-count;tag-extensions diff --git a/tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionFilterTests.cs b/tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionFilterTests.cs index 5758846..b6cc1f9 100644 --- a/tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionFilterTests.cs +++ b/tests/AspNetCore.Swagger.Extensions.Tests/AppendOperationCountToTagDescriptionFilterTests.cs @@ -1,6 +1,10 @@ using AspNetCore.Swagger.Extensions.Filters; using Microsoft.AspNetCore.Mvc.ApiExplorer; +#if NET10_0_OR_GREATER +using Microsoft.OpenApi; +#else using Microsoft.OpenApi.Models; +#endif using Shouldly; using Swashbuckle.AspNetCore.SwaggerGen; using System.Text.Json; @@ -60,7 +64,10 @@ public void Apply_WithEmptyDocument_DoesNothing() filter.Apply(document, context); // Assert - document.Tags.ShouldBeEmpty(); + // Tags is left untouched when there's nothing to append - its default depends on the + // Microsoft.OpenApi version (empty collection pre-v2, null from v2 onward), and either + // is correct here since the filter never had a reason to allocate one. + (document.Tags is null || document.Tags.Count == 0).ShouldBeTrue(); } [Fact] @@ -104,10 +111,17 @@ public void Apply_WithExistingDescription_AppendsCount() // Arrange var filter = new AppendOperationCountToTagDescriptionFilter(); var document = CreateDocumentWithTags(); +#if NET10_0_OR_GREATER + document.Tags = new HashSet + { + new() { Name = "Users", Description = "User management" } + }; +#else document.Tags = new List { new() { Name = "Users", Description = "User management" } }; +#endif var context = CreateContext(); // Act @@ -130,18 +144,33 @@ public void Apply_WithMultipleTags_CountsCorrectly() { ["/users"] = new OpenApiPathItem { +#if NET10_0_OR_GREATER + Operations = new Dictionary + { + [HttpMethod.Get] = new() { Tags = new HashSet { new("Users", null) } }, + [HttpMethod.Post] = new() { Tags = new HashSet { new("Users", null) } } + } +#else Operations = new Dictionary { [OperationType.Get] = new() { Tags = new List { new() { Name = "Users" } } }, [OperationType.Post] = new() { Tags = new List { new() { Name = "Users" } } } } +#endif }, ["/products"] = new OpenApiPathItem { +#if NET10_0_OR_GREATER + Operations = new Dictionary + { + [HttpMethod.Get] = new() { Tags = new HashSet { new("Products", null) } } + } +#else Operations = new Dictionary { [OperationType.Get] = new() { Tags = new List { new() { Name = "Products" } } } } +#endif } } }; @@ -172,6 +201,19 @@ private static OpenApiDocument CreateDocumentWithTags() { ["/users"] = new OpenApiPathItem { +#if NET10_0_OR_GREATER + Operations = new Dictionary + { + [HttpMethod.Get] = new() + { + Tags = new HashSet { new("Users", null) } + }, + [HttpMethod.Post] = new() + { + Tags = new HashSet { new("Users", null) } + } + } +#else Operations = new Dictionary { [OperationType.Get] = new() @@ -183,6 +225,7 @@ private static OpenApiDocument CreateDocumentWithTags() Tags = new List { new() { Name = "Users" } } } } +#endif } } }; diff --git a/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCore.Swagger.Extensions.Tests.csproj b/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCore.Swagger.Extensions.Tests.csproj index 888b824..5423612 100644 --- a/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCore.Swagger.Extensions.Tests.csproj +++ b/tests/AspNetCore.Swagger.Extensions.Tests/AspNetCore.Swagger.Extensions.Tests.csproj @@ -28,7 +28,8 @@ all - + + From 37a8b3489cdbca2c7aa5cafe1b3a0d71747c4421 Mon Sep 17 00:00:00 2001 From: Teuz Date: Mon, 17 Aug 2026 15:21:29 +0200 Subject: [PATCH 32/35] fix(themes): hide Swashbuckle 10.x's native dark-mode toggle Swashbuckle.AspNetCore 10.x's bundled swagger-ui-dist ships its own dark-mode toggle in the topbar, layering swagger-ui's own dark CSS on top of whatever theme is active - visibly clashing with it (confirmed in-browser: operation blocks change color/border once both are active). This library's own theme, plus EnableThemeSwitcher when enabled, already cover light/dark, so hide the native toggle. No-op on older Swashbuckle versions that don't render the element at all. Verified in-browser against Swashbuckle.AspNetCore 10.2.3: toggle no longer renders in the topbar; pin button and theme switcher unaffected. --- .../AspNetCore/Swagger/Themes/Styles/common.css | 9 +++++++++ .../AspNetCore/Swagger/Themes/Styles/common.min.css | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css index 5dc459a..30e94c0 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css @@ -283,6 +283,15 @@ body { color: var(--topbar-download-url-button-color, #fff); } +/* Swashbuckle.AspNetCore 10.x's swagger-ui-dist ships its own native dark-mode toggle in the + topbar. It layers swagger-ui's own bundled dark CSS on top of whatever theme is active here, + visibly clashing with it - hide it since this library's own theme (and, when enabled, its + EnableThemeSwitcher) already cover light/dark. Harmless no-op on older Swashbuckle versions + that don't render this element at all. */ +.swagger-ui .topbar .dark-mode-toggle { + display: none; +} + #theme-switcher-select { background: var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat); border: 2px solid var(--topbar-select-border-color, #62a03f); diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css index c34d2eb..0e51dc6 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css @@ -1 +1 @@ -/*Common Theme https://github.com/teociaps/SwaggerUI.Themes */@charset "UTF-8";:root{--topbar-pinned-height:4rem;--filterbar-pinned-height:5rem}::-webkit-scrollbar{width:16px}::-webkit-scrollbar-thumb{height:56px;border-radius:8px;border:4px solid transparent;background-clip:content-box;background-color:var(--scrollbar-thumb-color,#a5a5a5);transition:background-color .3s}::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover-color,#727272)}body::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-color,#a5a5a5)}*{font-family:system-ui,sans-serif}:focus{font-family:system-ui,sans-serif;outline:0!important}:focus-visible{outline:auto!important;outline-offset:1px}body{background-color:var(--body-background-color,#fafafa);padding-bottom:1rem}*,::after,::before{box-sizing:border-box}.swagger-ui{color:var(--swagger-main-color,#3b4151);-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.15;margin:0}.swagger-ui .loading-container .loading:after{color:var(--swagger-main-color,#3b4151)}.swagger-ui .loading-container .loading:before{border:2px solid var(--loading-container-border-color,rgba(85,85,85,.1));border-top-color:var(--loading-container-border-top-color,rgba(0,0,0,.6))}.swagger-ui .errors-wrapper{background:var(--errors-wrapper-background-color,rgba(249,62,62,.1));border:2px solid var(--errors-wrapper-border-color,#f93e3e)}.swagger-ui .errors-wrapper .errors h4,.swagger-ui .errors-wrapper hgroup h4{color:var(--swagger-main-color,#3b4151)}.swagger-ui .errors-wrapper .errors small{color:var(--errors-wrapper-errors-color,#606060)}.swagger-ui .topbar{background-color:transparent;z-index:1000;padding:0}.swagger-ui .topbar.pinned{position:sticky;top:10px;--pinned-topbar-offset:var(--topbar-pinned-height)}.swagger-ui:has(> .topbar.pinned){--pinned-topbar-offset:var(--topbar-pinned-height)}.swagger-ui .topbar.pinned::before{content:'';position:absolute;inset:-10px 0 0;pointer-events:none;user-select:none;backdrop-filter:blur(20px);-webkit-mask-image:linear-gradient(to bottom,black 40%,transparent);mask-image:linear-gradient(to bottom,black 40%,transparent);background:linear-gradient(90deg,rgba(0,0,0,0) 5%,rgba(8,8,8,.4) 50%,rgba(0,0,0,0) 95%)}.swagger-ui .topbar .topbar-wrapper{background-color:var(--topbar-background-color,#000);border:1px solid var(--topbar-border-color,gray);border-top:0;border-radius:0 0 .5rem .5rem;padding:.5rem;box-shadow:0 5px 15px 0 var(--topbar-pinned-shadow-color,rgba(0,0,0,.5))}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn svg{fill:var(--topbar-pin-icon-color,white)}.swagger-ui .filter #pin-filterbar-btn,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn{cursor:pointer;background:0 0;border:none;margin:.2rem;padding:.2rem;transition:all .2s;rotate:z 359deg}.swagger-ui .filter #pin-filterbar-btn:focus-within,.swagger-ui .filter #pin-filterbar-btn:hover,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:hover{scale:1.05;animation:pintobbar 1.2s infinite}.swagger-ui .filter #pin-filterbar-btn:active,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:active{scale:0.95}.swagger-ui .topbar.pinned .topbar-wrapper{background-color:var(--topbar-pinned-background-color,rgba(0,0,0,.7));border:1px solid var(--topbar-border-color,gray);border-radius:.5rem;backdrop-filter:blur(20px)}.swagger-ui .filter.pinned #pin-filterbar-btn,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn{rotate:z 0deg}.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn{translate:0 5px}.swagger-ui .filter.pinned #pin-filterbar-btn:focus-within,.swagger-ui .filter.pinned #pin-filterbar-btn:hover,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:hover{animation:unpintobbar 1.2s infinite}@keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-moz-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-webkit-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@keyframes unpintobbar{10%{translate:0 1px}70%{rotate:z 50deg;translate:-3px 1px}}@-moz-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}@-webkit-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}.swagger-ui .topbar .download-url-wrapper .select-label select,.swagger-ui .topbar .download-url-wrapper input[type=text]{border:2px solid var(--topbar-select-border-color,#62a03f)}.swagger-ui .topbar .download-url-wrapper .select-label{color:var(--topbar-select-label-color,#f0f0f0)}.swagger-ui .topbar .download-url-wrapper .download-url-button{background:var(--topbar-download-url-button-background-color,#62a03f);color:var(--topbar-download-url-button-color,#fff)}#theme-switcher-select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);border:2px solid var(--topbar-select-border-color,#62a03f);min-width:120px;box-shadow:none}.swagger-ui .info .base-url,.swagger-ui .info .title,.swagger-ui .info h1,.swagger-ui .info h2,.swagger-ui .info h3,.swagger-ui .info h4,.swagger-ui .info h5,.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info table{color:var(--swagger-main-color,#3b4151)}.swagger-ui .info a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .info a:hover{color:var(--swagger-info-link-hover,#1f69c0)}.swagger-ui .info .title small{background:var(--api-version-background-color,#7d8492)}.swagger-ui .info .title small.version-stamp{background-color:var(--api-version-stamp-background-color,#89bf04)}.swagger-ui .info .title small pre{color:var(--api-version-color,#fff)}.swagger-ui .scheme-container{background:0 0;box-shadow:none;margin:0;padding:0}.swagger-ui .auth-wrapper{display:flex;flex:1;justify-content:flex-end;border:1px solid var(--auth-wrapper-border-color,#d5d5d5);padding:0;border-radius:5px;box-shadow:0 3px 5px -5px var(--auth-wrapper-box-shadow-color,#000);background:var(--auth-wrapper-background-color,#fff)}.swagger-ui .scheme-container .schemes>label{color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container{border-bottom:1px solid var(--auth-container-border-bottom-color,#ebebeb);color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container .errors{background-color:var(--auth-container-background-color,#fee);color:var(--auth-container-errors-color,red)}.swagger-ui .btn.authorize{background-color:var(--btn-authorize-background-color,transparent);border-color:var(--btn-authorize-border-color,#49cc90);color:var(--btn-authorize-font-color,#49cc90)}.swagger-ui .btn.authorize svg{fill:var(--btn-authorize-svg-fill-color,#49cc90)}.swagger-ui .opblock-tag-section{display:flex;flex-direction:column;border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;margin-block:1rem;overflow:clip}.swagger-ui .opblock-tag-section .expand-collapse-all-btn{display:none;user-select:none}.swagger-ui .opblock-tag-section.is-open .expand-collapse-all-btn{background:var(--opblock-tag-section-expandcollapse-background-color,rgb(220 220 220 / 45%));color:var(--swagger-main-color,#3b4151);border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;padding:5px 10px;font-size:medium;margin-inline:10px;display:block}.swagger-ui .opblock-tag,.swagger-ui .opblock-tag small,.swagger-ui .parameter__type{color:var(--swagger-main-color,#3b4151)}.swagger-ui .opblock-tag{align-items:center;cursor:pointer;display:flex;padding:10px 20px 10px 10px;color:var(--swagger-main-color,#3b4151);font-size:24px;border-bottom:0 dashed var(--opblock-tag-border-bottom-color,#e1e1e1);margin:0;transition:all .2s}.swagger-ui .opblock-tag:focus-within,.swagger-ui .opblock-tag:hover{background:var(--opblock-tag-background-color-hover,rgba(0,0,0,.02))}.swagger-ui .opblock-tag+div .operation-tag-content{margin-inline:5px}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag{position:sticky;top:0;z-index:4;margin-bottom:1rem;border-bottom:1px dashed var(--opblock-tag-border-bottom-color,#e1e1e1);background:var(--body-background-color,#fafafa)}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:focus-within,#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:hover{background:var(--opblock-tag-pinned-background-color-hover,#f1f1f1)}.swagger-ui .opblock-tag-section.is-open .opblock-tag{top:calc(var(--pinned-topbar-offset,0px) + var(--pinned-filter-offset,0px))}.swagger-ui .opblock{border:1px solid var(--opblock-border-color,#000);box-shadow:0 0 3px var(--opblock-shadow-color,rgba(0,0,0,.19))}.swagger-ui .opblock .tab-header .tab-item.active h4 span:after{background:var(--opblock-tabheader-underline-color,gray)}.swagger-ui .opblock .opblock-summary button svg{fill:var(--opblock-summary-svg-icons-color,#000)}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid var(--opblock-summary-border-bottom-color,#000)}.swagger-ui .opblock .opblock-section-header{background:var(--opblock-section-header-background-color,hsla(0,0%,100%,.8));box-shadow:0 1px 2px var(--opblock-section-header-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-section-header h4,.swagger-ui .opblock .opblock-section-header>label{color:var(--swagger-main-color,#3b4151)}.swagger-ui .opblock .opblock-summary-method{background:var(--opblock-summary-method-background-color,#000);color:var(--opblock-summary-method-color,#fff);text-shadow:0 1px 0 var(--opblock-summary-method-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{color:var(--swagger-main-color,#3b4151);word-break:normal}.swagger-ui .opblock.opblock-post{background:var(--opblock-post-background-color,rgba(73,204,144,.1));border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock.opblock-post .opblock-summary-method{background:var(--opblock-post-method-color,#49cc90)}.swagger-ui .opblock.opblock-post .opblock-summary{border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span:after{background:var(--opblock-post-method-color,#49cc90);border-radius:1rem}.swagger-ui .opblock.opblock-put{background:var(--opblock-put-background-color,rgba(252,161,48,.1));border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-put .opblock-summary-method{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-put .opblock-summary{border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span:after{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-delete{background:var(--opblock-delete-background-color,rgba(249,62,62,.1));border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .opblock-summary-method{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span:after{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-get{background:var(--opblock-get-background-color,rgba(97,175,254,.1));border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get .opblock-summary-method{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-get .opblock-summary{border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span:after{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-patch{background:var(--opblock-patch-background-color,rgba(80,227,194,.1));border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .opblock-summary-method{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span:after{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-head{background:var(--opblock-head-background-color,rgba(144,18,254,.1));border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head .opblock-summary-method{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-head .opblock-summary{border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span:after{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-options{background:var(--opblock-options-background-color,rgba(13,90,167,.1));border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .opblock-summary-method{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .opblock-summary{border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span:after{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-deprecated{background:var(--opblock-deprecated-background-color,hsla(0,0%,92%,.1));border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span:after{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .tab li{color:var(--swagger-main-color,#3b4151)}.swagger-ui .tab li:first-of-type:after{background:var(--swagger-tabs-divider-color,rgba(0,0,0,.2))}.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-external-docs-wrapper,.swagger-ui .opblock-external-docs-wrapper h4,.swagger-ui .opblock-external-docs-wrapper p,.swagger-ui .opblock-title_normal,.swagger-ui .opblock-title_normal h4,.swagger-ui .opblock-title_normal p,.swagger-ui .response-col_links,.swagger-ui .response-col_status,.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5{color:var(--swagger-main-color,#3b4151)}.swagger-ui .response-col_links .response-undocumented,.swagger-ui .response-col_status .response-undocumented{color:var(--response-undocumented-color,#909090)}.swagger-ui .response-control-media-type--accept-controller select{border-color:var(--response-control-media-type-color,green)}.swagger-ui .response-control-media-type__accept-message{color:var(--response-control-media-type-color,green)}.swagger-ui .opblock-body pre.microlight{background:var(--opblock-pre-microlight-background-color,#333)!important;color:var(--opblock-pre-microlight-color,#fff)!important}.swagger-ui .download-contents{background:var(--download-contents-background-color,#7d8293);color:var(--download-contents-color,#fff)}.swagger-ui .copy-to-clipboard{background:var(--copy-to-clipboard-background-color,#7d8293)}.swagger-ui .copy-to-clipboard button{background:var(--copy-to-clipboard-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .filter{position:relative;display:flex;align-items:center}.swagger-ui .filter .filter-container{flex:1;min-width:0}.swagger-ui .filter #pin-filterbar-btn{position:absolute;right:1.5rem}.swagger-ui .filter .operation-filter-input{border:2px solid var(--filter-operation-filter-input-border-color,#d8dde7)}.swagger-ui .filter.pinned.stuck .operation-filter-input{box-shadow:0 19px 0 0 var(--body-background-color,#fafafa),0 20px 0 0 var(--opblock-tag-section-border-color,#ddd)}.swagger-ui div:has(> .filter-container > .filter.pinned){position:sticky;top:var(--pinned-topbar-offset,0);z-index:5;background:var(--body-background-color,#fafafa);--pinned-filter-offset:var(--filterbar-pinned-height)}.swagger-ui:has(.filter.pinned){--pinned-filter-offset:var(--filterbar-pinned-height)}.swagger-ui .filter #pin-filterbar-btn svg{fill:var(--filterbar-pin-icon-color,#707070)}.swagger-ui .download-url-wrapper .failed,.swagger-ui .filter .failed{color:var(--filter-download-failed-color,red)}.swagger-ui .download-url-wrapper .loading,.swagger-ui .filter .loading{color:var(--filter-download-loading-color,#aaa)}.swagger-ui section h3{color:var(--swagger-main-color,#3b4151)}.swagger-ui .servers>label,.swagger-ui table.headers td{color:var(--swagger-main-color,#3b4151)}.swagger-ui table.headers .header-example{color:var(--table-headers-example-color,#999)}.swagger-ui table thead tr td,.swagger-ui table thead tr th{border-bottom:1px solid var(--table-thead-border-bottom-color,rgba(59,65,81,.2));color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name{color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name.required span{color:var(--parameter-name-required-symbol-color,red)}.swagger-ui .parameter__name.required::after{color:var(--parameter-name-required-color,rgba(255,0,0,.6))}.swagger-ui .parameter__extension,.swagger-ui .parameter__in,.swagger-ui .response__extension{color:var(--parameter-in-extension-color,gray)}.swagger-ui .parameter__deprecated{color:var(--parameter-deprecated-color,red)}.swagger-ui .btn{background:var(--button-background-color,transparent);border:2px solid var(--button-border-color,gray);box-shadow:0 1px 2px var(--button-shadow-color,rgba(0,0,0,.1));color:var(--swagger-main-color,#3b4151)}.swagger-ui .btn:hover{box-shadow:0 0 5px var(--button-shadow-hover-color,rgba(0,0,0,.3))}.swagger-ui .btn.cancel{background-color:var(--button-cancel-background-color,transparent);border-color:var(--button-cancel-border-color,#ff6060);color:var(--button-cancel-color,#ff6060)}.swagger-ui .btn.execute{background-color:var(--button-execute-background-color,#4990e2);border-color:var(--button-execute-border-color,#4990e2);color:var(--button-execute-color,#fff)}.swagger-ui button.invalid{background:var(--button-invalid-background-color,#feebeb);border-color:var(--button-invalid-border-color,#f93e3e)}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{fill:var(--expand-operation-svg-arrow-color,#707070)}.swagger-ui .expand-methods:hover svg,.swagger-ui .expand-operation:hover svg{fill:var(--expand-operation-svg-arrow-hover-color,#404040)}.swagger-ui select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);border:2px solid var(--select-border-color,#41444e);box-shadow:0 1px 2px 0 var(--select-shadow-color,rgba(0,0,0,.25));color:var(--swagger-main-color,#3b4151);cursor:pointer}.swagger-ui select[multiple]{background:var(--select-background-color,#f7f7f7)}.swagger-ui select:disabled{opacity:.5;cursor:not-allowed}.swagger-ui label{color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text],.swagger-ui textarea{background:var(--input-background-color,#fff);border:1px solid var(--input-border-color,#d9d9d9);color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email].invalid,.swagger-ui input[type=file].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid,.swagger-ui select.invalid,.swagger-ui textarea.invalid{background:var(--input-invalid-background-color,#feebeb);border-color:var(--input-invalid-border-color,#f93e3e);color:var(--input-invalid-color,#3b4151)}.swagger-ui input[disabled],.swagger-ui select[disabled]{background-color:var(--input-disabled-background-color,#fafafa);color:var(--input-disabled-color,#888)}.swagger-ui select[disabled]{border-color:var(--select-disabled-border-color,#888)}.swagger-ui textarea[disabled]{background-color:var(--textarea-disabled-background-color,#41444e);color:var(--textarea-disabled-color,#fff)}.swagger-ui textarea{background:var(--textarea-background-color,hsla(0,0%,100%,.8));color:var(--swagger-main-color,#3b4151)}.swagger-ui textarea:focus{border:2px solid var(--textarea-border-focus-color,#61affe)}.swagger-ui textarea.curl{background:var(--textarea-curl-background-color,#41444e);color:var(--textarea-curl-color,#fff)}.swagger-ui .checkbox{color:var(--checkbox-color,#303030)}.swagger-ui .checkbox p{color:var(--swagger-main-color,#3b4151)}.swagger-ui .checkbox input[type=checkbox]+label>.item{background:var(--checkbox-label-item-background-color,#e8e8e8);box-shadow:0 0 0 2px var(--checkbox-label-item-shadow-color,#e8e8e8)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:var(--checkbox-label-item-icon, #e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .dialog-ux .backdrop-ux{background:var(--dialog-backdrop-color,rgba(0,0,0,.8))}.swagger-ui .dialog-ux .modal-ux{background:var(--dialog-background-color,#fff);border:1px solid var(--dialog-border-color,#ebebeb);box-shadow:0 10px 30px 0 var(--dialog-shadow-color,rgba(0,0,0,.2))}.swagger-ui .dialog-ux .modal-ux-content h4,.swagger-ui .dialog-ux .modal-ux-content p,.swagger-ui .dialog-ux .modal-ux-header h3{color:var(--swagger-main-color,#3b4151)}.swagger-ui .dialog-ux .modal-ux-header{border-bottom:1px solid var(--dialog-border-color,#ebebeb)}.swagger-ui .dialog-ux .modal-ux-header button svg{fill:var(--dialog-close-button-icon-color,black)}.swagger-ui .model{color:var(--swagger-main-color,#3b4151)}.swagger-ui .model .deprecated span,.swagger-ui .model .deprecated td{color:var(--model-deprecated-color,#a0a0a0)!important}.swagger-ui .model-toggle{cursor:pointer;-webkit-transition:-webkit-transform .15s ease-in;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}.swagger-ui .model-toggle.collapsed{-webkit-transform:rotate(0);transform:rotate(0)}.swagger-ui .model-toggle::after{background:var(--model-arrow-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .model-hint{background:var(--model-hint-background-color,rgba(0,0,0,.7));color:var(--model-hint-color,#ebebeb)}.swagger-ui .model .property{color:var(--model-property-color,#999)}.swagger-ui .model .property.primitive{color:var(--model-property-primitive-color,#6b6b6b)}.swagger-ui table.model tr.property-row .star{color:var(--model-property-required-symbol-color,red)}.swagger-ui .model .external-docs,.swagger-ui table.model tr.description{color:var(--model-property-description,#666)}.swagger-ui table.model tr.extension{color:var(--model-property-extension,#777)}.swagger-ui section.models{border:1px solid var(--model-section-border-color,rgba(59,65,81,.3));border-radius:.5rem;padding:0;margin:0}.swagger-ui section.models.is-open h4{border-bottom:1px solid var(--model-section-border-color,rgba(59,65,81,.3))}.swagger-ui section.models h4{color:var(--model-section-header-color,#606060);padding:0}.swagger-ui section.models h4:hover{background:var(--model-section-header-hover-background-color,rgba(0,0,0,.02))}.swagger-ui section.models h4 svg{fill:var(--expand-model-svg-arrow-color,#707070)}.swagger-ui section.models h4 button.models-control{color:var(--model-section-header-color,#606060);padding:20px}.swagger-ui section.models h5{color:var(--model-section-little-header-color,#707070)}.swagger-ui section.models .model-container{background:var(--model-container-background-color,rgba(0,0,0,.05))}.swagger-ui section.models .model-container:hover{background:var(--model-container-hover-background-color,rgba(0,0,0,.07))}.swagger-ui .model-box{background:var(--model-box-background-color,rgba(0,0,0,.1))}.swagger-ui .model-title{color:var(--model-title-color,#505050)}.swagger-ui .model-deprecated-warning{color:var(--model-deprecated-warning-color,#f93e3e)}.swagger-ui .prop-type{color:var(--model-prop-type-color,#55a)}.swagger-ui .prop-format{color:var(--model-prop-format-color,#606060)}.swagger-ui .scopes h2{color:var(--swagger-main-color,#3b4151)}.swagger-ui .scopes h2 a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .markdown pre,.swagger-ui .renderedMarkdown pre{color:var(--rendered-markdown-pre-color,#000)}.swagger-ui .markdown code,.swagger-ui .renderedMarkdown code{background:var(--rendered-markdown-code-background-color,rgba(0,0,0,.05));color:var(--rendered-markdown-code-color,#9012fe)}.scroll-to-top-wrapper{position:fixed;bottom:0;left:0;right:0;text-align:end;box-sizing:border-box;margin:0 auto;max-width:1460px;padding:0 20px;width:100%;pointer-events:none;z-index:1000}#scroll-to-top-btn{background-color:var(--scroll-to-top-button-background-color,#000000d1);border:1px solid var(--scroll-to-top-button-border-color,#d1c9c9);border-radius:50%;color:#fff;cursor:pointer;width:2.5rem;aspect-ratio:1;backdrop-filter:blur(20px);margin:10px;position:relative;transition:background-color .5s,opacity .6s .2s,translate .8s,scale .2s;opacity:0;translate:0 100px;box-shadow:0 0 10px 0 var(--scroll-to-top-button-shadow-color,rgba(0,0,0,.4))}#scroll-to-top-btn.showBtn{opacity:1;translate:0 0;pointer-events:auto}#scroll-to-top-btn::before{position:absolute;content:'';top:10px;left:12px;right:12px;background-color:var(--scroll-to-top-button-icon-color,#fff);border-radius:1rem;height:2px}#scroll-to-top-btn::after{position:absolute;content:'';top:15px;left:50%;border:solid var(--scroll-to-top-button-icon-color,#fff);border-width:0 2px 2px 0;padding:3px;transform:rotate(-135deg);translate:-50% 4px}#scroll-to-top-btn:focus-within,#scroll-to-top-btn:hover{background-color:var(--scroll-to-top-button-hover-background-color,#0d0d0dba)}#scroll-to-top-btn:focus-within::after,#scroll-to-top-btn:hover::after{animation:bounceUp 1.2s ease-in infinite}#scroll-to-top-btn:active{scale:0.95}@keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-moz-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-webkit-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@media only screen and (max-width:680px){.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{word-break:break-all}} \ No newline at end of file +/*Common Theme https://github.com/teociaps/SwaggerUI.Themes */@charset "UTF-8";:root{--topbar-pinned-height:4rem;--filterbar-pinned-height:5rem}::-webkit-scrollbar{width:16px}::-webkit-scrollbar-thumb{height:56px;border-radius:8px;border:4px solid transparent;background-clip:content-box;background-color:var(--scrollbar-thumb-color,#a5a5a5);transition:background-color .3s}::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover-color,#727272)}body::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-color,#a5a5a5)}*{font-family:system-ui,sans-serif}:focus{font-family:system-ui,sans-serif;outline:0!important}:focus-visible{outline:auto!important;outline-offset:1px}body{background-color:var(--body-background-color,#fafafa);padding-bottom:1rem}*,::after,::before{box-sizing:border-box}.swagger-ui{color:var(--swagger-main-color,#3b4151);-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.15;margin:0}.swagger-ui .loading-container .loading:after{color:var(--swagger-main-color,#3b4151)}.swagger-ui .loading-container .loading:before{border:2px solid var(--loading-container-border-color,rgba(85,85,85,.1));border-top-color:var(--loading-container-border-top-color,rgba(0,0,0,.6))}.swagger-ui .errors-wrapper{background:var(--errors-wrapper-background-color,rgba(249,62,62,.1));border:2px solid var(--errors-wrapper-border-color,#f93e3e)}.swagger-ui .errors-wrapper .errors h4,.swagger-ui .errors-wrapper hgroup h4{color:var(--swagger-main-color,#3b4151)}.swagger-ui .errors-wrapper .errors small{color:var(--errors-wrapper-errors-color,#606060)}.swagger-ui .topbar{background-color:transparent;z-index:1000;padding:0}.swagger-ui .topbar.pinned{position:sticky;top:10px;--pinned-topbar-offset:var(--topbar-pinned-height)}.swagger-ui:has(> .topbar.pinned){--pinned-topbar-offset:var(--topbar-pinned-height)}.swagger-ui .topbar.pinned::before{content:'';position:absolute;inset:-10px 0 0;pointer-events:none;user-select:none;backdrop-filter:blur(20px);-webkit-mask-image:linear-gradient(to bottom,black 40%,transparent);mask-image:linear-gradient(to bottom,black 40%,transparent);background:linear-gradient(90deg,rgba(0,0,0,0) 5%,rgba(8,8,8,.4) 50%,rgba(0,0,0,0) 95%)}.swagger-ui .topbar .topbar-wrapper{background-color:var(--topbar-background-color,#000);border:1px solid var(--topbar-border-color,gray);border-top:0;border-radius:0 0 .5rem .5rem;padding:.5rem;box-shadow:0 5px 15px 0 var(--topbar-pinned-shadow-color,rgba(0,0,0,.5))}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn svg{fill:var(--topbar-pin-icon-color,white)}.swagger-ui .filter #pin-filterbar-btn,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn{cursor:pointer;background:0 0;border:none;margin:.2rem;padding:.2rem;transition:all .2s;rotate:z 359deg}.swagger-ui .filter #pin-filterbar-btn:focus-within,.swagger-ui .filter #pin-filterbar-btn:hover,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:hover{scale:1.05;animation:pintobbar 1.2s infinite}.swagger-ui .filter #pin-filterbar-btn:active,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:active{scale:0.95}.swagger-ui .topbar.pinned .topbar-wrapper{background-color:var(--topbar-pinned-background-color,rgba(0,0,0,.7));border:1px solid var(--topbar-border-color,gray);border-radius:.5rem;backdrop-filter:blur(20px)}.swagger-ui .filter.pinned #pin-filterbar-btn,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn{rotate:z 0deg}.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn{translate:0 5px}.swagger-ui .filter.pinned #pin-filterbar-btn:focus-within,.swagger-ui .filter.pinned #pin-filterbar-btn:hover,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:hover{animation:unpintobbar 1.2s infinite}@keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-moz-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-webkit-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@keyframes unpintobbar{10%{translate:0 1px}70%{rotate:z 50deg;translate:-3px 1px}}@-moz-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}@-webkit-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}.swagger-ui .topbar .download-url-wrapper .select-label select,.swagger-ui .topbar .download-url-wrapper input[type=text]{border:2px solid var(--topbar-select-border-color,#62a03f)}.swagger-ui .topbar .download-url-wrapper .select-label{color:var(--topbar-select-label-color,#f0f0f0)}.swagger-ui .topbar .download-url-wrapper .download-url-button{background:var(--topbar-download-url-button-background-color,#62a03f);color:var(--topbar-download-url-button-color,#fff)}.swagger-ui .topbar .dark-mode-toggle{display:none}#theme-switcher-select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);border:2px solid var(--topbar-select-border-color,#62a03f);min-width:120px;box-shadow:none}.swagger-ui .info .base-url,.swagger-ui .info .title,.swagger-ui .info h1,.swagger-ui .info h2,.swagger-ui .info h3,.swagger-ui .info h4,.swagger-ui .info h5,.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info table{color:var(--swagger-main-color,#3b4151)}.swagger-ui .info a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .info a:hover{color:var(--swagger-info-link-hover,#1f69c0)}.swagger-ui .info .title small{background:var(--api-version-background-color,#7d8492)}.swagger-ui .info .title small.version-stamp{background-color:var(--api-version-stamp-background-color,#89bf04)}.swagger-ui .info .title small pre{color:var(--api-version-color,#fff)}.swagger-ui .scheme-container{background:0 0;box-shadow:none;margin:0;padding:0}.swagger-ui .auth-wrapper{display:flex;flex:1;justify-content:flex-end;border:1px solid var(--auth-wrapper-border-color,#d5d5d5);padding:0;border-radius:5px;box-shadow:0 3px 5px -5px var(--auth-wrapper-box-shadow-color,#000);background:var(--auth-wrapper-background-color,#fff)}.swagger-ui .scheme-container .schemes>label{color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container{border-bottom:1px solid var(--auth-container-border-bottom-color,#ebebeb);color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container .errors{background-color:var(--auth-container-background-color,#fee);color:var(--auth-container-errors-color,red)}.swagger-ui .btn.authorize{background-color:var(--btn-authorize-background-color,transparent);border-color:var(--btn-authorize-border-color,#49cc90);color:var(--btn-authorize-font-color,#49cc90)}.swagger-ui .btn.authorize svg{fill:var(--btn-authorize-svg-fill-color,#49cc90)}.swagger-ui .opblock-tag-section{display:flex;flex-direction:column;border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;margin-block:1rem;overflow:clip}.swagger-ui .opblock-tag-section .expand-collapse-all-btn{display:none;user-select:none}.swagger-ui .opblock-tag-section.is-open .expand-collapse-all-btn{background:var(--opblock-tag-section-expandcollapse-background-color,rgb(220 220 220 / 45%));color:var(--swagger-main-color,#3b4151);border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;padding:5px 10px;font-size:medium;margin-inline:10px;display:block}.swagger-ui .opblock-tag,.swagger-ui .opblock-tag small,.swagger-ui .parameter__type{color:var(--swagger-main-color,#3b4151)}.swagger-ui .opblock-tag{align-items:center;cursor:pointer;display:flex;padding:10px 20px 10px 10px;color:var(--swagger-main-color,#3b4151);font-size:24px;border-bottom:0 dashed var(--opblock-tag-border-bottom-color,#e1e1e1);margin:0;transition:all .2s}.swagger-ui .opblock-tag:focus-within,.swagger-ui .opblock-tag:hover{background:var(--opblock-tag-background-color-hover,rgba(0,0,0,.02))}.swagger-ui .opblock-tag+div .operation-tag-content{margin-inline:5px}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag{position:sticky;top:0;z-index:4;margin-bottom:1rem;border-bottom:1px dashed var(--opblock-tag-border-bottom-color,#e1e1e1);background:var(--body-background-color,#fafafa)}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:focus-within,#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:hover{background:var(--opblock-tag-pinned-background-color-hover,#f1f1f1)}.swagger-ui .opblock-tag-section.is-open .opblock-tag{top:calc(var(--pinned-topbar-offset,0px) + var(--pinned-filter-offset,0px))}.swagger-ui .opblock{border:1px solid var(--opblock-border-color,#000);box-shadow:0 0 3px var(--opblock-shadow-color,rgba(0,0,0,.19))}.swagger-ui .opblock .tab-header .tab-item.active h4 span:after{background:var(--opblock-tabheader-underline-color,gray)}.swagger-ui .opblock .opblock-summary button svg{fill:var(--opblock-summary-svg-icons-color,#000)}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid var(--opblock-summary-border-bottom-color,#000)}.swagger-ui .opblock .opblock-section-header{background:var(--opblock-section-header-background-color,hsla(0,0%,100%,.8));box-shadow:0 1px 2px var(--opblock-section-header-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-section-header h4,.swagger-ui .opblock .opblock-section-header>label{color:var(--swagger-main-color,#3b4151)}.swagger-ui .opblock .opblock-summary-method{background:var(--opblock-summary-method-background-color,#000);color:var(--opblock-summary-method-color,#fff);text-shadow:0 1px 0 var(--opblock-summary-method-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{color:var(--swagger-main-color,#3b4151);word-break:normal}.swagger-ui .opblock.opblock-post{background:var(--opblock-post-background-color,rgba(73,204,144,.1));border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock.opblock-post .opblock-summary-method{background:var(--opblock-post-method-color,#49cc90)}.swagger-ui .opblock.opblock-post .opblock-summary{border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span:after{background:var(--opblock-post-method-color,#49cc90);border-radius:1rem}.swagger-ui .opblock.opblock-put{background:var(--opblock-put-background-color,rgba(252,161,48,.1));border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-put .opblock-summary-method{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-put .opblock-summary{border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span:after{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-delete{background:var(--opblock-delete-background-color,rgba(249,62,62,.1));border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .opblock-summary-method{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span:after{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-get{background:var(--opblock-get-background-color,rgba(97,175,254,.1));border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get .opblock-summary-method{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-get .opblock-summary{border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span:after{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-patch{background:var(--opblock-patch-background-color,rgba(80,227,194,.1));border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .opblock-summary-method{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span:after{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-head{background:var(--opblock-head-background-color,rgba(144,18,254,.1));border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head .opblock-summary-method{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-head .opblock-summary{border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span:after{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-options{background:var(--opblock-options-background-color,rgba(13,90,167,.1));border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .opblock-summary-method{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .opblock-summary{border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span:after{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-deprecated{background:var(--opblock-deprecated-background-color,hsla(0,0%,92%,.1));border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span:after{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .tab li{color:var(--swagger-main-color,#3b4151)}.swagger-ui .tab li:first-of-type:after{background:var(--swagger-tabs-divider-color,rgba(0,0,0,.2))}.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-external-docs-wrapper,.swagger-ui .opblock-external-docs-wrapper h4,.swagger-ui .opblock-external-docs-wrapper p,.swagger-ui .opblock-title_normal,.swagger-ui .opblock-title_normal h4,.swagger-ui .opblock-title_normal p,.swagger-ui .response-col_links,.swagger-ui .response-col_status,.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5{color:var(--swagger-main-color,#3b4151)}.swagger-ui .response-col_links .response-undocumented,.swagger-ui .response-col_status .response-undocumented{color:var(--response-undocumented-color,#909090)}.swagger-ui .response-control-media-type--accept-controller select{border-color:var(--response-control-media-type-color,green)}.swagger-ui .response-control-media-type__accept-message{color:var(--response-control-media-type-color,green)}.swagger-ui .opblock-body pre.microlight{background:var(--opblock-pre-microlight-background-color,#333)!important;color:var(--opblock-pre-microlight-color,#fff)!important}.swagger-ui .download-contents{background:var(--download-contents-background-color,#7d8293);color:var(--download-contents-color,#fff)}.swagger-ui .copy-to-clipboard{background:var(--copy-to-clipboard-background-color,#7d8293)}.swagger-ui .copy-to-clipboard button{background:var(--copy-to-clipboard-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .filter{position:relative;display:flex;align-items:center}.swagger-ui .filter .filter-container{flex:1;min-width:0}.swagger-ui .filter #pin-filterbar-btn{position:absolute;right:1.5rem}.swagger-ui .filter .operation-filter-input{border:2px solid var(--filter-operation-filter-input-border-color,#d8dde7)}.swagger-ui .filter.pinned.stuck .operation-filter-input{box-shadow:0 19px 0 0 var(--body-background-color,#fafafa),0 20px 0 0 var(--opblock-tag-section-border-color,#ddd)}.swagger-ui div:has(> .filter-container > .filter.pinned){position:sticky;top:var(--pinned-topbar-offset,0);z-index:5;background:var(--body-background-color,#fafafa);--pinned-filter-offset:var(--filterbar-pinned-height)}.swagger-ui:has(.filter.pinned){--pinned-filter-offset:var(--filterbar-pinned-height)}.swagger-ui .filter #pin-filterbar-btn svg{fill:var(--filterbar-pin-icon-color,#707070)}.swagger-ui .download-url-wrapper .failed,.swagger-ui .filter .failed{color:var(--filter-download-failed-color,red)}.swagger-ui .download-url-wrapper .loading,.swagger-ui .filter .loading{color:var(--filter-download-loading-color,#aaa)}.swagger-ui section h3{color:var(--swagger-main-color,#3b4151)}.swagger-ui .servers>label,.swagger-ui table.headers td{color:var(--swagger-main-color,#3b4151)}.swagger-ui table.headers .header-example{color:var(--table-headers-example-color,#999)}.swagger-ui table thead tr td,.swagger-ui table thead tr th{border-bottom:1px solid var(--table-thead-border-bottom-color,rgba(59,65,81,.2));color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name{color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name.required span{color:var(--parameter-name-required-symbol-color,red)}.swagger-ui .parameter__name.required::after{color:var(--parameter-name-required-color,rgba(255,0,0,.6))}.swagger-ui .parameter__extension,.swagger-ui .parameter__in,.swagger-ui .response__extension{color:var(--parameter-in-extension-color,gray)}.swagger-ui .parameter__deprecated{color:var(--parameter-deprecated-color,red)}.swagger-ui .btn{background:var(--button-background-color,transparent);border:2px solid var(--button-border-color,gray);box-shadow:0 1px 2px var(--button-shadow-color,rgba(0,0,0,.1));color:var(--swagger-main-color,#3b4151)}.swagger-ui .btn:hover{box-shadow:0 0 5px var(--button-shadow-hover-color,rgba(0,0,0,.3))}.swagger-ui .btn.cancel{background-color:var(--button-cancel-background-color,transparent);border-color:var(--button-cancel-border-color,#ff6060);color:var(--button-cancel-color,#ff6060)}.swagger-ui .btn.execute{background-color:var(--button-execute-background-color,#4990e2);border-color:var(--button-execute-border-color,#4990e2);color:var(--button-execute-color,#fff)}.swagger-ui button.invalid{background:var(--button-invalid-background-color,#feebeb);border-color:var(--button-invalid-border-color,#f93e3e)}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{fill:var(--expand-operation-svg-arrow-color,#707070)}.swagger-ui .expand-methods:hover svg,.swagger-ui .expand-operation:hover svg{fill:var(--expand-operation-svg-arrow-hover-color,#404040)}.swagger-ui select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);border:2px solid var(--select-border-color,#41444e);box-shadow:0 1px 2px 0 var(--select-shadow-color,rgba(0,0,0,.25));color:var(--swagger-main-color,#3b4151);cursor:pointer}.swagger-ui select[multiple]{background:var(--select-background-color,#f7f7f7)}.swagger-ui select:disabled{opacity:.5;cursor:not-allowed}.swagger-ui label{color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text],.swagger-ui textarea{background:var(--input-background-color,#fff);border:1px solid var(--input-border-color,#d9d9d9);color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email].invalid,.swagger-ui input[type=file].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid,.swagger-ui select.invalid,.swagger-ui textarea.invalid{background:var(--input-invalid-background-color,#feebeb);border-color:var(--input-invalid-border-color,#f93e3e);color:var(--input-invalid-color,#3b4151)}.swagger-ui input[disabled],.swagger-ui select[disabled]{background-color:var(--input-disabled-background-color,#fafafa);color:var(--input-disabled-color,#888)}.swagger-ui select[disabled]{border-color:var(--select-disabled-border-color,#888)}.swagger-ui textarea[disabled]{background-color:var(--textarea-disabled-background-color,#41444e);color:var(--textarea-disabled-color,#fff)}.swagger-ui textarea{background:var(--textarea-background-color,hsla(0,0%,100%,.8));color:var(--swagger-main-color,#3b4151)}.swagger-ui textarea:focus{border:2px solid var(--textarea-border-focus-color,#61affe)}.swagger-ui textarea.curl{background:var(--textarea-curl-background-color,#41444e);color:var(--textarea-curl-color,#fff)}.swagger-ui .checkbox{color:var(--checkbox-color,#303030)}.swagger-ui .checkbox p{color:var(--swagger-main-color,#3b4151)}.swagger-ui .checkbox input[type=checkbox]+label>.item{background:var(--checkbox-label-item-background-color,#e8e8e8);box-shadow:0 0 0 2px var(--checkbox-label-item-shadow-color,#e8e8e8)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:var(--checkbox-label-item-icon, #e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .dialog-ux .backdrop-ux{background:var(--dialog-backdrop-color,rgba(0,0,0,.8))}.swagger-ui .dialog-ux .modal-ux{background:var(--dialog-background-color,#fff);border:1px solid var(--dialog-border-color,#ebebeb);box-shadow:0 10px 30px 0 var(--dialog-shadow-color,rgba(0,0,0,.2))}.swagger-ui .dialog-ux .modal-ux-content h4,.swagger-ui .dialog-ux .modal-ux-content p,.swagger-ui .dialog-ux .modal-ux-header h3{color:var(--swagger-main-color,#3b4151)}.swagger-ui .dialog-ux .modal-ux-header{border-bottom:1px solid var(--dialog-border-color,#ebebeb)}.swagger-ui .dialog-ux .modal-ux-header button svg{fill:var(--dialog-close-button-icon-color,black)}.swagger-ui .model{color:var(--swagger-main-color,#3b4151)}.swagger-ui .model .deprecated span,.swagger-ui .model .deprecated td{color:var(--model-deprecated-color,#a0a0a0)!important}.swagger-ui .model-toggle{cursor:pointer;-webkit-transition:-webkit-transform .15s ease-in;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}.swagger-ui .model-toggle.collapsed{-webkit-transform:rotate(0);transform:rotate(0)}.swagger-ui .model-toggle::after{background:var(--model-arrow-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .model-hint{background:var(--model-hint-background-color,rgba(0,0,0,.7));color:var(--model-hint-color,#ebebeb)}.swagger-ui .model .property{color:var(--model-property-color,#999)}.swagger-ui .model .property.primitive{color:var(--model-property-primitive-color,#6b6b6b)}.swagger-ui table.model tr.property-row .star{color:var(--model-property-required-symbol-color,red)}.swagger-ui .model .external-docs,.swagger-ui table.model tr.description{color:var(--model-property-description,#666)}.swagger-ui table.model tr.extension{color:var(--model-property-extension,#777)}.swagger-ui section.models{border:1px solid var(--model-section-border-color,rgba(59,65,81,.3));border-radius:.5rem;padding:0;margin:0}.swagger-ui section.models.is-open h4{border-bottom:1px solid var(--model-section-border-color,rgba(59,65,81,.3))}.swagger-ui section.models h4{color:var(--model-section-header-color,#606060);padding:0}.swagger-ui section.models h4:hover{background:var(--model-section-header-hover-background-color,rgba(0,0,0,.02))}.swagger-ui section.models h4 svg{fill:var(--expand-model-svg-arrow-color,#707070)}.swagger-ui section.models h4 button.models-control{color:var(--model-section-header-color,#606060);padding:20px}.swagger-ui section.models h5{color:var(--model-section-little-header-color,#707070)}.swagger-ui section.models .model-container{background:var(--model-container-background-color,rgba(0,0,0,.05))}.swagger-ui section.models .model-container:hover{background:var(--model-container-hover-background-color,rgba(0,0,0,.07))}.swagger-ui .model-box{background:var(--model-box-background-color,rgba(0,0,0,.1))}.swagger-ui .model-title{color:var(--model-title-color,#505050)}.swagger-ui .model-deprecated-warning{color:var(--model-deprecated-warning-color,#f93e3e)}.swagger-ui .prop-type{color:var(--model-prop-type-color,#55a)}.swagger-ui .prop-format{color:var(--model-prop-format-color,#606060)}.swagger-ui .scopes h2{color:var(--swagger-main-color,#3b4151)}.swagger-ui .scopes h2 a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .markdown pre,.swagger-ui .renderedMarkdown pre{color:var(--rendered-markdown-pre-color,#000)}.swagger-ui .markdown code,.swagger-ui .renderedMarkdown code{background:var(--rendered-markdown-code-background-color,rgba(0,0,0,.05));color:var(--rendered-markdown-code-color,#9012fe)}.scroll-to-top-wrapper{position:fixed;bottom:0;left:0;right:0;text-align:end;box-sizing:border-box;margin:0 auto;max-width:1460px;padding:0 20px;width:100%;pointer-events:none;z-index:1000}#scroll-to-top-btn{background-color:var(--scroll-to-top-button-background-color,#000000d1);border:1px solid var(--scroll-to-top-button-border-color,#d1c9c9);border-radius:50%;color:#fff;cursor:pointer;width:2.5rem;aspect-ratio:1;backdrop-filter:blur(20px);margin:10px;position:relative;transition:background-color .5s,opacity .6s .2s,translate .8s,scale .2s;opacity:0;translate:0 100px;box-shadow:0 0 10px 0 var(--scroll-to-top-button-shadow-color,rgba(0,0,0,.4))}#scroll-to-top-btn.showBtn{opacity:1;translate:0 0;pointer-events:auto}#scroll-to-top-btn::before{position:absolute;content:'';top:10px;left:12px;right:12px;background-color:var(--scroll-to-top-button-icon-color,#fff);border-radius:1rem;height:2px}#scroll-to-top-btn::after{position:absolute;content:'';top:15px;left:50%;border:solid var(--scroll-to-top-button-icon-color,#fff);border-width:0 2px 2px 0;padding:3px;transform:rotate(-135deg);translate:-50% 4px}#scroll-to-top-btn:focus-within,#scroll-to-top-btn:hover{background-color:var(--scroll-to-top-button-hover-background-color,#0d0d0dba)}#scroll-to-top-btn:focus-within::after,#scroll-to-top-btn:hover::after{animation:bounceUp 1.2s ease-in infinite}#scroll-to-top-btn:active{scale:0.95}@keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-moz-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-webkit-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@media only screen and (max-width:680px){.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{word-break:break-all}} \ No newline at end of file From 8d1752ab5c32d1360f7242233f5f87ce337d62fc Mon Sep 17 00:00:00 2001 From: Teuz Date: Mon, 17 Aug 2026 16:39:34 +0200 Subject: [PATCH 33/35] fix(themes): fix filter-bar pin race condition and dark-mode class leak Two bugs surfaced by Swashbuckle.AspNetCore 10.x's heavier initial render: - setUpPinnableFilterBar looked up .filter exactly once, at the same moment #swagger-ui's existence cleared the startup poll loop. Unlike #swagger-ui, .filter only renders after the OpenAPI document has been fetched and parsed, so it very often didn't exist yet at that point - the lookup would silently and permanently no-op, leaving the pin button missing "most of the time". Now polls for .filter with the same bounded-retry pattern setUpExpandAndCollapseOperationsButtons already uses. - Hiding Swashbuckle 10.x's native dark-mode toggle via CSS (previous commit) doesn't stop its React component from mounting: componentDidMount() still unconditionally adds a 'dark-mode' class to based on prefers-color-scheme, regardless of the button's visibility. A MutationObserver now strips the class back off whenever it's added, since there's no reliable synchronous point to catch it once (the toggle component can mount before or after this script runs). Verified in-browser: pin-filterbar-btn present across repeated reloads; manually re-adding the dark-mode class gets stripped back off within ~150ms by the observer. --- .../Program.cs | 21 ++++----- .../AspNetCore/Swagger/Themes/Scripts/ui.js | 45 +++++++++++++++++-- .../Swagger/Themes/Scripts/ui.min.js | 2 +- 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs index 14456d5..25489c7 100644 --- a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs +++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs @@ -129,11 +129,11 @@ // ======================================== // 13. Enable all advanced UI features - app.UseSwaggerUI(Theme.Dark, c => - { - c.DocumentTitle = "Sample API - All Features"; - c.EnableAllAdvancedOptions(); - }); + //app.UseSwaggerUI(Theme.Dark, c => + //{ + // c.DocumentTitle = "Sample API - All Features"; + // c.EnableAllAdvancedOptions(); + //}); // 14. Individual advanced features //app.UseSwaggerUI(Theme.Light, c => @@ -193,11 +193,12 @@ //}); // 19. Pinnable filter bar - //app.UseSwaggerUI(Theme.Dark, c => - //{ - // c.DocumentTitle = "Sample API - Pinnable Filter Bar"; - // c.EnableFilter(pinnable: true); - //}); + app.UseSwaggerUI(Theme.Dark, c => + { + c.DocumentTitle = "Sample API - Pinnable Filter Bar"; + c.EnableAllAdvancedOptions(); + c.EnableFilter(pinnable: true); + }); } app.UseHttpsRedirection(); diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js index 900b0a9..fbfbde1 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js @@ -13,6 +13,8 @@ window.onpageshow = function () { console.log('Hello Swagger UI!'); + suppressNativeDarkModeClass(); + setUpPinnableTopbar({$PINNABLE_TOPBAR}); setUpPinnableFilterBar({$PINNABLE_FILTER_BAR}); @@ -44,14 +46,51 @@ function setUpPinnableTopbar(enabled) { }); } +// Swashbuckle.AspNetCore 10.x's swagger-ui-dist ships its own dark-mode toggle, hidden via CSS +// (see common.css) because it layers swagger-ui's own dark styling on top of this library's +// theme. Hiding the button doesn't stop the toggle's React component from mounting though: it +// still auto-applies a 'dark-mode' class to based on prefers-color-scheme regardless of +// the button's visibility. A MutationObserver strips it back off whenever it appears, since +// there's no reliable synchronous point to catch it once and be done - the toggle component can +// mount before or after this script runs. +function suppressNativeDarkModeClass() { + // Only remove (and thus only mutate the attribute) when the class is actually present, so + // this can never trigger the observer with a mutation of its own. + const strip = () => { + if (rootElement.classList.contains('dark-mode')) + rootElement.classList.remove('dark-mode'); + }; + strip(); + new MutationObserver(strip).observe(rootElement, { attributes: true, attributeFilter: ['class'] }); +} + function setUpPinnableFilterBar(enabled) { if (enabled === false) return; - const filterBar = document.querySelector('.filter'); - if (!filterBar) - return; + // Unlike #swagger-ui itself, .filter only renders once the OpenAPI document has been + // fetched and parsed - it commonly doesn't exist yet at the moment #swagger-ui does, so a + // single lookup here would silently and permanently no-op. Poll for it with the same + // bounded-retry pattern used by setUpExpandAndCollapseOperationsButtons below. + const MAX_ATTEMPTS = 60; + const RETRY_DELAY_MS = 200; + let attempts = 0; + + (function waitForFilterBar() { + attempts++; + const filterBar = document.querySelector('.filter'); + + if (filterBar) { + initPinnableFilterBar(filterBar); + return; + } + + if (attempts < MAX_ATTEMPTS) + setTimeout(waitForFilterBar, RETRY_DELAY_MS); + })(); +} +function initPinnableFilterBar(filterBar) { // CSS alone can't tell whether a sticky element has actually reached its sticky top, so // toggle a 'stuck' class here (styled in common.css) by comparing the sticky wrapper's // viewport position against its computed sticky offset. The wrapper is 2 parentElement diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js index ef407d3..aa56ee4 100644 --- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js +++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js @@ -1 +1 @@ -ο»Ώ/*Swagger UI https://github.com/teociaps/SwaggerUI.Themes */const rootElement=document.documentElement;function setUpPinnableTopbar(e){!1!==e&&setUpPinnableToggle({buttonId:"pin-topbar-btn",container:document.querySelector(".topbar-wrapper"),pinnedAncestorDepth:3,storageKey:"swaggerui-pinnable-topbar-preference",pinTitle:"Pin topbar",unpinTitle:"Unpin topbar",defaultPinned:!0})}function setUpPinnableFilterBar(e){if(!1===e)return;const t=document.querySelector(".filter");if(!t)return;const n=t.parentElement?.parentElement;function o(){if(!n)return;const e=t.classList.contains("pinned")&&n.getBoundingClientRect().top<=parseFloat(getComputedStyle(n).top)+1;t.classList.toggle("stuck",e)}window.addEventListener("scroll",o,{passive:!0}),window.addEventListener("resize",o),setUpPinnableToggle({buttonId:"pin-filterbar-btn",container:t,pinnedAncestorDepth:1,storageKey:"swaggerui-pinnable-filterbar-preference",pinTitle:"Pin filter bar",unpinTitle:"Unpin filter bar",defaultPinned:!1,onPinnedStateApplied:o})}function setUpPinnableToggle({buttonId:e,container:t,pinnedAncestorDepth:n,storageKey:o,pinTitle:l,unpinTitle:a,defaultPinned:r,onPinnedStateApplied:p}){if(!t)return;const i=document.createElement("button");i.setAttribute("id",e),i.addEventListener("click",()=>{d(!c(i).classList.contains("pinned"),!0),i?.blur()}),t.appendChild(i);const s=localStorage.getItem(o);function c(e){let t=e;for(let e=0;e{scrollToTop(),n?.blur()}),t.appendChild(n);document.getElementById("swagger-ui").appendChild(t);const o=()=>{window.scrollY>=200?n.classList.add("showBtn"):n.classList.remove("showBtn")};window.addEventListener("scroll",o),window.addEventListener("resize",o)}function scrollToTop(){rootElement.scrollTo({top:0,behavior:"smooth"})}function setUpExpandAndCollapseOperationsButtons(e){if(!1===e)return;let t=0;const n=document.getElementById("swagger-ui")||document.querySelector(".swagger-ui");function o(){const e=document.querySelectorAll(".opblock-tag-section");return!(!e||0===e.length)&&(e.forEach(e=>function(e){if(!e||"true"===e.dataset.expandCollapseAttached)return;const t=e.querySelector("h3"),n=t?.querySelector("button.expand-operation");if(t&&!t.querySelector(".expand-collapse-all-btn")){const o=document.createElement("button");o.setAttribute("title","Expand/Collapse all the operations"),o.classList.add("expand-collapse-all-btn"),o.innerHTML="Expand/Collapse All",n?n.before(o):t.appendChild(o),o.addEventListener("click",t=>{t.preventDefault(),t.stopPropagation();const n=e.querySelectorAll(".opblock .opblock-control-arrow");n&&0!==n.length&&(Array.from(n).every(e=>"true"===e.getAttribute("aria-expanded"))?n.forEach(e=>{"true"===e.getAttribute("aria-expanded")&&e.click()}):n.forEach(e=>{"false"===e.getAttribute("aria-expanded")&&e.click()}))})}e.dataset.expandCollapseAttached="true"}(e)),!0)}!function e(){if(t++,o())try{const e=n||document.body;new MutationObserver(e=>{for(const t of e)t.addedNodes&&t.addedNodes.length>0&&o()}).observe(e,{childList:!0,subtree:!0})}catch(e){}else t<60&&setTimeout(e,200)}()}function setUpThemeSwitcher(e){if(!1===e)return;const t="swaggerui-theme-preference";let n=null,o=null;function l(e,l){const a=n?.themes.find(t=>t.name===e);if(!a)return void console.warn(`[ThemeSwitcher] Theme not found: ${e}`);const r=document.querySelectorAll('link[rel="stylesheet"]'),i=n.themes.map(e=>e.cssPath);let s=!1;r.forEach(t=>{if(t.dataset.theme||i.some(e=>t.href?.endsWith(e))){t.dataset.theme===e||t.href?.endsWith(a.cssPath)?(t.disabled=!1,t.dataset.theme=e,s=!0):t.disabled=!0}}),s||console.warn(`[ThemeSwitcher] Could not activate theme: ${e}`),o=e,l&&localStorage.setItem(t,e);const c=document.getElementById("theme-switcher-select");c&&c.value!==e&&(c.value=e)}fetch("/themes/metadata.json").then(e=>e.ok?e.json():(console.warn("[ThemeSwitcher] Failed to load theme metadata"),null)).then(e=>{!e?.themes||e.themes.length<2?console.warn("[ThemeSwitcher] Not enough themes available for switcher"):(n=e,o=detectCurrentTheme(e.themes),function(e){const n=localStorage.getItem(t);n&&e.some(e=>e.name===n)&&n!==o&&l(n,!1)}(e.themes),function(e){const t=document.querySelector(".topbar-wrapper");if(!t)return void console.warn("[ThemeSwitcher] Topbar not found");const n=document.createElement("select");n.id="theme-switcher-select",n.setAttribute("aria-label","Select theme"),n.title="Switch theme",e.themes.forEach(t=>{const l=document.createElement("option");l.value=t.name,l.textContent=formatThemeName(t.name,e.config?.displayFormat||"{name}"),t.name===o&&(l.selected=!0),n.appendChild(l)}),n.addEventListener("change",e=>{l(e.target.value,!0)});const a=document.getElementById("pin-topbar-btn");a?a.before(n):t.appendChild(n)}(e))}).catch(e=>{console.error("[ThemeSwitcher] Error loading themes:",e)})}function detectCurrentTheme(e){const t=document.querySelector('link[rel="stylesheet"]:not([disabled])[data-theme]');if(t?.dataset.theme)return t.dataset.theme;const n=document.querySelectorAll('link[rel="stylesheet"]:not([disabled])');for(const t of n){const n=e.find(e=>!!t.href&&(t.href.includes(e.cssPath)||t.href.endsWith(e.cssPath)||t.href.includes(e.name.toLowerCase())));if(n)return n.name}return e.length>0?e[0].name:null}function formatThemeName(e,t){const n=e.replaceAll(/([A-Z])/g," $1").trim().split(/[\s_-]+/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(" ");return t.replace("{name}",n)}window.onpageshow=function(){let e=setInterval(function(){null!=document.getElementById("swagger-ui")&&(clearInterval(e),console.log("Hello Swagger UI!"),setUpPinnableTopbar({$PINNABLE_TOPBAR}),setUpPinnableFilterBar({$PINNABLE_FILTER_BAR}),setUpScrollToTopButton({$BACK_TO_TOP}),setUpExpandAndCollapseOperationsButtons({$EXPAND_COLLAPSE_ALL_OPERATIONS}),setUpThemeSwitcher({$THEME_SWITCHER}))},100)}; \ No newline at end of file +ο»Ώ/*Swagger UI https://github.com/teociaps/SwaggerUI.Themes */const rootElement=document.documentElement;function setUpPinnableTopbar(e){!1!==e&&setUpPinnableToggle({buttonId:"pin-topbar-btn",container:document.querySelector(".topbar-wrapper"),pinnedAncestorDepth:3,storageKey:"swaggerui-pinnable-topbar-preference",pinTitle:"Pin topbar",unpinTitle:"Unpin topbar",defaultPinned:!0})}function suppressNativeDarkModeClass(){const e=()=>{rootElement.classList.contains("dark-mode")&&rootElement.classList.remove("dark-mode")};e(),new MutationObserver(e).observe(rootElement,{attributes:!0,attributeFilter:["class"]})}function setUpPinnableFilterBar(e){if(!1===e)return;let n=0;!function r(){n++;const t=document.querySelector(".filter");if(t)return void initPinnableFilterBar(t);n<60&&setTimeout(r,200)}()}function initPinnableFilterBar(t){const n=t.parentElement?.parentElement;function o(){if(!n)return;const e=t.classList.contains("pinned")&&n.getBoundingClientRect().top<=parseFloat(getComputedStyle(n).top)+1;t.classList.toggle("stuck",e)}window.addEventListener("scroll",o,{passive:!0}),window.addEventListener("resize",o),setUpPinnableToggle({buttonId:"pin-filterbar-btn",container:t,pinnedAncestorDepth:1,storageKey:"swaggerui-pinnable-filterbar-preference",pinTitle:"Pin filter bar",unpinTitle:"Unpin filter bar",defaultPinned:!1,onPinnedStateApplied:o})}function setUpPinnableToggle({buttonId:e,container:t,pinnedAncestorDepth:n,storageKey:o,pinTitle:l,unpinTitle:a,defaultPinned:r,onPinnedStateApplied:p}){if(!t)return;const i=document.createElement("button");i.setAttribute("id",e),i.addEventListener("click",()=>{d(!c(i).classList.contains("pinned"),!0),i?.blur()}),t.appendChild(i);const s=localStorage.getItem(o);function c(e){let t=e;for(let e=0;e{scrollToTop(),n?.blur()}),t.appendChild(n);document.getElementById("swagger-ui").appendChild(t);const o=()=>{window.scrollY>=200?n.classList.add("showBtn"):n.classList.remove("showBtn")};window.addEventListener("scroll",o),window.addEventListener("resize",o)}function scrollToTop(){rootElement.scrollTo({top:0,behavior:"smooth"})}function setUpExpandAndCollapseOperationsButtons(e){if(!1===e)return;let t=0;const n=document.getElementById("swagger-ui")||document.querySelector(".swagger-ui");function o(){const e=document.querySelectorAll(".opblock-tag-section");return!(!e||0===e.length)&&(e.forEach(e=>function(e){if(!e||"true"===e.dataset.expandCollapseAttached)return;const t=e.querySelector("h3"),n=t?.querySelector("button.expand-operation");if(t&&!t.querySelector(".expand-collapse-all-btn")){const o=document.createElement("button");o.setAttribute("title","Expand/Collapse all the operations"),o.classList.add("expand-collapse-all-btn"),o.innerHTML="Expand/Collapse All",n?n.before(o):t.appendChild(o),o.addEventListener("click",t=>{t.preventDefault(),t.stopPropagation();const n=e.querySelectorAll(".opblock .opblock-control-arrow");n&&0!==n.length&&(Array.from(n).every(e=>"true"===e.getAttribute("aria-expanded"))?n.forEach(e=>{"true"===e.getAttribute("aria-expanded")&&e.click()}):n.forEach(e=>{"false"===e.getAttribute("aria-expanded")&&e.click()}))})}e.dataset.expandCollapseAttached="true"}(e)),!0)}!function e(){if(t++,o())try{const e=n||document.body;new MutationObserver(e=>{for(const t of e)t.addedNodes&&t.addedNodes.length>0&&o()}).observe(e,{childList:!0,subtree:!0})}catch(e){}else t<60&&setTimeout(e,200)}()}function setUpThemeSwitcher(e){if(!1===e)return;const t="swaggerui-theme-preference";let n=null,o=null;function l(e,l){const a=n?.themes.find(t=>t.name===e);if(!a)return void console.warn(`[ThemeSwitcher] Theme not found: ${e}`);const r=document.querySelectorAll('link[rel="stylesheet"]'),i=n.themes.map(e=>e.cssPath);let s=!1;r.forEach(t=>{if(t.dataset.theme||i.some(e=>t.href?.endsWith(e))){t.dataset.theme===e||t.href?.endsWith(a.cssPath)?(t.disabled=!1,t.dataset.theme=e,s=!0):t.disabled=!0}}),s||console.warn(`[ThemeSwitcher] Could not activate theme: ${e}`),o=e,l&&localStorage.setItem(t,e);const c=document.getElementById("theme-switcher-select");c&&c.value!==e&&(c.value=e)}fetch("/themes/metadata.json").then(e=>e.ok?e.json():(console.warn("[ThemeSwitcher] Failed to load theme metadata"),null)).then(e=>{!e?.themes||e.themes.length<2?console.warn("[ThemeSwitcher] Not enough themes available for switcher"):(n=e,o=detectCurrentTheme(e.themes),function(e){const n=localStorage.getItem(t);n&&e.some(e=>e.name===n)&&n!==o&&l(n,!1)}(e.themes),function(e){const t=document.querySelector(".topbar-wrapper");if(!t)return void console.warn("[ThemeSwitcher] Topbar not found");const n=document.createElement("select");n.id="theme-switcher-select",n.setAttribute("aria-label","Select theme"),n.title="Switch theme",e.themes.forEach(t=>{const l=document.createElement("option");l.value=t.name,l.textContent=formatThemeName(t.name,e.config?.displayFormat||"{name}"),t.name===o&&(l.selected=!0),n.appendChild(l)}),n.addEventListener("change",e=>{l(e.target.value,!0)});const a=document.getElementById("pin-topbar-btn");a?a.before(n):t.appendChild(n)}(e))}).catch(e=>{console.error("[ThemeSwitcher] Error loading themes:",e)})}function detectCurrentTheme(e){const t=document.querySelector('link[rel="stylesheet"]:not([disabled])[data-theme]');if(t?.dataset.theme)return t.dataset.theme;const n=document.querySelectorAll('link[rel="stylesheet"]:not([disabled])');for(const t of n){const n=e.find(e=>!!t.href&&(t.href.includes(e.cssPath)||t.href.endsWith(e.cssPath)||t.href.includes(e.name.toLowerCase())));if(n)return n.name}return e.length>0?e[0].name:null}function formatThemeName(e,t){const n=e.replaceAll(/([A-Z])/g," $1").trim().split(/[\s_-]+/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(" ");return t.replace("{name}",n)}window.onpageshow=function(){let e=setInterval(function(){null!=document.getElementById("swagger-ui")&&(clearInterval(e),console.log("Hello Swagger UI!"),suppressNativeDarkModeClass(),setUpPinnableTopbar({$PINNABLE_TOPBAR}),setUpPinnableFilterBar({$PINNABLE_FILTER_BAR}),setUpScrollToTopButton({$BACK_TO_TOP}),setUpExpandAndCollapseOperationsButtons({$EXPAND_COLLAPSE_ALL_OPERATIONS}),setUpThemeSwitcher({$THEME_SWITCHER}))},100)}; \ No newline at end of file From 774cf9634c239b3093c0948d12e0acd2618e7dad Mon Sep 17 00:00:00 2001 From: Teuz Date: Tue, 18 Aug 2026 16:11:22 +0200 Subject: [PATCH 34/35] docs: link the wiki's new Troubleshooting page from README and readme.txt Getting-Started's Troubleshooting section moved to its own wiki page (wiki commit a9ff0de) since it had grown long enough to bury the actual setup instructions above it. Surfaces the new page from the root README's top nav row and the DOCUMENTATION section of all four packages' readme.txt. --- README.md | 2 +- .../Program.cs | 21 ++++++++++--------- .../readme.txt | 5 +++-- src/AspNetCore.SwaggerUI.Themes/readme.txt | 5 +++-- src/NSwag.AspNetCore.Extensions/readme.txt | 5 +++-- src/NSwag.AspNetCore.Themes/readme.txt | 5 +++-- 6 files changed, 24 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 534954f..1a4d957 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@
_**...and more!**_ -**[Get Started](https://github.com/teociaps/SwaggerUI.Themes/wiki/Getting-Started)** β€’ **[View Built-in Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Predefined-Themes)** β€’ **[Full Documentation](https://github.com/teociaps/SwaggerUI.Themes/wiki)** +**[Get Started](https://github.com/teociaps/SwaggerUI.Themes/wiki/Getting-Started)** β€’ **[View Built-in Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Predefined-Themes)** β€’ **[Troubleshooting](https://github.com/teociaps/SwaggerUI.Themes/wiki/Troubleshooting)** β€’ **[Full Documentation](https://github.com/teociaps/SwaggerUI.Themes/wiki)** SwaggerUI.Themes Demo diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs index 6ec243b..5560742 100644 --- a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs +++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs @@ -129,11 +129,11 @@ // ======================================== // 13. Enable all advanced UI features - app.UseSwaggerUi(Theme.Dark, c => - { - c.DocumentTitle = "Sample API - All Features"; - c.EnableAllAdvancedOptions(); - }); + //app.UseSwaggerUi(Theme.Dark, c => + //{ + // c.DocumentTitle = "Sample API - All Features"; + // c.EnableAllAdvancedOptions(); + //}); // 14. Individual advanced features //app.UseSwaggerUi(Theme.Light, c => @@ -192,11 +192,12 @@ //}); // 19. Pinnable filter bar - //app.UseSwaggerUi(Theme.Dark, c => - //{ - // c.DocumentTitle = "Sample API - Pinnable Filter Bar"; - // c.EnableFilter(pinnable: true); - //}); + app.UseSwaggerUi(Theme.Dark, c => + { + c.DocumentTitle = "Sample API - Pinnable Filter Bar"; + c.EnableAllAdvancedOptions(); + c.EnableFilter(pinnable: true); + }); } app.UseHttpsRedirection(); diff --git a/src/AspNetCore.SwaggerUI.Extensions/readme.txt b/src/AspNetCore.SwaggerUI.Extensions/readme.txt index b0378d2..c7c9bf2 100644 --- a/src/AspNetCore.SwaggerUI.Extensions/readme.txt +++ b/src/AspNetCore.SwaggerUI.Extensions/readme.txt @@ -17,7 +17,8 @@ API Counter: DOCUMENTATION ------------------------------------------------------------------------------------- -Full docs: https://github.com/teociaps/SwaggerUI.Themes/wiki -Repository: https://github.com/teociaps/SwaggerUI.Themes +Full docs: https://github.com/teociaps/SwaggerUI.Themes/wiki +Troubleshooting: https://github.com/teociaps/SwaggerUI.Themes/wiki/Troubleshooting +Repository: https://github.com/teociaps/SwaggerUI.Themes ===================================================================================== \ No newline at end of file diff --git a/src/AspNetCore.SwaggerUI.Themes/readme.txt b/src/AspNetCore.SwaggerUI.Themes/readme.txt index 0d29907..e6da52a 100644 --- a/src/AspNetCore.SwaggerUI.Themes/readme.txt +++ b/src/AspNetCore.SwaggerUI.Themes/readme.txt @@ -24,7 +24,8 @@ Pinnable filter bar: DOCUMENTATION ------------------------------------------------------------------------------------- -Full docs: https://github.com/teociaps/SwaggerUI.Themes/wiki -Repository: https://github.com/teociaps/SwaggerUI.Themes +Full docs: https://github.com/teociaps/SwaggerUI.Themes/wiki +Troubleshooting: https://github.com/teociaps/SwaggerUI.Themes/wiki/Troubleshooting +Repository: https://github.com/teociaps/SwaggerUI.Themes ===================================================================================== \ No newline at end of file diff --git a/src/NSwag.AspNetCore.Extensions/readme.txt b/src/NSwag.AspNetCore.Extensions/readme.txt index b73f740..20470f7 100644 --- a/src/NSwag.AspNetCore.Extensions/readme.txt +++ b/src/NSwag.AspNetCore.Extensions/readme.txt @@ -17,7 +17,8 @@ API Counter: DOCUMENTATION ------------------------------------------------------------------------------------- -Full docs: https://github.com/teociaps/SwaggerUI.Themes/wiki -Repository: https://github.com/teociaps/SwaggerUI.Themes +Full docs: https://github.com/teociaps/SwaggerUI.Themes/wiki +Troubleshooting: https://github.com/teociaps/SwaggerUI.Themes/wiki/Troubleshooting +Repository: https://github.com/teociaps/SwaggerUI.Themes ===================================================================================== \ No newline at end of file diff --git a/src/NSwag.AspNetCore.Themes/readme.txt b/src/NSwag.AspNetCore.Themes/readme.txt index a62f21e..8f83215 100644 --- a/src/NSwag.AspNetCore.Themes/readme.txt +++ b/src/NSwag.AspNetCore.Themes/readme.txt @@ -29,7 +29,8 @@ Pinnable filter bar: DOCUMENTATION ------------------------------------------------------------------------------------- -Full docs: https://github.com/teociaps/SwaggerUI.Themes/wiki -Repository: https://github.com/teociaps/SwaggerUI.Themes +Full docs: https://github.com/teociaps/SwaggerUI.Themes/wiki +Troubleshooting: https://github.com/teociaps/SwaggerUI.Themes/wiki/Troubleshooting +Repository: https://github.com/teociaps/SwaggerUI.Themes ===================================================================================== \ No newline at end of file From c1c67fa5e4d8c0b09f56f70a51d307bc6839bb34 Mon Sep 17 00:00:00 2001 From: Teuz Date: Tue, 18 Aug 2026 17:33:31 +0200 Subject: [PATCH 35/35] chore(build): split package versioning into per-family props files Each of the 4 package csproj files repeated the same Major/Minor/Patch numbers across separate Release- and Debug-conditioned property groups (Extensions doubly so, once per package, after the fix for Extensions inheriting Themes' Debug version). Versioning.props no longer hardcodes any package's version - it only computes the Debug/Release suffix and the final Version string generically from whatever Major/Minor/PatchVersion is already set. Each family now declares its version exactly once, unconditionally, in its own Versioning.Themes.props / Versioning.Extensions.props, imported by that family's csproj files before NuGet.props. Forgetting that import now leaves Major/Minor/PatchVersion blank (an obviously broken version, fails loud) instead of silently inheriting whichever family's props last set them. Also restores the Extensions packages' NuGet Description (API Counter specifically, not just "additional capabilities") and scopes the v3.x-breaking-changes warning in the README to Theme packages, since Extensions started fresh at v1.0 with nothing to migrate from. Verified: dotnet msbuild -t:GetVersion resolves 3.1.0/3.1.0- for both Theme packages and 1.0.0/1.0.0- for both Extensions packages in Release/Debug; full 354-test suite passes; dotnet pack produces correctly versioned nupkgs for both families. --- README.md | 2 +- build/Versioning.Extensions.props | 12 ++++++++++ build/Versioning.Themes.props | 12 ++++++++++ build/Versioning.props | 24 +++++++++++++++---- .../AspNetCore.SwaggerUI.Extensions.csproj | 22 ++--------------- .../AspNetCore.SwaggerUI.Themes.csproj | 10 +------- .../NSwag.AspNetCore.Extensions.csproj | 22 ++--------------- .../NSwag.AspNetCore.Themes.csproj | 10 +------- 8 files changed, 50 insertions(+), 64 deletions(-) create mode 100644 build/Versioning.Extensions.props create mode 100644 build/Versioning.Themes.props diff --git a/README.md b/README.md index 1a4d957..cee55a3 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Enhance your Swagger UI with additional functional capabilities beyond theming. > [!WARNING] -> **Version 3.x Breaking Changes** +> **Version 3.x Breaking Changes** (Theme packages only) > > Upgrading from v2.x? Please review the **[Migration Guide](https://github.com/teociaps/SwaggerUI.Themes/wiki/Migration-v3)** for important API changes. diff --git a/build/Versioning.Extensions.props b/build/Versioning.Extensions.props new file mode 100644 index 0000000..6c7c96e --- /dev/null +++ b/build/Versioning.Extensions.props @@ -0,0 +1,12 @@ + + + + + 1 + 0 + 0 + + + diff --git a/build/Versioning.Themes.props b/build/Versioning.Themes.props new file mode 100644 index 0000000..cdb5aed --- /dev/null +++ b/build/Versioning.Themes.props @@ -0,0 +1,12 @@ + + + + + 3 + 1 + 0 + + + diff --git a/build/Versioning.props b/build/Versioning.props index 8456379..2c30a01 100644 --- a/build/Versioning.props +++ b/build/Versioning.props @@ -1,6 +1,15 @@ - + + + true $([System.DateTime]::Now.Ticks) @@ -10,10 +19,15 @@ $([System.Math]::Floor($(MinutesSinceMidnight))) $([System.DateTime]::Today.ToString("yyMMdd")) $(Today)-$(Floored) - 3 - 1 - 0 -$(Revision) + + + + + + + + $(MajorVersion).$(MinorVersion).$(PatchVersion)$(VersionSuffix) @@ -24,4 +38,4 @@
- \ No newline at end of file + diff --git a/src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj b/src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj index b5b52c2..393052a 100644 --- a/src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj +++ b/src/AspNetCore.SwaggerUI.Extensions/AspNetCore.SwaggerUI.Extensions.csproj @@ -1,31 +1,13 @@ + - - - 1 - 0 - 0 - - $(MajorVersion).$(MinorVersion).$(PatchVersion)$(VersionSuffix) - - - - - 1 - 0 - 0 - -$(Revision) - $(MajorVersion).$(MinorVersion).$(PatchVersion)$(VersionSuffix) - - AspNetCore.SwaggerUI.Extensions - Enhance your ASP.NET Core Swagger UI with additional capabilities. + Enhance your ASP.NET Core Swagger UI with functional extensions like API Counter. swagger;swagger-ui;swaggerui;swashbuckle;aspnetcore;webapi;openapi;extension;api-counter;operation-count;tag-extensions diff --git a/src/AspNetCore.SwaggerUI.Themes/AspNetCore.SwaggerUI.Themes.csproj b/src/AspNetCore.SwaggerUI.Themes/AspNetCore.SwaggerUI.Themes.csproj index 0ede010..abe46e9 100644 --- a/src/AspNetCore.SwaggerUI.Themes/AspNetCore.SwaggerUI.Themes.csproj +++ b/src/AspNetCore.SwaggerUI.Themes/AspNetCore.SwaggerUI.Themes.csproj @@ -1,17 +1,9 @@ ο»Ώ + - - - 3 - 1 - 0 - - $(MajorVersion).$(MinorVersion).$(PatchVersion)$(VersionSuffix) - - AspNetCore.SwaggerUI.Themes diff --git a/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj b/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj index 1e81c1d..2b36d1c 100644 --- a/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj +++ b/src/NSwag.AspNetCore.Extensions/NSwag.AspNetCore.Extensions.csproj @@ -1,31 +1,13 @@ ο»Ώ + - - - 1 - 0 - 0 - - $(MajorVersion).$(MinorVersion).$(PatchVersion)$(VersionSuffix) - - - - - 1 - 0 - 0 - -$(Revision) - $(MajorVersion).$(MinorVersion).$(PatchVersion)$(VersionSuffix) - - NSwag.AspNetCore.Extensions - Enhance your ASP.NET Core Swagger UI with additional capabilities. + Enhance your ASP.NET Core Swagger UI with functional extensions like API Counter. swagger;swagger-ui;swaggerui;nswag;aspnetcore;webapi;openapi;extension;api-counter;operation-count;tag-extensions diff --git a/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj b/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj index 8d5e107..a966f7d 100644 --- a/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj +++ b/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj @@ -1,17 +1,9 @@ ο»Ώ + - - - 3 - 1 - 0 - - $(MajorVersion).$(MinorVersion).$(PatchVersion)$(VersionSuffix) - - NSwag.AspNetCore.Themes