diff --git a/CLAUDE.md b/CLAUDE.md index ad248ccb79..5ceff405ae 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -150,11 +150,103 @@ Each folder contains `_category_.json`: --- +## Frontmatter Naming Convention +**CRITICAL RULE**: All custom frontmatter fields MUST use `snake_case`. Never use camelCase for custom frontmatter. +- ✅ Correct: `published_at`, `proficiency_level`, `external_url`, `img_alt`, `repository_url`, `license_url` +- ❌ Wrong: `publishedAt`, `proficiencyLevel`, `externalUrl`, `imgAlt`, `repositoryUrl`, `licenseUrl` +- Standard Docusaurus fields (e.g., `title`, `description`, `slug`) remain as-is. +- Tag values in guides and samples MUST use `kebab-case` (e.g., `vector-search`, `azure-storage-queues-etl`). + +--- + ## Guides - Tags defined in `guides/tags.yml` (~40 predefined tags — do not invent new ones without adding there first). -- Guide-specific frontmatter: `tags`, `description`, `icon`, `image`, `publishedAt` (ISO date), `externalUrl`. +- Guide-specific frontmatter: `tags`, `description`, `icon`, `image`, `published_at` (ISO date), `external_url`, `proficiency_level`, `author`. - Indexed and sorted by `src/plugins/recent-guides-plugin.ts`. +### Guides Frontmatter Example +```yaml +--- +title: "Guide Title" +published_at: 2026-04-02 +author: "Author Name" +tags: [ai, vector-search, getting-started] +description: "Short description shown in cards." +image: "/img/guides-example.webp" +proficiency_level: "Beginner" +--- +``` + +For external guides (linking to blog posts): +```yaml +--- +title: "External Article Title" +published_at: 2026-04-02 +tags: [integration] +description: "Short description." +external_url: "https://ravendb.net/articles/example" +image: "https://ravendb.net/path/to/image.jpg" +--- +``` + +--- + +## Samples +- Production-ready code samples demonstrating RavenDB features and architecture patterns. +- Located in `samples/` directory. +- Three-category tag system: Challenges & Solutions, Features, Tech Stack. +- Tags defined in `samples/tags/` YAML files — do not invent new ones without adding there first. +- Indexed by `src/plugins/recent-samples-plugin.ts`. +- Hub page at `/samples` with filtering by tags. + +### Sample Tag Categories +1. **Challenges & Solutions** (`samples/tags/challenges-solutions.yml`) - Business problems the sample solves + - Examples: `semantic-search`, `integration-patterns`, `cloud-tax`, `gen-ai-data-enrichment` + +2. **Features** (`samples/tags/feature.yml`) - RavenDB features demonstrated + - Examples: `vector-search`, `document-refresh`, `include`, `azure-storage-queues-etl` + +3. **Tech Stack** (`samples/tags/tech-stack.yml`) - Technologies used + - Examples: `csharp`, `aspire`, `azure-functions`, `nodejs`, `nextjs` + +### Sample Frontmatter Example +```yaml +--- +title: "Sample Application Name" +description: "Brief description for sample cards." +challenges_solutions_tags: [semantic-search, integration-patterns] +feature_tags: [vector-search, document-refresh, include] +tech_stack_tags: [csharp, aspire, azure-functions] +image: "/img/samples/my-sample/cover.webp" +img_alt: "Screenshot of the application" +category: "Ecommerce" +license: "MIT License" +license_url: "https://opensource.org/licenses/MIT" +repository_url: "https://github.com/ravendb/sample-repo" +demo_url: "https://demo.example.com" +languages: ["C#"] +gallery: + - src: "/img/samples/my-sample/screenshot-1.webp" + alt: "Main interface" + - src: "/img/samples/my-sample/screenshot-2.webp" + alt: "Admin dashboard" +related_resources: + - type: documentation + documentation_type: docs + subtitle: "Vector Search Overview" + article_key: "ai-integration/vector-search/overview" + - type: guide + subtitle: "Related Guide Title" + article_key: "guide-slug" +--- +``` + +**Required fields**: `title`, `description`, `challenges_solutions_tags`, `feature_tags`, `tech_stack_tags` + +**Optional fields**: `image`, `img_alt`, `category`, `license`, `license_url`, `repository_url`, `demo_url`, `languages`, `gallery`, `related_resources` + +**SEO**: `repository_url` and `languages` feed `SoftwareSourceCode` JSON-LD schema for better search visibility. + --- ## Icon System @@ -392,11 +484,31 @@ Each folder contains `_category_.json`: ### `cloud/` — RavenDB Cloud service documentation (~24 files) Account management, instance configuration, security (TLS, MFA, certificates), pricing/billing, scaling, backup/restore, migration, AWS/Azure Marketplace setup, and the cloud portal UI (home, products, billing, backups, support tabs). -### `guides/` — Practical how-to guides (~63 files, flat structure) -Community guides covering: connecting specific frameworks (ASP.NET Core, Next.js, SvelteKit, FastAPI), AI/ML integration, DevOps (Docker, Kubernetes/EKS, Helm, Ansible), observability (Datadog, Grafana/OpenTelemetry), data pipelines (Elasticsearch, Azure Queue, OLAP ETL), testing (unit test drivers for .NET/Java/Python), and troubleshooting specific problems. Tags defined in `guides/tags.yml`. +### `guides/` — Practical how-to guides (~64 files, flat structure) +Community guides covering: connecting specific frameworks (ASP.NET Core, Next.js, SvelteKit, FastAPI), AI/ML integration, DevOps (Docker, Kubernetes/EKS, Helm, Ansible), observability (Datadog, Grafana/OpenTelemetry), data pipelines (Elasticsearch, Azure Queue, OLAP ETL), testing (unit test drivers for .NET/Java/Python), and troubleshooting specific problems. + +**Frontmatter**: `title`, `published_at`, `author`, `tags`, `description`, `icon`, `image`, `proficiency_level`, `external_url` (for external guides). + +**Tags**: Defined in `guides/tags.yml` (~40 predefined tags — do not invent new ones without adding there first). All tag values use kebab-case. + +**Indexed by**: `src/plugins/recent-guides-plugin.ts` + +### `samples/` — Production-ready code samples (~1+ files) +Production-ready code samples, architecture patterns, and starter kits demonstrating RavenDB features and integration scenarios. Hub page at `/samples` with tag-based filtering. + +**Frontmatter**: `title`, `description`, `challenges_solutions_tags`, `feature_tags`, `tech_stack_tags`, `image`, `img_alt`, `category`, `license`, `license_url`, `repository_url`, `languages`, `gallery`. + +**Tags**: Three categories defined in `samples/tags/`: +- `challenges-solutions.yml` - Business problems solved (e.g., `semantic-search`, `integration-patterns`) +- `feature.yml` - RavenDB features demonstrated (e.g., `vector-search`, `document-refresh`) +- `tech-stack.yml` - Technologies used (e.g., `csharp`, `aspire`, `azure-functions`) + +All tag values use kebab-case. + +**Indexed by**: `src/plugins/recent-samples-plugin.ts` ### `templates/` — Authoring reference templates (~9 files) -Style guide and live examples for documentation building blocks: ContentFrame/Panel layouts, icon gallery, themed images, tag reference, see-also cross-links, featured/new guide blocks. +Style guide and live examples for documentation building blocks: ContentFrame/Panel layouts, icon gallery, themed images, tag reference, see-also cross-links, featured/new guide blocks, sample authoring templates. --- diff --git a/cloud/home.mdx b/cloud/home.mdx index 0f3454e3ed..3dc1b64666 100644 --- a/cloud/home.mdx +++ b/cloud/home.mdx @@ -2,7 +2,7 @@ slug: / pagination_next: null pagination_prev: null -wrapperClassName: cloud-home-page +wrapperClassName: cloudHomePage hide_table_of_contents: true --- diff --git a/docs/ai-integration/ai-agents/start.mdx b/docs/ai-integration/ai-agents/start.mdx index e6cb781634..49909921b4 100644 --- a/docs/ai-integration/ai-agents/start.mdx +++ b/docs/ai-integration/ai-agents/start.mdx @@ -3,6 +3,15 @@ title: "AI Agents: Start" sidebar_label: Start description: "Create conversational AI agent proxies that connect RavenDB clients to AI models for question answering, data analysis, and workflow automation." sidebar_position: 0 +see_also: + - title: "Sample: Human Resources Assistant" + link: "/samples/human-resources-assistant" + source: "samples" + path: "Samples > AI Agents" + - title: "Sample: Fit Assistant" + link: "/samples/fit-assistant" + source: "samples" + path: "Samples > AI Agents" --- import Admonition from '@theme/Admonition'; @@ -52,4 +61,3 @@ Watch our webinars to see AI agents in action and learn practical implementation - diff --git a/docs/ai-integration/gen-ai-integration/start.mdx b/docs/ai-integration/gen-ai-integration/start.mdx index cdddbfbadb..ac9edfc880 100644 --- a/docs/ai-integration/gen-ai-integration/start.mdx +++ b/docs/ai-integration/gen-ai-integration/start.mdx @@ -3,6 +3,15 @@ title: "GenAI tasks: Start" sidebar_label: Start description: "Build intelligent workflows with GenAI tasks that continuously monitor collections and process documents as they are added or modified." sidebar_position: 0 +see_also: + - title: "Sample: BrainSlop" + link: "/samples/brain-slop" + source: "samples" + path: "Samples > GenAI Workflows" + - title: "Sample: Verity" + link: "/samples/verity" + source: "samples" + path: "Samples > GenAI Workflows" --- import Admonition from '@theme/Admonition'; diff --git a/docs/ai-integration/vector-search/overview.mdx b/docs/ai-integration/vector-search/overview.mdx index dd241ee890..52a5619172 100644 --- a/docs/ai-integration/vector-search/overview.mdx +++ b/docs/ai-integration/vector-search/overview.mdx @@ -3,6 +3,15 @@ title: "Vector Search - Overview" sidebar_label: "Overview" description: "RavenDB serves as a vector database, combining traditional document storage with semantic vector search for AI-powered applications." sidebar_position: 1 +see_also: + - title: "Sample: The Library of Ravens" + link: "/samples/the-ravens-library" + source: "samples" + path: "Samples > Semantic Search" + - title: "Sample: Human Resources Assistant" + link: "/samples/human-resources-assistant" + source: "samples" + path: "Samples > Semantic Search" --- import Admonition from '@theme/Admonition'; @@ -238,4 +247,4 @@ It explains how to define the index, configure indexing & query-time parameters, - \ No newline at end of file + diff --git a/docs/client-api/session/querying/text-search/full-text-search.mdx b/docs/client-api/session/querying/text-search/full-text-search.mdx index b7a60598c9..8c6fdef1bc 100644 --- a/docs/client-api/session/querying/text-search/full-text-search.mdx +++ b/docs/client-api/session/querying/text-search/full-text-search.mdx @@ -25,6 +25,10 @@ see_also: link: "indexes/querying/searching" source: "docs" path: "Indexes > Querying" + - title: "Hugin" + link: "/samples/hugin" + source: "samples" + path: "Samples > Offline Search" --- import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; @@ -55,4 +59,3 @@ import FullTextSearchNodejs from './content/_full-text-search-nodejs.mdx'; - diff --git a/docs/home.mdx b/docs/home.mdx index 09a567c382..70c13740be 100644 --- a/docs/home.mdx +++ b/docs/home.mdx @@ -3,7 +3,7 @@ slug: / title: "Guides, API Reference & Tutorials" pagination_next: null pagination_prev: null -wrapperClassName: docs-home-page +wrapperClassName: docsHomePage hide_table_of_contents: true description: "Official RavenDB documentation. Guides for installation, client APIs, indexing, querying, AI integration, clustering, security, and the Studio UI across all supported versions." keywords: diff --git a/docs/indexes/search-engine/corax.mdx b/docs/indexes/search-engine/corax.mdx index 84681b0877..7c392a5833 100644 --- a/docs/indexes/search-engine/corax.mdx +++ b/docs/indexes/search-engine/corax.mdx @@ -3,6 +3,11 @@ title: "Search Engine: Corax" sidebar_label: Corax description: "Corax is RavenDB's native search engine, offering faster indexing and querying performance as an alternative to the Lucene engine." sidebar_position: 0 +see_also: + - title: "Hugin" + link: "/samples/hugin" + source: "samples" + path: "Samples > Offline Search" --- import Admonition from '@theme/Admonition'; @@ -600,4 +605,3 @@ process will add unnecessary overhead. - diff --git a/docs/server/ongoing-tasks/etl/olap.mdx b/docs/server/ongoing-tasks/etl/olap.mdx index d0673f2da4..2ac428fce1 100644 --- a/docs/server/ongoing-tasks/etl/olap.mdx +++ b/docs/server/ongoing-tasks/etl/olap.mdx @@ -3,6 +3,11 @@ title: "Ongoing Tasks: OLAP ETL" sidebar_label: OLAP ETL description: "Export RavenDB data to Apache Parquet files on S3, Azure, or local storage using OLAP ETL tasks for analytics and data warehouse workloads." sidebar_position: 3 +see_also: + - title: "Sample: Fit Assistant" + link: "/samples/fit-assistant" + source: "samples" + path: "Samples > Reporting Offload" --- import Admonition from '@theme/Admonition'; @@ -437,4 +442,3 @@ instead of rows being stored together (the same fields from multiple documents, whole documents). This makes queries more efficient. - diff --git a/docs/server/ongoing-tasks/etl/queue-etl/overview.mdx b/docs/server/ongoing-tasks/etl/queue-etl/overview.mdx index 70e557937d..572024f97d 100644 --- a/docs/server/ongoing-tasks/etl/queue-etl/overview.mdx +++ b/docs/server/ongoing-tasks/etl/queue-etl/overview.mdx @@ -3,6 +3,15 @@ title: "Queue ETL Overview" sidebar_label: Overview description: "Send RavenDB document changes to message queues like Kafka, RabbitMQ, Amazon SQS, and Azure Queue using queue ETL tasks." sidebar_position: 0 +see_also: + - title: "Sample: Fit Assistant" + link: "/samples/fit-assistant" + source: "samples" + path: "Samples > Durable Event Delivery" + - title: "Sample: Verity" + link: "/samples/verity" + source: "samples" + path: "Samples > Durable Event Delivery" --- import Admonition from '@theme/Admonition'; @@ -138,4 +147,3 @@ to see various statistics related to data extraction, transformation, and loadin - diff --git a/docs/server/storage/documents-compression.mdx b/docs/server/storage/documents-compression.mdx index 22432dd7b8..1ab89c8f5d 100644 --- a/docs/server/storage/documents-compression.mdx +++ b/docs/server/storage/documents-compression.mdx @@ -29,6 +29,10 @@ see_also: link: "studio/database/settings/database-record" source: "docs" path: "Studio > Database Management > Settings" + - title: "Hugin" + link: "/samples/hugin" + source: "samples" + path: "Samples > Offline Search" --- import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; @@ -49,4 +53,3 @@ import DocumentsCompressionNodejs from './content/_documents-compression-nodejs. - diff --git a/docs/start/test-driver.mdx b/docs/start/test-driver.mdx index cae3f64b2c..2c0520dbf9 100644 --- a/docs/start/test-driver.mdx +++ b/docs/start/test-driver.mdx @@ -17,6 +17,10 @@ see_also: link: "server/troubleshooting/collect-info" source: "docs" path: "Server > Troubleshooting" + - title: "Sample: YABT" + link: "/samples/yabt" + source: "samples" + path: "Samples > Tested Application Architecture" --- import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; @@ -37,4 +41,3 @@ import TestDriverJava from './content/_test-driver-java.mdx'; - diff --git a/docusaurus.config.ts b/docusaurus.config.ts index d5b923e667..f8bf1d8b6c 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -117,6 +117,15 @@ const config: Config = { showLastUpdateTime: true, }, ], + [ + "content-docs", + { + id: "samples", + path: "samples", + routeBasePath: "samples", + sidebarPath: require.resolve("./sidebarsSamples.js"), + }, + ], [ "@docusaurus/plugin-ideal-image", { @@ -129,6 +138,7 @@ const config: Config = { ], require.resolve("./src/plugins/recent-guides-plugin"), require.resolve("./src/plugins/versioned-seo-plugin"), + require.resolve("./src/plugins/recent-samples-plugin"), ], headTags: [ { diff --git a/eslint.config.js b/eslint.config.js index 58dbbfa56d..eb9dd6969f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -74,7 +74,7 @@ module.exports = [ "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/prefer-namespace-keyword": "off", - "no-console": "warn", + "no-console": ["warn", { allow: ["warn", "error"] }], "no-debugger": "error", "no-alert": "warn", "no-var": "error", @@ -101,6 +101,12 @@ module.exports = [ "no-undef": "off", }, }, + { + files: ["**/*.ts", "**/*.tsx"], + rules: { + "no-undef": "off", + }, + }, { ignores: [ "node_modules/", diff --git a/guides/ai-agents-attachments.mdx b/guides/ai-agents-attachments.mdx index 45c45b72d9..5cd09fca7c 100644 --- a/guides/ai-agents-attachments.mdx +++ b/guides/ai-agents-attachments.mdx @@ -3,9 +3,9 @@ title: "Beyond Text: Adding File Attachments to RavenDB AI Agents" tags: [ai, attachments, csharp, demo, use-case] icon: "ai-agents" description: "Learn how to attach binary files such as PDFs and images to RavenDB AI Agent conversations, reducing token usage while keeping files accessible across the conversation lifecycle." -publishedAt: 2026-04-15 +published_at: 2026-04-15 author: "Paweł Lachowski" -proficiencyLevel: "Intermediate" +proficiency_level: "Intermediate" see_also: - title: "AI Agents Overview" link: "ai-integration/ai-agents/overview" @@ -15,6 +15,10 @@ see_also: link: "document-extensions/attachments/overview" source: "docs" path: "Document Extensions > Attachments" + - title: "Sample: Human Resources Assistant" + link: "/samples/human-resources-assistant" + source: "samples" + path: "Samples > AI Agents" --- import Admonition from '@theme/Admonition'; diff --git a/guides/ai-image-search-with-ravendb.mdx b/guides/ai-image-search-with-ravendb.mdx index d3d84cb0ef..d826930e20 100644 --- a/guides/ai-image-search-with-ravendb.mdx +++ b/guides/ai-image-search-with-ravendb.mdx @@ -2,9 +2,9 @@ title: "AI Image Search with RavenDB" tags: [ai, csharp, demo, use-case] description: "Read about AI Image Search with RavenDB on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/ai-image-search-with-ravendb" -publishedAt: 2025-09-09 +external_url: "https://ravendb.net/articles/ai-image-search-with-ravendb" +published_at: 2025-09-09 icon: "ai" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/backups-in-ravendb.mdx b/guides/backups-in-ravendb.mdx index fb56c9a8a1..65728a2bfd 100644 --- a/guides/backups-in-ravendb.mdx +++ b/guides/backups-in-ravendb.mdx @@ -2,7 +2,7 @@ title: "Backups in RavenDB: How to keep your database safe" tags: [administration, security, clusters, background-tasks, csharp] icon: "backup" -publishedAt: 2026-04-14 +published_at: 2026-04-14 description: "Learn how RavenDB backups work, when to use logical backups versus snapshots, and how to configure scheduled and server-wide backup tasks via Studio and C# code." see_also: - title: "Backup Overview" @@ -22,7 +22,7 @@ see_also: source: "docs" path: "Backup > FAQ" author: "Paweł Lachowski" -proficiencyLevel: "Intermediate" +proficiency_level: "Intermediate" --- import Admonition from '@theme/Admonition'; diff --git a/guides/begin-analysis-with-olap-etl.mdx b/guides/begin-analysis-with-olap-etl.mdx index 41c110fdb1..5d74d47624 100644 --- a/guides/begin-analysis-with-olap-etl.mdx +++ b/guides/begin-analysis-with-olap-etl.mdx @@ -2,9 +2,9 @@ title: "Begin Analysis with OLAP ETL" tags: [integration, background-tasks, architecture, use-case] description: "Read about Begin Analysis with OLAP ETL on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/begin-analysis-with-olap-etl" -publishedAt: 2025-12-22 +external_url: "https://ravendb.net/articles/begin-analysis-with-olap-etl" +published_at: 2025-12-22 image: "https://ravendb.net/wp-content/uploads/2025/12/OLAP-article-image.svg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/building-a-beer-vending-machine-program-with-ravendb-embedded-server.mdx b/guides/building-a-beer-vending-machine-program-with-ravendb-embedded-server.mdx index 01d2dfd14a..ebd2fa68f9 100644 --- a/guides/building-a-beer-vending-machine-program-with-ravendb-embedded-server.mdx +++ b/guides/building-a-beer-vending-machine-program-with-ravendb-embedded-server.mdx @@ -2,9 +2,9 @@ title: "Building a Beer Vending Machine with RavenDB Embedded" tags: [demo, getting-started] description: "Read about Building a Beer Vending Machine with RavenDB Embedded on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/building-a-beer-vending-machine-program-with-ravendb-embedded-server" -publishedAt: 2025-12-23 +external_url: "https://ravendb.net/articles/building-a-beer-vending-machine-program-with-ravendb-embedded-server" +published_at: 2025-12-23 image: "https://ravendb.net/wp-content/uploads/2024/11/article-beer-2.jpg" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/connecting-c-application-to-ravendb-cloud.mdx b/guides/connecting-c-application-to-ravendb-cloud.mdx index 7b4e88c0a2..c6c54b1457 100644 --- a/guides/connecting-c-application-to-ravendb-cloud.mdx +++ b/guides/connecting-c-application-to-ravendb-cloud.mdx @@ -2,8 +2,8 @@ title: "Connecting C# application to RavenDB Cloud" tags: [csharp, getting-started] description: "Read about Connecting C# application to RavenDB Cloud on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/connecting-c-application-to-ravendb-cloud" -publishedAt: 2025-02-25 +external_url: "https://ravendb.net/articles/connecting-c-application-to-ravendb-cloud" +published_at: 2025-02-25 image: "https://ravendb.net/wp-content/uploads/2025/01/connecting-c-application-article-cover.jpg" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/connecting-node-js-application-to-ravendb-cloud.mdx b/guides/connecting-node-js-application-to-ravendb-cloud.mdx index 57e044d41d..04190edaf1 100644 --- a/guides/connecting-node-js-application-to-ravendb-cloud.mdx +++ b/guides/connecting-node-js-application-to-ravendb-cloud.mdx @@ -2,8 +2,8 @@ title: "Connecting Node.js application to RavenDB Cloud" tags: [nodejs, getting-started] description: "Read about Connecting Node.js application to RavenDB Cloud on the RavenDB.net news section." -externalUrl: "https://ravendb.net/articles/connecting-node-js-application-to-ravendb-cloud" -publishedAt: 2025-02-25 +external_url: "https://ravendb.net/articles/connecting-node-js-application-to-ravendb-cloud" +published_at: 2025-02-25 image: "https://ravendb.net/wp-content/uploads/2025/01/connecting-nodejs-to-ravendb-article-cover.jpg" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/connecting-node-js-web-application-to-ravendb.mdx b/guides/connecting-node-js-web-application-to-ravendb.mdx index 8cb195a14e..714b23d4ba 100644 --- a/guides/connecting-node-js-web-application-to-ravendb.mdx +++ b/guides/connecting-node-js-web-application-to-ravendb.mdx @@ -2,8 +2,8 @@ title: "Connecting Node.JS web application to RavenDB" tags: [nodejs, getting-started] description: "Read about Connecting Node.JS web application to RavenDB on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/connecting-node-js-web-application-to-ravendb" -publishedAt: 2025-02-25 +external_url: "https://ravendb.net/articles/connecting-node-js-web-application-to-ravendb" +published_at: 2025-02-25 image: "https://ravendb.net/wp-content/uploads/2024/12/NodeJS-article-cover.jpg" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/deploying-ravendb-with-helm-chart.mdx b/guides/deploying-ravendb-with-helm-chart.mdx index 70cd92656a..5271e4bd52 100644 --- a/guides/deploying-ravendb-with-helm-chart.mdx +++ b/guides/deploying-ravendb-with-helm-chart.mdx @@ -2,8 +2,8 @@ title: "Deploying RavenDB with Helm Chart" tags: [deployment, kubernetes, docker, containers] description: "Read about Deploying RavenDB with Helm Chart on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/deploying-ravendb-with-helm-chart" -publishedAt: 2025-11-18 +external_url: "https://ravendb.net/articles/deploying-ravendb-with-helm-chart" +published_at: 2025-11-18 image: "https://ravendb.net/wp-content/uploads/2025/05/helm-chart-article-cover.jpg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/diacritic-sensitive-search-in-ravendb.mdx b/guides/diacritic-sensitive-search-in-ravendb.mdx index a745231c3c..d6ca285117 100644 --- a/guides/diacritic-sensitive-search-in-ravendb.mdx +++ b/guides/diacritic-sensitive-search-in-ravendb.mdx @@ -2,9 +2,9 @@ title: "Diacritic-Sensitive Search in RavenDB" tags: [demo, querying, use-case] description: "Read about Diacritic-Sensitive Search in RavenDB on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/diacritic-sensitive-search-in-ravendb" -publishedAt: 2025-05-09 +external_url: "https://ravendb.net/articles/diacritic-sensitive-search-in-ravendb" +published_at: 2025-05-09 image: "https://ravendb.net/wp-content/uploads/2025/05/diacritic-sensitive-article.jpg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/dynamic-fields.mdx b/guides/dynamic-fields.mdx index 93ab001595..48ab7499e4 100644 --- a/guides/dynamic-fields.mdx +++ b/guides/dynamic-fields.mdx @@ -2,9 +2,9 @@ title: "Dynamic Fields in RavenDB" tags: [deep-dive, indexes] description: "Read about Dynamic Fields in RavenDB on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/dynamic-fields" -publishedAt: 2024-10-28 +external_url: "https://ravendb.net/articles/dynamic-fields" +published_at: 2024-10-28 image: "https://ravendb.net/wp-content/uploads/2024/10/dynamic-fields-cover.jpg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/embeddings-generation-with-ravendb.mdx b/guides/embeddings-generation-with-ravendb.mdx index f69476030e..0e684ac13f 100644 --- a/guides/embeddings-generation-with-ravendb.mdx +++ b/guides/embeddings-generation-with-ravendb.mdx @@ -2,9 +2,9 @@ title: "Embeddings Generation with RavenDB" tags: [ai, use-case] description: "Read about Embeddings Generation with RavenDB on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/embeddings-generation-with-ravendb" -publishedAt: 2025-03-31 +external_url: "https://ravendb.net/articles/embeddings-generation-with-ravendb" +published_at: 2025-03-31 image: "https://ravendb.net/wp-content/uploads/2025/03/ai-search-article-cover.jpg" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/employing-data-archival-guide.mdx b/guides/employing-data-archival-guide.mdx index 14bff44889..f6271049db 100644 --- a/guides/employing-data-archival-guide.mdx +++ b/guides/employing-data-archival-guide.mdx @@ -3,10 +3,10 @@ title: "Employing Data Archival" tags: [data-governance, csharp, use-case] icon: "real-time-statistics" image: "https://ravendb.net/wp-content/uploads/2025/10/employing-data-archival-article.svg" -publishedAt: 2025-11-17 +published_at: 2025-11-17 description: "How to set up and use data archival in RavenDB" author: "Paweł Lachowski" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- import Admonition from '@theme/Admonition'; diff --git a/guides/employing-schema-validation-to-standardize-your-data.mdx b/guides/employing-schema-validation-to-standardize-your-data.mdx index 92e5f1c7c7..2bb9c7a6d0 100644 --- a/guides/employing-schema-validation-to-standardize-your-data.mdx +++ b/guides/employing-schema-validation-to-standardize-your-data.mdx @@ -3,9 +3,9 @@ title: "Employing Schema Validation to standardize your data" tags: [data-governance, csharp, use-case] icon: "document-schema" description: "Learn more about configuring schema validation in RavenDB to incrase trust in your data across teams." -publishedAt: 2026-02-01 +published_at: 2026-02-01 author: "Paweł Lachowski" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- import Admonition from '@theme/Admonition'; diff --git a/guides/enable-ai-search-in-your-web-app-in-5-minutes.mdx b/guides/enable-ai-search-in-your-web-app-in-5-minutes.mdx index 161e546763..06d4535d99 100644 --- a/guides/enable-ai-search-in-your-web-app-in-5-minutes.mdx +++ b/guides/enable-ai-search-in-your-web-app-in-5-minutes.mdx @@ -2,9 +2,9 @@ title: "Enable AI Search in Your Web App in 5 Minutes" tags: [ai, demo, nextjs, use-case] description: "Read about Enable AI Search in Your Web App in 5 Minutes on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/enable-ai-search-in-your-web-app-in-5-minutes" -publishedAt: 2025-04-28 +external_url: "https://ravendb.net/articles/enable-ai-search-in-your-web-app-in-5-minutes" +published_at: 2025-04-28 image: "https://ravendb.net/wp-content/uploads/2025/04/enable-ai-search-cover.jpg" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/feed-elasticsearch-from-your-ravendb.mdx b/guides/feed-elasticsearch-from-your-ravendb.mdx index 042c5c67d9..f7e84c297a 100644 --- a/guides/feed-elasticsearch-from-your-ravendb.mdx +++ b/guides/feed-elasticsearch-from-your-ravendb.mdx @@ -2,9 +2,9 @@ title: "Feed Elasticsearch from Your RavenDB" tags: [background-tasks, integration, architecture, use-case] description: "Read about Feed Elasticsearch from Your RavenDB on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/feed-elasticsearch-from-your-ravendb" -publishedAt: 2025-12-14 +external_url: "https://ravendb.net/articles/feed-elasticsearch-from-your-ravendb" +published_at: 2025-12-14 image: "https://ravendb.net/wp-content/uploads/2025/12/elastic-search-article-image.png" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/full-lifecycle-automation-of-ravendb-with-ansible.mdx b/guides/full-lifecycle-automation-of-ravendb-with-ansible.mdx index d3b4195c14..d1520b116e 100644 --- a/guides/full-lifecycle-automation-of-ravendb-with-ansible.mdx +++ b/guides/full-lifecycle-automation-of-ravendb-with-ansible.mdx @@ -2,9 +2,9 @@ title: "Full Lifecycle Automation of RavenDB with Ansible" tags: [deployment, integration] description: "Read about Full Lifecycle Automation of RavenDB with Ansible on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/full-lifecycle-automation-of-ravendb-with-ansible" -publishedAt: 2025-06-16 +external_url: "https://ravendb.net/articles/full-lifecycle-automation-of-ravendb-with-ansible" +published_at: 2025-06-16 image: "https://ravendb.net/wp-content/uploads/2025/06/Ansible-article-cover.jpg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/genai-how-we-make-you-work-less.mdx b/guides/genai-how-we-make-you-work-less.mdx index 8ebf99fe16..5bde86e6e4 100644 --- a/guides/genai-how-we-make-you-work-less.mdx +++ b/guides/genai-how-we-make-you-work-less.mdx @@ -2,9 +2,9 @@ title: "GenAI: How We Make You Work Less" tags: [demo, ai, deep-dive, use-case] description: "Read about GenAI: How We Make You Work Less on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/genai-how-we-make-you-work-less" -publishedAt: 2025-08-11 +external_url: "https://ravendb.net/articles/genai-how-we-make-you-work-less" +published_at: 2025-08-11 image: "https://ravendb.net/wp-content/uploads/2025/05/GenAI-how-we-make-you-work-less.png" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/getting-started-with-graphql-and-ravendb.mdx b/guides/getting-started-with-graphql-and-ravendb.mdx index 596349f097..5a41318dd6 100644 --- a/guides/getting-started-with-graphql-and-ravendb.mdx +++ b/guides/getting-started-with-graphql-and-ravendb.mdx @@ -2,9 +2,9 @@ title: "Getting Started with GraphQL and RavenDB" tags: [integration, getting-started] description: "Read about Getting Started with GraphQL and RavenDB on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/getting-started-with-graphql-and-ravendb" -publishedAt: 2024-06-27 +external_url: "https://ravendb.net/articles/getting-started-with-graphql-and-ravendb" +published_at: 2024-06-27 image: "https://ravendb.net/wp-content/uploads/2024/06/graphql.jpg" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/grafana-cloud-configuration-with-opentelemetry.mdx b/guides/grafana-cloud-configuration-with-opentelemetry.mdx index f8773df57b..cf34321afe 100644 --- a/guides/grafana-cloud-configuration-with-opentelemetry.mdx +++ b/guides/grafana-cloud-configuration-with-opentelemetry.mdx @@ -2,9 +2,9 @@ title: "Grafana Cloud Configuration with OpenTelemetry" tags: [monitoring, integration] description: "Read about Grafana Cloud Configuration with OpenTelemetry on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/grafana-cloud-configuration-with-opentelemetry" -publishedAt: 2025-04-25 +external_url: "https://ravendb.net/articles/grafana-cloud-configuration-with-opentelemetry" +published_at: 2025-04-25 image: "https://ravendb.net/wp-content/uploads/2025/03/open-telemetry-grafana-integration.jpg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/home.mdx b/guides/home.mdx index f7c0941f64..2dbe87d3cb 100644 --- a/guides/home.mdx +++ b/guides/home.mdx @@ -4,7 +4,7 @@ hide_title: true slug: / pagination_next: null pagination_prev: null -wrapperClassName: guides-home-page +wrapperClassName: guidesHomePage hide_table_of_contents: true --- diff --git a/guides/how-is-my-database-today.mdx b/guides/how-is-my-database-today.mdx index 63afc8a7e1..339d07ffdd 100644 --- a/guides/how-is-my-database-today.mdx +++ b/guides/how-is-my-database-today.mdx @@ -2,10 +2,10 @@ title: "How is my database today?" tags: [monitoring, troubleshooting, clusters] icon: "cluster-dashboard" -publishedAt: 2026-03-20 +published_at: 2026-03-20 description: "Learn tools you can use to check state of your database." author: "Paweł Lachowski" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- import Admonition from '@theme/Admonition'; @@ -243,4 +243,4 @@ In our case, the disk is under the most pressure. Now we have a lead, and we can Now that you know where to look, you might need a way to fix your problems or look even deeper into them. If you need help with investigating high CPU usage, you may be interested in [this](https://ravendb.net/articles/how-to-troubleshoot-ravendbs-high-cpu-usage) article. Or maybe you are having trouble with high memory usage; we have an [article](https://ravendb.net/articles/master-ravendb-troubleshoot-fix-high-memory-usage) about that, too. -Interested in RavenDB? Grab the developer license dedicated for testing under this link [here](https://ravendb.net/dev?_gl=1*x014vj*_gcl_au*NTAyODIzOTk4LjE3NzAwMTY5NTcuMzYwMzg1MDIwLjE3NzMwNDQ3NjcuMTc3MzA0NDc2Nw..), or get a free cloud database [here](https://ravendb.net/cloud?_gl=1*s4ciyf*_gcl_au*NTAyODIzOTk4LjE3NzAwMTY5NTcuMzYwMzg1MDIwLjE3NzMwNDQ3NjcuMTc3MzA0NDc2Nw..). If you have questions about this feature, or want to hang out and talk with the RavenDB team, join our Discord Community Server \- invitation link is [here](https://discord.com/invite/ravendb). \ No newline at end of file +Interested in RavenDB? Grab the developer license dedicated for testing under this link [here](https://ravendb.net/dev?_gl=1*x014vj*_gcl_au*NTAyODIzOTk4LjE3NzAwMTY5NTcuMzYwMzg1MDIwLjE3NzMwNDQ3NjcuMTc3MzA0NDc2Nw..), or get a free cloud database [here](https://ravendb.net/cloud?_gl=1*s4ciyf*_gcl_au*NTAyODIzOTk4LjE3NzAwMTY5NTcuMzYwMzg1MDIwLjE3NzMwNDQ3NjcuMTc3MzA0NDc2Nw..). If you have questions about this feature, or want to hang out and talk with the RavenDB team, join our Discord Community Server \- invitation link is [here](https://discord.com/invite/ravendb). diff --git a/guides/how-to-query-using-ravendb-and-asp-net-core-9.mdx b/guides/how-to-query-using-ravendb-and-asp-net-core-9.mdx index bbb7d05cdf..4a7d702348 100644 --- a/guides/how-to-query-using-ravendb-and-asp-net-core-9.mdx +++ b/guides/how-to-query-using-ravendb-and-asp-net-core-9.mdx @@ -2,8 +2,8 @@ title: "How to query using RavenDB and ASP.NET Core 9" tags: [querying, asp-net, getting-started] description: "Read about How to query using RavenDB and ASP.NET Core 9 on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/how-to-query-using-ravendb-and-asp-net-core-9" -publishedAt: 2025-09-21 +external_url: "https://ravendb.net/articles/how-to-query-using-ravendb-and-asp-net-core-9" +published_at: 2025-09-21 image: "https://ravendb.net/wp-content/uploads/2024/12/asp_net_core8_article_cover.jpg" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/how-to-setup-ravendb-with-asp-net-core-8-application.mdx b/guides/how-to-setup-ravendb-with-asp-net-core-8-application.mdx index 3f85589aae..69c45d86ca 100644 --- a/guides/how-to-setup-ravendb-with-asp-net-core-8-application.mdx +++ b/guides/how-to-setup-ravendb-with-asp-net-core-8-application.mdx @@ -2,8 +2,8 @@ title: "How to setup RavenDB with ASP.NET Core 8 application" tags: [asp-net, getting-started] description: "Read about How to setup RavenDB with ASP.NET Core 8 application on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/how-to-setup-ravendb-with-asp-net-core-8-application" -publishedAt: 2025-02-25 +external_url: "https://ravendb.net/articles/how-to-setup-ravendb-with-asp-net-core-8-application" +published_at: 2025-02-25 image: "https://ravendb.net/wp-content/uploads/2024/12/asp_net_core8_article_cover.jpg" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/how-to-troubleshoot-ravendbs-high-cpu-usage.mdx b/guides/how-to-troubleshoot-ravendbs-high-cpu-usage.mdx index e74ce4ba56..e498de34bd 100644 --- a/guides/how-to-troubleshoot-ravendbs-high-cpu-usage.mdx +++ b/guides/how-to-troubleshoot-ravendbs-high-cpu-usage.mdx @@ -2,9 +2,9 @@ title: "How to Troubleshoot High CPU Usage" tags: [perf-tuning, troubleshooting, deep-dive] description: "Read about How to Troubleshoot High CPU Usage on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/how-to-troubleshoot-ravendbs-high-cpu-usage" -publishedAt: 2025-02-23 +external_url: "https://ravendb.net/articles/how-to-troubleshoot-ravendbs-high-cpu-usage" +published_at: 2025-02-23 image: "https://ravendb.net/wp-content/uploads/2025/02/troubleshoot-cpu-usage-cover.jpg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/incorporating-machine-learning-into-ravendb-indexing.mdx b/guides/incorporating-machine-learning-into-ravendb-indexing.mdx index 525e1b9281..59e84b46e2 100644 --- a/guides/incorporating-machine-learning-into-ravendb-indexing.mdx +++ b/guides/incorporating-machine-learning-into-ravendb-indexing.mdx @@ -2,9 +2,9 @@ title: "Machine Learning Inside RavenDB Indexing" tags: [ai, indexes, demo, use-case] description: "Read about Machine Learning Inside RavenDB Indexing on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/incorporating-machine-learning-into-ravendb-indexing" -publishedAt: 2024-12-02 +external_url: "https://ravendb.net/articles/incorporating-machine-learning-into-ravendb-indexing" +published_at: 2024-12-02 image: "https://ravendb.net/wp-content/uploads/2024/12/machine-learning-article-cover.jpg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/integrate-ravendb-into-sveltekit-app.mdx b/guides/integrate-ravendb-into-sveltekit-app.mdx index 16ca837c80..dbbc6af03a 100644 --- a/guides/integrate-ravendb-into-sveltekit-app.mdx +++ b/guides/integrate-ravendb-into-sveltekit-app.mdx @@ -2,8 +2,8 @@ title: "Integrate RavenDB into SvelteKit app" tags: [svelte, getting-started, nodejs] description: "Read about Integrate RavenDB into SvelteKit app on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/integrate-ravendb-into-sveltekit-app" -publishedAt: 2025-01-27 +external_url: "https://ravendb.net/articles/integrate-ravendb-into-sveltekit-app" +published_at: 2025-01-27 image: "https://ravendb.net/wp-content/uploads/2025/01/svelte-cover-article.jpg" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/integrating-ravendb-into-next-js-app.mdx b/guides/integrating-ravendb-into-next-js-app.mdx index 1e849efcae..2bbe8d2dad 100644 --- a/guides/integrating-ravendb-into-next-js-app.mdx +++ b/guides/integrating-ravendb-into-next-js-app.mdx @@ -2,8 +2,8 @@ title: "Integrating RavenDB into Next.js app" tags: [nextjs, getting-started] description: "Read about Integrating RavenDB into Next.js app on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/integrating-ravendb-into-next-js-app" -publishedAt: 2024-08-26 +external_url: "https://ravendb.net/articles/integrating-ravendb-into-next-js-app" +published_at: 2024-08-26 image: "https://ravendb.net/wp-content/uploads/2024/08/next-js-logo.png" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/integrating-ravendb-with-net-aspire-project-for-enhanced-development.mdx b/guides/integrating-ravendb-with-net-aspire-project-for-enhanced-development.mdx index 75d2e715c1..cc075db17f 100644 --- a/guides/integrating-ravendb-with-net-aspire-project-for-enhanced-development.mdx +++ b/guides/integrating-ravendb-with-net-aspire-project-for-enhanced-development.mdx @@ -2,9 +2,9 @@ title: "Integrating RavenDB with .NET Aspire" tags: [aspire, csharp, getting-started, integration] description: "Read about Integrating RavenDB with .NET Aspire on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/integrating-ravendb-with-net-aspire-project-for-enhanced-development" -publishedAt: 2025-03-25 +external_url: "https://ravendb.net/articles/integrating-ravendb-with-net-aspire-project-for-enhanced-development" +published_at: 2025-03-25 image: "https://ravendb.net/wp-content/uploads/2025/03/net-aspire-integration-cover.jpg" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/leverage-ravendb-observability-with-datadog.mdx b/guides/leverage-ravendb-observability-with-datadog.mdx index 7fb31262e5..c6c5bb524b 100644 --- a/guides/leverage-ravendb-observability-with-datadog.mdx +++ b/guides/leverage-ravendb-observability-with-datadog.mdx @@ -2,9 +2,9 @@ title: "Leverage RavenDB Observability with Datadog" tags: [integration, monitoring] description: "Read about Leverage RavenDB Observability with Datadog on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/leverage-ravendb-observability-with-datadog" -publishedAt: 2025-06-10 +external_url: "https://ravendb.net/articles/leverage-ravendb-observability-with-datadog" +published_at: 2025-06-10 image: "https://ravendb.net/wp-content/uploads/2025/06/datadog-article.jpg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/master-ravendb-indexing-staleness.mdx b/guides/master-ravendb-indexing-staleness.mdx index de5412fc1f..5cf11c8ec3 100644 --- a/guides/master-ravendb-indexing-staleness.mdx +++ b/guides/master-ravendb-indexing-staleness.mdx @@ -2,9 +2,9 @@ title: "Indexing Staleness Explained" tags: [perf-tuning, deep-dive, troubleshooting, indexes] description: "Read about Indexing Staleness Explained on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/master-ravendb-indexing-staleness" -publishedAt: 2025-11-25 +external_url: "https://ravendb.net/articles/master-ravendb-indexing-staleness" +published_at: 2025-11-25 image: "https://ravendb.net/wp-content/uploads/2025/11/acid-base-article-cover.svg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/master-ravendb-projections-performance.mdx b/guides/master-ravendb-projections-performance.mdx index e45d591e08..6a4cc2ab02 100644 --- a/guides/master-ravendb-projections-performance.mdx +++ b/guides/master-ravendb-projections-performance.mdx @@ -2,9 +2,9 @@ title: "Master RavenDB Projections Performance" tags: [indexes, perf-tuning, deep-dive] description: "Read about Master RavenDB Projections Performance on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/master-ravendb-projections-performance" -publishedAt: 2025-07-30 +external_url: "https://ravendb.net/articles/master-ravendb-projections-performance" +published_at: 2025-07-30 image: "https://ravendb.net/wp-content/uploads/2025/07/master-ravendb-article-cover.png" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/master-ravendb-spotting-red-flags-in-index-definitions-guide.mdx b/guides/master-ravendb-spotting-red-flags-in-index-definitions-guide.mdx index 9e5d563686..dd02483afc 100644 --- a/guides/master-ravendb-spotting-red-flags-in-index-definitions-guide.mdx +++ b/guides/master-ravendb-spotting-red-flags-in-index-definitions-guide.mdx @@ -3,10 +3,10 @@ title: "Master RavenDB: Spotting red flags in index definitions" tags: [indexes, perf-tuning, deep-dive] icon: "ravendb-etl" description: "Learn about index definitions and thier optimization." -publishedAt: 2025-09-17 +published_at: 2025-09-17 image: "https://ravendb.net/wp-content/uploads/2025/09/spotting-red-flags-article-image.png" author: "Paweł Lachowski" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- import Admonition from '@theme/Admonition'; diff --git a/guides/master-ravendb-troubleshoot-fix-high-memory-usage.mdx b/guides/master-ravendb-troubleshoot-fix-high-memory-usage.mdx index 3fe949d494..0216afce71 100644 --- a/guides/master-ravendb-troubleshoot-fix-high-memory-usage.mdx +++ b/guides/master-ravendb-troubleshoot-fix-high-memory-usage.mdx @@ -2,9 +2,9 @@ title: "Fix High Memory Usage in RavenDB" tags: [perf-tuning, indexes, deep-dive, troubleshooting] description: "Read about Fix High Memory Usage in RavenDB on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/master-ravendb-troubleshoot-fix-high-memory-usage" -publishedAt: 2025-04-09 +external_url: "https://ravendb.net/articles/master-ravendb-troubleshoot-fix-high-memory-usage" +published_at: 2025-04-09 image: "https://ravendb.net/wp-content/uploads/2025/04/high-memory-usage-article-cover.jpg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/migrate-your-databases-in-ravendb.mdx b/guides/migrate-your-databases-in-ravendb.mdx index fdd8757ca0..5b99da305a 100644 --- a/guides/migrate-your-databases-in-ravendb.mdx +++ b/guides/migrate-your-databases-in-ravendb.mdx @@ -2,9 +2,9 @@ title: "Migrate Your Databases in RavenDB" tags: [migration] description: "Read about Migrate Your Databases in RavenDB on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/migrate-your-databases-in-ravendb" -publishedAt: 2025-05-14 +external_url: "https://ravendb.net/articles/migrate-your-databases-in-ravendb" +published_at: 2025-05-14 image: "https://ravendb.net/wp-content/uploads/2025/04/migrate-to-ravendb-article-cover.jpg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/multi-agents.mdx b/guides/multi-agents.mdx index abbb69dd2a..3f7cbf977f 100644 --- a/guides/multi-agents.mdx +++ b/guides/multi-agents.mdx @@ -3,9 +3,14 @@ title: "One Chat, Many Agents: RavenDB Multi-Agents" tags: [ai, demo, use-case, csharp] icon: "ai-agents" description: "Learn how to connect multiple RavenDB AI Agents into a multi-agent system, where specialized sub-agents handle focused tasks while a coordinator routes work between them." -publishedAt: 2026-04-27 +published_at: 2026-04-27 author: "Paweł Lachowski" -proficiencyLevel: "Expert" +proficiency_level: "Expert" +see_also: + - title: "Sample: Human Resources Assistant" + link: "/samples/human-resources-assistant" + source: "samples" + path: "Samples > AI Agents" --- import Admonition from '@theme/Admonition'; @@ -165,4 +170,4 @@ The Listing Manager then becomes the coordinator, combining the results into a s Now that you know how to connect multiple AI Agents, you might like to see how we use new [AI Agent Attachments](https://docs.ravendb.net/guides/ai-agents-attachments) to provide context for our Agents. -Interested in RavenDB? Grab a [free developer license](https://ravendb.net/dev) for testing, or start with a [free RavenDB Cloud database](https://ravendb.net/cloud). If you have questions about this feature, or want to hang out and talk with the RavenDB team, join the [RavenDB Discord Community Server](https://discord.com/invite/ravendb). \ No newline at end of file +Interested in RavenDB? Grab a [free developer license](https://ravendb.net/dev) for testing, or start with a [free RavenDB Cloud database](https://ravendb.net/cloud). If you have questions about this feature, or want to hang out and talk with the RavenDB team, join the [RavenDB Discord Community Server](https://discord.com/invite/ravendb). diff --git a/guides/new-in-7-0-ravendb-and-amazon-sqs-etl.mdx b/guides/new-in-7-0-ravendb-and-amazon-sqs-etl.mdx index 6211a9361f..97164967bb 100644 --- a/guides/new-in-7-0-ravendb-and-amazon-sqs-etl.mdx +++ b/guides/new-in-7-0-ravendb-and-amazon-sqs-etl.mdx @@ -2,9 +2,9 @@ title: "New in 7.0: RavenDB and Amazon SQS ETL" tags: [integration, background-tasks] description: "Read about New in 7.0: RavenDB and Amazon SQS ETL on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/new-in-7-0-ravendb-and-amazon-sqs-etl" -publishedAt: 2025-02-25 +external_url: "https://ravendb.net/articles/new-in-7-0-ravendb-and-amazon-sqs-etl" +published_at: 2025-02-25 image: "https://ravendb.net/wp-content/uploads/2025/02/amazon-sqs-etl-article-cover.jpg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/new-in-7-0-ravendb-to-snowflake.mdx b/guides/new-in-7-0-ravendb-to-snowflake.mdx index 2b96f67d17..fe6ca9c808 100644 --- a/guides/new-in-7-0-ravendb-to-snowflake.mdx +++ b/guides/new-in-7-0-ravendb-to-snowflake.mdx @@ -2,9 +2,9 @@ title: "New in 7.0: RavenDB to Snowflake" tags: [integration, background-tasks] description: "Read about New in 7.0: RavenDB to Snowflake on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/new-in-7-0-ravendb-to-snowflake" -publishedAt: 2025-03-12 +external_url: "https://ravendb.net/articles/new-in-7-0-ravendb-to-snowflake" +published_at: 2025-03-12 image: "https://ravendb.net/wp-content/uploads/2025/02/snowflake-article-cover.jpg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/new-in-7-0-ravendbs-vector-search.mdx b/guides/new-in-7-0-ravendbs-vector-search.mdx index d12b5125ef..f737d2f4b2 100644 --- a/guides/new-in-7-0-ravendbs-vector-search.mdx +++ b/guides/new-in-7-0-ravendbs-vector-search.mdx @@ -2,9 +2,9 @@ title: "New in 7.0: RavenDB Vector Search" tags: [ai, querying] description: "Read about New in 7.0: RavenDB Vector Search on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/new-in-7-0-ravendbs-vector-search" -publishedAt: 2025-10-27 +external_url: "https://ravendb.net/articles/new-in-7-0-ravendbs-vector-search" +published_at: 2025-10-27 image: "https://ravendb.net/wp-content/uploads/2025/02/vector-search-article-cover.jpg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/practical-look-at-ai-agents-with-ravendb.mdx b/guides/practical-look-at-ai-agents-with-ravendb.mdx index 7a7948168d..3f7b46c037 100644 --- a/guides/practical-look-at-ai-agents-with-ravendb.mdx +++ b/guides/practical-look-at-ai-agents-with-ravendb.mdx @@ -2,9 +2,9 @@ title: "A Practical Look at AI Agents with RavenDB" tags: [ai, csharp, deep-dive, demo, getting-started] description: "Read about A Practical Look at AI Agents with RavenDB on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/practical-look-at-ai-agents-with-ravendb" -publishedAt: 2025-12-24 +external_url: "https://ravendb.net/articles/practical-look-at-ai-agents-with-ravendb" +published_at: 2025-12-24 image: "https://ravendb.net/wp-content/uploads/2025/09/practical-look-ai-agents-article-image.jpg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/processing-invoices-using-data-subscriptions-in-ravendb.mdx b/guides/processing-invoices-using-data-subscriptions-in-ravendb.mdx index 3f2c1b4bc8..047a18b69d 100644 --- a/guides/processing-invoices-using-data-subscriptions-in-ravendb.mdx +++ b/guides/processing-invoices-using-data-subscriptions-in-ravendb.mdx @@ -2,10 +2,10 @@ title: "Processing Invoices Using Data Subscriptions" tags: [demo, background-tasks, csharp, use-case] description: "Read about Processing Invoices Using Data Subscriptions" -publishedAt: 2024-12-08 +published_at: 2024-12-08 author: "Egor Shamanaev" image: "https://ravendb.net/wp-content/uploads/2024/12/processing-invoices-article-cover.jpg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- import Admonition from '@theme/Admonition'; diff --git a/guides/programmatic-backup-and-restore-operations-in-ravendb-with-node-js.mdx b/guides/programmatic-backup-and-restore-operations-in-ravendb-with-node-js.mdx index 009f5ffefb..05859a85fa 100644 --- a/guides/programmatic-backup-and-restore-operations-in-ravendb-with-node-js.mdx +++ b/guides/programmatic-backup-and-restore-operations-in-ravendb-with-node-js.mdx @@ -2,9 +2,9 @@ title: "Programmatic Backup and Restore with Node.js" tags: [nodejs, data-governance] description: "Read about Programmatic Backup and Restore with Node.js on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/programmatic-backup-and-restore-operations-in-ravendb-with-node-js" -publishedAt: 2024-07-29 +external_url: "https://ravendb.net/articles/programmatic-backup-and-restore-operations-in-ravendb-with-node-js" +published_at: 2024-07-29 icon: "backup" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/ravendb-client-certificates.mdx b/guides/ravendb-client-certificates.mdx index e19f1c2cc8..d804dec55c 100644 --- a/guides/ravendb-client-certificates.mdx +++ b/guides/ravendb-client-certificates.mdx @@ -3,9 +3,9 @@ title: "RavenDB Client Certificates with Vault-Backed Key Reuse" tags: [security, csharp] icon: "guides" description: "RavenDB client certificates that renew automatically without re-registration using vault-backed key reuse patterns." -publishedAt: 2026-02-12 +published_at: 2026-02-12 author: "Paweł Lachowski" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- import Admonition from '@theme/Admonition'; diff --git a/guides/ravendb-data-visualization-with-power-bi.mdx b/guides/ravendb-data-visualization-with-power-bi.mdx index 50d5ddbb91..90e045e972 100644 --- a/guides/ravendb-data-visualization-with-power-bi.mdx +++ b/guides/ravendb-data-visualization-with-power-bi.mdx @@ -2,9 +2,9 @@ title: "RavenDB Data Visualization with Power BI" tags: [integration, monitoring, use-case] description: "Read about RavenDB Data Visualization with Power BI on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/ravendb-data-visualization-with-power-bi" -publishedAt: 2025-03-25 +external_url: "https://ravendb.net/articles/ravendb-data-visualization-with-power-bi" +published_at: 2025-03-25 image: "https://ravendb.net/wp-content/uploads/2025/03/power-BI-article-cover.jpg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/ravendb-deployment-guide-docker-compose-cluster.mdx b/guides/ravendb-deployment-guide-docker-compose-cluster.mdx index 984beaf13f..fabf0090c5 100644 --- a/guides/ravendb-deployment-guide-docker-compose-cluster.mdx +++ b/guides/ravendb-deployment-guide-docker-compose-cluster.mdx @@ -2,8 +2,8 @@ title: "RavenDB Deployment Guide – Docker Compose Cluster" tags: [deployment, getting-started, docker, containers] description: "Read about RavenDB Deployment Guide – Docker Compose Cluster on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/ravendb-deployment-guide-docker-compose-cluster" -publishedAt: 2025-03-10 +external_url: "https://ravendb.net/articles/ravendb-deployment-guide-docker-compose-cluster" +published_at: 2025-03-10 image: "https://ravendb.net/wp-content/uploads/2025/03/docker-ravendb-integration-article-cover.jpg" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/semantic-search-with-ravendb-python-and-fastapi.mdx b/guides/semantic-search-with-ravendb-python-and-fastapi.mdx index 4c113efe86..8fea2efba6 100644 --- a/guides/semantic-search-with-ravendb-python-and-fastapi.mdx +++ b/guides/semantic-search-with-ravendb-python-and-fastapi.mdx @@ -2,10 +2,10 @@ title: "Semantic Search with RavenDB, Python, and FastAPI" tags: [python, fastapi, ai, demo, use-case] description: "Read about Semantic Search with RavenDB and Python" -publishedAt: 2025-07-22 +published_at: 2025-07-22 image: "https://ravendb.net/wp-content/uploads/2025/07/Semantic-Search-cover.png" author: "Paweł Lachowski" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- import Admonition from '@theme/Admonition'; diff --git a/guides/sending-your-ravendb-7-0-logs-to-grafana-cloud.mdx b/guides/sending-your-ravendb-7-0-logs-to-grafana-cloud.mdx index d9ee574423..c7114e808e 100644 --- a/guides/sending-your-ravendb-7-0-logs-to-grafana-cloud.mdx +++ b/guides/sending-your-ravendb-7-0-logs-to-grafana-cloud.mdx @@ -2,9 +2,9 @@ title: "Sending Your RavenDB 7.0 Logs to Grafana Cloud" tags: [integration, monitoring] description: "Read about Sending Your RavenDB 7.0 Logs to Grafana Cloud on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/sending-your-ravendb-7-0-logs-to-grafana-cloud" -publishedAt: 2025-02-25 +external_url: "https://ravendb.net/articles/sending-your-ravendb-7-0-logs-to-grafana-cloud" +published_at: 2025-02-25 image: "https://ravendb.net/wp-content/uploads/2025/02/grafana-cloud-article-cover.jpg" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/set-up-ravendb-server-using-aws-marketplace.mdx b/guides/set-up-ravendb-server-using-aws-marketplace.mdx index 1a717ac6f8..9a4e140911 100644 --- a/guides/set-up-ravendb-server-using-aws-marketplace.mdx +++ b/guides/set-up-ravendb-server-using-aws-marketplace.mdx @@ -2,8 +2,8 @@ title: "Set up RavenDB Server using AWS Marketplace" tags: [getting-started, deployment] description: "Read about Set up RavenDB Server using AWS Marketplace on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/set-up-ravendb-server-using-aws-marketplace" -publishedAt: 2025-11-13 +external_url: "https://ravendb.net/articles/set-up-ravendb-server-using-aws-marketplace" +published_at: 2025-11-13 image: "https://ravendb.net/wp-content/uploads/2025/11/aws-ravendb-article.png" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/setting-up-ravendb-cluster-on-aws-eks.mdx b/guides/setting-up-ravendb-cluster-on-aws-eks.mdx index 81e538c45c..b207ba781c 100644 --- a/guides/setting-up-ravendb-cluster-on-aws-eks.mdx +++ b/guides/setting-up-ravendb-cluster-on-aws-eks.mdx @@ -2,8 +2,8 @@ title: "Setting Up RavenDB Cluster on AWS EKS" tags: [deployment, getting-started, containers, kubernetes] description: "Read about Setting Up RavenDB Cluster on AWS EKS on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/setting-up-ravendb-cluster-on-aws-eks" -publishedAt: 2025-03-09 +external_url: "https://ravendb.net/articles/setting-up-ravendb-cluster-on-aws-eks" +published_at: 2025-03-09 image: "https://ravendb.net/wp-content/uploads/2025/03/kubernetes-aws-ravendb-article-cover.jpg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/setting-up-ravendb-with-docker-and-https.mdx b/guides/setting-up-ravendb-with-docker-and-https.mdx index a0ff5d4768..6af2dc93ef 100644 --- a/guides/setting-up-ravendb-with-docker-and-https.mdx +++ b/guides/setting-up-ravendb-with-docker-and-https.mdx @@ -2,8 +2,8 @@ title: "Setting up RavenDB with Docker and HTTPS" tags: [deployment, getting-started, docker, containers] description: "Read about Setting up RavenDB with Docker and HTTPS on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/setting-up-ravendb-with-docker-and-https" -publishedAt: 2024-12-06 +external_url: "https://ravendb.net/articles/setting-up-ravendb-with-docker-and-https" +published_at: 2024-12-06 icon: "docker" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/simple-crud-operations-with-ravendb-and-asp-net-core-8-application.mdx b/guides/simple-crud-operations-with-ravendb-and-asp-net-core-8-application.mdx index 31439c4604..d2bc0fd2bb 100644 --- a/guides/simple-crud-operations-with-ravendb-and-asp-net-core-8-application.mdx +++ b/guides/simple-crud-operations-with-ravendb-and-asp-net-core-8-application.mdx @@ -2,8 +2,8 @@ title: "Simple CRUD operations with RavenDB and ASP.NET Core 8 application" tags: [querying, asp-net, getting-started] description: "Read about Simple CRUD operations with RavenDB and ASP.NET Core 8 application on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/simple-crud-operations-with-ravendb-and-asp-net-core-8-application" -publishedAt: 2025-03-31 +external_url: "https://ravendb.net/articles/simple-crud-operations-with-ravendb-and-asp-net-core-8-application" +published_at: 2025-03-31 image: "https://ravendb.net/wp-content/uploads/2025/03/crud-operations-asp_net-article-cover.jpg" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/spatial-search-in-ravendb.mdx b/guides/spatial-search-in-ravendb.mdx index ff62822ca0..0a278918ad 100644 --- a/guides/spatial-search-in-ravendb.mdx +++ b/guides/spatial-search-in-ravendb.mdx @@ -4,8 +4,8 @@ tags: [python, demo, querying, indexes, spatial, fastapi, use-case] icon: "spatial-map-view" description: "Learn how to implement spatial search in RavenDB with Python. Covers radius queries, custom polygon shapes, WKT syntax, and reverse point-in-polygon lookup using the Flat Finder demo." author: "Paweł Lachowski" -publishedAt: 2026-03-11 -proficiencyLevel: "Expert" +published_at: 2026-03-11 +proficiency_level: "Expert" keywords: ["spatial search", "geospatial query", "radius search", "polygon search", "WKT", "BoundingBox", "QuadPrefixTree", "GeoHashPrefixTree", "RavenDB Python"] see_also: - title: "Indexing Spatial Data" diff --git a/guides/survive-the-ai-tidal-wave-with-ravendb-genai.mdx b/guides/survive-the-ai-tidal-wave-with-ravendb-genai.mdx index 904dd0afbf..5f0c07dd2f 100644 --- a/guides/survive-the-ai-tidal-wave-with-ravendb-genai.mdx +++ b/guides/survive-the-ai-tidal-wave-with-ravendb-genai.mdx @@ -2,10 +2,10 @@ title: "Survive the AI Tidal Wave with RavenDB GenAI" tags: [ai, demo, deep-dive, use-case] description: "Read about Survive the AI Tidal Wave with RavenDB GenAI on the RavenDB.net news section" -publishedAt: 2025-07-07 +published_at: 2025-07-07 author: "Gracjan Sadowicz" image: "https://ravendb.net/wp-content/uploads/2025/06/article-cover-genai.png" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- import Admonition from '@theme/Admonition'; diff --git a/guides/the-ravendb-kubernetes-operator-way.mdx b/guides/the-ravendb-kubernetes-operator-way.mdx index 3100a92d2e..7417872c76 100644 --- a/guides/the-ravendb-kubernetes-operator-way.mdx +++ b/guides/the-ravendb-kubernetes-operator-way.mdx @@ -2,10 +2,10 @@ title: "RavenDB Kubernetes Operator: Secured Cluster Setup Guide" tags: [deep-dive, kubernetes, docker, containers, deployment] icon: "kubernetes" -publishedAt: 2026-02-23 +published_at: 2026-02-23 description: "Step-by-step guide to deploying a fully secured RavenDB cluster on Kubernetes using the RavenDB Operator, covering TLS, cert-manager, storage, and rolling upgrades." author: "Omer Ratsaby" -proficiencyLevel: "Expert" +proficiency_level: "Expert" see_also: - title: "Container Deployment Guide" link: "start/containers/general-guide" diff --git a/guides/transactional-outbox.mdx b/guides/transactional-outbox.mdx index d23ea2b26d..0a12c9bcef 100644 --- a/guides/transactional-outbox.mdx +++ b/guides/transactional-outbox.mdx @@ -4,7 +4,7 @@ tags: [ongoing-tasks, architecture, csharp, integration, use-case] icon: "etl" description: "Learn to implement the transactional outbox pattern in C# using RavenDB data subscriptions or Queue ETL, with RabbitMQ and Kafka code examples." keywords: [transactional outbox, outbox pattern, RavenDB ETL, RabbitMQ, Kafka, distributed systems, event-driven architecture] -publishedAt: 2026-02-13 +published_at: 2026-02-13 see_also: - title: "RabbitMQ Queue ETL" link: "server/ongoing-tasks/etl/queue-etl/rabbit-mq" @@ -18,8 +18,12 @@ see_also: link: "client-api/data-subscriptions/what-are-data-subscriptions" source: "docs" path: "Client API > Data Subscriptions" + - title: "Sample: The Library of Ravens" + link: "/samples/the-ravens-library" + source: "samples" + path: "Samples > Integration Patterns" author: "Paweł Lachowski" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- import Admonition from '@theme/Admonition'; diff --git a/guides/troubleshooting-which-index-ate-my-disk.mdx b/guides/troubleshooting-which-index-ate-my-disk.mdx index f61bbf060e..000f6c3ded 100644 --- a/guides/troubleshooting-which-index-ate-my-disk.mdx +++ b/guides/troubleshooting-which-index-ate-my-disk.mdx @@ -2,9 +2,9 @@ title: "Which Index Ate My Disk?" tags: [troubleshooting, indexes] description: "Read about Which Index Ate My Disk? on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/troubleshooting-which-index-ate-my-disk" -publishedAt: 2025-08-26 +external_url: "https://ravendb.net/articles/troubleshooting-which-index-ate-my-disk" +published_at: 2025-08-26 image: "https://ravendb.net/wp-content/uploads/2024/09/Designer.png" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/unlock-ravendb-genai-potential-with-attachments.mdx b/guides/unlock-ravendb-genai-potential-with-attachments.mdx index 1cdcda1872..7bc3be717a 100644 --- a/guides/unlock-ravendb-genai-potential-with-attachments.mdx +++ b/guides/unlock-ravendb-genai-potential-with-attachments.mdx @@ -2,9 +2,9 @@ title: "Unlock RavenDB GenAI Potential with Attachments" tags: [demo, csharp, ai, document-extensions, attachments, use-case] description: "Read about Unlock RavenDB GenAI Potential with Attachments on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/unlock-ravendb-genai-potential-with-attachments" -publishedAt: 2025-10-08 +external_url: "https://ravendb.net/articles/unlock-ravendb-genai-potential-with-attachments" +published_at: 2025-10-08 image: "https://ravendb.net/wp-content/uploads/2025/10/unlock-genai-potential-article-image.svg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/unlocking-the-benefits-of-on-demand-production-database-replication.mdx b/guides/unlocking-the-benefits-of-on-demand-production-database-replication.mdx index 1481fd61ce..d592a9d1c7 100644 --- a/guides/unlocking-the-benefits-of-on-demand-production-database-replication.mdx +++ b/guides/unlocking-the-benefits-of-on-demand-production-database-replication.mdx @@ -2,9 +2,9 @@ title: "On-Demand Production Database Replication" tags: [data-governance, administration, clusters, architecture, replication, use-case] description: "Read about On-Demand Production Database Replication on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/unlocking-the-benefits-of-on-demand-production-database-replication" -publishedAt: 2025-02-23 +external_url: "https://ravendb.net/articles/unlocking-the-benefits-of-on-demand-production-database-replication" +published_at: 2025-02-23 image: "https://ravendb.net/wp-content/uploads/2025/02/unlocking-the-benefits.jpg" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/using-azure-queue-storage-etl-to-process-ravendb-documents-in-a-serverless-manner.mdx b/guides/using-azure-queue-storage-etl-to-process-ravendb-documents-in-a-serverless-manner.mdx index b1bc515fc8..afc7b8b419 100644 --- a/guides/using-azure-queue-storage-etl-to-process-ravendb-documents-in-a-serverless-manner.mdx +++ b/guides/using-azure-queue-storage-etl-to-process-ravendb-documents-in-a-serverless-manner.mdx @@ -2,9 +2,9 @@ title: "Using Azure Queue Storage ETL for Serverless Processing" tags: [integration, background-tasks, architecture, demo, use-case] description: "Read about Using Azure Queue Storage ETL for Serverless Processing on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/using-azure-queue-storage-etl-to-process-ravendb-documents-in-a-serverless-manner" -publishedAt: 2025-10-22 +external_url: "https://ravendb.net/articles/using-azure-queue-storage-etl-to-process-ravendb-documents-in-a-serverless-manner" +published_at: 2025-10-22 image: "https://ravendb.net/wp-content/uploads/2024/09/azure_queue_storage.png" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/using-ravendb-persistence-in-an-akka-net-application.mdx b/guides/using-ravendb-persistence-in-an-akka-net-application.mdx index e6b7be5fea..7e1a865acf 100644 --- a/guides/using-ravendb-persistence-in-an-akka-net-application.mdx +++ b/guides/using-ravendb-persistence-in-an-akka-net-application.mdx @@ -2,9 +2,9 @@ title: "Using RavenDB Persistence in an Akka.NET Application" tags: [integration, csharp, use-case] description: "Read about Using RavenDB Persistence in an Akka.NET Application on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/using-ravendb-persistence-in-an-akka-net-application" -publishedAt: 2025-08-26 +external_url: "https://ravendb.net/articles/using-ravendb-persistence-in-an-akka-net-application" +published_at: 2025-08-26 image: "https://ravendb.net/wp-content/uploads/2024/08/akka.png" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/using-ravendb-with-phpfastcache.mdx b/guides/using-ravendb-with-phpfastcache.mdx index a7fd4d8e5f..5183fee078 100644 --- a/guides/using-ravendb-with-phpfastcache.mdx +++ b/guides/using-ravendb-with-phpfastcache.mdx @@ -2,9 +2,9 @@ title: "Using RavenDB with PHPFastCache" tags: [php] description: "Read about Using RavenDB with PHPFastCache on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/using-ravendb-with-phpfastcache" -publishedAt: 2024-08-05 +external_url: "https://ravendb.net/articles/using-ravendb-with-phpfastcache" +published_at: 2024-08-05 image: "https://ravendb.net/wp-content/uploads/2024/08/phpfastcache.png" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/using-remote-attachments-to-cut-storage-costs.mdx b/guides/using-remote-attachments-to-cut-storage-costs.mdx index 0ac3cd196e..a21f2614df 100644 --- a/guides/using-remote-attachments-to-cut-storage-costs.mdx +++ b/guides/using-remote-attachments-to-cut-storage-costs.mdx @@ -3,9 +3,9 @@ title: "Using Remote Attachments to cut storage costs" tags: [document-extensions, attachments, data-governance, architecture, csharp, use-case] icon: "remote-attachment" description: "Store RavenDB attachments in Amazon S3 or Azure Blob Storage to cut costs. Studio and C# setup guide with bulk migration of existing attachments." -publishedAt: 2026-02-01 +published_at: 2026-02-01 author: "Paweł Lachowski" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- import Admonition from '@theme/Admonition'; diff --git a/guides/vibe-coding-with-ravendb-and-context7.mdx b/guides/vibe-coding-with-ravendb-and-context7.mdx index 532032b2b2..0c25aa1a0a 100644 --- a/guides/vibe-coding-with-ravendb-and-context7.mdx +++ b/guides/vibe-coding-with-ravendb-and-context7.mdx @@ -4,7 +4,7 @@ tags: [getting-started, ai, integration] icon: "ai" description: "Set up Context7 MCP with OpenAI Codex in VS Code to give your AI direct access to RavenDB docs — faster, context-rich vibe coding with fewer interruptions." keywords: [vibe coding, Context7, MCP, model context protocol, RavenDB, AI coding assistant, OpenAI Codex, VS Code, NoSQL prototype] -publishedAt: 2026-03-09 +published_at: 2026-03-09 see_also: - title: "AI Integration Overview" link: "ai-integration/start" @@ -15,7 +15,7 @@ see_also: source: "docs" path: "AI Integration > Generating Embeddings" author: "Paweł Lachowski" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- import Admonition from '@theme/Admonition'; diff --git a/guides/what-requests-hit-my-cloud-cluster.mdx b/guides/what-requests-hit-my-cloud-cluster.mdx index 26b37958a7..d7e7c480de 100644 --- a/guides/what-requests-hit-my-cloud-cluster.mdx +++ b/guides/what-requests-hit-my-cloud-cluster.mdx @@ -2,9 +2,9 @@ title: "What Requests Hit My Cloud Cluster?" tags: [troubleshooting, deep-dive, administration, monitoring] description: "Read about What Requests Hit My Cloud Cluster? on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/what-requests-hit-my-cloud-cluster" -publishedAt: 2025-10-23 +external_url: "https://ravendb.net/articles/what-requests-hit-my-cloud-cluster" +published_at: 2025-10-23 image: "https://ravendb.net/wp-content/uploads/2025/07/troublshooting-cluster-cover.png" -proficiencyLevel: "Expert" +proficiency_level: "Expert" --- diff --git a/guides/writing-unit-tests-with-ravendb-java-test-driver.mdx b/guides/writing-unit-tests-with-ravendb-java-test-driver.mdx index 0b753d5998..2fba2ce3d7 100644 --- a/guides/writing-unit-tests-with-ravendb-java-test-driver.mdx +++ b/guides/writing-unit-tests-with-ravendb-java-test-driver.mdx @@ -2,8 +2,8 @@ title: "Writing unit tests with RavenDB Java Test Driver" tags: [java, testing] description: "Read about Writing unit tests with RavenDB Java Test Driver on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/writing-unit-tests-with-ravendb-java-test-driver" -publishedAt: 2024-09-05 +external_url: "https://ravendb.net/articles/writing-unit-tests-with-ravendb-java-test-driver" +published_at: 2024-09-05 icon: "java" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/writing-unit-tests-with-ravendb-net-test-driver.mdx b/guides/writing-unit-tests-with-ravendb-net-test-driver.mdx index aad46a79c4..8b72c4704e 100644 --- a/guides/writing-unit-tests-with-ravendb-net-test-driver.mdx +++ b/guides/writing-unit-tests-with-ravendb-net-test-driver.mdx @@ -2,8 +2,8 @@ title: "Writing unit tests with RavenDB .NET Test Driver" tags: [csharp, testing] description: "Read about Writing unit tests with RavenDB .NET Test Driver on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/writing-unit-tests-with-ravendb-net-test-driver" -publishedAt: 2024-08-28 +external_url: "https://ravendb.net/articles/writing-unit-tests-with-ravendb-net-test-driver" +published_at: 2024-08-28 image: "https://ravendb.net/wp-content/uploads/2024/08/csharp-in-mem.png" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/writing-unit-tests-with-ravendb-python-test-driver.mdx b/guides/writing-unit-tests-with-ravendb-python-test-driver.mdx index 83158125e7..5d328db5fd 100644 --- a/guides/writing-unit-tests-with-ravendb-python-test-driver.mdx +++ b/guides/writing-unit-tests-with-ravendb-python-test-driver.mdx @@ -2,8 +2,8 @@ title: "Writing unit tests with RavenDB Python Test Driver" tags: [python, testing] description: "Read about Writing unit tests with RavenDB Python Test Driver on the RavenDB.net news section" -externalUrl: "https://ravendb.net/articles/writing-unit-tests-with-ravendb-python-test-driver" -publishedAt: 2024-08-24 +external_url: "https://ravendb.net/articles/writing-unit-tests-with-ravendb-python-test-driver" +published_at: 2024-08-24 image: "https://ravendb.net/wp-content/uploads/2024/09/in-mem-python2-2.png" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- diff --git a/guides/zabbix-setup-guide.mdx b/guides/zabbix-setup-guide.mdx index 86a3ad55bc..f3cd04d23a 100644 --- a/guides/zabbix-setup-guide.mdx +++ b/guides/zabbix-setup-guide.mdx @@ -3,10 +3,10 @@ title: "Set up Zabbix Monitoring for RavenDB Cloud" tags: [monitoring] icon: "real-time-statistics" description: "How to set up your Zabbix to monitor your RavenDB Cloud" -publishedAt: 2025-11-16 +published_at: 2025-11-16 image: "https://ravendb.net/wp-content/uploads/2025/11/zabbix-article-image.svg" author: "Paweł Lachowski" -proficiencyLevel: "Beginner" +proficiency_level: "Beginner" --- import Admonition from '@theme/Admonition'; diff --git a/package-lock.json b/package-lock.json index 98a204e0c4..5a20d2590a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,10 +16,12 @@ "@mdx-js/react": "^3.0.0", "autoprefixer": "^10.4.21", "clsx": "^2.1.1", + "motion": "^12.38.0", "prettier": "^3.6.2", "prism-react-renderer": "^2.3.0", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-github-btn": "^1.4.0", "yet-another-react-lightbox": "^3.24.0" }, "devDependencies": { @@ -11611,6 +11613,33 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz", + "integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.38.0", + "motion-utils": "^12.36.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -16436,6 +16465,47 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "license": "MIT" }, + "node_modules/motion": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.38.0.tgz", + "integrity": "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w==", + "license": "MIT", + "dependencies": { + "framer-motion": "^12.38.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz", + "integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.36.0" + } + }, + "node_modules/motion-utils": { + "version": "12.36.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz", + "integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==", + "license": "MIT" + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", diff --git a/package.json b/package.json index 2f1e0b715d..6b907797a3 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@mdx-js/react": "^3.0.0", "autoprefixer": "^10.4.21", "clsx": "^2.1.1", + "motion": "^12.38.0", "prettier": "^3.6.2", "prism-react-renderer": "^2.3.0", "react": "^19.0.0", diff --git a/samples/brain-slop.mdx b/samples/brain-slop.mdx new file mode 100644 index 0000000000..f3b71ae6a6 --- /dev/null +++ b/samples/brain-slop.mdx @@ -0,0 +1,156 @@ +--- +title: "BrainSlop" +description: "RavenDB keeps AI-suggested project changes governed, approved, and recorded as normal document updates." +challenges_solutions_tags: [governed-ai-workflows, single-database-ai] +feature_tags: [ai-agents, gen-ai-tasks, document-session, revisions] +tech_stack_tags: [nodejs, nextjs, typescript] +category: "Project Management" +license: "MIT License" +license_url: "https://github.com/ravendb/samples-brain-slop/blob/main/LICENSE" +repository_url: "https://github.com/ravendb/samples-brain-slop" +languages: ["Node.js"] +image: "/img/samples/brain-slop/cover.png" +img_alt: "BrainSlop project-management interface with AI-created tasks" +gallery: + - src: "/img/samples/brain-slop/cover.png" + alt: "BrainSlop turns free-form chat input into organized project tasks" +related_resources: + - type: documentation + documentation_type: docs + subtitle: "AI Agents" + article_key: "ai-integration/ai-agents/start" + - type: documentation + documentation_type: docs + subtitle: "GenAI Integration" + article_key: "ai-integration/gen-ai-integration/start" + - type: guide + subtitle: "Multi-Agent Collaboration with RavenDB" + article_key: "multi-agents" + - type: video + subtitle: "RavenDB AI Agents Webinar" + url: "https://www.youtube.com/watch?v=A17GSLGN-cQ" +--- + +import { FeatureAccordion, SampleLayout } from '@site/src/components/Samples'; + + + +## Overview + +BrainSlop shows how RavenDB helps applications turn messy AI-assisted input into controlled business changes. A user can describe project work in plain language, while RavenDB AI Agents resolve the request against current project and task documents and return proposed actions for the application to approve. + +The value is governance without slowing the user down. Conversations, pending tool calls, approved changes, project documents, task documents, and generated titles all stay in RavenDB, so AI can help move work forward while validation, approval, history, and persistence remain application-owned. + +## Features used + + + BrainSlop configures one RavenDB AI Agent with RQL query tools for projects and tasks plus action tools for mutations. The agent can answer from RavenDB data, but writes are represented as tool calls first. + + ```typescript + await store.maintenance.send(new AddOrUpdateAiAgentOperation({ + identifier: "assistant", + name: "Assistant", + connectionStringName: "OpenAI", + systemPrompt: SYSTEM_PROMPT, + queries: AGENT_QUERIES, + actions: AGENT_ACTIONS, + disabled: false, + })); + ``` + + + + BrainSlop also configures a GenAI task over the `@conversations` collection. RavenDB watches the stored conversation, sends a compact context object to the model, and writes the generated title back into the same document. + + ```typescript + const genAiConfig = new GenAiConfiguration(); + genAiConfig.name = "Conversation Title Generator"; + genAiConfig.collection = "@conversations"; + genAiConfig.prompt = "Create a concise title summarizing the main topic of this conversation."; + genAiConfig.sampleObject = '{"Title": "The title of the conversation"}'; + genAiConfig.updateScript = "this.Title = $output.Title"; + ``` + + + + The application extracts open AI action calls, validates their arguments with typed schemas, and only then runs the matching repository operation. Approval or rejection is sent back into the RavenDB AI conversation as a tool response. + + ```typescript + export async function executeAction(chatId: string, action: Action, onChunk: (chunk: string) => void) { + const executionResponse = await executeMappedAction(action); + + return await sendToolMessage( + chatId, + { toolId: action.id, response: executionResponse }, + onChunk + ); + } + ``` + + + + RavenDB keeps the project-management state in ordinary documents. AI output becomes typed application input that updates those documents through repository code, so teams can add assistance without creating a second state model for the assistant. + + ```typescript + const executors = { + CreateProject: executeCreateProjectAction, + AddNewTask: executeAddNewTaskAction, + EditTask: executeEditTaskAction, + EditProject: executeEditProjectAction, + DeleteProject: executeDeleteProjectAction, + DeleteTask: executeDeleteTaskAction + }; + ``` + + + + Revisions are valuable when AI is allowed near business data. BrainSlop demonstrates a workflow where changes are still normal RavenDB document updates, so the same revision history used for human edits can also cover AI-assisted edits. + + +## Technologies + +- [RavenDB](https://ravendb.net/) +- [Next.js](https://nextjs.org/) +- [Node.js](https://nodejs.org/) + +## Run locally + +1. Check out the repository. +2. Install Node.js and RavenDB. +3. Start RavenDB in unsecured mode for local development. +4. Run `npm install`. +5. Run `npm run dev`. + +## Community & Support + +If you spot a bug, have an idea, or want to ask a question, open an issue or pull request in the repository. The RavenDB team also hangs out on the [RavenDB Discord server](https://discord.gg/ravendb). + +## Contributing + +Contributions are welcome. See the repository's [CONTRIBUTING](https://github.com/ravendb/samples-brain-slop/blob/main/CONTRIBUTING.md) file for details. + +## License + +This project is licensed with the [MIT license](https://github.com/ravendb/samples-brain-slop/blob/main/LICENSE). + + diff --git a/samples/fit-assistant.mdx b/samples/fit-assistant.mdx new file mode 100644 index 0000000000..1fcf331d62 --- /dev/null +++ b/samples/fit-assistant.mdx @@ -0,0 +1,241 @@ +--- +title: "Fit Assistant" +description: "RavenDB keeps AI assistance, time-based activity data, live updates, event delivery, and reporting connected in one operational application workflow." +challenges_solutions_tags: [single-database-ai, governed-ai-workflows, event-driven-workflows, reporting-offload] +feature_tags: [ai-agents, gen-ai-tasks, time-series, data-subscriptions, queue-etl, olap-etl, document-refresh, document-expiration] +tech_stack_tags: [csharp, dotnet, aspnet-core, aspire, nodejs, react, typescript, rabbitmq, minio, duckdb] +category: "Health & Fitness" +license: "MIT License" +license_url: "https://github.com/ravendb/samples-fit/blob/main/LICENSE" +repository_url: "https://github.com/ravendb/samples-fit" +languages: ["C#"] +image: "/img/samples/fit/dashboard-top.png" +img_alt: "Fit Assistant dashboard showing KPI tiles, daily goals, live workouts, heart-rate trends, and coach shortcuts" +gallery: + - src: "/img/samples/fit/dashboard-top.png" + alt: "Fit Assistant dashboard showing KPI tiles, daily goals, live workouts, heart-rate trends, and coach shortcuts" + - src: "/img/samples/fit/dashboard-trends-coach.png" + alt: "Fit Assistant trends tab showing OLAP ETL reporting and AI coach conversation" + - src: "/img/samples/fit/social.png" + alt: "Fit Assistant social feed showing Queue ETL activity events and friend list" +related_resources: + - type: documentation + documentation_type: docs + subtitle: "AI Integration" + article_key: "ai-integration/start" + - type: documentation + documentation_type: docs + subtitle: "Time Series" + article_key: "document-extensions/timeseries/overview" + - type: guide + subtitle: "Transactional Outbox with RavenDB Queue ETL" + article_key: "transactional-outbox" + - type: video + subtitle: "RavenDB AI Agents Webinar" + url: "https://www.youtube.com/watch?v=A17GSLGN-cQ" +--- + +import { FeatureAccordion, SampleLayout } from '@site/src/components/Samples'; + + + +## Overview + +Fit Assistant presents a familiar product challenge: users expect guided experiences, live feedback, activity feeds, and reporting without a fragile chain of specialized data stores. RavenDB keeps those moving parts connected in one operational workflow, so AI decisions, time-based activity data, live reactions, and reporting data all stay tied to the records that drive the application. + +The fitness domain is just the wrapper. The same RavenDB pattern applies anywhere a product needs personalized AI, measured activity, live updates, durable integration, and reporting. RavenDB remains the operational system of record, with RabbitMQ, MinIO, and DuckDB used for delivery, file storage, and reporting. + +## Features used + + + Fit Assistant registers multiple AI connection strings and creates a parent coach agent in RavenDB. The parent has scoped queries, typed actions, user parameters, and sub-agents, so the model can act from RavenDB data without receiving an unlimited database surface. The same workflow also supports attachments, streaming responses, and AI usage tracking with time series and counters. + + ```csharp + var chatAgent = new AiAgentConfiguration( + Constants.Agent.Id, + Constants.Agent.MiniConnectionStringName, + Prompts.CoachParent) + { + Parameters = CoachAgentDefinition.Parameters(), + Queries = CoachAgentDefinition.UserScopedQueries(), + Actions = CoachAgentDefinition.Actions(), + SubAgents = CoachAgentDefinition.SubAgents() + }; + + await _store.AI.CreateAgentAsync(chatAgent); + ``` + + Tool parameters keep queries inside the current user's boundary, and usage tracking stays in RavenDB documents: + + ```csharp + new AiAgentParameter("userId", + "The ID of the current user profile document.", + sendToModel: false, + policy: AiAgentParameterPolicy.ForbidModelGeneration); + + new AiAgentToolQuery( + "GetRecentHeartRate", + "Get the user's heart rate time series data.", + "from UserProfiles where id() == $userId select timeseries(from HeartRates between $from and $to)"); + + session.TimeSeriesFor(doc, Constants.TimeSeries.Requests).Append(DateTime.UtcNow, 1); + session.CountersFor(doc).Increment("TotalPromptTokens", usage.PromptTokens); + ``` + + + + The `daily-goals` GenAI task runs over `UserProfiles`, builds context from profile and activity data, writes a typed `DailyGoals` document, sets expiration on that document, and re-arms the user profile with `@refresh` for the next generation cycle. The same pattern works for recurring business decisions that need governed AI output, lifecycle rules, and repeatable database-side triggers. + + ```csharp + var config = new GenAiConfiguration + { + Collection = "UserProfiles", + GenAiTransformation = new GenAiTransformation + { + Script = DailyGoalsGenAiDefinition.TransformScript + }, + JsonSchema = DailyGoalsGenAiDefinition.JsonSchema, + UpdateScript = DailyGoalsGenAiDefinition.UpdateScript((int)cadenceMs), + Queries = DailyGoalsGenAiDefinition.Queries(), + }; + + await _store.Maintenance.SendAsync(new AddGenAiOperation(config)); + ``` + + + + The sample stores heart-rate points as RavenDB time series on `UserProfiles`. The database keeps raw points for short windows and maintains hourly, daily, and monthly rollups so charts query the right resolution for the selected range. The same screen also uses map-reduce indexes for daily calorie totals and includes to load related profile data without N+1 reads. + + ```csharp + ["UserProfiles"] = new TimeSeriesCollectionConfiguration + { + RawPolicy = new RawTimeSeriesPolicy(TimeValue.FromDays(30)), + Policies = + [ + new("ByHour", TimeValue.FromHours(1), TimeValue.FromDays(30)), + new("ByDay", TimeValue.FromDays(1), TimeValue.FromDays(180)), + new("ByMonth", TimeValue.FromDays(30), TimeValue.FromYears(100)), + ], + }; + ``` + + + + Fit Assistant shows three update patterns with different durability needs. Data Subscriptions re-evaluate generated goals when food or workout documents change. Changes API pushes fresh workout documents to the UI without polling. Queue ETL publishes per-follower activity messages to RabbitMQ so the FitFeed worker can consume at its own pace. + + ```csharp + await store.Subscriptions.CreateAsync( + new SubscriptionCreationOptions + { + Name = Constants.Subscriptions.GoalAutoFulfillFromExercise + }); + + await worker.Run(async batch => + { + foreach (var userId in batch.Items.Select(i => i.Result.UserProfileId).Distinct()) + await EvaluatePredicatesAsync(userId, GoalType.Burn, SumBurnedKcalAsync, stoppingToken); + }, stoppingToken); + ``` + + The Queue ETL transform keeps routing close to the data: + + ```javascript + var actor = load(this.UserProfileId); + if (!actor || !actor.Follows || actor.Follows.length === 0) return; + + for (var i = 0; i < actor.Follows.length; i++) { + loadToactivity_feed({ + recipientUserId: actor.Follows[i], + actorUserId: this.UserProfileId, + sessionId: id(this), + caloriesBurned: this.CaloriesBurned + }); + } + ``` + + The live UI path is intentionally lighter: + + ```csharp + var changes = _store.Changes(); + await changes.EnsureConnectedNow(); + + _changesSubscription = changes + .ForDocumentsInCollection() + .Subscribe(new ChangeObserver(this, stoppingToken)); + + var doc = await session + .Include(x => x.UserProfileId) + .LoadAsync(change.Id, ct); + ``` + + + + Fit Assistant keeps OLTP writes in RavenDB and exports completed workouts through OLAP ETL to MinIO as Parquet. DuckDB reads that reporting copy for peer ranking and trends without making the operational database do warehouse-style scans. + + ```javascript + var t = new Date(this.StartTime); + var year = t.getUTCFullYear(); + var month = t.getUTCMonth() + 1; + var day = t.getUTCDate(); + + loadToexercises(partitionBy(['year', year], ['month', month], ['day', day]), { + userId: this.UserProfileId, + exerciseType: this.Type, + caloriesBurned: this.CaloriesBurned + }); + ``` + + +## Technologies + +- [RavenDB](https://ravendb.net/) +- [.NET 10](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) +- [ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/) +- [Aspire](https://aspire.dev/) +- [React](https://react.dev/) +- [Node.js](https://nodejs.org/) +- [RabbitMQ](https://www.rabbitmq.com/) +- [MinIO](https://min.io/) +- [DuckDB](https://duckdb.org/) + +## Run locally + +1. Check out the repository. +2. Install .NET 10, Aspire, Node.js 22 or newer, and Docker Desktop. +3. Install frontend dependencies with `npm install` in `src/FitAssistant.Frontend`. +4. Run `aspire run` from the repository root. +5. Provide the Aspire parameters `openai-api-key` and `ravendb-license`. + +## Community & Support + +If you spot a bug, have an idea, or want to ask a question, open an issue in the repository. The RavenDB team also hangs out on the [RavenDB Discord server](https://discord.gg/ravendb). + +## Contributing + +Contributions are welcome. See the repository's [CONTRIBUTING](https://github.com/ravendb/samples-fit/blob/main/CONTRIBUTING.md) file for details. + +## License + +This project is licensed with the [MIT license](https://github.com/ravendb/samples-fit/blob/main/LICENSE). + + diff --git a/samples/home.mdx b/samples/home.mdx new file mode 100644 index 0000000000..e39b0f2a60 --- /dev/null +++ b/samples/home.mdx @@ -0,0 +1,14 @@ +--- +title: Code Samples & Starter Kits +description: "Browse production-ready RavenDB code samples, architecture patterns, and starter kits. Each sample includes a working codebase, documented architecture, and step-by-step setup instructions." +hide_title: true +slug: / +pagination_next: null +pagination_prev: null +wrapperClassName: samplesHomePage +hide_table_of_contents: true +--- + +import { SamplesHomePage } from "@site/src/components/Samples"; + + diff --git a/samples/hugin.mdx b/samples/hugin.mdx new file mode 100644 index 0000000000..8df42129dd --- /dev/null +++ b/samples/hugin.mdx @@ -0,0 +1,151 @@ +--- +title: "Hugin" +description: "RavenDB brings fast full-text search to tiny edge hardware, serving over 2.6 million Stack Exchange Q&As from a compressed local database on a 512MB device." +challenges_solutions_tags: [edge-data-performance, self-contained-appliance] +feature_tags: [full-text-search, corax, map-reduce-indexes, document-compression, include] +tech_stack_tags: [nodejs, react, express, raspberry-pi] +category: "Offline Search" +repository_url: "https://github.com/ravendb/samples-hugin" +languages: ["Node.js"] +image: "/img/samples/hugin/cover.png" +img_alt: "Hugin offline knowledge base homepage with RavenDB search" +gallery: + - src: "/img/samples/hugin/cover.png" + alt: "Hugin offline knowledge base homepage with RavenDB search" +related_resources: + - type: documentation + documentation_type: docs + subtitle: "Full-Text Search" + article_key: "client-api/session/querying/text-search/full-text-search" + - type: documentation + documentation_type: docs + subtitle: "Corax" + article_key: "indexes/search-engine/corax" + - type: documentation + documentation_type: docs + subtitle: "Documents Compression" + article_key: "server/storage/documents-compression" + - type: guide + subtitle: "Diacritic-Sensitive Search in RavenDB" + article_key: "diacritic-sensitive-search-in-ravendb" +--- + +import { FeatureAccordion, SampleLayout } from '@site/src/components/Samples'; + + + +## Overview + +Hugin shows RavenDB making large local data practical on tiny hardware. The appliance runs on a Raspberry Pi Zero 2 W with a 1GHz CPU and 512MB of RAM, yet it serves a searchable offline knowledge base with over 2.6 million Stack Exchange Q&As and 4.3 million documents. + +For teams building edge, field, kiosk, or offline experiences, the point is simple: RavenDB can keep a large compressed data set queryable close to the user, even without cloud access. The sample keeps indexes, full-text search, includes, statistics, and document relationships available on a microSD-backed device, with query timings visible in the application. + +## Features used + + + Hugin defines a JavaScript static index for questions and marks the combined title/tag field with RavenDB's full-text search mode. The index uses Corax, so the appliance can provide keyword search locally without a separate search service or a network connection. + + ```javascript + this.map('Questions', q => ({ + Community: q.Community, + Tags: q.Tags, + CreationDate: q.CreationDate, + ViewCount: q.ViewCount, + Query: [q.Title, q.Tags] + })); + + this.index('Query', 'Search'); + this.searchEngineType = 'Corax'; + ``` + + + + Hugin uses a map-reduce index to count tags across the imported question corpus and split those counts by community. That turns the tag browser into a maintained RavenDB projection instead of a hand-built cache. + + ```javascript + this.map("Questions", q => q.Tags.map(t => { + const communities = {}; + communities[q.Community] = 1; + return { Tag: t, Count: 1, Communities: communities }; + })); + + this.reduce(g => g.groupBy(x => x.Tag).aggregate(g => ({ + Tag: g.key, + Count: g.values.reduce((count, val) => val.Count + count, 0) + }))); + ``` + + + + The included Stack Exchange data set contains 4.3 million documents and over 2.6 million Q&As while using just over 5GB of disk space with RavenDB document compression. Without compression, the same data is about 11GB. On a microSD-backed device, that saves storage and reduces disk I/O. + + + + Question detail pages use includes for owners, answer owners, and comment users, then batch-load the remaining user documents. That keeps the document model natural while avoiding the usual relationship-loading tax. + + ```javascript + const question = await session + .include("Owner") + .include("Answers[].Owner") + .include("Answers[].Comments[].User") + .load(req.query.id); + + const users = await session.load(userIds); + ``` + + + + The appliance runs RavenDB locally, stores the Stack Exchange data on the device, and serves the React application through the same offline environment. Hugin warms common RavenDB queries after startup because cold microSD I/O is expensive, then keeps the user experience fast enough to make the hardware constraints visible in the best possible way. + + ```javascript + for (const community of communities) { + await session.query({ indexName: QuestionsSearch.name }) + .whereEquals("Community", community.id) + .orderByDescending("CreationDate") + .take(15) + .all(); + } + ``` + + +## Technologies + +- [RavenDB](https://ravendb.net/) +- [Node.js](https://nodejs.org/) +- [Express](https://expressjs.com/) +- [React](https://react.dev/) +- [Raspberry Pi OS](https://www.raspberrypi.com/software/) + +## Run the appliance + +1. Power on the Hugin device. +2. Connect to the `Hugin (ravendb)` Wi-Fi network. +3. Open `http://start.ravendb`. + +The repository also contains the Raspberry Pi setup files, RavenDB service configuration, and application source if you want to inspect or adapt the appliance. + +## Community & Support + +If you spot a bug, have an idea, or want to ask a question, open an issue or pull request in the repository. The RavenDB team also hangs out on the [RavenDB Discord server](https://discord.gg/ravendb). + + diff --git a/samples/human-resources-assistant.mdx b/samples/human-resources-assistant.mdx new file mode 100644 index 0000000000..dc7fef1fe4 --- /dev/null +++ b/samples/human-resources-assistant.mdx @@ -0,0 +1,163 @@ +--- +title: "Human Resources Assistant" +description: "RavenDB grounds enterprise AI assistants in governed business data, semantic search, usage limits, and document lifecycle controls." +challenges_solutions_tags: [single-database-ai, usage-governance, semantic-discovery, lifecycle-governance] +feature_tags: [ai-agents, time-series, vector-search, attachments, counters, document-expiration] +tech_stack_tags: [csharp, dotnet, aspire, nodejs, react, signalr] +category: "Enterprise AI" +license: "MIT License" +license_url: "https://github.com/ravendb/samples-hr/blob/main/LICENSE" +repository_url: "https://github.com/ravendb/samples-hr" +languages: ["C#"] +image: "/img/samples/hr/cover.png" +img_alt: "Human Resources Assistant chat interface" +gallery: + - src: "/img/samples/hr/cover.png" + alt: "Human Resources Assistant answering an employee vacation question" + - src: "/img/samples/hr/usage-limit.png" + alt: "Human Resources Assistant API usage dashboard" + - src: "/img/samples/hr/seed-db.png" + alt: "Aspire dashboard seed data action for the HR sample" +related_resources: + - type: documentation + documentation_type: docs + subtitle: "AI Agents" + article_key: "ai-integration/ai-agents/start" + - type: documentation + documentation_type: docs + subtitle: "Vector Search" + article_key: "ai-integration/vector-search/start" + - type: guide + subtitle: "Practical Look at AI Agents with RavenDB" + article_key: "practical-look-at-ai-agents-with-ravendb" + - type: video + subtitle: "AI Agents and Attachments" + url: "https://www.youtube.com/watch?v=QphtDGe23WE" +--- + +import { FeatureAccordion, SampleLayout } from '@site/src/components/Samples'; + + + +## Overview + +Human Resources Assistant shows how RavenDB keeps an enterprise AI assistant grounded in governed business data. Employee records, HR policies, signature documents, expenses, conversations, and usage telemetry live together, so answers and actions come from the same operational record the application already trusts. + +That matters when an assistant is allowed near internal workflows. RavenDB AI Agents limit what the model can query or do, Vector Search finds the right policies by meaning, Time Series and Counters support usage governance, and Document Expiration cleans up sessions without adding another service. + +## Features used + + + The HR assistant is created as a RavenDB AI Agent with an employee ID parameter, RQL query tools, tool actions, and an expense-management sub-agent. The model can answer HR questions only through the application-defined surface. + + ```csharp + return store.AI.CreateAgentAsync(new AiAgentConfiguration + { + Identifier = AgentIdentifier, + Parameters = [new AiAgentParameter(EmployeeIdParameter, "Employee ID; answer only for this employee")], + SubAgents = [new AiAgentToolSubAgent { Identifier = ExpenseAgentIdentifier }], + Queries = [new AiAgentToolQuery { Name = "GetEmployeeInfo" }], + Actions = [new AiAgentToolAction { Name = RaiseIssueAction }] + }); + ``` + + + + HR policies and employee questions rarely use the same words. The agent uses RavenDB vector search directly in RQL to retrieve policies, issues, and signature documents by semantic similarity before answering. + + ```csharp + Query = @" + from HRPolicies + where (vector.search(embedding.text(Title), $query) + or vector.search(embedding.text(Content), $query)) + limit 5" + ``` + + + + Each AI request appends a point to a RavenDB time series on the session usage document. That gives the application a database-native way to enforce limits over rolling windows and display usage without exporting metrics elsewhere. + + ```csharp + session.TimeSeriesFor(doc, Constants.TimeSeries.Requests) + .Append(DateTime.UtcNow, 1); + + await session.SaveChangesAsync(); + ``` + + + + Token totals are counters on the same session usage document. RavenDB keeps the operational record and the accumulating usage numbers together, which makes live dashboards and limits much simpler. + + ```csharp + session.CountersFor(doc) + .Increment("TotalCompletionTokens", usage.CompletionTokens); + + session.CountersFor(doc) + .Increment("TotalPromptTokens", usage.PromptTokens); + ``` + + + + The expense sub-agent can inspect receipt images attached to the conversation and turn confirmed receipts into business-trip expense records. Signature flows use RavenDB documents and attachments together, so the assistant can find the right document and ask for a controlled action. + + + + Conversation and session data should not live forever by accident. Document Expiration gives the sample a database-native cleanup mechanism without a separate cron service. + + +## Technologies + +- [RavenDB](https://ravendb.net/) +- [.NET Aspire](https://learn.microsoft.com/en-us/dotnet/aspire/) +- [.NET 10](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) +- [ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/) +- [Node.js 22](https://nodejs.org/) +- [React](https://react.dev/) +- [SignalR](https://learn.microsoft.com/en-us/aspnet/core/signalr/introduction) + +## Run locally + +1. Check out the repository. +2. Install .NET 10.x, Node.js 22.x, and Aspire. +3. Provide the required Aspire parameters: `openai-api-key` and `ravendb-license`. +4. Run `npm install` in `/sampleshr-frontend`. +5. Start the .NET Aspire AppHost with `aspire run`. +6. Open the Aspire dashboard and use the seed data action to populate the sample database. + +## Community & Support + +If you spot a bug, have an idea, or want to ask a question, open an issue or pull request in the repository. The RavenDB team also hangs out on the [RavenDB Discord server](https://discord.gg/ravendb). + +## Contributing + +Contributions are welcome. See the repository's [CONTRIBUTING](https://github.com/ravendb/samples-hr/blob/main/CONTRIBUTING.md) file for details. + +## License + +This project is licensed with the [MIT license](https://github.com/ravendb/samples-hr/blob/main/LICENSE). + + diff --git a/samples/tags/challenges-solutions.yml b/samples/tags/challenges-solutions.yml new file mode 100644 index 0000000000..ea3c24a387 --- /dev/null +++ b/samples/tags/challenges-solutions.yml @@ -0,0 +1,48 @@ +auditable-data-history: + label: "Auditable Data History" +cloud-tax: + label: "Cloud Tax" +gen-ai-data-enrichment: + label: "Gen AI Data Enrichment" +semantic-discovery: + label: "Semantic Discovery" +governed-ai-workflows: + label: "Governed AI Workflows" +event-driven-workflows: + label: "Event-Driven Workflows" +reporting-offload: + label: "Reporting Offload" +azure-native-integration: + label: "Azure-Native Integration" +cloud-cost-control: + label: "Cloud Cost Control" +regulated-document-workflows: + label: "Regulated Document Workflows" +integration-patterns: + label: "Integration Patterns" +lifecycle-governance: + label: "Lifecycle Governance" +time-series-visibility: + label: "Time-Series Visibility" +usage-governance: + label: "Usage Governance" +resilient-integrations: + label: "Resilient Integrations" +search-consolidation: + label: "Search Consolidation" +single-database-ai: + label: "Single-Database AI" +natural-language-actions: + label: "Natural-Language Actions" +edge-deployment: + label: "Edge Deployment" +self-contained-appliance: + label: "Self-Contained Appliance" +edge-data-performance: + label: "Edge Data Performance" +enterprise-app-architecture: + label: "Enterprise App Architecture" +multi-tenant-data-access: + label: "Multi-Tenant Data Access" +testable-data-access: + label: "Testable Data Access" diff --git a/samples/tags/feature.yml b/samples/tags/feature.yml new file mode 100644 index 0000000000..0597411ce9 --- /dev/null +++ b/samples/tags/feature.yml @@ -0,0 +1,50 @@ +vector-search: + label: "Vector Search" +document-refresh: + label: "Document Refresh" +include: + label: "Include" +ai-agents: + label: "AI Agents" +full-text-search: + label: "Full-Text Search" +gen-ai-tasks: + label: "GenAI Tasks" +gen-ai: + label: "GenAI" +remote-attachments: + label: "Remote Attachments" +revisions: + label: "Revisions" +data-subscriptions: + label: "Data Subscriptions" +queue-etl: + label: "Queue ETL" +olap-etl: + label: "OLAP ETL" +hub-sink-replication: + label: "Hub/Sink Replication" +document-expiration: + label: "Document Expiration" +data-archival: + label: "Data Archival" +time-series: + label: "Time Series" +attachments: + label: "Attachments" +counters: + label: "Counters" +corax: + label: "Corax" +map-reduce-indexes: + label: "Map-Reduce Indexes" +static-indexes: + label: "Static Indexes" +document-session: + label: "Document Session" +patch-by-query: + label: "Patch by Query" +test-driver: + label: "Test Driver" +document-compression: + label: "Document Compression" diff --git a/samples/tags/tech-stack.yml b/samples/tags/tech-stack.yml new file mode 100644 index 0000000000..a619241dd3 --- /dev/null +++ b/samples/tags/tech-stack.yml @@ -0,0 +1,46 @@ +csharp: + label: "C#" +dotnet: + label: ".NET" +nodejs: + label: "Node.js" +nextjs: + label: "Next.js" +python: + label: "Python" +java: + label: "Java" +php: + label: "PHP" +aspire: + label: "Aspire" +azure-storage-queues: + label: "Azure Storage Queues" +azure-storage: + label: "Azure Storage" +azure-functions: + label: "Azure Functions" +react: + label: "React" +signalr: + label: "SignalR" +svelte: + label: "Svelte" +sveltekit: + label: "SvelteKit" +typescript: + label: "TypeScript" +express: + label: "Express" +raspberry-pi: + label: "Raspberry Pi" +aspnet-core: + label: "ASP.NET Core" +angular: + label: "Angular" +rabbitmq: + label: "RabbitMQ" +minio: + label: "MinIO" +duckdb: + label: "DuckDB" diff --git a/samples/the-ravens-library.mdx b/samples/the-ravens-library.mdx new file mode 100644 index 0000000000..fc94a3dc02 --- /dev/null +++ b/samples/the-ravens-library.mdx @@ -0,0 +1,226 @@ +--- +title: "The Library of Ravens" +description: "A simple library management application. Built with RavenDB, Aspire, Azure Storage Queues, and Azure Functions. " +challenges_solutions_tags: [cloud-tax, gen-ai-data-enrichment, integration-patterns, semantic-discovery] +feature_tags: [queue-etl, document-refresh, include, vector-search] +tech_stack_tags: [csharp, aspire, azure-storage-queues, azure-functions] +category: "Ecommerce" +license: "MIT License" +license_url: "https://github.com/ravendb/samples-library/blob/main/LICENSE" +repository_url: "https://github.com/ravendb/samples-library" +languages: ["C#"] +image: "/img/samples/library-of-ravens/cover.webp" +img_alt: "The Library of Ravens App Screenshot" +gallery: + - src: "/img/samples/library-of-ravens/01.webp" + alt: "The Library of Ravens - Main Interface" + - src: "/img/samples/library-of-ravens/02.webp" + alt: "The Library of Ravens - Author Profile" + - src: "/img/samples/library-of-ravens/03.webp" + alt: "The Library of Ravens - User Profile" +related_resources: + - type: documentation + documentation_type: docs + subtitle: "Vector Search Overview" + article_key: "ai-integration/vector-search/overview" + - type: documentation + documentation_type: docs + subtitle: "Azure Storage Queues ETL" + article_key: "server/ongoing-tasks/etl/queue-etl/azure-queue" + - type: video + subtitle: "Learn How to Build a Modern .NET App with ease: Azure Functions and Aspire with RavenDB" + url: "https://www.youtube.com/watch?v=TEvBGNMSq9g" +--- + +import { FeatureAccordion, SampleLayout } from '@site/src/components/Samples'; + + + +## Overview + +The Library of Ravens **solves the "architecture bloat"** of managing separate databases by **consolidating full-text and vector search into a single database**. Instead of wrestling with data synchronization across multiple platforms, you get a unified search experience that keeps your infrastructure lean and your development cycle fast. + +The app demonstrates **robust integration patterns using Azure Storage Queues**. By leveraging RavenDB’s change tracking and ETL services, the system ensures that critical library updates are never lost, providing architectural resilience. + +Finally, the app **addresses the "cloud tax" of high egress fees** by utilizing ETags and native HTTP caching driven by RavenDB’s metadata. This approach significantly reduces the volume of data sent over the wire, slashing public cloud billing while providing a snappier, more responsive experience for the end user through efficient data reuse. + +Built with [RavenDB](https://ravendb.net/), [Aspire](https://aspire.dev/), [Azure Storage Queues](https://azure.microsoft.com/en-us/products/storage/queues), and [Azure Functions](https://azure.microsoft.com/en-us/products/functions). + + +## Features used + + + RavenDB's Include feature allows loading related documents in a single request, eliminating the N+1 query problem. In the Library application, when loading a book, we simultaneously fetch the author and category documents without additional roundtrips to the database. + + Implementation example: + + ```csharp + // Load a book with its related author in one request + using var session = store.OpenAsyncSession(); + + var book = await session + .Include(b => b.AuthorId) + .Include(b => b.CategoryId) + .LoadAsync("books/1-A"); + + // These are already loaded - no additional DB calls + var author = await session.LoadAsync(book.AuthorId); + var category = await session.LoadAsync(book.CategoryId); + + // Example: Loading multiple books with their authors + var books = await session.Query() + .Include(b => b.AuthorId) + .Where(b => b.IsAvailable) + .ToListAsync(); + ``` + + + + RavenDB's Vector Search enables semantic similarity queries for discovering related books. The Library uses AI-generated embeddings to power a 'Similar Books' feature, finding conceptually related titles even when they share no common keywords. + + Implementation example: + + ```csharp + // Define a vector index for book embeddings + public class Books_ByEmbedding : AbstractIndexCreationTask + { + public Books_ByEmbedding() + { + Map = books => from book in books + select new + { + book.Title, + book.Description, + // Vector field for semantic search + Embedding = CreateField("Embedding", + book.Embedding, + stored: false, + indexing: FieldIndexing.Default) + }; + } + } + + // Find similar books using vector search + var similarBooks = await session + .Query() + .VectorSearch( + field: b => b.Embedding, + queryVector: currentBook.Embedding, + minimumSimilarity: 0.7f) + .Take(5) + .ToListAsync(); + ``` + + + + RavenDB's ETL (Extract, Transform, Load) to Azure Storage Queues enables real-time data streaming. Combined with @refresh, the Library sends notifications about expiring book loans to Azure Functions for processing email reminders. + + Implementation example: + + ```csharp + // RavenDB ETL Script for Azure Storage Queues + // Configured in RavenDB Studio + + // This script runs when documents are refreshed + loadToAzureQueueStorage('expiring-loans', { + BookId: id(this), + Title: this.Title, + BorrowerId: this.CurrentLoan.BorrowerId, + DueDate: this.CurrentLoan.DueDate, + BorrowerEmail: load(this.CurrentLoan.BorrowerId).Email +}); + + // Azure Function triggered by the queue message + [Function("ProcessExpiringLoan")] + public async Task Run( + [QueueTrigger("expiring-loans")] LoanNotification notification) + { + await _emailService.SendReminderAsync( + notification.BorrowerEmail, + notification.Title, + notification.DueDate); + } + ``` + + + + Document Refresh enables automatic re-indexing of documents at specified times using the @refresh metadata. The Library uses this for handling book loan timeouts - when a book's return date approaches, RavenDB automatically refreshes the document, triggering downstream processes. + + Implementation example: + + ```csharp + // Set a document to refresh at a specific time + public async Task SetBookReturnReminder( + string bookId, + DateTime returnDate) + { + using var session = store.OpenAsyncSession(); + var book = await session.LoadAsync(bookId); + + // Set the @refresh metadata + var metadata = session.Advanced.GetMetadataFor(book); + metadata["@refresh"] = returnDate.AddDays(-1); // Day before due + + await session.SaveChangesAsync(); + } + + // The document will be re-indexed when refresh time arrives, + // triggering any subscriptions or ETL processes watching for + // books with approaching due dates + ``` + + +## Technologies + +The following technologies were used to build this application: + +- [RavenDB](https://ravendb.net/) +- [.NET 10](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) +- [Aspire](https://aspire.dev/) +- [Azure Functions](https://azure.microsoft.com/en-us/products/functions) +- [Azure Storage Queues](https://azure.microsoft.com/products/storage/queues) +- [SvelteKit](https://svelte.dev/) + +## Run locally + +If you want to run the application locally, please follow the steps: + +1. Check out the GIT repository +2. Install prerequisites: + 1. [.NET 10](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) + 2. [Aspire.dev](https://aspire.dev/get-started/install-cli/) + 3. [nodejs](https://nodejs.org/) +3. Request a [dev license](https://ravendb.net/license/request/dev-ai-agent-inside) and set it under `RAVEN_LICENSE` env variable +4. Get the app running by opening `/src/RavenDB.Samples.Library.sln` and starting the `Aspire` `AppHost` project + +## Community & Support + +If you spot a bug, have an idea or a question, please let us know by raising an issue or creating a pull request. + +We do use a [Discord server](https://discord.gg/ravendb). If you have any doubts, don't hesitate to reach out! + +## Contributing + +We encourage you to contribute! Please read our [CONTRIBUTING](https://github.com/ravendb/samples-library/blob/main/CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests. + +## License + +This project is licensed with the [MIT license](https://github.com/ravendb/samples-library/blob/main/LICENSE). + + diff --git a/samples/verity.mdx b/samples/verity.mdx new file mode 100644 index 0000000000..c7919b4aa3 --- /dev/null +++ b/samples/verity.mdx @@ -0,0 +1,172 @@ +--- +title: "Verity: The Fiscal Truth Engine" +description: "RavenDB keeps regulated filing workflows auditable from ingestion through AI analysis, downstream delivery, and lifecycle controls." +challenges_solutions_tags: [regulated-document-workflows, auditable-data-history, governed-ai-workflows, lifecycle-governance, azure-native-integration] +feature_tags: [gen-ai-tasks, ai-agents, remote-attachments, queue-etl, revisions, data-subscriptions, hub-sink-replication, document-expiration, data-archival] +tech_stack_tags: [csharp, dotnet, aspire, nodejs, svelte, azure-storage, azure-functions] +category: "Finance" +license: "MIT License" +license_url: "https://github.com/ravendb/samples-verity/blob/main/LICENSE" +repository_url: "https://github.com/ravendb/samples-verity" +languages: ["C#"] +image: "/img/samples/verity/cover.png" +img_alt: "Verity financial reporting dashboard showing SEC filing data and revenue charts" +gallery: + - src: "/img/samples/verity/cover.png" + alt: "Verity financial reporting dashboard showing quarterly SEC filing metrics" +related_resources: + - type: documentation + documentation_type: docs + subtitle: "GenAI Integration" + article_key: "ai-integration/gen-ai-integration/start" + - type: documentation + documentation_type: docs + subtitle: "AI Agents" + article_key: "ai-integration/ai-agents/start" + - type: guide + subtitle: "Using Remote Attachments to Cut Storage Costs" + article_key: "using-remote-attachments-to-cut-storage-costs" + - type: video + subtitle: "RavenDB GenAI Tasks Webinar" + url: "https://www.youtube.com/watch?v=NgvyeHwwVjM" +--- + +import { FeatureAccordion, SampleLayout } from '@site/src/components/Samples'; + + + +## Overview + +Verity shows how RavenDB supports regulated document workflows where ingestion, AI analysis, auditability, and downstream delivery all need to stay traceable. SEC EDGAR filings are imported into RavenDB, large payloads are stored as remote attachments, and database-managed AI tasks analyze filing chunks and profitability signals. + +The business value is accountability. RavenDB keeps the operational records, revisions, subscriptions, queue delivery, replication setup, and AI configuration connected, so teams can see what changed, what was analyzed, and what downstream consumers received. Azure Storage and Azure Queues support the workflow without becoming competing operational records. + +## Features used + + + Verity configures the OpenAI connection and adds GenAI tasks during database migration. The tasks are part of the RavenDB database setup, so analysis is tied to document changes instead of being a detached worker convention. + + ```csharp + DocumentStore.Maintenance.Send(new PutConnectionStringOperation( + new AiConnectionString { Name = connectionName, ModelType = AiModelType.Chat } + )); + + DocumentStore.Maintenance.Send(new AddGenAiOperation(new ChunkAnalysisTask(connectionName))); + DocumentStore.Maintenance.Send(new AddGenAiOperation(new ProfitabilityTask(connectionName))); + ``` + + + + The assistant layer is configured from the same RavenDB AI connection string as the GenAI tasks. That keeps interactive review close to the governed database instead of letting a separate chatbot copy filings into its own store. + + ```csharp + VerityAgentCreator + .Create(DocumentStore, connectionName) + .GetAwaiter() + .GetResult(); + ``` + + + + SEC filings and derived artifacts can be large. Remote Attachments let the application keep those files in Azure Storage while RavenDB maintains the document relationship, metadata, and retrieval flow. This keeps the database model coherent without forcing every byte of filing content into local database files. + + + + Verity configures RavenDB Queue ETL with Azure Queue Storage. RavenDB can project relevant revision or audit data into a queue without custom polling code watching every document change. + + ```csharp + DocumentStore.Maintenance.Send(new PutConnectionStringOperation( + new QueueConnectionString + { + BrokerType = QueueBrokerType.AzureQueueStorage, + AzureQueueStorageConnectionSettings = new AzureQueueStorageConnectionSettings + { + ConnectionString = context.AzureStorageConnectionString + } + })); + + DocumentStore.Maintenance.Send(new AddEtlOperation( + AuditRevisionQueueEtlTask.Create())); + ``` + + + + The subscription app watches selected companies and receives report notifications from RavenDB. The worker can stay simple because RavenDB handles delivery, batching, and subscription ownership. + + ```csharp + var worker = store.Subscriptions.GetSubscriptionWorker( + new SubscriptionWorkerOptions(subscriptionName) + { + Strategy = SubscriptionOpeningStrategy.WaitForFree + }); + + await worker.Run(batch => + { + foreach (var item in batch.Items) + AnsiConsole.MarkupLine($"[cyan]Analyzed:[/] {item.Result.Filing}"); + }, ct); + ``` + + + + Financial reports are not disposable notes. Verity uses RavenDB revisions for historical accountability, document expiration for cleanup, and data archival for records that should remain retrievable without staying active in indexes and subscriptions. + + +## Technologies + +- [RavenDB](https://ravendb.net/) +- [.NET 10](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) +- [Aspire](https://aspire.dev/) +- [Node.js 22](https://nodejs.org/) +- [Svelte](https://svelte.dev/) +- [Azure Storage](https://azure.microsoft.com/en-us/products/storage) +- [Azure Functions](https://azure.microsoft.com/en-us/products/functions) + +## Run locally + +1. Check out the repository. +2. Install Docker, .NET 10.x, Node.js 22.x, Aspire, and Azure Functions Core Tools. +3. Run `aspire run`. +4. When Aspire prompts for parameters, provide a SEC user agent, an OpenAI API key, an Azure Storage connection string, and a RavenDB license. + +## Notes + +Verity focuses on U.S.-based companies because the workflow expects 10-K and 10-Q reports from SEC EDGAR. + +## Community & Support + +If you spot a bug, have an idea, or want to ask a question, open an issue or pull request in the repository. The RavenDB team also hangs out on the [RavenDB Discord server](https://discord.gg/ravendb). + +## Contributing + +Contributions are welcome. See the repository's [CONTRIBUTING](https://github.com/ravendb/samples-verity/blob/main/CONTRIBUTING.md) file for details. + +## License + +This project is licensed with the [MIT license](https://github.com/ravendb/samples-verity/blob/main/LICENSE). + + diff --git a/samples/yabt.mdx b/samples/yabt.mdx new file mode 100644 index 0000000000..bb1df255f7 --- /dev/null +++ b/samples/yabt.mdx @@ -0,0 +1,156 @@ +--- +title: "YABT" +description: "RavenDB fits cleanly into layered .NET business applications with tenant-aware sessions, static indexes, set-based updates, and real integration tests." +challenges_solutions_tags: [enterprise-app-architecture, multi-tenant-data-access, testable-data-access] +feature_tags: [document-session, static-indexes, patch-by-query, test-driver] +tech_stack_tags: [csharp, dotnet, aspnet-core, angular] +category: "Enterprise Applications" +license: "MIT License" +license_url: "https://github.com/ravendb/samples-yabt/blob/master/LICENSE" +repository_url: "https://github.com/ravendb/samples-yabt" +demo_url: "https://yabt.ravendb.net" +languages: ["C#"] +image: "/img/samples/yabt/cover.png" +img_alt: "YABT backlog items list showing filters, tags, assignees, and item states" +gallery: + - src: "/img/samples/yabt/cover.png" + alt: "YABT backlog items list showing filters, tags, assignees, and item states" + - src: "/img/samples/yabt/projects-diagram.png" + alt: "YABT solution diagram showing WebAPI, Domain, Database, and shared database common projects" + - src: "/img/samples/yabt/apikey-auth.png" + alt: "YABT Swagger API key authentication prompt" +related_resources: + - type: documentation + documentation_type: docs + subtitle: "What is a Session" + article_key: "client-api/session/what-is-a-session-and-how-does-it-work" + - type: documentation + documentation_type: docs + subtitle: "Test Driver" + article_key: "start/test-driver" + - type: guide + subtitle: "Writing Unit Tests with RavenDB .NET Test Driver" + article_key: "writing-unit-tests-with-ravendb-net-test-driver" + - type: guide + subtitle: "How to Setup RavenDB with ASP.NET Core" + article_key: "how-to-setup-ravendb-with-asp-net-core-8-application" +--- + +import { FeatureAccordion, SampleLayout } from '@site/src/components/Samples'; + + + +## Overview + +YABT shows how RavenDB fits into a layered .NET business application without forcing database details through every layer. The document store is registered once, domain code works through a database abstraction, tenant rules live in the session wrapper, and the API layer stays focused on application behavior. + +For teams evaluating RavenDB in custom line-of-business systems, the sample highlights the pieces that matter in real projects: tenant-aware document access, static indexes for application screens, patch-by-query updates for denormalized references, and tests that run against a real RavenDB database instead of mocked persistence. + +## Features used + + + YABT registers `IDocumentStore` as the application-wide RavenDB entry point and exposes a scoped `IAsyncTenantedDocumentSession` to the domain layer. The session wrapper centralizes tenant behavior instead of spreading tenant filters across every query handler. + + ```csharp + services.AddSingleton(x => + { + var config = x.GetRequiredService(); + return SetupDocumentStore.GetDocumentStore(config, customInit); + }); + + services.AddScoped(x => + new AsyncTenantedDocumentSession(docStore, getCurrentTenantIdFunc)); + ``` + + + + The database project owns the document models and index definitions. Tests create those indexes against RavenDB before exercising domain behavior, so query behavior is verified against the same index model the application uses. + + ```csharp + protected override void PreInitialize(IDocumentStore store) + { + store.PreInitializeDocumentStore(); + store.Conventions.MaxNumberOfRequestsPerSession = 200; + base.PreInitialize(store); + } + + IndexCreation.CreateIndexes(typeof(SetupDocumentStore).Assembly, store); + ``` + + + + YABT queues deferred `IndexQuery` patches and sends them as `PatchByQueryOperation` after saving the main session changes. That pattern is useful when denormalized document references need to stay consistent without loading every matching document into application code. + + ```csharp + while (_deferredPatchQueries.TryDequeue(out var queryIndex)) + { + queryIndex.WaitForNonStaleResults = true; + + await DbSession.Advanced.DocumentStore.Operations.SendAsync( + new PatchByQueryOperation(queryIndex), + DbSession.Advanced.SessionInfo, + token); + } + ``` + + + + The test project uses RavenDB's test driver, creates the application's indexes, and opens no-cache sessions with index-waiting settings. The result is fast feedback while still testing real persistence, indexing, tenant filtering, and query behavior. + + ```csharp + services.AddScoped(c => + { + var docStore = c.GetRequiredService(); + return new AsyncTenantedDocumentSession( + docStore, + GetCurrentTenantId, + TimeSpan.FromSeconds(30), + true, + new SessionOptions { NoCaching = true }); + }); + ``` + + +## Technologies + +- [RavenDB](https://ravendb.net/) +- [ASP.NET Core 8](https://dotnet.microsoft.com/apps/aspnet) +- [.NET 8](https://dotnet.microsoft.com/download) +- [Angular 14](https://angular.io/) +- [Swagger / OpenAPI](https://swagger.io/) + +## Run locally + +1. Check out the repository. +2. Install the .NET 8 SDK. +3. Create a RavenDB database locally, in Docker, or in RavenDB Cloud. +4. Import `documentation/exported_data.ravendbdump`. +5. Set the database name and server URL in `back-end/WebApi/appsettings.Development.json`. +6. Run the `WebAPI` project and open `https://localhost:5001/swagger`. +7. Follow the front-end README to run the Angular UI. + +## Community & Support + +If you spot a bug, have an idea, or want to ask a question, open an issue or pull request in the repository. + +## License + +This project is licensed with the [MIT license](https://github.com/ravendb/samples-yabt/blob/master/LICENSE). + + diff --git a/scripts/handle_redirects.js b/scripts/handle_redirects.js index 944c3bfa65..2c0fb3d6e1 100644 --- a/scripts/handle_redirects.js +++ b/scripts/handle_redirects.js @@ -57,7 +57,11 @@ async function handler(event) { return request; } - if (normalizedUri.startsWith("/guides") || normalizedUri.startsWith("/cloud")) { + if ( + normalizedUri.startsWith("/guides") || + normalizedUri.startsWith("/cloud") || + normalizedUri.startsWith("/samples") + ) { try { const redirectData = await kvsHandle.get(normalizedUri); const redirectJsonValue = JSON.parse(redirectData); diff --git a/sidebarsSamples.js b/sidebarsSamples.js new file mode 100644 index 0000000000..ee2905168e --- /dev/null +++ b/sidebarsSamples.js @@ -0,0 +1,15 @@ +// This file has to be in JavaScript, otherwise @docusaurus/plugin-content-docs doesn't work properly +export default { + guides: [ + { + type: "category", + label: "Samples", + link: { + type: "doc", + id: "home", + }, + collapsible: false, + items: [{ type: "autogenerated", dirName: "." }], + }, + ], +}; diff --git a/sidebarsTemplates.js b/sidebarsTemplates.js index 6b1cfd05f1..7cebde406e 100644 --- a/sidebarsTemplates.js +++ b/sidebarsTemplates.js @@ -41,11 +41,52 @@ export default { }, ], }, + { + type: "category", + label: "Samples authoring", + items: [ + { + type: "doc", + id: "introduction-samples", + label: "Introduction", + }, + { + type: "doc", + id: "new-samples", + label: "Adding new samples", + }, + { + type: "doc", + id: "components-samples", + label: "Components", + }, + { + type: "doc", + id: "filtering-samples", + label: "Filtering", + }, + { + type: "doc", + id: "tags-samples", + label: "Tags", + }, + { + type: "doc", + id: "best-practices-samples", + label: "Best practices", + }, + ], + }, { type: "doc", id: "frames", label: "Frames", }, + { + type: "doc", + id: "gallery-example", + label: "Gallery example", + }, { type: "doc", id: "icon-gallery", diff --git a/src/components/Common/Button.tsx b/src/components/Common/Button.tsx index c26afee97f..484dc9ea88 100644 --- a/src/components/Common/Button.tsx +++ b/src/components/Common/Button.tsx @@ -12,13 +12,14 @@ export interface ButtonProps extends React.ButtonHTMLAttributes = { default: "bg-primary !text-white dark:!text-black hover:bg-primary-darker", - secondary: "bg-gray-300 hover:bg-gray-400 dark:bg-secondary !text-black dark:hover:bg-secondary-darker", + secondary: + "bg-black/10 dark:bg-white/10 !text-black dark:!text-white hover:bg-black/20 dark:hover:bg-white/20 border border-black/10 dark:border-white/10", outline: "border !text-black border-black/25 !text-foreground hover:bg-black/5 dark:!text-white dark:border-white/25 dark:hover:bg-white/5", ghost: "hover:bg-muted !text-foreground", @@ -26,9 +27,8 @@ const variantClasses: Record = { }; const sizeClasses: Record, string> = { - sm: "h-8 px-3 text-xs", - md: "h-10 px-4 text-sm", - lg: "h-12 px-6 text-base", + xs: "py-2 px-3 text-xs", + sm: "py-2 px-3 text-sm", }; export default function Button({ @@ -36,12 +36,12 @@ export default function Button({ url, className = "", variant = "secondary", - size = "md", + size = "sm", iconName, ...props }: ButtonProps) { const baseClasses = clsx( - "inline-flex items-center justify-center rounded-md font-medium", + "cursor-pointer inline-flex items-center justify-center rounded-md font-medium leading-none", "!no-underline !transition-all", "disabled:opacity-50 disabled:pointer-events-none", variantClasses[variant], @@ -53,15 +53,14 @@ export default function Button({ const isExternal = !isInternalUrl(url); return ( - {children} {iconName && } - {isExternal && } + {children} {iconName && } ); } return ( ); } diff --git a/src/components/Common/CardWithIcon.tsx b/src/components/Common/CardWithIcon.tsx index dc6c94b1dc..ea53cb255c 100644 --- a/src/components/Common/CardWithIcon.tsx +++ b/src/components/Common/CardWithIcon.tsx @@ -10,10 +10,9 @@ export interface CardWithIconProps { icon: IconName; description?: ReactNode; url: string; - animationDelay?: number; } -export default function CardWithIcon({ title, icon, description, url, animationDelay = 0 }: CardWithIconProps) { +export default function CardWithIcon({ title, icon, description, url }: CardWithIconProps) { return (
diff --git a/src/components/Common/CardWithImage.tsx b/src/components/Common/CardWithImage.tsx index 79886c4da3..64634d3dcf 100644 --- a/src/components/Common/CardWithImage.tsx +++ b/src/components/Common/CardWithImage.tsx @@ -13,7 +13,7 @@ import Tag from "@site/src/theme/Tag"; export interface CardWithImageProps { title: string; description: ReactNode; - imgSrc?: string | { light: string; dark: string }; + imgSrc?: string; imgAlt?: string; imgWidth?: number; imgHeight?: number; @@ -21,7 +21,6 @@ export interface CardWithImageProps { imgIcon?: IconName; tags?: Array<{ label: string; permalink: string }>; date?: string; - animationDelay?: number; } export default function CardWithImage({ @@ -35,7 +34,6 @@ export default function CardWithImage({ imgIcon, tags = [], date, - animationDelay = 0, }: CardWithImageProps) { const hasImage = Boolean(imgSrc); const hasTags = tags.length > 0; @@ -46,25 +44,20 @@ export default function CardWithImage({ }); return ( -
- +
+
{description}

{(hasTags || hasDate) && ( -
+
{hasTags && (
{visibleTags.map((tag) => ( - + {tag.label} ))} @@ -124,6 +122,7 @@ export default function CardWithImage({ expandTags(); }} title="Show all tags" + className="pointer-events-auto" > +{hiddenCount} more diff --git a/src/components/Common/CardWithImageHorizontal.tsx b/src/components/Common/CardWithImageHorizontal.tsx index 3e4a52817e..3d24dd6521 100644 --- a/src/components/Common/CardWithImageHorizontal.tsx +++ b/src/components/Common/CardWithImageHorizontal.tsx @@ -10,7 +10,7 @@ import { clsx } from "clsx"; export interface CardWithImageHorizontalProps { title: string; description: ReactNode; - imgSrc: string | { light: string; dark: string }; + imgSrc: string; imgAlt?: string; url: string; iconName?: IconName; diff --git a/src/components/Common/Checkbox.tsx b/src/components/Common/Checkbox.tsx new file mode 100644 index 0000000000..7f1e3a59a8 --- /dev/null +++ b/src/components/Common/Checkbox.tsx @@ -0,0 +1,36 @@ +import React, { ChangeEvent } from "react"; +import clsx from "clsx"; +import { Icon } from "./Icon"; + +interface CheckboxProps { + checked: boolean; + onChange: (x: ChangeEvent) => void; + label?: string; + className?: string; +} + +export default function Checkbox({ checked, onChange, label, className }: CheckboxProps) { + return ( + + ); +} diff --git a/src/components/Common/Drawer.tsx b/src/components/Common/Drawer.tsx new file mode 100644 index 0000000000..064870db3f --- /dev/null +++ b/src/components/Common/Drawer.tsx @@ -0,0 +1,99 @@ +import React, { useEffect } from "react"; +import { motion, AnimatePresence, type PanInfo } from "motion/react"; +import clsx from "clsx"; +import { Icon } from "./Icon"; + +interface DrawerProps { + open: boolean; + onClose: () => void; + children: React.ReactNode; + title?: string; + headerAction?: React.ReactNode; +} + +export default function Drawer({ open, onClose, children, title, headerAction }: DrawerProps) { + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === "Escape" && open) { + onClose(); + } + }; + + document.addEventListener("keydown", handleEscape); + return () => document.removeEventListener("keydown", handleEscape); + }, [open, onClose]); + + const handleDragEnd = (_: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => { + const shouldClose = info.velocity.y > 500 || info.offset.y > 150; + if (shouldClose) { + onClose(); + } + }; + + return ( + + {open && ( + <> + + +
+
+
+ + {title && ( +
+

{title}

+
+ {headerAction} + +
+
+ )} + +
+ {children} +
+ + + )} + + ); +} diff --git a/src/components/Common/Gallery.tsx b/src/components/Common/Gallery.tsx new file mode 100644 index 0000000000..91b60d8f62 --- /dev/null +++ b/src/components/Common/Gallery.tsx @@ -0,0 +1,187 @@ +import React, { useRef, useState } from "react"; +import clsx from "clsx"; +import LazyImage from "./LazyImage"; + +export interface GalleryImage { + src: string; + alt?: string; +} + +export interface GalleryProps { + images: GalleryImage[]; + className?: string; +} + +export default function Gallery({ images, className }: GalleryProps) { + const thirdImageRef = useRef(null); + const [currentSlide, setCurrentSlide] = useState(0); + const carouselRef = useRef(null); + + if (!images || images.length === 0) { + return null; + } + + const visibleImages = images.slice(0, 3); + const remainingCount = Math.max(0, images.length - 3); + + const handleOverlayClick = () => { + const imgElement = thirdImageRef.current?.querySelector("img"); + if (imgElement) { + imgElement.click(); + } + }; + + const handleScroll = () => { + if (carouselRef.current) { + const scrollLeft = carouselRef.current.scrollLeft; + const slideWidth = carouselRef.current.offsetWidth; + const newSlide = Math.round(scrollLeft / slideWidth); + setCurrentSlide(newSlide); + } + }; + + return ( + <> +
+
+ {images.map((image, index) => ( +
+ +
+ ))} +
+ {images.length > 1 && ( +
+ {images.map((_, index) => ( +
+ )} +
+ +
+ {visibleImages[0] && ( +
= 3 && "flex-[592]" + )} + > + +
+ )} + {visibleImages.length === 2 && visibleImages[1] && ( +
+ +
+ )} + {visibleImages.length >= 3 && ( +
+ {visibleImages[1] && ( +
+ +
+ )} + + {visibleImages[2] && ( +
+ + {remainingCount > 0 && ( +
+ +{remainingCount} +
+ )} +
+ )} +
+ )} +
+ + {images.length > 3 && ( +
+ {images.slice(3).map((img, index) => ( + + ))} +
+ )} + + ); +} diff --git a/src/components/Common/Icon.tsx b/src/components/Common/Icon.tsx index d7f7f7fc2b..6bc6b28118 100644 --- a/src/components/Common/Icon.tsx +++ b/src/components/Common/Icon.tsx @@ -2,7 +2,7 @@ import React from "react"; import { IconName } from "../../typescript/iconName"; import clsx from "clsx"; -export type IconSize = "xs" | "sm" | "md" | "lg" | "xl" | "2xl"; +export type IconSize = "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl"; export interface IconProps { icon: IconName; @@ -34,6 +34,8 @@ function getSvg(base64String: string, sizeClass: string): string { function getSizeClass(size?: IconSize): `w-${number} h-${number}` { switch (size) { + case "2xs": + return "w-3 h-3"; case "xs": return "w-4 h-4"; case "sm": diff --git a/src/components/Common/LazyImage.tsx b/src/components/Common/LazyImage.tsx index 0b82e8f560..c41ac6d8b8 100644 --- a/src/components/Common/LazyImage.tsx +++ b/src/components/Common/LazyImage.tsx @@ -1,10 +1,11 @@ import React, { useState, useEffect, useRef } from "react"; import clsx from "clsx"; -import ThemedImage, { Props as ThemedImageProps } from "@theme/ThemedImage"; export interface LazyImageProps extends React.ImgHTMLAttributes { - imgSrc?: string | { light: string; dark: string }; + imgSrc?: string; minContentHeight?: number; + isRounded?: boolean; + aspectRatio?: string; } // @docusaurus/plugin-ideal-image transforms image imports into objects like @@ -34,9 +35,13 @@ export default function LazyImage({ className, style, minContentHeight = 100, + isRounded = true, + loading = "lazy", + aspectRatio, ...props }: LazyImageProps) { - const [isLoaded, setIsLoaded] = useState(false); + const isEager = loading === "eager"; + const [isLoaded, setIsLoaded] = useState(isEager); const imgRef = useRef(null); // Check if image is already loaded after hydration @@ -46,44 +51,40 @@ export default function LazyImage({ } }, []); - const sources = getSources({ imgSrc, src }); + const imageSrc = src || toUrl(imgSrc); + + if (!imageSrc) { + return null; + } return ( - {!isLoaded && ); } - -function getSources({ imgSrc, src }: Pick): ThemedImageProps["sources"] { - if (src) { - return { light: src, dark: src }; - } - - if (imgSrc && typeof imgSrc === "object" && "light" in imgSrc && "dark" in imgSrc) { - return imgSrc; - } - - const resolved = toUrl(imgSrc); - if (resolved) { - return { light: resolved, dark: resolved }; - } - - return imgSrc; -} diff --git a/src/components/Common/Toggle.tsx b/src/components/Common/Toggle.tsx new file mode 100644 index 0000000000..ef157f86ca --- /dev/null +++ b/src/components/Common/Toggle.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import clsx from "clsx"; + +interface ToggleOption { + value: T; + label: string; +} + +interface ToggleProps { + options: ToggleOption[]; + value: T; + onChange: (value: T) => void; + className?: string; +} + +export default function Toggle({ options, value, onChange, className }: ToggleProps) { + return ( +
+ {options.map((option) => ( + + ))} +
+ ); +} diff --git a/src/components/Guides/FeaturedGuides.tsx b/src/components/Guides/FeaturedGuides.tsx index b1ac008ce2..fb6a603fbc 100644 --- a/src/components/Guides/FeaturedGuides.tsx +++ b/src/components/Guides/FeaturedGuides.tsx @@ -33,16 +33,15 @@ export default function FeaturedGuides({ guidesTitles }: FeaturedGuidesProps) { Featured guides
- {featuredGuides.map((guide, index) => ( + {featuredGuides.map((guide) => ( ))}
diff --git a/src/components/Guides/RecentGuides.tsx b/src/components/Guides/RecentGuides.tsx index b079aefb23..1dda02d565 100644 --- a/src/components/Guides/RecentGuides.tsx +++ b/src/components/Guides/RecentGuides.tsx @@ -38,7 +38,7 @@ export default function RecentGuides() { .filter((doc) => doc.id !== "home") .map((doc: any) => ({ title: doc.title || doc.id, - url: doc.externalUrl || doc.permalink, + url: doc.external_url || doc.permalink, tags: doc.tags || [], time: doc.lastUpdatedAt ? getRelativeTime(doc.lastUpdatedAt) : "Recently", lastUpdatedAt: doc.lastUpdatedAt || 0, diff --git a/src/components/IconGallery/IconGalleryCard.tsx b/src/components/IconGallery/IconGalleryCard.tsx index 1939cdbacf..e95dae7dab 100644 --- a/src/components/IconGallery/IconGalleryCard.tsx +++ b/src/components/IconGallery/IconGalleryCard.tsx @@ -15,7 +15,6 @@ export default function IconGalleryCard({ iconName }: IconGalleryCardProps) { setCopied(true); window.setTimeout(() => setCopied(false), 2000); } catch (err) { - // eslint-disable-next-line no-console console.error("Failed to copy icon name to clipboard:", err); } }; diff --git a/src/components/LanguageSwitcher.tsx b/src/components/LanguageSwitcher.tsx index 3ac46a6b19..7fefef3f7a 100644 --- a/src/components/LanguageSwitcher.tsx +++ b/src/components/LanguageSwitcher.tsx @@ -1,21 +1,8 @@ import React, { useEffect } from "react"; import { useLanguage, type DocsLanguage } from "./LanguageStore"; +import { languageConfig } from "./languageConfig"; import clsx from "clsx"; -interface LanguageOption { - label: string; - value: DocsLanguage; - brand: string; -} - -const languageOptions: LanguageOption[] = [ - { label: "C#", value: "csharp", brand: "#9179E4" }, - { label: "Java", value: "java", brand: "#f89820" }, - { label: "Python", value: "python", brand: "#fbcb24" }, - { label: "PHP", value: "php", brand: "#8993be" }, - { label: "Node.js", value: "nodejs", brand: "#3c873a" }, -]; - type LanguageSwitcherProps = { supportedLanguages: DocsLanguage[]; flush?: boolean; @@ -35,7 +22,7 @@ export default function LanguageSwitcher({ supportedLanguages, flush = false }: return (
- {languageOptions + {languageConfig .filter((lang) => supportedLanguages.includes(lang.value)) .map((lang) => { const isActive = language === lang.value; diff --git a/src/components/MarkdownImageLightbox.tsx b/src/components/MarkdownImageLightbox.tsx index 7bbdcc1fa3..48d48a0de9 100644 --- a/src/components/MarkdownImageLightbox.tsx +++ b/src/components/MarkdownImageLightbox.tsx @@ -1,9 +1,11 @@ import React, { useEffect, useState } from "react"; +import { useLocation } from "@docusaurus/router"; import Lightbox from "yet-another-react-lightbox"; -import "yet-another-react-lightbox/styles.css"; import Captions from "yet-another-react-lightbox/plugins/captions"; -import { useLocation } from "@docusaurus/router"; -import { Share } from "yet-another-react-lightbox/plugins"; +import Download from "yet-another-react-lightbox/plugins/download"; +import Zoom from "yet-another-react-lightbox/plugins/zoom"; +import "yet-another-react-lightbox/styles.css"; +import "yet-another-react-lightbox/plugins/captions.css"; type Slide = { src: string; description?: string }; type CandidateElement = HTMLDivElement | HTMLImageElement; @@ -132,11 +134,11 @@ export default function MarkdownImageLightbox() { close={() => setOpen(false)} index={currentIndex} slides={slides} - plugins={[Share, Captions]} + plugins={[Download, Captions, Zoom]} captions={{ - descriptionTextAlign: "center", + descriptionTextAlign: "start", descriptionMaxLines: 2, - showToggle: false, + showToggle: true, }} /> ); diff --git a/src/components/Samples/Hub/Partials/FilterCategory.tsx b/src/components/Samples/Hub/Partials/FilterCategory.tsx new file mode 100644 index 0000000000..52e559d8b3 --- /dev/null +++ b/src/components/Samples/Hub/Partials/FilterCategory.tsx @@ -0,0 +1,106 @@ +import React from "react"; +import { motion, AnimatePresence } from "motion/react"; +import { Icon } from "@site/src/components/Common/Icon"; +import Checkbox from "@site/src/components/Common/Checkbox"; +import useBoolean from "@site/src/hooks/useBoolean"; + +interface FilterTag { + key: string; + label: string; +} + +interface FilterCategoryProps { + name: string; + label: string; + tags: FilterTag[]; + selectedTags: Set; + onTagToggle: (tagKey: string) => void; + isExpanded: boolean; + onToggleExpanded: () => void; +} + +export default function FilterCategory({ + label, + tags, + selectedTags, + onTagToggle, + isExpanded, + onToggleExpanded, +}: FilterCategoryProps) { + const { value: isTagsExpanded, setTrue: expandTags, setFalse: collapseTags } = useBoolean(false); + const [manuallyCollapsed, setManuallyCollapsed] = React.useState(false); + + const hasSelectedHiddenTag = tags.length > 5 && tags.slice(5).some((tag) => selectedTags.has(tag.key)); + const showAllTags = isTagsExpanded || (!manuallyCollapsed && hasSelectedHiddenTag); + const visibleTags = showAllTags ? tags : tags.slice(0, 5); + const hiddenCount = Math.max(0, tags.length - 5); + + const handleToggleTagsExpanded = () => { + if (showAllTags) { + setManuallyCollapsed(true); + collapseTags(); + } else { + setManuallyCollapsed(false); + expandTags(); + } + }; + + return ( +
+ + + + {isExpanded && ( + +
+ {visibleTags.map((tag) => ( + onTagToggle(tag.key)} + label={tag.label} + /> + ))} + + {hiddenCount > 0 && ( + + )} +
+
+ )} +
+
+ ); +} diff --git a/src/components/Samples/Hub/Partials/LanguageTag.tsx b/src/components/Samples/Hub/Partials/LanguageTag.tsx new file mode 100644 index 0000000000..409ffa5f5c --- /dev/null +++ b/src/components/Samples/Hub/Partials/LanguageTag.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import Tag from "@site/src/theme/Tag"; +import { getLanguageConfig } from "../../../languageConfig"; + +export interface LanguageTagProps { + languageKey: string; + className?: string; + onClick?: (e: React.MouseEvent) => void; +} + +export default function LanguageTag({ languageKey, className, onClick }: LanguageTagProps) { + const config = getLanguageConfig(languageKey); + + if (!config) { + return ( + + {languageKey} + + ); + } + + return ( + + {config.label} + + ); +} diff --git a/src/components/Samples/Hub/Partials/SampleCard.tsx b/src/components/Samples/Hub/Partials/SampleCard.tsx new file mode 100644 index 0000000000..62ab8252bf --- /dev/null +++ b/src/components/Samples/Hub/Partials/SampleCard.tsx @@ -0,0 +1,166 @@ +import React, { ReactNode } from "react"; +import Link from "@docusaurus/Link"; +import Heading from "@theme/Heading"; +import LazyImage from "@site/src/components/Common/LazyImage"; +import clsx from "clsx"; +import Tag from "@site/src/theme/Tag"; +import LanguageTag from "@site/src/components/Samples/Hub/Partials/LanguageTag"; + +interface TagWithCategory { + label: string; + key: string; + category?: string; +} + +export interface SampleCardProps { + title: string; + description: ReactNode; + imgSrc?: string; + imgAlt?: string; + imgWidth?: number; + imgHeight?: number; + url: string; + tags?: TagWithCategory[]; + onTagClick?: (tagKey: string) => void; + selectedTags?: Set; +} + +export default function SampleCard({ + title, + description, + imgSrc, + imgAlt = "", + url, + tags = [], + onTagClick, + selectedTags, +}: SampleCardProps) { + const hasImage = Boolean(imgSrc); + + const challengesSolutionsTags = tags.filter((t) => t.category === "challenges-solutions"); + const featureTags = tags.filter((t) => t.category === "feature"); + const techStackTags = tags.filter((t) => t.category === "tech-stack"); + + const languageTags = techStackTags.filter((t) => ["csharp", "java", "python", "php", "nodejs"].includes(t.key)); + + const handleTagClick = (e: React.MouseEvent, tag: TagWithCategory) => { + e.preventDefault(); + e.stopPropagation(); + onTagClick?.(tag.key); + }; + + const isTagSelected = (tag: TagWithCategory) => { + if (!selectedTags || selectedTags.size === 0) { + return true; + } + return selectedTags.has(tag.key); + }; + + return ( +
+
+ {hasImage && ( + <> + + {languageTags.length > 0 && ( +
+ {languageTags.map((tag) => ( + handleTagClick(e, tag) : undefined} + className={clsx(!isTagSelected(tag) && "opacity-50")} + /> + ))} +
+ )} + + )} +
+
+ +
+
+ + {title} + +
+ +

{description}

+ +
+ {challengesSolutionsTags.length > 0 && ( +
+ Challenges & Solutions +
+ {challengesSolutionsTags.map((tag) => ( + handleTagClick(e, tag) : undefined} + className={clsx( + onTagClick && "cursor-pointer", + !isTagSelected(tag) && "opacity-50" + )} + > + {tag.label} + + ))} +
+
+ )} + + {featureTags.length > 0 && ( +
+ Features +
+ {featureTags.map((tag) => ( + handleTagClick(e, tag) : undefined} + className={clsx( + onTagClick && "cursor-pointer", + !isTagSelected(tag) && "opacity-50" + )} + > + {tag.label} + + ))} +
+
+ )} +
+
+
+
+ ); +} diff --git a/src/components/Samples/Hub/Partials/SamplesDecoration.tsx b/src/components/Samples/Hub/Partials/SamplesDecoration.tsx new file mode 100644 index 0000000000..a17bb052ca --- /dev/null +++ b/src/components/Samples/Hub/Partials/SamplesDecoration.tsx @@ -0,0 +1,547 @@ +import { useState, useEffect } from "react"; + +const defaultColors = { + lightest: "#86BAF2", + darkest: "#2382E7", + lighter: "#5FA4ED", +}; + +export default function SamplesDecoration() { + const [colors, setColors] = useState(defaultColors); + + useEffect(() => { + const getColorValue = (varName: string, fallback: string): string => { + const value = getComputedStyle(document.documentElement).getPropertyValue(varName).trim(); + return value || fallback; + }; + + const updateColors = () => { + setColors({ + lightest: getColorValue("--ifm-color-primary-lightest", defaultColors.lightest), + darkest: getColorValue("--ifm-color-primary-darkest", defaultColors.darkest), + lighter: getColorValue("--ifm-color-primary-lighter", defaultColors.lighter), + }); + }; + + updateColors(); + + const observer = new MutationObserver(updateColors); + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ["data-theme"], + }); + + return () => observer.disconnect(); + }, []); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/components/Samples/Hub/Partials/SamplesFilter.tsx b/src/components/Samples/Hub/Partials/SamplesFilter.tsx new file mode 100644 index 0000000000..e96275a0e5 --- /dev/null +++ b/src/components/Samples/Hub/Partials/SamplesFilter.tsx @@ -0,0 +1,144 @@ +import React, { useState, useMemo } from "react"; +import Heading from "@theme/Heading"; +import clsx from "clsx"; +import Toggle from "@site/src/components/Common/Toggle"; +import FilterCategory from "./FilterCategory"; +import Button from "@site/src/components/Common/Button"; + +interface FilterTag { + key: string; + label: string; +} + +interface FilterCategoryData { + name: string; + label: string; + tags: FilterTag[]; +} + +interface SamplesFilterProps { + categories: FilterCategoryData[]; + selectedTags: Set; + onTagToggle: (tagKey: string) => void; + matchLogic: "any" | "all"; + onMatchLogicChange: (logic: "any" | "all") => void; + onClearFilters?: () => void; + showHeader?: boolean; +} + +export default function SamplesFilter({ + categories, + selectedTags, + onTagToggle, + matchLogic, + onMatchLogicChange, + onClearFilters, + showHeader = true, +}: SamplesFilterProps) { + const [expandedCategories, setExpandedCategories] = useState>(new Set(categories.map((c) => c.name))); + const [manuallyCollapsed, setManuallyCollapsed] = useState>(new Set()); + const [searchQuery, setSearchQuery] = useState(""); + + const effectiveExpanded = useMemo(() => { + const result = new Set(expandedCategories); + categories.forEach((category) => { + const hasSelectedTag = category.tags.some((tag) => selectedTags.has(tag.key)); + if (hasSelectedTag && !manuallyCollapsed.has(category.name)) { + result.add(category.name); + } + }); + return result; + }, [expandedCategories, categories, selectedTags, manuallyCollapsed]); + + const toggleCategory = (categoryName: string) => { + const newExpanded = new Set(expandedCategories); + const newManuallyCollapsed = new Set(manuallyCollapsed); + + if (effectiveExpanded.has(categoryName)) { + newExpanded.delete(categoryName); + newManuallyCollapsed.add(categoryName); + } else { + newExpanded.add(categoryName); + newManuallyCollapsed.delete(categoryName); + } + setExpandedCategories(newExpanded); + setManuallyCollapsed(newManuallyCollapsed); + }; + + const filteredCategories = categories + .map((category) => ({ + ...category, + tags: category.tags.filter((tag) => tag.label.toLowerCase().includes(searchQuery.toLowerCase())), + })) + .filter((category) => category.tags.length > 0); + + const handleClearAll = () => { + setSearchQuery(""); + onMatchLogicChange("any"); + onClearFilters?.(); + }; + + const hasActiveFilters = selectedTags.size > 0 || searchQuery.length > 0 || matchLogic !== "any"; + + return ( +
+ {showHeader && ( +
+ + Filters + + {hasActiveFilters && ( + + )} +
+ )} +
+ setSearchQuery(e.target.value)} + className={clsx( + "px-3 py-2 rounded-full", + "border border-black/10 dark:border-white/10", + "text-sm text-black dark:text-white", + "placeholder-black/30 dark:placeholder-white/30" + )} + /> +
+ +
+ + Match logic + + +
+ + {filteredCategories.length > 0 ? ( + filteredCategories.map((category) => ( + toggleCategory(category.name)} + /> + )) + ) : ( +
Nothing found
+ )} +
+ ); +} diff --git a/src/components/Samples/Hub/Partials/SamplesGrid.tsx b/src/components/Samples/Hub/Partials/SamplesGrid.tsx new file mode 100644 index 0000000000..ece052a973 --- /dev/null +++ b/src/components/Samples/Hub/Partials/SamplesGrid.tsx @@ -0,0 +1,73 @@ +import React, { useMemo } from "react"; +import clsx from "clsx"; +import { SampleCard } from "@site/src/components/Samples"; +import Heading from "@theme/Heading"; +import type { Sample } from "../../types"; + +interface SamplesGridProps { + samples: Sample[]; + selectedTags: Set; + matchLogic: "any" | "all"; + onTagClick?: (tagKey: string) => void; +} + +export default function SamplesGrid({ samples, selectedTags, matchLogic, onTagClick }: SamplesGridProps) { + const filteredSamples = useMemo(() => { + if (selectedTags.size === 0) { + return samples; + } + + return samples.filter((sample) => { + const sampleTagKeys = new Set(sample.tags.map((t) => t.key)); + + if (matchLogic === "any") { + return Array.from(selectedTags).some((tag) => sampleTagKeys.has(tag)); + } else { + return Array.from(selectedTags).every((tag) => sampleTagKeys.has(tag)); + } + }); + }, [samples, selectedTags, matchLogic]); + + if (filteredSamples.length === 0) { + return ( +
+

No samples found matching your filters.

+
+ ); + } + + return ( +
+
+ + Samples + + + {filteredSamples.length} + +
+
+ {filteredSamples.map((sample) => ( + + ))} +
+
+ ); +} diff --git a/src/components/Samples/Hub/Partials/SamplesHeader.tsx b/src/components/Samples/Hub/Partials/SamplesHeader.tsx new file mode 100644 index 0000000000..cbe3fa2452 --- /dev/null +++ b/src/components/Samples/Hub/Partials/SamplesHeader.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import Heading from "@theme/Heading"; +import clsx from "clsx"; +import SamplesDecoration from "./SamplesDecoration"; + +export default function SamplesHeader() { + return ( +
+
+ + Explore Samples + +

Production-ready code samples, architecture patterns, and starter kits.

+
+ +
+ ); +} diff --git a/src/components/Samples/Hub/SamplesHomePage.tsx b/src/components/Samples/Hub/SamplesHomePage.tsx new file mode 100644 index 0000000000..827b434943 --- /dev/null +++ b/src/components/Samples/Hub/SamplesHomePage.tsx @@ -0,0 +1,219 @@ +import React, { useState, useMemo, useEffect } from "react"; +import { SamplesHeader, SamplesFilter, SamplesGrid } from "@site/src/components/Samples"; +import { usePluginData } from "@docusaurus/useGlobalData"; +import { useHistory, useLocation } from "@docusaurus/router"; +import Head from "@docusaurus/Head"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import type { Tag, PluginData } from "../types"; +import Drawer from "@site/src/components/Common/Drawer"; +import useBoolean from "@site/src/hooks/useBoolean"; +import Button from "@site/src/components/Common/Button"; +import clsx from "clsx"; + +const categoryLabels: Record = { + "tech-stack": "Tech Stack", + "challenges-solutions": "Challenges & Solutions", + feature: "Features", + other: "Other", +}; + +export default function SamplesHomePage() { + const pluginData = usePluginData("recent-samples-plugin") as PluginData | undefined; + const history = useHistory(); + const location = useLocation(); + + const [selectedTags, setSelectedTags] = useState>(() => { + const params = new URLSearchParams(location.search); + const tagsParam = params.get("tags"); + return tagsParam ? new Set(tagsParam.split(",")) : new Set(); + }); + + const [matchLogic, setMatchLogic] = useState<"any" | "all">(() => { + const params = new URLSearchParams(location.search); + const logicParam = params.get("match"); + return logicParam === "all" ? "all" : "any"; + }); + + const samples = pluginData?.samples || []; + const allTags = pluginData?.tags || []; + + const categories = useMemo(() => { + const categoryMap: Record = {}; + + allTags.forEach((tag) => { + const category = tag.category || "other"; + if (!categoryMap[category]) { + categoryMap[category] = { + name: category, + label: categoryLabels[category] || "Other", + tags: [], + }; + } + categoryMap[category].tags.push({ + key: tag.key, + label: tag.label, + count: tag.count, + }); + }); + + return Object.values(categoryMap); + }, [allTags]); + + useEffect(() => { + const params = new URLSearchParams(); + + if (selectedTags.size > 0) { + params.set("tags", Array.from(selectedTags).join(",")); + } + + if (matchLogic !== "any") { + params.set("match", matchLogic); + } + + const newSearch = params.toString(); + const currentSearch = location.search.replace(/^\?/, ""); + + if (newSearch !== currentSearch) { + history.replace({ + pathname: location.pathname, + search: newSearch ? `?${newSearch}` : "", + }); + } + }, [selectedTags, matchLogic, history, location.pathname, location.search]); + + const handleTagToggle = (tagKey: string) => { + const newSelected = new Set(selectedTags); + if (newSelected.has(tagKey)) { + newSelected.delete(tagKey); + } else { + newSelected.add(tagKey); + } + setSelectedTags(newSelected); + }; + + const { value: isFilterDrawerOpen, setTrue: openFilterDrawer, setFalse: closeFilterDrawer } = useBoolean(false); + + const hasActiveFilters = selectedTags.size > 0 || matchLogic !== "any"; + + const { siteConfig } = useDocusaurusContext(); + const siteUrl = siteConfig.url.replace(/\/$/, ""); + const samplesUrl = `${siteUrl}/samples`; + + const filteredSamples = samples.filter((s) => s.id !== "home"); + + const collectionPageJsonLd = JSON.stringify({ + "@context": "https://schema.org", + "@type": "CollectionPage", + "@id": samplesUrl, + name: "RavenDB Code Samples", + description: "Production-ready code samples, architecture patterns, and starter kits built with RavenDB.", + url: samplesUrl, + isPartOf: { "@type": "WebSite", "@id": `${siteUrl}/` }, + breadcrumb: { + "@type": "BreadcrumbList", + itemListElement: [ + { + "@type": "ListItem", + position: 1, + name: "RavenDB Documentation", + item: `${siteUrl}/`, + }, + { + "@type": "ListItem", + position: 2, + name: "Samples", + item: samplesUrl, + }, + ], + }, + mainEntity: { + "@type": "ItemList", + name: "RavenDB Code Samples", + numberOfItems: filteredSamples.length, + itemListElement: filteredSamples.map((sample, index) => ({ + "@type": "ListItem", + position: index + 1, + url: `${siteUrl}${sample.permalink}`, + name: sample.title, + ...(sample.description ? { description: sample.description } : {}), + })), + }, + }); + + return ( + <> + + + + +
+ +
+ +
+
+ setSelectedTags(new Set())} + /> +
+ + { + setSelectedTags(new Set()); + setMatchLogic("any"); + }} + variant="secondary" + size="xs" + > + Clear all + + ) + } + > +
+ setSelectedTags(new Set())} + showHeader={false} + /> + +
+
+ +
+ +
+
+ + ); +} diff --git a/src/components/Samples/Overview/Partials/ActionsCard.tsx b/src/components/Samples/Overview/Partials/ActionsCard.tsx new file mode 100644 index 0000000000..3860d5ce6f --- /dev/null +++ b/src/components/Samples/Overview/Partials/ActionsCard.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import clsx from "clsx"; +import Button from "@site/src/components/Common/Button"; + +export interface ActionsCardProps { + className?: string; + githubUrl?: string; + demoUrl?: string; +} + +export default function ActionsCard({ className, githubUrl, demoUrl }: ActionsCardProps) { + return ( +
+
+ {githubUrl && ( + + )} + {demoUrl && ( + + )} +
+
+ ); +} diff --git a/src/components/Samples/Overview/Partials/FeatureAccordion.tsx b/src/components/Samples/Overview/Partials/FeatureAccordion.tsx new file mode 100644 index 0000000000..e69423583a --- /dev/null +++ b/src/components/Samples/Overview/Partials/FeatureAccordion.tsx @@ -0,0 +1,78 @@ +import React, { ReactNode } from "react"; +import clsx from "clsx"; +import { motion } from "motion/react"; +import { Icon } from "@site/src/components/Common/Icon"; +import { IconName } from "@site/src/typescript/iconName"; +import useBoolean from "@site/src/hooks/useBoolean"; + +export interface FeatureAccordionProps { + className?: string; + title: string; + description: string; + icon?: IconName; + children?: ReactNode; + defaultExpanded?: boolean; +} + +export default function FeatureAccordion({ + className, + title, + description, + icon = "link", + children, + defaultExpanded = false, +}: FeatureAccordionProps) { + const { value: isExpanded, toggle } = useBoolean(defaultExpanded); + + return ( +
+ + {children && ( +
+
+
+ {children} +
+
+
+ )} +
+ ); +} diff --git a/src/components/Samples/Overview/Partials/RelatedResource.tsx b/src/components/Samples/Overview/Partials/RelatedResource.tsx new file mode 100644 index 0000000000..5d8d51d9dc --- /dev/null +++ b/src/components/Samples/Overview/Partials/RelatedResource.tsx @@ -0,0 +1,94 @@ +import React from "react"; +import clsx from "clsx"; +import { Icon } from "@site/src/components/Common/Icon"; +import { IconName } from "@site/src/typescript/iconName"; +import Link from "@docusaurus/Link"; +import { useLatestVersion } from "@site/src/hooks/useLatestVersion"; + +type ResourceType = "guide" | "documentation" | "video"; + +type DocumentationType = "docs" | "cloud"; + +export interface RelatedResourceProps { + className?: string; + type: ResourceType; + documentationType?: DocumentationType; + subtitle: string; + articleKey?: string; + externalUrl?: string; +} + +const TYPE_CONFIG: Record = { + guide: { + title: "Guide", + icon: "guides", + }, + documentation: { + title: "Documentation", + icon: "database", + }, + video: { + title: "Video Walkthrough", + icon: "play", + }, +}; + +export default function RelatedResource({ + className, + type, + documentationType = "docs", + subtitle, + articleKey, + externalUrl, +}: RelatedResourceProps) { + const latestVersion = useLatestVersion() as string; + const config = TYPE_CONFIG[type]; + + const url = React.useMemo(() => { + if (externalUrl) { + return externalUrl; + } + + if (type === "guide") { + return `/guides/${articleKey}`; + } + + const basePath = documentationType === "cloud" ? "/cloud" : `/${latestVersion}`; + return `${basePath}/${articleKey}`; + }, [type, documentationType, articleKey, externalUrl, latestVersion]); + + const icon = type === "documentation" && documentationType === "cloud" ? "cloud" : config.icon; + + return ( + +
+ +
+
+

+ {config.title} +

+

+ {subtitle} +

+
+ + ); +} diff --git a/src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx b/src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx new file mode 100644 index 0000000000..46e6cfd1bd --- /dev/null +++ b/src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx @@ -0,0 +1,160 @@ +import React, { useMemo } from "react"; +import { useDoc } from "@docusaurus/plugin-content-docs/client"; +import { usePluginData } from "@docusaurus/useGlobalData"; +import clsx from "clsx"; +import Heading from "@theme/Heading"; +import Tag from "@site/src/theme/Tag"; +import type { PluginData } from "@site/src/components/Samples/types"; +import ActionsCard from "./ActionsCard"; +import RelatedResource from "./RelatedResource"; + +export interface SampleMetadataColumnProps { + className?: string; +} + +interface TagData { + [key: string]: { + label: string; + }; +} + +function createFilterUrl(tagKey: string): string { + return `/samples?tags=${tagKey}`; +} + +function getTagsWithLabels(tagKeys: string[] | undefined, tagData: TagData) { + if (!tagKeys || tagKeys.length === 0) { + return []; + } + + return tagKeys.map((key) => ({ + key, + label: tagData[key]?.label || key, + })); +} + +interface TagSectionProps { + title: string; + tags: Array<{ key: string; label: string }>; +} + +function TagSection({ title, tags }: TagSectionProps) { + if (tags.length === 0) { + return null; + } + + return ( +
+ + {title} + +
+ {tags.map((tag) => ( + + {tag.label} + + ))} +
+
+ ); +} + +export default function SampleMetadataColumn({ className }: SampleMetadataColumnProps) { + const { frontMatter } = useDoc(); + const pluginData = usePluginData("recent-samples-plugin") as PluginData | undefined; + + const { challengesSolutionsTagsData, featureTagsData, techStackTagsData } = useMemo(() => { + const allTags = pluginData?.tags || []; + + const challengesSolutionsTags: TagData = {}; + const featureTags: TagData = {}; + const techStackTags: TagData = {}; + + allTags.forEach((tag) => { + const tagEntry = { label: tag.label }; + if (tag.category === "challenges-solutions") { + challengesSolutionsTags[tag.key] = tagEntry; + } else if (tag.category === "feature") { + featureTags[tag.key] = tagEntry; + } else if (tag.category === "tech-stack") { + techStackTags[tag.key] = tagEntry; + } + }); + + return { + challengesSolutionsTagsData: challengesSolutionsTags, + featureTagsData: featureTags, + techStackTagsData: techStackTags, + }; + }, [pluginData]); + + const challengesSolutionsTagKeys = frontMatter.challenges_solutions_tags; + const featureTagKeys = frontMatter.feature_tags; + const techStackTagKeys = frontMatter.tech_stack_tags; + const category = frontMatter.category; + const license = frontMatter.license; + const licenseUrl = frontMatter.license_url; + const repositoryUrl = frontMatter.repository_url; + const demoUrl = frontMatter.demo_url; + const relatedResourceItems = frontMatter.related_resources; + + const challengesSolutionsTags = getTagsWithLabels(challengesSolutionsTagKeys, challengesSolutionsTagsData); + const featureTags = getTagsWithLabels(featureTagKeys, featureTagsData); + const techStackTags = getTagsWithLabels(techStackTagKeys, techStackTagsData); + + return ( +
+ {(repositoryUrl || demoUrl) && } + + + + + + {category && ( +
+ + Category + +

{category}

+
+ )} + + {license && ( +
+ + License + +

+ {licenseUrl ? ( + + {license} + + ) : ( + license + )} +

+
+ )} + + {relatedResourceItems && relatedResourceItems.length > 0 && ( +
+ + Related Resources + +
+ {relatedResourceItems.map((resource, index) => ( + + ))} +
+
+ )} +
+ ); +} diff --git a/src/components/Samples/Overview/SampleLayout.tsx b/src/components/Samples/Overview/SampleLayout.tsx new file mode 100644 index 0000000000..5801fe01da --- /dev/null +++ b/src/components/Samples/Overview/SampleLayout.tsx @@ -0,0 +1,26 @@ +import React, { ReactNode } from "react"; +import SampleMetadataColumn from "./Partials/SampleMetadataColumn"; +import Gallery from "@site/src/components/Common/Gallery"; +import { useDoc } from "@docusaurus/plugin-content-docs/client"; + +interface SampleLayoutProps { + children: ReactNode; +} + +export default function SampleLayout({ children }: SampleLayoutProps) { + const { frontMatter } = useDoc(); + const gallery = frontMatter.gallery?.length ? : null; + + return ( +
+
+
{gallery}
+ {children} +
+
+
{gallery}
+ +
+
+ ); +} diff --git a/src/components/Samples/index.ts b/src/components/Samples/index.ts new file mode 100644 index 0000000000..10837552e1 --- /dev/null +++ b/src/components/Samples/index.ts @@ -0,0 +1,15 @@ +// Sample overview page components +export { default as ActionsCard } from "./Overview/Partials/ActionsCard"; +export { default as RelatedResource } from "./Overview/Partials/RelatedResource"; +export { default as FeatureAccordion } from "./Overview/Partials/FeatureAccordion"; +export { default as SampleMetadataColumn } from "./Overview/Partials/SampleMetadataColumn"; +export { default as SampleLayout } from "./Overview/SampleLayout"; + +// Samples hub components +export { default as SampleCard } from "./Hub/Partials/SampleCard"; +export { default as SamplesGrid } from "./Hub/Partials/SamplesGrid"; +export { default as SamplesFilter } from "./Hub/Partials/SamplesFilter"; +export { default as FilterCategory } from "./Hub/Partials/FilterCategory"; +export { default as SamplesHeader } from "./Hub/Partials/SamplesHeader"; +export { default as SamplesDecoration } from "./Hub/Partials/SamplesDecoration"; +export { default as SamplesHomePage } from "./Hub/SamplesHomePage"; diff --git a/src/components/Samples/types.ts b/src/components/Samples/types.ts new file mode 100644 index 0000000000..a49af9fc8b --- /dev/null +++ b/src/components/Samples/types.ts @@ -0,0 +1,21 @@ +export interface Tag { + key: string; + label: string; + category?: string; + count?: number; +} + +export interface Sample { + id: string; + title: string; + description?: string; + permalink: string; + image?: string; + img_alt?: string; + tags: Array<{ label: string; key: string; category?: string }>; +} + +export interface PluginData { + samples: Sample[]; + tags: Tag[]; +} diff --git a/src/components/SeeAlso/types.ts b/src/components/SeeAlso/types.ts index 8625ff80ba..7f6d0c9774 100644 --- a/src/components/SeeAlso/types.ts +++ b/src/components/SeeAlso/types.ts @@ -1,6 +1,6 @@ export interface SeeAlsoItemType { title: string; link: string; - source: "docs" | "cloud" | "guides" | "external"; + source: "docs" | "cloud" | "guides" | "samples" | "external"; path: string; } diff --git a/src/components/SeeAlso/useVersionedLink.ts b/src/components/SeeAlso/useVersionedLink.ts index e0c96ae566..2aba099d07 100644 --- a/src/components/SeeAlso/useVersionedLink.ts +++ b/src/components/SeeAlso/useVersionedLink.ts @@ -35,14 +35,17 @@ export function useVersionedLink() { return link; } - const isUnversionedSection = /^\/(cloud|guides|templates)(\/|$)/.test(link); + const isUnversionedSection = /^\/(cloud|guides|samples|templates)(\/|$)/.test(link); if (isUnversionedSection) { return link; } const versionToUse = - currentPluginId === "cloud" || currentPluginId === "guides" || currentPluginId === "templates" + currentPluginId === "cloud" || + currentPluginId === "guides" || + currentPluginId === "samples" || + currentPluginId === "templates" ? latestVersion : currentVersionPath; diff --git a/src/components/SeeAlso/utils.ts b/src/components/SeeAlso/utils.ts index e1c0d510d9..00770d93ef 100644 --- a/src/components/SeeAlso/utils.ts +++ b/src/components/SeeAlso/utils.ts @@ -9,6 +9,8 @@ export const getIconName = (source: SeeAlsoItemType["source"]): IconName => { return "cloud"; case "guides": return "guides"; + case "samples": + return "code"; case "external": return "newtab"; default: diff --git a/src/components/languageConfig.ts b/src/components/languageConfig.ts new file mode 100644 index 0000000000..96b08f23d2 --- /dev/null +++ b/src/components/languageConfig.ts @@ -0,0 +1,23 @@ +import type { DocsLanguage } from "./LanguageStore"; + +export interface LanguageConfig { + label: string; + value: DocsLanguage; + brand: string; +} + +export const languageConfig: LanguageConfig[] = [ + { label: "C#", value: "csharp", brand: "#9179E4" }, + { label: "Java", value: "java", brand: "#f89820" }, + { label: "Python", value: "python", brand: "#fbcb24" }, + { label: "PHP", value: "php", brand: "#8993be" }, + { label: "Node.js", value: "nodejs", brand: "#3c873a" }, +]; + +export function getLanguageConfig(languageKey: string): LanguageConfig | undefined { + return languageConfig.find((lang) => lang.value === languageKey); +} + +export function getLanguageBrandColor(languageKey: string): string | undefined { + return getLanguageConfig(languageKey)?.brand; +} diff --git a/src/css/custom.css b/src/css/custom.css index 06d7a3470c..8eb2f123f3 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -10,29 +10,18 @@ } } -/* Custom animations for layout switcher */ -@keyframes slide-in-from-bottom { - from { - opacity: 0; - transform: translateY(16px); - } - to { - opacity: 1; - transform: translateY(0); - } +/* Prevent body scroll when drawer is open */ +body:has([data-drawer-open="true"]) { + overflow: hidden; } -@keyframes slide-in-from-left { - from { - opacity: 0; - transform: translateX(-16px); - } - to { - opacity: 1; - transform: translateX(0); - } +/* Hide TOC column for sample pages */ +.plugin-id-samples .col.col--3 { + display: none !important; } +/* Custom animations for layout switcher */ + @keyframes fade-in { from { opacity: 0; @@ -48,14 +37,7 @@ .fade-in { animation-name: fade-in; -} - -.slide-in-from-bottom-4 { - animation-name: slide-in-from-bottom; -} - -.slide-in-from-left-4 { - animation-name: slide-in-from-left; + animation-duration: 400ms; } /* Custom selection */ @@ -252,16 +234,6 @@ pre[class*="language-"] { display: none; } -/* Custom styling for lightbox */ -.yarl__flex_center { - @apply !p-[64px]; - flex-direction: column; -} - -.yarl__slide_description_container { - @apply mt-4 text-white; -} - /* Custom border for alerts */ .alert { @apply border; @@ -381,7 +353,7 @@ a.breadcrumbs__link:hover { } /* Custom border-radius for document images */ -.theme-doc-markdown img { +.plugin-id-default .theme-doc-markdown img { @apply rounded-md; } @@ -597,10 +569,12 @@ hr { @apply !mt-8; } -/* Home Page full width override */ -.guides-home-page [class*="docItemCol"], -.cloud-home-page [class*="docItemCol"], -.docs-home-page [class*="docItemCol"] { +/* Full width override */ +.guidesHomePage [class*="docItemCol"], +.cloudHomePage [class*="docItemCol"], +.docsHomePage [class*="docItemCol"], +.samplesHomePage [class*="docItemCol"], +.plugin-id-samples [class*="docItemCol"] { max-width: 100% !important; } @@ -654,6 +628,16 @@ a { animation: skeleton-loading 2000ms infinite linear; } +/* Hide scrollbar utility */ +.scrollbar-none { + scrollbar-width: none; + -ms-overflow-style: none; +} + +.scrollbar-none::-webkit-scrollbar { + display: none; +} + [data-theme="dark"] .skeleton { background-image: linear-gradient( 45deg, @@ -716,7 +700,8 @@ a { /* Disable navigation for Docs */ .plugin-id-default .pagination-nav, -.plugin-id-cloud .pagination-nav { +.plugin-id-cloud .pagination-nav, +.plugin-id-samples .pagination-nav { display: none; } @@ -783,3 +768,8 @@ a { font-size: 0.75rem; line-height: 1.2; } + +/* Hide TOC for Samples */ +.plugin-id-samples [class*="tocCollapsibleButton"] { + display: none; +} diff --git a/src/hooks/useBoolean.ts b/src/hooks/useBoolean.ts new file mode 100644 index 0000000000..7ecfd51a9a --- /dev/null +++ b/src/hooks/useBoolean.ts @@ -0,0 +1,14 @@ +import { useCallback, useState } from "react"; + +const useBoolean = (initial: boolean) => { + const [value, setValue] = useState(initial); + return { + value, + setValue, + toggle: useCallback(() => setValue((value: boolean) => !value), []), + setTrue: useCallback(() => setValue(true), []), + setFalse: useCallback(() => setValue(false), []), + }; +}; + +export default useBoolean; diff --git a/src/lib/split-sitemap/__tests__/split.test.ts b/src/lib/split-sitemap/__tests__/split.test.ts index 06aaba4d9e..454e216487 100644 --- a/src/lib/split-sitemap/__tests__/split.test.ts +++ b/src/lib/split-sitemap/__tests__/split.test.ts @@ -46,6 +46,8 @@ test("splitSitemap groups URLs by section and version", () => { `${BASE_URL}/6.2/baz`, `${BASE_URL}/cloud/account`, `${BASE_URL}/guides/intro`, + `${BASE_URL}/samples`, + `${BASE_URL}/samples/fit-assistant`, `${BASE_URL}/search`, ]; fs.writeFileSync(path.join(dir, "sitemap.xml"), buildSitemap(urls)); @@ -61,7 +63,10 @@ test("splitSitemap groups URLs by section and version", () => { "sitemap-docs-7.2.xml", "sitemap-guides.xml", "sitemap-misc.xml", + "sitemap-samples.xml", ]); + const samples = result.files.find((f) => f.name === "sitemap-samples.xml"); + assert.equal(samples?.urls, 2); const docs72 = result.files.find((f) => f.name === "sitemap-docs-7.2.xml"); assert.equal(docs72?.urls, 2); }); diff --git a/src/lib/split-sitemap/lib/split.ts b/src/lib/split-sitemap/lib/split.ts index 5c3fafeb4e..e01c3d03d4 100644 --- a/src/lib/split-sitemap/lib/split.ts +++ b/src/lib/split-sitemap/lib/split.ts @@ -6,8 +6,9 @@ * per-version sitemap size small enough to satisfy search-engine limits * and lets us re-ping only changed sections on deploy. * - * /cloud/* → sitemap-cloud.xml - * /guides/* → sitemap-guides.xml + * /cloud/* → sitemap-cloud.xml + * /guides/* → sitemap-guides.xml + * /samples/* → sitemap-samples.xml * /X.Y/* → sitemap-docs-X.Y.xml (if X.Y isn't legacy) * other → sitemap-misc.xml (search, root pages, etc.) * @@ -46,6 +47,7 @@ export interface SplitSucceeded { const SECTION_MAP: Record = { cloud: "sitemap-cloud.xml", guides: "sitemap-guides.xml", + samples: "sitemap-samples.xml", }; const XML_HEADER = ''; diff --git a/src/pages/guides/all.tsx b/src/pages/guides/all.tsx index 58af25a19b..126aa9da58 100644 --- a/src/pages/guides/all.tsx +++ b/src/pages/guides/all.tsx @@ -72,7 +72,7 @@ function AllGuidesPageContent(): ReactNode { "animate-in fade-in" )} > - {sortedGuides.map((guide, index) => { + {sortedGuides.map((guide) => { const formattedDate = guide.lastUpdatedAt ? new Date(guide.lastUpdatedAt * 1000).toLocaleDateString("en-US", { month: "short", @@ -85,19 +85,18 @@ function AllGuidesPageContent(): ReactNode { key={guide.id} title={guide.title} description={guide.description} - url={guide.externalUrl || guide.permalink} + url={guide.external_url || guide.permalink} imgSrc={guide.image} imgIcon={guide.icon} tags={guide.tags} date={formattedDate} - animationDelay={index * 50} /> ); })}
) : (
- {sortedGuides.map((guide, index) => { + {sortedGuides.map((guide) => { const formattedDate = guide.lastUpdatedAt ? new Date(guide.lastUpdatedAt * 1000).toLocaleDateString("en-US", { month: "short", @@ -106,18 +105,10 @@ function AllGuidesPageContent(): ReactNode { }) : undefined; return ( -
+
diff --git a/src/plugins/recent-guides-plugin.ts b/src/plugins/recent-guides-plugin.ts index 49a307ca17..a6d520254c 100644 --- a/src/plugins/recent-guides-plugin.ts +++ b/src/plugins/recent-guides-plugin.ts @@ -12,9 +12,10 @@ export interface Guide { tags: { label: string; permalink: string }[]; lastUpdatedAt: number; description?: string; - image?: string | { light: string; dark: string }; + image?: string; + img_alt?: string; icon?: IconName; - externalUrl?: string; + external_url?: string; } export interface PluginData { @@ -40,7 +41,7 @@ function getFiles(dir: string, files: string[] = []) { return files; } -const recentGuidesPlugin: Plugin = function recentGuidesPlugin(context, _options) { +export default function recentGuidesPlugin(context, _options): Plugin { return { name: "recent-guides-plugin", async loadContent() { @@ -57,7 +58,6 @@ const recentGuidesPlugin: Plugin = function recentGuidesPlugin(context, _options const fileContent = fs.readFileSync(tagsYmlPath, "utf8"); predefinedTags = (yaml.load(fileContent) as any) || {}; } catch (e) { - // eslint-disable-next-line no-console console.error("Failed to load tags.yml", e); } } @@ -85,9 +85,9 @@ const recentGuidesPlugin: Plugin = function recentGuidesPlugin(context, _options const slug = baseName.endsWith("/index") ? baseName.replace(/\/index$/, "") : baseName; const permalink = `/guides/${slug === "index" ? "" : slug}`; - const externalUrl: string | undefined = (data as any).externalUrl || (data as any).external_url; + const externalUrl: string | undefined = (data as any).external_url; - const frontmatterDate: unknown = (data as any).publishedAt; + const frontmatterDate: unknown = (data as any).published_at; let lastUpdatedAt: number; if (frontmatterDate) { @@ -133,14 +133,15 @@ const recentGuidesPlugin: Plugin = function recentGuidesPlugin(context, _options return { id: path.basename(filePath, path.extname(filePath)), - title: data.title || path.basename(filePath, path.extname(filePath)), + title: data.title, permalink: data.slug || permalink, tags: formattedTags, lastUpdatedAt, description: data.description, image: data.image, + img_alt: data.img_alt, icon: data.icon, - externalUrl, + external_url: externalUrl, }; }); @@ -160,6 +161,4 @@ const recentGuidesPlugin: Plugin = function recentGuidesPlugin(context, _options setGlobalData(content); }, }; -}; - -export default recentGuidesPlugin; +} diff --git a/src/plugins/recent-samples-plugin.ts b/src/plugins/recent-samples-plugin.ts new file mode 100644 index 0000000000..7c67c0e89a --- /dev/null +++ b/src/plugins/recent-samples-plugin.ts @@ -0,0 +1,182 @@ +import type { Plugin } from "@docusaurus/types"; +import path from "path"; +import fs from "fs"; +import matter from "gray-matter"; +const yaml = require("js-yaml"); + +interface TagDefinition { + label: string; + description?: string; +} + +interface TagsByCategory { + [category: string]: { + [tagKey: string]: TagDefinition; + }; +} + +export interface Sample { + id: string; + title: string; + permalink: string; + tags: { label: string; key: string; category: string }[]; + description?: string; + image?: string; + imgAlt?: string; +} + +export interface PluginData { + samples: Sample[]; + tags: Array<{ + label: string; + key: string; + count: number; + category: string; + }>; +} + +function getFiles(dir: string, files: string[] = []) { + const fileList = fs.readdirSync(dir); + for (const file of fileList) { + const name = path.join(dir, file); + if (fs.statSync(name).isDirectory()) { + getFiles(name, files); + } else { + files.push(name); + } + } + return files; +} + +export default function recentSamplesPlugin(context, _options): Plugin { + return { + name: "recent-samples-plugin", + async loadContent() { + const samplesDir = path.join(context.siteDir, "samples"); + + if (!fs.existsSync(samplesDir)) { + return []; + } + + const tagsByCategory: TagsByCategory = {}; + const tagsDir = path.join(samplesDir, "tags"); + + if (fs.existsSync(tagsDir)) { + const categoryFiles = fs.readdirSync(tagsDir).filter((f) => f.endsWith(".yml")); + for (const file of categoryFiles) { + const category = path.basename(file, ".yml"); + const filePath = path.join(tagsDir, file); + try { + const fileContent = fs.readFileSync(filePath, "utf8"); + tagsByCategory[category] = (yaml.load(fileContent) as any) || {}; + } catch (e) { + console.error(`Failed to load tags/${file}`, e); + } + } + } + + const tagCounts: Record = {}; + + const files = getFiles(samplesDir) + .filter((f) => /\.(md|mdx)$/.test(f)) + .filter((f) => { + const relativePath = path.relative(samplesDir, f); + const normalized = relativePath.split(path.sep).join("/"); + return normalized !== "home.mdx"; + }); + + const samples = files.map((filePath) => { + const fileContent = fs.readFileSync(filePath, "utf-8"); + const { data } = matter(fileContent); + + const relativePath = path.relative(samplesDir, filePath); + const relativePathNormalized = relativePath.split(path.sep).join("/"); + const baseName = relativePathNormalized.replace(/\.(md|mdx)$/, ""); + + const slug = baseName.endsWith("/index") ? baseName.replace(/\/index$/, "") : baseName; + const permalink = `/samples/${slug === "index" ? "" : slug}`; + + const allTagsArray: Array<{ key: string; category: string }> = []; + + const challengesSolutionsTags = data.challenges_solutions_tags; + + if (Array.isArray(challengesSolutionsTags)) { + challengesSolutionsTags.forEach((tag: string) => { + allTagsArray.push({ key: tag, category: "challenges-solutions" }); + }); + } + + const featureTags = data.feature_tags; + if (Array.isArray(featureTags)) { + featureTags.forEach((tag: string) => { + allTagsArray.push({ key: tag, category: "feature" }); + }); + } + + const techStackTags = data.tech_stack_tags; + if (Array.isArray(techStackTags)) { + techStackTags.forEach((tag: string) => { + allTagsArray.push({ key: tag, category: "tech-stack" }); + }); + } + + allTagsArray.forEach(({ key }) => { + tagCounts[key] = (tagCounts[key] || 0) + 1; + }); + + const formattedTags = allTagsArray.map(({ key, category }) => { + const categoryTags = tagsByCategory[category]; + const definedTag = categoryTags[key]; + + return { + label: definedTag?.label || key, + key, + category, + }; + }); + + return { + id: path.basename(filePath, path.extname(filePath)), + title: data.title, + permalink: data.slug || permalink, + tags: formattedTags, + description: data.description, + image: data.image, + img_alt: data.img_alt, + }; + }); + + const allTags: Array<{ + label: string; + key: string; + count: number; + category: string; + }> = []; + + Object.entries(tagsByCategory).forEach(([category, tags]) => { + Object.entries(tags).forEach(([key, value]) => { + const count = tagCounts[key] || 0; + if (count === 0) { + return; + } + + allTags.push({ + label: value.label, + key, + count, + category, + }); + }); + }); + + return { + samples: samples, + tags: allTags, + }; + }, + async contentLoaded({ content, actions }) { + const { setGlobalData } = actions; + setGlobalData(content); + }, + }; +} diff --git a/src/plugins/versioned-seo-plugin/__tests__/rewrite.test.ts b/src/plugins/versioned-seo-plugin/__tests__/rewrite.test.ts index be4eef6a93..dcecaed6ee 100644 --- a/src/plugins/versioned-seo-plugin/__tests__/rewrite.test.ts +++ b/src/plugins/versioned-seo-plugin/__tests__/rewrite.test.ts @@ -183,7 +183,17 @@ test("rewriteHtml respects minimumVersion gating when the file's version gates t test("buildScopedRoutes partitions routes by version and section, skipping legacy/templates/root", () => { const map = buildScopedRoutes( - ["/7.2/a", "/7.2/b/", "/6.2/c", "/4.2/legacy", "/cloud/x", "/guides/y", "/templates/z", "/"], + [ + "/7.2/a", + "/7.2/b/", + "/6.2/c", + "/4.2/legacy", + "/cloud/x", + "/guides/y", + "/samples/fit-assistant", + "/templates/z", + "/", + ], LEGACY ); assert.equal(map.get("7.2")?.size, 2); @@ -194,6 +204,7 @@ test("buildScopedRoutes partitions routes by version and section, skipping legac assert.equal(map.get("4.2"), undefined, "legacy versions should not appear in the map"); assert.equal(map.get("cloud")?.size, 1); assert.equal(map.get("guides")?.size, 1); + assert.equal(map.get("samples")?.size, 1); assert.equal(map.get("templates"), undefined, "templates are never valid see_also targets"); assert.equal(map.get("root"), undefined, "root is never a valid see_also target"); }); diff --git a/src/plugins/versioned-seo-plugin/__tests__/verify-seealso.test.ts b/src/plugins/versioned-seo-plugin/__tests__/verify-seealso.test.ts index dac53d1901..020eefc7c2 100644 --- a/src/plugins/versioned-seo-plugin/__tests__/verify-seealso.test.ts +++ b/src/plugins/versioned-seo-plugin/__tests__/verify-seealso.test.ts @@ -79,6 +79,15 @@ test("verifySeeAlsoLinks routes source: guides hrefs to the guides slice", () => assert.deepEqual(issues, []); }); +test("verifySeeAlsoLinks routes source: samples hrefs to the samples slice", () => { + const issues = verifySeeAlsoLinks({ + records: [record({ source: "samples", href: "/samples/fit-assistant" })], + routesByScope: scoped({ "7.1": [], samples: ["/samples/fit-assistant"] }), + latestVersion: CURRENT, + }); + assert.deepEqual(issues, []); +}); + test("verifySeeAlsoLinks reports a guides href whose target doesn't exist", () => { const issues = verifySeeAlsoLinks({ records: [record({ source: "guides", href: "/guides/nonexistent" })], diff --git a/src/plugins/versioned-seo-plugin/index.ts b/src/plugins/versioned-seo-plugin/index.ts index f37da0bc52..28150e951f 100644 --- a/src/plugins/versioned-seo-plugin/index.ts +++ b/src/plugins/versioned-seo-plugin/index.ts @@ -193,10 +193,10 @@ const versionedSeoPlugin = function versionedSeoPlugin( const info = extractVersionInfo(rel); const originalHtml = fs.readFileSync(filePath, "utf8"); - // SeeAlso audit: currently-maintained versioned pages + /cloud + /guides articles. + // SeeAlso audit: currently-maintained versioned pages + unversioned content sections. const shouldAuditSeeAlso = (info && !LEGACY_VERSIONS.includes(info.version)) || - (!info && (rel.startsWith("cloud/") || rel.startsWith("guides/"))); + (!info && (rel.startsWith("cloud/") || rel.startsWith("guides/") || rel.startsWith("samples/"))); if (shouldAuditSeeAlso) { seeAlsoRecords.push( ...fanoutSeeAlsoRecords( diff --git a/src/plugins/versioned-seo-plugin/lib/verify-seealso.ts b/src/plugins/versioned-seo-plugin/lib/verify-seealso.ts index eb4e3a1eda..eff73babc9 100644 --- a/src/plugins/versioned-seo-plugin/lib/verify-seealso.ts +++ b/src/plugins/versioned-seo-plugin/lib/verify-seealso.ts @@ -65,7 +65,14 @@ export function verifySeeAlsoLinks(input: VerifySeeAlsoInput): SeeAlsoIssue[] { } const expectedVersion = record.articleVersion ?? latestVersion; - const scopeKey = record.source === "cloud" ? "cloud" : record.source === "guides" ? "guides" : expectedVersion; + const scopeKey = + record.source === "cloud" + ? "cloud" + : record.source === "guides" + ? "guides" + : record.source === "samples" + ? "samples" + : expectedVersion; if (routesByScope.get(scopeKey)?.has(record.href)) { continue; @@ -75,7 +82,7 @@ export function verifySeeAlsoLinks(input: VerifySeeAlsoInput): SeeAlsoIssue[] { articlePath: record.articlePath, href: record.href, reason: `see_also href ${record.href} does not resolve in scope "${scopeKey}"`, - fix: `edit ${record.articlePath}: link: must be versionless and point at a real page in ${scopeKey === "cloud" || scopeKey === "guides" ? `/${scopeKey}/` : `version ${scopeKey}`}.`, + fix: `edit ${record.articlePath}: link: must be versionless and point at a real page in ${scopeKey === "cloud" || scopeKey === "guides" || scopeKey === "samples" ? `/${scopeKey}/` : `version ${scopeKey}`}.`, }); } diff --git a/src/plugins/versioned-seo-plugin/lib/verify.ts b/src/plugins/versioned-seo-plugin/lib/verify.ts index d2be679c7b..7f4d27c9f5 100644 --- a/src/plugins/versioned-seo-plugin/lib/verify.ts +++ b/src/plugins/versioned-seo-plugin/lib/verify.ts @@ -82,10 +82,10 @@ function normalizeRoute(route: string): string { return stripTrailingSlash(route); } -// Map>. Scope keys: currently-maintained version strings, "cloud", "guides". +// Map>. Scope keys: currently-maintained version strings, "cloud", "guides", "samples". // Legacy versions, /templates, and root are excluded — invalid see_also targets, no slice needed. const VERSION_PREFIX_REGEX = /^\/(\d+\.\d+)(\/|$)/; -const SECTION_PREFIX_REGEX = /^\/(cloud|guides)(\/|$)/; +const SECTION_PREFIX_REGEX = /^\/(cloud|guides|samples)(\/|$)/; export function buildScopedRoutes( routePaths: readonly string[], diff --git a/src/theme/DocItem/Authors/index.tsx b/src/theme/DocItem/Authors/index.tsx index 1bba0891f8..19ded05074 100644 --- a/src/theme/DocItem/Authors/index.tsx +++ b/src/theme/DocItem/Authors/index.tsx @@ -18,7 +18,6 @@ type Author = { function getAuthorData(authorKey: string): Author | null { const authorInfo = authorsData[authorKey]; if (!authorInfo) { - // eslint-disable-next-line no-console console.warn(`No author data found for key '${authorKey}' in authors.json`); return null; } diff --git a/src/theme/DocItem/BannerImage/index.tsx b/src/theme/DocItem/BannerImage/index.tsx index 9a0634a55e..b1bc65db3f 100644 --- a/src/theme/DocItem/BannerImage/index.tsx +++ b/src/theme/DocItem/BannerImage/index.tsx @@ -6,7 +6,7 @@ import { IconName } from "@site/src/typescript/iconName"; import LazyImage from "@site/src/components/Common/LazyImage"; export interface BannerImageProps { - imgSrc?: string | { light: string; dark: string }; + imgSrc?: string; imgAlt?: string; imgIcon?: IconName; className?: string; diff --git a/src/theme/DocItem/Metadata/DocPageMetadata.tsx b/src/theme/DocItem/Metadata/DocPageMetadata.tsx index 0d9a4c0d65..9dda519921 100644 --- a/src/theme/DocItem/Metadata/DocPageMetadata.tsx +++ b/src/theme/DocItem/Metadata/DocPageMetadata.tsx @@ -17,11 +17,16 @@ export interface DocPageMetadataProps { ogImageUrl: string; // Shared (optional) lastUpdatedAt?: number; + keywords?: string[]; // Guide-only (optional) proficiencyLevel?: string; authorKey?: string; publishedAt?: string; - keywords?: string[]; + // Sample-only (optional) + schemaType?: "TechArticle" | "SoftwareSourceCode"; + repositoryUrl?: string; + licenseUrl?: string; + languages?: string[]; } export default function DocPageMetadata({ @@ -34,9 +39,46 @@ export default function DocPageMetadata({ authorKey, publishedAt, keywords, + schemaType = "TechArticle", + repositoryUrl, + licenseUrl, + languages, }: DocPageMetadataProps): ReactNode { const authorInfo = authorKey ? authorsData[authorKey as keyof typeof authorsData] : null; + // Generate SoftwareSourceCode schema for samples + if (schemaType === "SoftwareSourceCode") { + const softwareSourceCodeJsonLd = JSON.stringify({ + "@context": "https://schema.org", + "@type": "SoftwareSourceCode", + name: title, + ...(description ? { description } : {}), + url: canonicalUrl, + ...(repositoryUrl ? { codeRepository: repositoryUrl } : {}), + ...(licenseUrl ? { license: licenseUrl } : {}), + ...(languages?.length ? { programmingLanguage: languages } : {}), + ...(keywords?.length ? { keywords } : {}), + runtimePlatform: "RavenDB", + publisher: { + "@type": "Organization", + name: "RavenDB", + url: "https://ravendb.net", + }, + isPartOf: { + "@type": "CollectionPage", + "@id": `${canonicalUrl.split("/samples/")[0]}/samples`, + name: "RavenDB Code Samples", + }, + }); + + return ( + + + + ); + } + + // Generate TechArticle schema for guides and docs const techArticleJsonLd = JSON.stringify({ "@context": "https://schema.org", "@type": "TechArticle", diff --git a/src/theme/DocItem/Metadata/index.tsx b/src/theme/DocItem/Metadata/index.tsx index 55db8ac27f..f4e73d8a63 100644 --- a/src/theme/DocItem/Metadata/index.tsx +++ b/src/theme/DocItem/Metadata/index.tsx @@ -20,11 +20,13 @@ export default function MetadataWrapper(props: Props): ReactNode { const isGuide = source?.startsWith("@site/guides/") || source?.startsWith("guides/") || false; const isCloud = source?.startsWith("@site/cloud/") || source?.startsWith("cloud/") || false; const isTemplate = source?.startsWith("@site/templates/") || source?.startsWith("templates/") || false; - const isDocumentationPage = !isGuide && !isCloud && !isTemplate; + const isSample = source?.startsWith("@site/samples/") || source?.startsWith("samples/") || false; + const isDocumentationPage = !isGuide && !isCloud && !isTemplate && !isSample; - // Exclude landing pages (e.g. guides/home.mdx) from guide-specific metadata + // Exclude landing pages (e.g. guides/home.mdx, samples/home.mdx) from type-specific metadata const fileName = source?.split("/").pop(); const isGuidePage = isGuide && fileName !== "home.mdx"; + const isSamplePage = isSample && fileName !== "home.mdx"; // Strip trailing slash from base URL to avoid double slashes const baseUrl = (siteConfig.url as string).replace(/\/$/, ""); @@ -76,6 +78,23 @@ export default function MetadataWrapper(props: Props): ReactNode { lastUpdatedAt={metadata.lastUpdatedAt} /> )} + {isSamplePage && ( + tag.replace(/-/g, " "))} + /> + )} ); @@ -105,19 +124,19 @@ function ValidatedGuideDocPageMetadata({ if (!title) { throw new Error(`Guide "${permalink}" is missing a required "title" in frontmatter.`); } - if (!frontMatter.proficiencyLevel) { - throw new Error(`Guide "${permalink}" is missing a required "proficiencyLevel" in frontmatter.`); + if (!frontMatter.proficiency_level) { + throw new Error(`Guide "${permalink}" is missing a required "proficiency_level" in frontmatter.`); } return ( diff --git a/src/theme/DocItem/index.tsx b/src/theme/DocItem/index.tsx index 4c27d8108e..63b9641529 100644 --- a/src/theme/DocItem/index.tsx +++ b/src/theme/DocItem/index.tsx @@ -4,6 +4,8 @@ import type DocItemType from "@theme/DocItem"; import type { WrapperProps } from "@docusaurus/types"; import DocsTopbar from "@site/src/components/DocsTopbar"; import { CustomDocFrontMatter } from "@site/src/typescript/docMetadata"; +import { useActivePlugin } from "@docusaurus/plugin-content-docs/client"; +import Head from "@docusaurus/Head"; type Props = WrapperProps; @@ -11,6 +13,8 @@ export default function DocItemWrapper(props: Props): ReactNode { const title = props.content.metadata?.title; const source = props.content.metadata?.source as string | undefined; const frontMatter = props.content.frontMatter as CustomDocFrontMatter; + const activePlugin = useActivePlugin(); + const pluginId = activePlugin?.pluginId; const isDocsOrVersioned = source?.startsWith("@site/docs/") || @@ -25,8 +29,15 @@ export default function DocItemWrapper(props: Props): ReactNode { const showTopbar = Boolean(isDocsOrVersioned && !isExcluded); + const isTemplatesPlugin = pluginId === "templates"; + return ( <> + {isTemplatesPlugin && ( + + + + )} {showTopbar && }
diff --git a/src/theme/DocSidebar/Desktop/index.tsx b/src/theme/DocSidebar/Desktop/index.tsx index 227d09dcf4..bf55cb4632 100644 --- a/src/theme/DocSidebar/Desktop/index.tsx +++ b/src/theme/DocSidebar/Desktop/index.tsx @@ -30,6 +30,8 @@ function DocSidebarDesktop({ path, sidebar, onCollapse, isHidden }: Props) { const pathType = getPathType(path); const landingPagePath = getLandingPagePath(pathType, versionLabel); + const shouldDisplayContent = pathType !== PathType.Guides && pathType !== PathType.Samples; + return (
)} + {pathType !== PathType.Samples && ( + + Samples + + Switch + + + )} {pathType !== PathType.Documentation && ( RavenDB Docs @@ -83,9 +93,9 @@ function DocSidebarDesktop({ path, sidebar, onCollapse, isHidden }: Props) { )}
- {pathType !== PathType.Guides &&
} + {shouldDisplayContent &&
} {pathType === PathType.Documentation && } - {pathType !== PathType.Guides && } + {shouldDisplayContent && } {hideable && }
); diff --git a/src/theme/DocSidebar/Mobile/index.tsx b/src/theme/DocSidebar/Mobile/index.tsx index efc62ea098..f9fd75907d 100644 --- a/src/theme/DocSidebar/Mobile/index.tsx +++ b/src/theme/DocSidebar/Mobile/index.tsx @@ -22,6 +22,8 @@ function DocSidebarMobileSecondaryMenu({ sidebar, path }: DocSidebarProps) { const pathType = getPathType(path); const landingPagePath = getLandingPagePath(pathType, versionLabel); + const shouldDisplayContent = pathType !== PathType.Guides && pathType !== PathType.Samples; + return (
  • @@ -47,6 +49,14 @@ function DocSidebarMobileSecondaryMenu({ sidebar, path }: DocSidebarProps) { )} + {pathType !== PathType.Samples && ( + + Samples + + Switch + + + )} {pathType !== PathType.Cloud && ( RavenDB Cloud Docs @@ -59,7 +69,7 @@ function DocSidebarMobileSecondaryMenu({ sidebar, path }: DocSidebarProps) { Community - {pathType !== PathType.Cloud && pathType !== PathType.Guides && ( + {pathType === PathType.Documentation && (
  • )} - {pathType !== PathType.Guides && ( + {shouldDisplayContent && (

  • )} - {pathType !== PathType.Cloud && pathType !== PathType.Guides && } - {pathType !== PathType.Guides && ( + {pathType === PathType.Documentation && } + {shouldDisplayContent && ( { - // Mobile sidebar should only be closed if the category has a link if (item.type === "category" && item.href) { mobileSidebar.toggle(); } diff --git a/src/theme/DocTagDocListPage/index.tsx b/src/theme/DocTagDocListPage/index.tsx index 751d4e873e..0ad9f15d6c 100644 --- a/src/theme/DocTagDocListPage/index.tsx +++ b/src/theme/DocTagDocListPage/index.tsx @@ -87,7 +87,7 @@ function DocTagDocListPageContent({ tag }: Props): ReactNode { "animate-in fade-in" )} > - {sortedItems.map((doc, index) => { + {sortedItems.map((doc) => { const guide = guides.find((g: Guide) => g.permalink === doc.permalink); const formattedDate = guide?.lastUpdatedAt ? new Date(guide.lastUpdatedAt * 1000).toLocaleDateString("en-US", { @@ -101,19 +101,18 @@ function DocTagDocListPageContent({ tag }: Props): ReactNode { key={doc.id} title={doc.title} description={doc.description} - url={guide?.externalUrl || doc.permalink} + url={guide?.external_url || doc.permalink} imgSrc={guide?.image} imgIcon={guide?.icon} tags={guide?.tags} date={formattedDate} - animationDelay={index * 50} /> ); })}
) : (
- {sortedItems.map((doc, index) => { + {sortedItems.map((doc) => { const guide = guides.find((g: Guide) => g.permalink === doc.permalink); const formattedDate = guide?.lastUpdatedAt ? new Date(guide.lastUpdatedAt * 1000).toLocaleDateString("en-US", { @@ -123,18 +122,10 @@ function DocTagDocListPageContent({ tag }: Props): ReactNode { }) : undefined; return ( -
+
diff --git a/src/theme/NotFound/Content/index.tsx b/src/theme/NotFound/Content/index.tsx index 145310dce2..0c59a4794b 100644 --- a/src/theme/NotFound/Content/index.tsx +++ b/src/theme/NotFound/Content/index.tsx @@ -14,6 +14,9 @@ export default function NotFoundContent({ className }: Props): ReactNode { const versionLabel = activeVersion?.label ?? latestVersion.label; const { pathname } = useLocation(); const isCloudPath = pathname.includes("/cloud"); + const isGuidesPath = pathname.includes("/guides"); + const isSamplesPath = pathname.includes("/samples"); + const homeUrl = isCloudPath ? "/cloud" : isGuidesPath ? "/guides" : isSamplesPath ? "/samples" : `/${versionLabel}`; return (
@@ -65,7 +68,7 @@ export default function NotFoundContent({ className }: Props): ReactNode { & { diff --git a/src/typescript/pathUtils.ts b/src/typescript/pathUtils.ts index 438fe3e043..48abb5d651 100644 --- a/src/typescript/pathUtils.ts +++ b/src/typescript/pathUtils.ts @@ -3,6 +3,7 @@ export const PathType = { Guides: "GUIDES", Documentation: "DOCUMENTATION", Templates: "TEMPLATES", + Samples: "SAMPLES", } as const; export type PathTypeValue = (typeof PathType)[keyof typeof PathType]; @@ -14,6 +15,9 @@ export function getPathType(path: string): PathTypeValue { if (path.includes("/guides")) { return PathType.Guides; } + if (path.includes("/samples")) { + return PathType.Samples; + } if (path.includes("/templates")) { return PathType.Templates; } @@ -27,6 +31,9 @@ export function getLandingPagePath(pathType: PathTypeValue, versionLabel: string if (pathType === PathType.Guides) { return "/guides"; } + if (pathType === PathType.Samples) { + return "/samples"; + } if (pathType === PathType.Templates) { return "/templates"; } diff --git a/static/img/samples/brain-slop/cover.png b/static/img/samples/brain-slop/cover.png new file mode 100644 index 0000000000..f961e614da Binary files /dev/null and b/static/img/samples/brain-slop/cover.png differ diff --git a/static/img/samples/fit/dashboard-top.png b/static/img/samples/fit/dashboard-top.png new file mode 100644 index 0000000000..aa1b058870 Binary files /dev/null and b/static/img/samples/fit/dashboard-top.png differ diff --git a/static/img/samples/fit/dashboard-trends-coach.png b/static/img/samples/fit/dashboard-trends-coach.png new file mode 100644 index 0000000000..d698d111a6 Binary files /dev/null and b/static/img/samples/fit/dashboard-trends-coach.png differ diff --git a/static/img/samples/fit/social.png b/static/img/samples/fit/social.png new file mode 100644 index 0000000000..1efc42a81b Binary files /dev/null and b/static/img/samples/fit/social.png differ diff --git a/static/img/samples/hr/cover.png b/static/img/samples/hr/cover.png new file mode 100644 index 0000000000..c271f51679 Binary files /dev/null and b/static/img/samples/hr/cover.png differ diff --git a/static/img/samples/hr/seed-db.png b/static/img/samples/hr/seed-db.png new file mode 100644 index 0000000000..cd357aa0e3 Binary files /dev/null and b/static/img/samples/hr/seed-db.png differ diff --git a/static/img/samples/hr/usage-limit.png b/static/img/samples/hr/usage-limit.png new file mode 100644 index 0000000000..808b0d4c6e Binary files /dev/null and b/static/img/samples/hr/usage-limit.png differ diff --git a/static/img/samples/hugin/cover.png b/static/img/samples/hugin/cover.png new file mode 100644 index 0000000000..5445ba5c9f Binary files /dev/null and b/static/img/samples/hugin/cover.png differ diff --git a/static/img/samples/library-of-ravens/01.webp b/static/img/samples/library-of-ravens/01.webp new file mode 100644 index 0000000000..83addb6c52 Binary files /dev/null and b/static/img/samples/library-of-ravens/01.webp differ diff --git a/static/img/samples/library-of-ravens/02.webp b/static/img/samples/library-of-ravens/02.webp new file mode 100644 index 0000000000..a99e320f42 Binary files /dev/null and b/static/img/samples/library-of-ravens/02.webp differ diff --git a/static/img/samples/library-of-ravens/03.webp b/static/img/samples/library-of-ravens/03.webp new file mode 100644 index 0000000000..2122e3a367 Binary files /dev/null and b/static/img/samples/library-of-ravens/03.webp differ diff --git a/static/img/samples/library-of-ravens/cover.webp b/static/img/samples/library-of-ravens/cover.webp new file mode 100644 index 0000000000..d60815d443 Binary files /dev/null and b/static/img/samples/library-of-ravens/cover.webp differ diff --git a/static/img/samples/verity/cover.png b/static/img/samples/verity/cover.png new file mode 100644 index 0000000000..843580f50c Binary files /dev/null and b/static/img/samples/verity/cover.png differ diff --git a/static/img/samples/yabt/apikey-auth.png b/static/img/samples/yabt/apikey-auth.png new file mode 100644 index 0000000000..6de74ae522 Binary files /dev/null and b/static/img/samples/yabt/apikey-auth.png differ diff --git a/static/img/samples/yabt/cover.png b/static/img/samples/yabt/cover.png new file mode 100644 index 0000000000..d70133d78d Binary files /dev/null and b/static/img/samples/yabt/cover.png differ diff --git a/static/img/samples/yabt/projects-diagram.png b/static/img/samples/yabt/projects-diagram.png new file mode 100644 index 0000000000..57bb77f5c7 Binary files /dev/null and b/static/img/samples/yabt/projects-diagram.png differ diff --git a/static/llms.txt b/static/llms.txt index c5eb0d8c5f..652c498398 100644 --- a/static/llms.txt +++ b/static/llms.txt @@ -149,6 +149,11 @@ Documentation is versioned. The current version is 7.2. Use the `/7.2/` URL pref - [Data Migration](https://docs.ravendb.net/7.2/migration/server/data-migration): Migrate data between RavenDB versions - [Breaking Changes](https://docs.ravendb.net/7.2/migration/server/server-breaking-changes): Server-side breaking changes by version +## Sample Applications + +- [Samples Home](https://docs.ravendb.net/samples/): Browse production-ready code samples, architecture patterns, and starter kits built with RavenDB +- [The Library of Ravens](https://docs.ravendb.net/samples/the-ravens-library): Library management app demonstrating vector search, Azure Storage Queues ETL, Include for N+1 elimination, and Document Refresh — built with C#, Aspire, and Azure Functions + ## Cloud - [RavenDB Cloud Documentation](https://docs.ravendb.net/cloud/): Cloud service portal documentation diff --git a/templates/best-practices-samples.mdx b/templates/best-practices-samples.mdx new file mode 100644 index 0000000000..1cb1d679ae --- /dev/null +++ b/templates/best-practices-samples.mdx @@ -0,0 +1,274 @@ +--- +title: "Samples: Best practices" +hide_table_of_contents: false +sidebar_label: Best practices +see_also: + - title: "Adding new samples" + link: "/templates/new-samples" + source: "docs" + path: "Templates > Samples" + - title: "Samples Introduction" + link: "/templates/introduction-samples" + source: "docs" + path: "Templates > Samples" +--- + +import Admonition from "@theme/Admonition"; +import Panel from "@site/src/components/Panel"; + +Guidelines and recommendations for creating high-quality sample documentation. + +## Content Guidelines + +### Tag Selection + +**Challenges & Solutions Tags:** +- Focus on primary business problems +- Be specific: `semantic-search` not just `search` + +**Feature Tags:** +- Select key RavenDB features +- Prioritize features with code examples +- Include both basic and advanced features + +**Tech Stack Tags:** +- List all major technologies +- Include language, framework, and cloud services +- Order by importance: language first, then frameworks + +**Example:** + +```yaml +challenges_solutions_tags: [semantic-search, integration-patterns] +feature_tags: [vector-search, include, document-refresh, azure-storage-queues-etl] +tech_stack_tags: [csharp, aspire, azure-storage-queues, azure-functions] +``` + +### Images + +**Cover Image:** +- Recommended size: 1200x630px +- Format: WebP for best compression +- Show the app in action, not just a logo +- Use high-quality screenshots +- Ensure text is readable + +**Gallery Screenshots:** +- At least 2 images showing key features +- Consistent size and aspect ratio +- Add descriptive alt text +- Show different parts of the app +- Highlight unique features + +**File organization:** + +``` +static/img/samples/{sample-name}/ +├── cover.webp # Main cover image +├── 01.webp # Gallery image 1 +├── 02.webp # Gallery image 2 +└── 03.webp # Gallery image 3 +``` + +## Structure Guidelines + +### Features Section + +Use `FeatureAccordion` components for each major feature: + +**Guidelines:** +- 3-5 accordions per sample +- Order by importance + +**Template:** + +```mdx +## Features used + + + [Detailed explanation of how and why this feature is used] + + Implementation example: + + // Code showing the feature in action + + [Additional context or benefits] + +``` + +### Related Resources + +**Guidelines:** +- Link to 2-3 related resources +- Mix guides and documentation + +**Priority order:** +1. Related guides (how-to articles) +2. Feature documentation (RavenDB docs) +3. Cloud documentation (if applicable) + +## Component Usage + +### Sidebar (frontmatter-driven) + +The metadata sidebar is fully driven by frontmatter — no JSX props needed. + +**Actions (repository_url / demo_url):** +- Always provide `repository_url` if the code is public +- Add `demo_url` only if a live demo is available and maintained +- Don't link to private repositories or broken demo URLs + +**Related resources (related_resources):** +- Link to 2–3 related resources +- Mix guides and documentation +- Verify links are correct and current; use descriptive `subtitle` values +- Don't link to deprecated documentation, use vague subtitles like "Related Article", or duplicate links + +### FeatureAccordion + +**Do:** +- Use descriptive icons matching the feature +- Keep descriptions to one sentence + +**Don't:** +- Leave children empty (omit the accordion instead) +- Include broken code examples + +## Tag Management + +### Adding New Tags + +**Before adding a tag:** +1. Check if a similar tag exists +2. Verify it fits the category (challenges-solutions/feature/tech-stack) +3. Ensure it will be used by multiple samples +4. Use consistent naming conventions + +**Naming conventions:** +- Use kebab-case: `azure-storage-queues` not `AzureStorageQueues` +- Be specific: `azure-storage-queues-etl` not `etl` +- Avoid abbreviations: `semantic-search` not `sem-search` +- Use full names: `csharp` not `cs` + +### Tag Maintenance + +**Periodically review:** +- Unused tags (count = 0) +- Duplicate or similar tags +- Outdated technology tags +- Inconsistent naming + +## Common Mistakes + +### ❌ Missing Required Frontmatter + +```yaml +--- +title: "My Sample" +# Missing description, tags +--- +``` + +### ✅ Complete Frontmatter + +```yaml +--- +title: "My Sample" +description: "Complete description" +challenges_solutions_tags: [semantic-search] +feature_tags: [vector-search] +tech_stack_tags: [csharp] +license: "MIT License" +license_url: "https://opensource.org/licenses/MIT" +repository_url: "https://github.com/ravendb/my-sample-repo" +languages: ["C#"] +--- +``` + +### ❌ Adding H1 Heading + +```mdx +--- +title: "My Sample" +--- + +# My Sample + +## Overview +``` + +### ✅ Start with H2 + +```mdx +--- +title: "My Sample" +--- + +## Overview +``` + +### ❌ Undefined Tags + +```yaml +challenges_solutions_tags: [nonexistent-tag] # Tag doesn't exist in YAML +``` + +### ✅ Valid Tags + +```yaml +challenges_solutions_tags: [semantic-search] # Tag exists in samples/tags/challenges-solutions.yml +``` + +### ❌ Empty FeatureAccordion + +```tsx + + {/* Empty - don't do this */} + +``` + +### ✅ Complete FeatureAccordion + +```tsx + + Detailed explanation with code examples... + +``` + +## Testing Checklist + +Before publishing a sample: + +- [ ] All frontmatter fields are present and valid +- [ ] `repository_url` and `languages` are set (feeds `SoftwareSourceCode` JSON-LD) +- [ ] All tags exist in their respective YAML files +- [ ] Cover image loads correctly +- [ ] Gallery images load and have alt text +- [ ] GitHub repository link works +- [ ] Demo URL works (if provided) +- [ ] All related resource links work +- [ ] Code examples are syntactically correct +- [ ] Sample appears in hub page grid +- [ ] Filtering works with all tags +- [ ] Metadata sidebar displays correctly +- [ ] No console errors or warnings + +## Performance Tips + +**Images:** +- Use WebP format for smaller file sizes +- Optimize images before committing +- Use appropriate dimensions (don't serve 4K images) + +**Code Examples:** +- Keep examples concise +- Use syntax highlighting + +**Content:** +- Keep descriptions brief +- Link to external resources instead of duplicating content + diff --git a/templates/components-samples.mdx b/templates/components-samples.mdx new file mode 100644 index 0000000000..f1cc582992 --- /dev/null +++ b/templates/components-samples.mdx @@ -0,0 +1,228 @@ +--- +title: "Samples: Components" +hide_table_of_contents: false +sidebar_label: Components +see_also: + - title: "Adding new samples" + link: "/templates/new-samples" + source: "docs" + path: "Templates > Samples" + - title: "Samples Introduction" + link: "/templates/introduction-samples" + source: "docs" + path: "Templates > Samples" +--- + +import Admonition from "@theme/Admonition"; +import Panel from "@site/src/components/Panel"; + +This guide documents all React components used in the Samples system. + +## Import Statement + +All sample components are exported from a single entry point: + +```tsx +import { + // Layout (includes sidebar and gallery automatically) + SampleLayout, + + // Content + FeatureAccordion, + + // Hub (not imported in sample pages) + SamplesHomePage, + SampleCard, + SamplesGrid, + SamplesFilter, +} from '@site/src/components/Samples'; +``` + +## Layout Components + +### SampleLayout + +Two-column responsive layout for sample detail pages. Renders the metadata sidebar and gallery automatically from frontmatter — no props needed beyond `children`. + +**Location:** `src/components/Samples/Overview/SampleLayout.tsx` + +**Props:** + +| Prop | Type | Required | Description | +|---|---|---|---| +| `children` | `ReactNode` | ✅ | Main content area | + +**Usage:** + +```tsx + + {/* Main content */} + +``` + +**Auto-rendered from frontmatter:** +- Sidebar — `SampleMetadataColumn` (tags, actions, license, related resources) +- Gallery — `gallery` field; omitted automatically when the field is absent + +**Behavior:** +- Desktop (lg+): Two columns, sidebar on right (300px fixed width); gallery above content +- Mobile: Stacked, sidebar and gallery appear above content + +--- + +## Metadata Sidebar Components + +### SampleMetadataColumn + +Container for the metadata sidebar. Reads all data from frontmatter — no props required. + +**Location:** `src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx` + +**Props:** + +| Prop | Type | Required | Description | +|---|---|---|---| +| `className` | `string` | ❌ | Additional CSS classes | + +**Usage:** + +```tsx + +``` + +**Populated from frontmatter:** +- `repository_url` — renders "Browse code" button +- `demo_url` — renders "View demo" button +- `challenges_solutions_tags` — Challenges & Solutions tag pills +- `feature_tags` — Feature tag pills +- `tech_stack_tags` — Tech stack tag pills +- `category` — optional, displayed in sidebar +- `license` / `license_url` — optional, displayed in sidebar +- `related_resources` — renders linked resource items (see schema below) + +**`related_resources` item schema (frontmatter):** + +| Field | Type | Required | Description | +|---|---|---|---| +| `type` | `"guide"` \| `"documentation"` | ✅ | Resource type | +| `documentation_type` | `"docs"` \| `"cloud"` | ❌ | Documentation section | +| `subtitle` | `string` | ✅ | Display text | +| `article_key` | `string` | ✅ | Article path without version prefix | + +**Example frontmatter:** + +```yaml +repository_url: "https://github.com/ravendb/my-sample-repo" +demo_url: "https://demo.example.com" +related_resources: + - type: guide + subtitle: "Bookkeeping with RavenDB & ETLs" + article_key: "bookkeeping-with-ravendb" + - type: documentation + documentation_type: docs + subtitle: "Vector Search Overview" + article_key: "ai-integration/vector-search/overview" + - type: documentation + documentation_type: cloud + subtitle: "Cloud Backup" + article_key: "portal-backup" +``` + +**Auto-versioning for docs links:** +- `documentation_type: docs` → current version is automatically prepended +- Example: `ai-integration/vector-search/overview` → `/7.2/ai-integration/vector-search/overview` + +--- + +## Content Components + +### FeatureAccordion + +Expandable accordion for describing RavenDB features used in the sample. + +**Location:** `src/components/Samples/Overview/Partials/FeatureAccordion.tsx` + +**Props:** + +| Prop | Type | Required | Default | Description | +|---|---|---|---|---| +| `className` | `string` | ❌ | - | Additional CSS classes | +| `title` | `string` | ✅ | - | Feature name | +| `description` | `string` | ✅ | - | Short description (visible when collapsed) | +| `icon` | `IconName` | ❌ | `"link"` | Icon name from icon gallery | +| `children` | `ReactNode` | ❌ | - | Detailed content (visible when expanded) | +| `defaultExpanded` | `boolean` | ❌ | `false` | Initial expanded state | + +**Usage:** + +```tsx + + Detailed explanation of how vector search is used... + + // Code example + +``` + +**Behavior:** +- Click header to expand/collapse +- Smooth animation with Framer Motion +- Chevron icon rotates on expand/collapse + +--- + +## Hub Components + +These components power the `/samples` hub page. They are typically not used in individual sample pages. + +### SamplesHomePage + +Main hub page component with filters and grid. + +**Location:** `src/components/Samples/Hub/SamplesHomePage.tsx` + +**Usage:** + +```mdx +--- +title: Samples +slug: / +--- + +import { SamplesHomePage } from "@site/src/components/Samples"; + + +``` +**Features:** +- URL-based filter state (`?tags=vector-search,include&match=all`) +- Responsive filter sidebar +- Sample grid with cards +- Match logic toggle (Any/All) + +--- + +### SampleCard + +Individual sample card in the grid. + +**Location:** `src/components/Samples/Hub/Partials/SampleCard.tsx` + +**Auto-rendered by `SamplesGrid`** - not typically used directly. + +--- + +### SamplesFilter + +Filter sidebar with tag categories. + +**Location:** `src/components/Samples/Hub/Partials/SamplesFilter.tsx` + +**Auto-rendered by `SamplesHomePage`** - not typically used directly. + + + All components are re-exported from `src/components/Samples/index.ts` for convenient importing. + + diff --git a/templates/featured-guides.mdx b/templates/featured-guides.mdx index 25326d6cdf..4ef04c3e28 100644 --- a/templates/featured-guides.mdx +++ b/templates/featured-guides.mdx @@ -47,7 +47,7 @@ import FeaturedGuides from "@site/src/components/Guides/FeaturedGuides"; ```mdx --- title: "Example Guide" - publishedAt: 2026-02-27 + published_at: 2026-02-27 author: "Author Name" tags: [ai, demo] image: "/img/guides-ai-agents.webp" @@ -68,5 +68,5 @@ import FeaturedGuides from "@site/src/components/Guides/FeaturedGuides"; section will update automatically. - If you omit the `guidesTitles` prop, `FeaturedGuides` will fall back to showing the two most recently updated guides based on file timestamps or `publishedAt`. + If you omit the `guidesTitles` prop, `FeaturedGuides` will fall back to showing the two most recently updated guides based on file timestamps or `published_at`. diff --git a/templates/filtering-samples.mdx b/templates/filtering-samples.mdx new file mode 100644 index 0000000000..26b3c7b594 --- /dev/null +++ b/templates/filtering-samples.mdx @@ -0,0 +1,173 @@ +--- +title: "Samples: Filtering" +hide_table_of_contents: false +sidebar_label: Filtering +see_also: + - title: "Samples Introduction" + link: "/templates/introduction-samples" + source: "docs" + path: "Templates > Samples" + - title: "Sample tags" + link: "/templates/tags-samples" + source: "docs" + path: "Templates > Samples" +--- + +import Admonition from "@theme/Admonition"; +import Panel from "@site/src/components/Panel"; + +The Samples hub page features a powerful filtering system with URL-based state, multi-category tag selection, and flexible match logic. + +## Features + +### 1. Multi-Category Filtering + +Tags are organized into three categories: +- **Challenges & Solutions** - Business problems solved +- **Features** - RavenDB features demonstrated +- **Tech Stack** - Technologies used + +Users can select tags from any or all categories simultaneously. + +### 2. Match Logic Toggle + +Two matching modes: +- **Any** (OR logic) - Show samples matching *any* selected tag +- **All** (AND logic) - Show samples matching *all* selected tags + +### 3. URL-Based State + +Filter state is stored in URL query parameters for: +- Shareable filtered views +- Browser back/forward navigation +- Bookmarkable searches + +**Example URLs:** +``` +/samples?tags=vector-search +/samples?tags=vector-search,include&match=all +/samples?tags=csharp,aspire +``` + +### 4. Tag Search + +Filter sidebar includes a search box to quickly find tags by name. + +### 5. Expandable Categories + +Each category can be expanded/collapsed independently. Categories with many tags show only the first 5 by default with a "More" button. + +## User Flows + +### Flow 1: Browse and Filter + +1. User visits `/samples` +2. Sees all samples in grid +3. Clicks "Vector Search" tag in filter sidebar +4. URL updates to `/samples?tags=vector-search` +5. Grid filters to show only samples with vector search +6. User clicks "Include" tag +7. URL updates to `/samples?tags=vector-search,include` +8. Grid shows samples with *either* tag (Any mode) + +### Flow 2: Refine with AND Logic + +1. User has selected `vector-search` and `include` tags +2. Switches match logic from "Any" to "All" +3. URL updates to `/samples?tags=vector-search,include&match=all` +4. Grid shows only samples with *both* tags + +### Flow 3: Quick Filter from Card + +1. User sees a sample card with "C#" language badge +2. Clicks the "C#" badge +3. All filters clear, only "C#" is selected +4. URL updates to `/samples?tags=csharp` +5. Grid shows all C# samples + +### Flow 4: Share Filtered View + +1. User has filtered to `csharp,aspire` with "All" logic +2. Copies URL: `/samples?tags=csharp,aspire&match=all` +3. Shares URL with colleague +4. Colleague opens URL and sees the same filtered view + +## UI Components + +### SamplesFilter + +**Features:** +- Search box for filtering tags by name +- Match logic toggle (Any/All) +- Clear filters button +- Expandable categories +- Tag checkboxes with counts + +**Props:** + +```typescript +interface SamplesFilterProps { + categories: FilterCategoryData[]; + selectedTags: Set; + onTagToggle: (tagKey: string) => void; + matchLogic: "any" | "all"; + onMatchLogicChange: (logic: "any" | "all") => void; + onClearFilters?: () => void; +} +``` + +### FilterCategory + +**Features:** +- Collapsible category header +- Shows first 5 tags by default +- "More" button to expand remaining tags +- Checkbox for each tag with label + +**Props:** + +```typescript +interface FilterCategoryProps { + name: string; + label: string; + tags: FilterTag[]; + selectedTags: Set; + onTagToggle: (tagKey: string) => void; + isExpanded: boolean; + onToggleExpanded: () => void; +} +``` + +### SamplesGrid + +**Features:** +- Displays filtered samples +- Shows sample count badge +- Responsive grid (1 column mobile, 2 columns desktop) +- Staggered animation on load + +**Props:** + +```typescript +interface SamplesGridProps { + samples: Sample[]; + selectedTags: Set; + matchLogic: "any" | "all"; + onTagClick?: (tagKey: string) => void; +} +``` + +## Styling + +### Active Tag State + +Selected tags are highlighted in the filter sidebar: + +```tsx + onTagToggle(tag.key)} + label={tag.label} +/> +``` diff --git a/templates/frames.mdx b/templates/frames.mdx index 8e867cb2be..111e21c2e1 100644 --- a/templates/frames.mdx +++ b/templates/frames.mdx @@ -23,15 +23,13 @@ import ContentFrame from '@site/src/components/ContentFrame'; ``` ### Basic usage -````mdx +```mdx Text, lists, images, and even code blocks. - ```ts - console.log('Hello world!') - ``` + // Code example -```` +``` #### Live example @@ -164,41 +162,31 @@ You can combine `Panel` and `ContentFrame`: ### Panels with code and tabs -````mdx +```mdx import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; - ```csharp - // sample code - Console.WriteLine("Hello"); - ``` + // Code example 1 - ```csharp - await Console.Out.WriteLineAsync("Hello"); - ``` + // Code example 2 -```` +``` #### Live example - ```csharp - // sample code - Console.WriteLine("Hello"); - ``` + // Code example 1 - ```csharp - await Console.Out.WriteLineAsync("Hello"); - ``` + // Code example 2 diff --git a/templates/gallery-example.mdx b/templates/gallery-example.mdx new file mode 100644 index 0000000000..fb362c7751 --- /dev/null +++ b/templates/gallery-example.mdx @@ -0,0 +1,97 @@ +--- +title: "Gallery Component Example" +description: "Example of using the Gallery component with multiple images" +gallery: + - src: "/img/samples/library-of-ravens/01.webp" + alt: "First example image" + - src: "/img/samples/library-of-ravens/02.webp" + alt: "Second example image" + - src: "/img/samples/library-of-ravens/03.webp" + alt: "Third example image" + - src: "/img/discord.webp" + alt: "Fourth example image" + - src: "/img/webinar.webp" + alt: "Fifth example image" +--- + +import Gallery from '@site/src/components/Common/Gallery'; + +# Gallery Component Example + +This page demonstrates how to use the Gallery component with images defined in frontmatter. + +## Basic Usage + +The Gallery component provides responsive layouts optimized for each device: + +### Desktop (≥ lg breakpoint) +- One large image on the left (taking ~2/3 of the width) +- Two smaller images stacked on the right (taking ~1/3 of the width) +- If there are more than 3 images, a "+x" overlay appears on the third image + +### Mobile (< lg breakpoint) +- Single-image carousel with swipe navigation +- All images accessible via horizontal scroll +- Dot indicators showing current position +- Tap dots to jump to specific images +- Images display in their natural aspect ratio + +### Example Gallery + + + +## How to Use + +### 1. Define images in frontmatter + +```yaml +--- +title: "Your Page Title" +gallery: + - src: "/img/path/to/image1.png" + alt: "Description of first image" + - src: "/img/path/to/image2.png" + alt: "Description of second image" + - src: "/img/path/to/image3.png" + alt: "Description of third image" + - src: "/img/path/to/image4.png" + alt: "Description of fourth image" +--- +``` + +### 2. Import and use the Gallery component + +#### Standalone Usage +```mdx +import Gallery from '@site/src/components/Common/Gallery'; + + +``` + +#### With SampleLayout +```mdx +import { SampleLayout } from '@site/src/components/Samples'; +import Gallery from '@site/src/components/Common/Gallery'; + +} +> + ## Overview + ... + +``` + +When using the `gallery` prop with `SampleLayout`: +- **Mobile**: Gallery appears at the top, right after the title and above the ActionsCard +- **Desktop**: Gallery appears in the main content area before the Overview section + + +## Customization + +You can pass a custom className to adjust the gallery styling: + +```mdx + +``` + diff --git a/templates/introduction-samples.mdx b/templates/introduction-samples.mdx new file mode 100644 index 0000000000..ed287592f2 --- /dev/null +++ b/templates/introduction-samples.mdx @@ -0,0 +1,112 @@ +--- +title: "Samples: Introduction" +hide_table_of_contents: false +sidebar_label: Introduction +see_also: + - title: "Adding new samples" + link: "/templates/new-samples" + source: "docs" + path: "Templates > Samples" + - title: "Sample tags" + link: "/templates/tags-samples" + source: "docs" + path: "Templates > Samples" +--- + +import Admonition from "@theme/Admonition"; +import Panel from "@site/src/components/Panel"; + +The Samples system provides a comprehensive platform for showcasing production-ready code samples, architecture patterns, and starter kits. It features a filterable hub page, detailed sample pages with metadata, and a plugin-based architecture for automatic indexing. + +## Architecture Overview + +The Samples system consists of three main parts: + +### 1. Samples Hub (`/samples`) + +A filterable, searchable landing page that displays all available samples with: +- **Filter sidebar** with three tag categories (Challenges & Solutions, Features, Tech Stack) +- **Sample cards** showing title, description, image, and tags +- **Match logic toggle** (Any/All) for flexible filtering +- **URL-based state** for shareable filtered views + +### 2. Sample Detail Pages + +Individual sample pages with a two-column layout: +- **Main content area** for documentation, code examples, and feature descriptions +- **Metadata sidebar** with actions, tags, and related resources + +### 3. Plugin System + +The `recent-samples-plugin` automatically: +- Scans the `samples/` directory for `.mdx` files +- Loads tag definitions from `samples/tags/` (organized by category) +- Indexes samples with their metadata and tags +- Exposes data via Docusaurus global plugin data + +## Key Components + +| Component | Purpose | Location | +|---|---|---| +| `SamplesHomePage` | Main hub page with filters and grid | `src/components/Samples/Hub/SamplesHomePage.tsx` | +| `SampleCard` | Individual sample card in the grid | `src/components/Samples/Hub/Partials/SampleCard.tsx` | +| `SamplesFilter` | Filter sidebar with categories | `src/components/Samples/Hub/Partials/SamplesFilter.tsx` | +| `SampleLayout` | Two-column layout for detail pages | `src/components/Samples/Overview/SampleLayout.tsx` | +| `SampleMetadataColumn` | Sidebar with tags and metadata | `src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx` | +| `ActionsCard` | GitHub and demo links | `src/components/Samples/Overview/Partials/ActionsCard.tsx` | +| `RelatedResource` | Links to guides/docs | `src/components/Samples/Overview/Partials/RelatedResource.tsx` | +| `FeatureAccordion` | Expandable feature descriptions | `src/components/Samples/Overview/Partials/FeatureAccordion.tsx` | + +## Tag System + +Samples use a **three-category tag system**: + +1. **Challenges & Solutions** (`samples/tags/challenges-solutions.yml`) - What business problems the sample solves + - Examples: `cloud-tax`, `semantic-search`, `integration-patterns` + +2. **Features** (`samples/tags/feature.yml`) - Which RavenDB features are demonstrated + - Examples: `vector-search`, `document-refresh`, `include`, `ai-agents` + +3. **Tech Stack** (`samples/tags/tech-stack.yml`) - Technologies and frameworks used + - Examples: `csharp`, `nodejs`, `aspire`, `azure-functions` + + + Unlike Guides which use a single `tags.yml` file, Samples organize tags into separate category files. This enables the filter UI to group tags logically and allows for "Any/All" matching within categories. + + +## File Structure + +``` +samples/ +├── home.mdx # Hub page (uses SamplesHomePage component) +├── the-ravens-library.mdx # Example sample +├── tags/ +│ ├── challenges-solutions.yml # Challenges & solutions tags +│ ├── feature.yml # RavenDB feature tags +│ └── tech-stack.yml # Technology stack tags +└── ... # Additional sample files + +src/components/Samples/ +├── Hub/ # Hub page components +│ ├── SamplesHomePage.tsx +│ └── Partials/ +│ ├── SampleCard.tsx +│ ├── SamplesGrid.tsx +│ ├── SamplesFilter.tsx +│ ├── FilterCategory.tsx +│ ├── SamplesHeader.tsx +│ └── SamplesDecoration.tsx +├── Overview/ # Detail page components +│ ├── SampleLayout.tsx +│ └── Partials/ +│ ├── SampleMetadataColumn.tsx +│ ├── ActionsCard.tsx +│ ├── RelatedResource.tsx +│ └── FeatureAccordion.tsx +├── index.ts # Component exports +└── types.ts # TypeScript types + +src/plugins/ +└── recent-samples-plugin.ts # Indexing plugin +``` + diff --git a/templates/new-guides.mdx b/templates/new-guides.mdx index 4b8761d957..2a55ed122c 100644 --- a/templates/new-guides.mdx +++ b/templates/new-guides.mdx @@ -53,7 +53,7 @@ Intro paragraph... **Fields explained** - `title` – main page title displayed in the header. -- `publishedAt` – publication date (YYYY-MM-DD format). Displayed in the article header. +- `published_at` – publication date (YYYY-MM-DD format). Displayed in the article header. - `author` – name of the guide author (single author only). Displayed in the article header below the title. Must match exactly with a key in `docs/authors.json`. - `tags` – keys from `guides/tags.yml` (see Tags). Displayed below the title. - `image` – banner image displayed at the top of the article; can be a string or a themed image object (see Themed Images template). If not provided, a gradient background with icon is shown. @@ -101,11 +101,11 @@ Use this pattern when the actual content lives elsewhere but you still want it l ```mdx --- title: "Begin analysis with OLAP ETL" -publishedAt: 2025-12-23 +published_at: 2025-12-23 author: "Author Name" tags: [integration, getting-started] description: "Short description shown in cards and lists." -externalUrl: "https://ravendb.net/articles/begin-analysis-with-olap-etl" +external_url: "https://ravendb.net/articles/begin-analysis-with-olap-etl" image: "https://ravendb.net/wp-content/uploads/2025/12/OLAP-article-image.svg" --- diff --git a/templates/new-samples.mdx b/templates/new-samples.mdx new file mode 100644 index 0000000000..7c397d3294 --- /dev/null +++ b/templates/new-samples.mdx @@ -0,0 +1,192 @@ +--- +title: "Samples: Adding new samples" +hide_table_of_contents: false +sidebar_label: Adding new samples +see_also: + - title: "Samples Introduction" + link: "/templates/introduction-samples" + source: "docs" + path: "Templates > Samples" + - title: "Sample tags" + link: "/templates/tags-samples" + source: "docs" + path: "Templates > Samples" + - title: "Sample components" + link: "/templates/components-samples" + source: "docs" + path: "Templates > Samples" +--- + +import Admonition from "@theme/Admonition"; +import Panel from "@site/src/components/Panel"; +import Link from "@docusaurus/Link"; + +This guide explains how to create new sample pages in the Samples section. + +## Create the file + +Place the file under the `samples` directory: + +``` +samples/my-new-sample.mdx +``` + +## Frontmatter Schema + + + +```mdx +--- +title: "My Sample Application" +description: "A production-ready example demonstrating RavenDB features." +challenges_solutions_tags: [semantic-search, integration-patterns] +feature_tags: [vector-search, include, document-refresh] +tech_stack_tags: [csharp, aspire, azure-functions] +image: "/img/samples/my-sample/cover.webp" +img_alt: "My Sample Application Screenshot" +category: "Ecommerce" +license: "MIT License" +license_url: "https://opensource.org/licenses/MIT" +repository_url: "https://github.com/ravendb/my-sample-repo" +demo_url: "https://demo.example.com" +languages: ["C#"] +gallery: + - src: "/img/samples/my-sample/screenshot-1.webp" + alt: "Main interface" + - src: "/img/samples/my-sample/screenshot-2.webp" + alt: "Admin dashboard" +related_resources: + - type: guide + subtitle: "Related Guide Title" + article_key: "guide-slug" + - type: documentation + documentation_type: docs + subtitle: "Vector Search Overview" + article_key: "ai-integration/vector-search/overview" +--- +``` + + + +### Frontmatter Fields + +| Field | Type | Required | Description | +|---|---|---|---| +| `title` | `string` | ✅ | Sample name displayed in cards and page header | +| `description` | `string` | ✅ | Short summary shown in sample cards | +| `challenges_solutions_tags` | `string[]` | ✅ | Challenges & solutions tags from `samples/tags/challenges-solutions.yml` (kebab-case) | +| `feature_tags` | `string[]` | ✅ | RavenDB feature tags from `samples/tags/feature.yml` (kebab-case) | +| `tech_stack_tags` | `string[]` | ✅ | Technology tags from `samples/tags/tech-stack.yml` (kebab-case) | +| `image` | `string` | ❌ | Cover image path (shown in sample cards) | +| `img_alt` | `string` | ❌ | Alt text for the cover image | +| `category` | `string` | ❌ | Business category (e.g., "Ecommerce", "Healthcare") - displayed in metadata sidebar | +| `license` | `string` | ❌ | License type (e.g., "MIT License", "Apache 2.0") - displayed in metadata sidebar | +| `license_url` | `string` | ❌ | License URL (e.g., `https://opensource.org/licenses/MIT`) - links the sidebar label and feeds JSON-LD | +| `repository_url` | `string` | ❌ | GitHub repository URL - renders "Browse code" button; used in `SoftwareSourceCode` JSON-LD for SEO | +| `demo_url` | `string` | ❌ | Live demo URL - renders "View demo" button in the sidebar | +| `languages` | `string[]` | ❌ | Programming languages (e.g., `["C#"]`) - used in `SoftwareSourceCode` JSON-LD for SEO | +| `gallery` | `array` | ❌ | Array of screenshot objects with `src` and `alt` properties | +| `related_resources` | `array` | ❌ | Related guides and docs shown in the sidebar — see schema below | + +Each `related_resources` item accepts: + +| Field | Type | Required | Description | +|---|---|---|---| +| `type` | `"guide"` \| `"documentation"` | ✅ | Resource type | +| `documentation_type` | `"docs"` \| `"cloud"` | ❌ | Documentation section (only for `type: documentation`) | +| `subtitle` | `string` | ✅ | Display text | +| `article_key` | `string` | ✅ | Article path without version prefix (e.g., `ai-integration/vector-search/overview`) | + + + All tag keys must exist in their respective YAML files (`samples/tags/challenges-solutions.yml`, `feature.yml`, `tech-stack.yml`). Using undefined tags will cause them to display with their raw key value instead of a formatted label. + + +## Page Structure + +### Basic Layout + + + +```mdx +--- +title: "My Sample" +description: "Sample description" +challenges_solutions_tags: [semantic-search] +feature_tags: [vector-search] +tech_stack_tags: [csharp] +license: "MIT License" +license_url: "https://opensource.org/licenses/MIT" +repository_url: "https://github.com/ravendb/my-sample-repo" +languages: ["C#"] +related_resources: + - type: guide + subtitle: "Related Guide Title" + article_key: "guide-slug" + - type: documentation + documentation_type: docs + subtitle: "Vector Search Overview" + article_key: "ai-integration/vector-search/overview" +--- + +import { FeatureAccordion, SampleLayout } from '@site/src/components/Samples'; + + + +## Overview + +Describe what the sample does and what problems it solves... + +## Features used + + + Detailed explanation of how vector search is used in this sample... + + // Code example + + + +``` + + + + + The `gallery` prop on `SampleLayout` provides optimal responsive behavior: + + - **Mobile**: Gallery appears at the top, right after the page title and above the ActionsCard + - **Desktop**: Gallery appears in the main content area, before the Overview section + + `SampleLayout` reads `gallery` from the `gallery` frontmatter field automatically. If the field is absent or empty, no gallery is rendered. + + + + **Do NOT add a `#` heading in your markdown content.** + + The `title` frontmatter is automatically rendered as the page heading. Adding a `#` heading breaks the layout. + + ✅ Correct: + ```mdx + --- + title: "My Sample" + --- + + ## Overview + + Content starts here... + ``` + + ❌ Wrong: + ```mdx + --- + title: "My Sample" + --- + + # My Sample + + ## Overview + ``` + + diff --git a/templates/see-also.mdx b/templates/see-also.mdx index 5537c9def2..b9e36d8667 100644 --- a/templates/see-also.mdx +++ b/templates/see-also.mdx @@ -28,6 +28,10 @@ see_also: link: "/guides/sso-setup" source: "guides" path: "Security > Authentication" + - title: "Fit Assistant" + link: "/samples/fit-assistant" + source: "samples" + path: "Samples > AI & Time Series" - title: "Website" link: "https://web.site" source: "external" @@ -41,7 +45,7 @@ see_also: | :------- | :------- | :------------------------------------------------------------------------------------------------------------------------------ | | `title` | `string` | The title of the related article. | | `link` | `string` | The URL or internal path. For `docs`, it's automatically versioned. | -| `source` | `Enum` | Icons are assigned based on the source: `docs`, `cloud`, `guides`, or `external`. | +| `source` | `Enum` | Icons are assigned based on the source: `docs`, `cloud`, `guides`, `samples`, or `external`. | | `path` | `string` | Breadcrumb-style path (e.g., `Category > Subcategory`). The first part is shown by default, and the full path appears on hover. | ## Features @@ -53,6 +57,7 @@ The component automatically selects an icon based on the `source` property: - `docs`: Document icon - `cloud`: Cloud icon - `guides`: Guides icon +- `samples`: Samples icon - `external`: External link icon ### 2. Versioned Documentation Links @@ -61,6 +66,7 @@ For items with `source: "docs"`, the component automatically prepends the curren - If you are on version `7.1` of the docs, a link like `/api/basics` becomes `/7.1/api/basics`. - If the link already starts with a version (e.g., `/8.0/api/basics`), it is used as is. +- Links to `/cloud`, `/guides`, `/samples`, and `/templates` stay unversioned. ### 3. Progressive Path Display diff --git a/templates/tags-samples.mdx b/templates/tags-samples.mdx new file mode 100644 index 0000000000..5d9bfcf1fe --- /dev/null +++ b/templates/tags-samples.mdx @@ -0,0 +1,176 @@ +--- +title: "Samples: Tags" +hide_table_of_contents: false +sidebar_label: Tags +see_also: + - title: "Adding new samples" + link: "/templates/new-samples" + source: "docs" + path: "Templates > Samples" + - title: "Samples Introduction" + link: "/templates/introduction-samples" + source: "docs" + path: "Templates > Samples" +--- + +import Admonition from "@theme/Admonition"; +import Panel from "@site/src/components/Panel"; + +Samples use a **three-category tag system** to enable powerful filtering and discovery. Unlike Guides which use a single flat tag list, Samples organize tags into separate category files. + +## Tag Categories + +### 1. Challenges & Solutions Tags (`samples/tags/challenges-solutions.yml`) + +These tags describe **what business problems** the sample solves. + +**Location:** `samples/tags/challenges-solutions.yml` + +**Some of the existing tags:** +- `cloud-tax` +- `gen-ai-data-enrichment` +- `integration-patterns` +- `semantic-search` + +**Example usage in frontmatter:** +```yaml +challenges_solutions_tags: [semantic-search, integration-patterns] +``` + +### 2. Feature Tags (`samples/tags/feature.yml`) + +These tags indicate **which RavenDB features** are demonstrated. + +**Location:** `samples/tags/feature.yml` + +**Some of the existing tags:** +- `vector-search` +- `document-refresh` +- `include` +- `azure-storage-queues-etl` +- `ai-agents` +- `full-text-search` + +**Example usage in frontmatter:** +```yaml +feature_tags: [vector-search, include, document-refresh] +``` + +### 3. Tech Stack Tags (`samples/tags/tech-stack.yml`) + +These tags specify **technologies and frameworks** used in the sample. + +**Location:** `samples/tags/tech-stack.yml` + +**Some of the existing tags:** +- `csharp` +- `nodejs` +- `python` +- `java` +- `php` +- `aspire` +- `azure-storage-queues` +- `azure-functions` + +**Example usage in frontmatter:** +```yaml +tech_stack_tags: [csharp, aspire, azure-functions] +``` + +## Adding New Tags + +### Step 1: Choose the correct category + +Determine which category your tag belongs to: +- **Challenges & Solutions** - Business problem or use case +- **Feature** - RavenDB feature or capability +- **Tech Stack** - Programming language, framework, or service + +### Step 2: Edit the appropriate YAML file + + + +Edit `samples/tags/challenges-solutions.yml`: + +```yaml +cloud-tax: + label: "Cloud Tax" +gen-ai-data-enrichment: + label: "Gen AI Data Enrichment" +# Add your new tag: +real-time-analytics: + label: "Real-Time Analytics" +``` + + + + + +Edit `samples/tags/feature.yml`: + +```yaml +vector-search: + label: "Vector Search" +document-refresh: + label: "Document Refresh" +# Add your new tag: +time-series: + label: "Time Series" +``` + + + + + +Edit `samples/tags/tech-stack.yml`: + +```yaml +csharp: + label: "C#" +nodejs: + label: "Node.js" +# Add your new tag: +nextjs: + label: "Next.js" +``` + + + +### Step 3: Use the tag in a sample + +Reference the tag key in your sample's frontmatter: + +```mdx +--- +title: "My Sample" +challenges_solutions_tags: [real-time-analytics] +feature_tags: [time-series] +tech_stack_tags: [nextjs] +--- +``` + + + Each tag entry requires only a `label` field. The key (e.g., `cloud-tax`) is used in frontmatter, while the label (e.g., "Cloud Tax") is displayed in the UI. + + +## Tag Guidelines + + + - **Be specific**: Use `azure-storage-queues-etl` instead of just `etl` + - **Use kebab-case**: `gen-ai-data-enrichment` not `GenAIDataEnrichment` + - **Keep labels concise**: "Vector Search" not "Vector Search Functionality" + - **Avoid duplicates**: Check existing tags before adding new ones + - **Stay consistent**: Follow naming patterns of existing tags + + + + The following tech stack tags are treated specially as "language tags" and displayed with colored badges: + - `csharp` + - `java` + - `python` + - `php` + - `nodejs` + + These should only be used for the primary programming language of the sample. + + diff --git a/templates/themed-images.mdx b/templates/themed-images.mdx index 7c3596b6f2..989cc95019 100644 --- a/templates/themed-images.mdx +++ b/templates/themed-images.mdx @@ -27,7 +27,7 @@ import ThemedImage from '@theme/ThemedImage'; ### Basic usage -````mdx +```mdx import ThemedImage from '@theme/ThemedImage'; import lightImage from '@site/static/img/templates/light-image.png'; import darkImage from '@site/static/img/templates/dark-image.png'; @@ -39,7 +39,7 @@ import darkImage from '@site/static/img/templates/dark-image.png'; dark: darkImage, }} /> -```` +``` #### Live example @@ -95,96 +95,7 @@ import darkImage from '@site/static/img/templates/dark-image.png'; /> ``` -## Using ThemedImage in Card components - -Both `CardWithImage` and `CardWithImageHorizontal` support themed images by passing an object with `light` and `dark` properties to the `imgSrc` prop. - -### CardWithImage with themed images - -```mdx -import CardWithImage from "@site/src/components/Common/CardWithImage"; -import lightImage from '@site/static/img/templates/light-image.png'; -import darkImage from '@site/static/img/templates/dark-image.png'; - - -``` - -#### Live example - - - - - -### CardWithImageHorizontal with themed images - -```mdx -import CardWithImageHorizontal from "@site/src/components/Common/CardWithImageHorizontal"; -import lightImage from '@site/static/img/templates/light-image.png'; -import darkImage from '@site/static/img/templates/dark-image.png'; - - -``` - -#### Live example - - - - -### Fallback to single image - -Both card components maintain backward compatibility. If you pass a string instead of an object, they'll use a regular `` tag: - -```mdx - -``` -#### Live example - - - - ## Best practices @@ -194,9 +105,6 @@ Both card components maintain backward compatibility. If you pass a string inste - **Optimize images**: Use appropriate image formats and compression for web performance. - **Test both themes**: Verify that images look good in both light and dark modes. - -When using themed images in card components, the component automatically detects the object format and renders the appropriate image component. For single images, use a string; for themed images, use an object with `light` and `dark` properties. - ## Props @@ -211,12 +119,3 @@ When using themed images in card components, the component automatically detects | height | number \| string | — | Image height (optional). | | style | React.CSSProperties | — | Inline styles for the image (optional). | -### CardWithImage / CardWithImageHorizontal - -The `imgSrc` prop accepts either: - -- **String**: Single image URL (backward compatible) -- **Object**: `{ light: string; dark: string }` for themed images - -When an object is provided, the component automatically uses `ThemedImage` internally. - diff --git a/versioned_docs/version-1.0/home.mdx b/versioned_docs/version-1.0/home.mdx index 614ef33eea..0c34d76a6f 100644 --- a/versioned_docs/version-1.0/home.mdx +++ b/versioned_docs/version-1.0/home.mdx @@ -2,7 +2,7 @@ slug: / pagination_next: null pagination_prev: null -wrapperClassName: docs-home-page +wrapperClassName: docsHomePage hide_table_of_contents: true --- diff --git a/versioned_docs/version-2.0/home.mdx b/versioned_docs/version-2.0/home.mdx index 614ef33eea..0c34d76a6f 100644 --- a/versioned_docs/version-2.0/home.mdx +++ b/versioned_docs/version-2.0/home.mdx @@ -2,7 +2,7 @@ slug: / pagination_next: null pagination_prev: null -wrapperClassName: docs-home-page +wrapperClassName: docsHomePage hide_table_of_contents: true --- diff --git a/versioned_docs/version-2.5/home.mdx b/versioned_docs/version-2.5/home.mdx index 614ef33eea..0c34d76a6f 100644 --- a/versioned_docs/version-2.5/home.mdx +++ b/versioned_docs/version-2.5/home.mdx @@ -2,7 +2,7 @@ slug: / pagination_next: null pagination_prev: null -wrapperClassName: docs-home-page +wrapperClassName: docsHomePage hide_table_of_contents: true --- diff --git a/versioned_docs/version-3.0/home.mdx b/versioned_docs/version-3.0/home.mdx index f694bf8226..a1935c0184 100644 --- a/versioned_docs/version-3.0/home.mdx +++ b/versioned_docs/version-3.0/home.mdx @@ -2,7 +2,7 @@ slug: / pagination_next: null pagination_prev: null -wrapperClassName: docs-home-page +wrapperClassName: docsHomePage hide_table_of_contents: true --- diff --git a/versioned_docs/version-3.5/home.mdx b/versioned_docs/version-3.5/home.mdx index f694bf8226..a1935c0184 100644 --- a/versioned_docs/version-3.5/home.mdx +++ b/versioned_docs/version-3.5/home.mdx @@ -2,7 +2,7 @@ slug: / pagination_next: null pagination_prev: null -wrapperClassName: docs-home-page +wrapperClassName: docsHomePage hide_table_of_contents: true --- diff --git a/versioned_docs/version-4.0/home.mdx b/versioned_docs/version-4.0/home.mdx index f694bf8226..a1935c0184 100644 --- a/versioned_docs/version-4.0/home.mdx +++ b/versioned_docs/version-4.0/home.mdx @@ -2,7 +2,7 @@ slug: / pagination_next: null pagination_prev: null -wrapperClassName: docs-home-page +wrapperClassName: docsHomePage hide_table_of_contents: true --- diff --git a/versioned_docs/version-4.1/home.mdx b/versioned_docs/version-4.1/home.mdx index f694bf8226..a1935c0184 100644 --- a/versioned_docs/version-4.1/home.mdx +++ b/versioned_docs/version-4.1/home.mdx @@ -2,7 +2,7 @@ slug: / pagination_next: null pagination_prev: null -wrapperClassName: docs-home-page +wrapperClassName: docsHomePage hide_table_of_contents: true --- diff --git a/versioned_docs/version-4.2/home.mdx b/versioned_docs/version-4.2/home.mdx index f694bf8226..a1935c0184 100644 --- a/versioned_docs/version-4.2/home.mdx +++ b/versioned_docs/version-4.2/home.mdx @@ -2,7 +2,7 @@ slug: / pagination_next: null pagination_prev: null -wrapperClassName: docs-home-page +wrapperClassName: docsHomePage hide_table_of_contents: true --- diff --git a/versioned_docs/version-5.0/home.mdx b/versioned_docs/version-5.0/home.mdx index f694bf8226..a1935c0184 100644 --- a/versioned_docs/version-5.0/home.mdx +++ b/versioned_docs/version-5.0/home.mdx @@ -2,7 +2,7 @@ slug: / pagination_next: null pagination_prev: null -wrapperClassName: docs-home-page +wrapperClassName: docsHomePage hide_table_of_contents: true --- diff --git a/versioned_docs/version-5.1/home.mdx b/versioned_docs/version-5.1/home.mdx index f694bf8226..a1935c0184 100644 --- a/versioned_docs/version-5.1/home.mdx +++ b/versioned_docs/version-5.1/home.mdx @@ -2,7 +2,7 @@ slug: / pagination_next: null pagination_prev: null -wrapperClassName: docs-home-page +wrapperClassName: docsHomePage hide_table_of_contents: true --- diff --git a/versioned_docs/version-5.2/home.mdx b/versioned_docs/version-5.2/home.mdx index f694bf8226..a1935c0184 100644 --- a/versioned_docs/version-5.2/home.mdx +++ b/versioned_docs/version-5.2/home.mdx @@ -2,7 +2,7 @@ slug: / pagination_next: null pagination_prev: null -wrapperClassName: docs-home-page +wrapperClassName: docsHomePage hide_table_of_contents: true --- diff --git a/versioned_docs/version-5.3/home.mdx b/versioned_docs/version-5.3/home.mdx index f694bf8226..a1935c0184 100644 --- a/versioned_docs/version-5.3/home.mdx +++ b/versioned_docs/version-5.3/home.mdx @@ -2,7 +2,7 @@ slug: / pagination_next: null pagination_prev: null -wrapperClassName: docs-home-page +wrapperClassName: docsHomePage hide_table_of_contents: true --- diff --git a/versioned_docs/version-5.4/home.mdx b/versioned_docs/version-5.4/home.mdx index f694bf8226..a1935c0184 100644 --- a/versioned_docs/version-5.4/home.mdx +++ b/versioned_docs/version-5.4/home.mdx @@ -2,7 +2,7 @@ slug: / pagination_next: null pagination_prev: null -wrapperClassName: docs-home-page +wrapperClassName: docsHomePage hide_table_of_contents: true --- diff --git a/versioned_docs/version-6.0/home.mdx b/versioned_docs/version-6.0/home.mdx index f694bf8226..a1935c0184 100644 --- a/versioned_docs/version-6.0/home.mdx +++ b/versioned_docs/version-6.0/home.mdx @@ -2,7 +2,7 @@ slug: / pagination_next: null pagination_prev: null -wrapperClassName: docs-home-page +wrapperClassName: docsHomePage hide_table_of_contents: true --- diff --git a/versioned_docs/version-6.2/home.mdx b/versioned_docs/version-6.2/home.mdx index f694bf8226..a1935c0184 100644 --- a/versioned_docs/version-6.2/home.mdx +++ b/versioned_docs/version-6.2/home.mdx @@ -2,7 +2,7 @@ slug: / pagination_next: null pagination_prev: null -wrapperClassName: docs-home-page +wrapperClassName: docsHomePage hide_table_of_contents: true --- diff --git a/versioned_docs/version-7.0/home.mdx b/versioned_docs/version-7.0/home.mdx index f694bf8226..a1935c0184 100644 --- a/versioned_docs/version-7.0/home.mdx +++ b/versioned_docs/version-7.0/home.mdx @@ -2,7 +2,7 @@ slug: / pagination_next: null pagination_prev: null -wrapperClassName: docs-home-page +wrapperClassName: docsHomePage hide_table_of_contents: true --- diff --git a/versioned_docs/version-7.1/home.mdx b/versioned_docs/version-7.1/home.mdx index f694bf8226..a1935c0184 100644 --- a/versioned_docs/version-7.1/home.mdx +++ b/versioned_docs/version-7.1/home.mdx @@ -2,7 +2,7 @@ slug: / pagination_next: null pagination_prev: null -wrapperClassName: docs-home-page +wrapperClassName: docsHomePage hide_table_of_contents: true ---