diff --git a/CLAUDE.md b/CLAUDE.md index a4483aacbb..c7bc21e658 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -135,11 +135,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 @@ -377,11 +469,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/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/docusaurus.config.ts b/docusaurus.config.ts index a80074d39c..893b6f7e9c 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -123,6 +123,15 @@ const config: Config = { showLastUpdateTime: true, }, ], + [ + "content-docs", + { + id: "samples", + path: "samples", + routeBasePath: "samples", + sidebarPath: require.resolve("./sidebarsSamples.js"), + }, + ], [ "@docusaurus/plugin-ideal-image", { @@ -134,6 +143,7 @@ const config: Config = { }, ], require.resolve("./src/plugins/recent-guides-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-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/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 586858f096..f43fc89ec1 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 702a1e102d..3284ba8ef9 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 ef48d05daf..a0ee47a33e 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/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 54c4e78d81..9a747fe578 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 712cc0551f..4d6b249b4d 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 acb8497142..49dd4f0aad 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 cefa677bfd..7f2045ed6d 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: "toggle-on" -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 eec06c2d4f..aed477cfb8 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" @@ -19,7 +19,7 @@ see_also: source: "docs" path: "Client API > Data Subscriptions" 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 3076c43feb..3ea85e78ef 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 28bb2bdf58..2fa700862c 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 0d08e4b244..667a0869da 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 35b8f6642c..49a0bc52e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,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", @@ -10942,6 +10943,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", @@ -15754,6 +15782,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 ad67343d53..9d7ef40ce4 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,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/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/tags/challenges-solutions.yml b/samples/tags/challenges-solutions.yml new file mode 100644 index 0000000000..0284737f52 --- /dev/null +++ b/samples/tags/challenges-solutions.yml @@ -0,0 +1,8 @@ +cloud-tax: + label: "Cloud Tax" +gen-ai-data-enrichment: + label: "Gen AI Data Enrichment" +integration-patterns: + label: "Integration Patterns" +semantic-search: + label: "Semantic Search" diff --git a/samples/tags/feature.yml b/samples/tags/feature.yml new file mode 100644 index 0000000000..7d16c8e681 --- /dev/null +++ b/samples/tags/feature.yml @@ -0,0 +1,12 @@ +vector-search: + label: "Vector Search" +document-refresh: + label: "Document Refresh" +include: + label: "Include" +azure-storage-queues-etl: + label: "Azure Storage Queues ETL" +ai-agents: + label: "AI Agents" +full-text-search: + label: "Full-Text Search" diff --git a/samples/tags/tech-stack.yml b/samples/tags/tech-stack.yml new file mode 100644 index 0000000000..d186346d3c --- /dev/null +++ b/samples/tags/tech-stack.yml @@ -0,0 +1,16 @@ +csharp: + label: "C#" +nodejs: + label: "Node.js" +python: + label: "Python" +java: + label: "Java" +php: + label: "PHP" +aspire: + label: "Aspire" +azure-storage-queues: + label: "Azure Storage Queues" +azure-functions: + label: "Azure Functions" diff --git a/samples/the-ravens-library.mdx b/samples/the-ravens-library.mdx new file mode 100644 index 0000000000..208ef1c08a --- /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-search] +feature_tags: [azure-storage-queues-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/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/Badge.tsx b/src/components/Common/Badge.tsx index e2a51a6846..39f026791b 100644 --- a/src/components/Common/Badge.tsx +++ b/src/components/Common/Badge.tsx @@ -3,7 +3,6 @@ import clsx from "clsx"; import { Icon } from "./Icon"; import { IconName } from "@site/src/typescript/iconName"; -// eslint-disable-next-line no-undef export interface BadgeProps extends React.HTMLAttributes { children: React.ReactNode; className?: string; 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 c33e506cea..f989a8b997 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; @@ -33,6 +33,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 e083d5d20d..c41ac6d8b8 100644 --- a/src/components/Common/LazyImage.tsx +++ b/src/components/Common/LazyImage.tsx @@ -1,21 +1,30 @@ 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 // { src: { src: "/img/file.hash.png" } } instead of plain URL strings. // Extract the URL string from such objects. function toUrl(value: unknown): string | undefined { - if (typeof value === "string") return value; - if (!value || typeof value !== "object") return undefined; + if (typeof value === "string") { + return value; + } + if (!value || typeof value !== "object") { + return undefined; + } const { src } = value as Record; - if (typeof src === "string") return src; - if (src && typeof src === "object") return (src as Record).src as string; + if (typeof src === "string") { + return src; + } + if (src && typeof src === "object") { + return (src as Record).src as string; + } return undefined; } @@ -26,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 @@ -38,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..f586431b35 --- /dev/null +++ b/src/components/Samples/Hub/Partials/FilterCategory.tsx @@ -0,0 +1,114 @@ +import React, { useEffect } 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 visibleTags = isTagsExpanded ? tags : tags.slice(0, 5); + const hiddenCount = Math.max(0, tags.length - 5); + + useEffect(() => { + if (!isTagsExpanded && !manuallyCollapsed && tags.length > 5) { + const hiddenTags = tags.slice(5); + const hasSelectedHiddenTag = hiddenTags.some((tag) => selectedTags.has(tag.key)); + if (hasSelectedHiddenTag) { + expandTags(); + } + } + }, [selectedTags, tags, isTagsExpanded, expandTags, manuallyCollapsed]); + + const handleToggleTagsExpanded = () => { + if (isTagsExpanded) { + 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..a9c3ad66cf --- /dev/null +++ b/src/components/Samples/Hub/Partials/SamplesFilter.tsx @@ -0,0 +1,150 @@ +import React, { useState, useEffect } 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(""); + + useEffect(() => { + const categoriesToExpand = new Set(expandedCategories); + let hasChanges = false; + + categories.forEach((category) => { + const hasSelectedTag = category.tags.some((tag) => selectedTags.has(tag.key)); + if (hasSelectedTag && !categoriesToExpand.has(category.name) && !manuallyCollapsed.has(category.name)) { + categoriesToExpand.add(category.name); + hasChanges = true; + } + }); + + if (hasChanges) { + setExpandedCategories(categoriesToExpand); + } + }, [selectedTags, categories, manuallyCollapsed, expandedCategories]); + + const toggleCategory = (categoryName: string) => { + const newExpanded = new Set(expandedCategories); + const newManuallyCollapsed = new Set(manuallyCollapsed); + + if (newExpanded.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..276e2adc60 --- /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; + 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/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 4b558a679a..a0e77991b2 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; } @@ -570,10 +542,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; } @@ -627,6 +601,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, @@ -689,7 +673,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; } @@ -756,3 +741,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/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..7f9d06a2f4 --- /dev/null +++ b/src/plugins/recent-samples-plugin.ts @@ -0,0 +1,177 @@ +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]) => { + allTags.push({ + label: value.label, + key, + count: tagCounts[key] || 0, + category, + }); + }); + }); + + return { + samples: samples, + tags: allTags, + }; + }, + async contentLoaded({ content, actions }) { + const { setGlobalData } = actions; + setGlobalData(content); + }, + }; +} diff --git a/src/theme/DocItem/Authors/index.tsx b/src/theme/DocItem/Authors/index.tsx index 1bba0891f8..5daefadef7 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; } @@ -63,15 +62,15 @@ const socialIconMap: Record = { export default function DocItemAuthors() { const { frontMatter } = useDoc(); - const { publishedAt, author: authorKey } = frontMatter; + const { published_at, author: authorKey } = frontMatter; - if (!authorKey && !publishedAt) { + if (!authorKey && !published_at) { return null; } const author = authorKey ? getAuthorData(authorKey) : null; - if (!author && !publishedAt) { + if (!author && !published_at) { return null; } @@ -122,10 +121,10 @@ export default function DocItemAuthors() {
)} - {publishedAt && ( + {published_at && (
Published on{" "} - {new Date(publishedAt).toLocaleDateString("en-US", { + {new Date(published_at).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", 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..42b9f29431 100644 --- a/src/theme/DocItem/Metadata/DocPageMetadata.tsx +++ b/src/theme/DocItem/Metadata/DocPageMetadata.tsx @@ -16,12 +16,17 @@ export interface DocPageMetadataProps { canonicalUrl: string; ogImageUrl: string; // Shared (optional) - lastUpdatedAt?: number; + lastUpdatedAt?: number | null; + 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..f437323fbf 100644 --- a/src/theme/DocItem/Metadata/index.tsx +++ b/src/theme/DocItem/Metadata/index.tsx @@ -21,16 +21,18 @@ export default function MetadataWrapper(props: Props): ReactNode { 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; - // 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(/\/$/, ""); const canonicalUrl = ( isDocumentationPage - ? `${baseUrl}/${siteConfig.customFields.latestVersion}${metadata.slug}` + ? `${baseUrl}/${siteConfig.customFields?.latestVersion}${metadata.slug}` : `${baseUrl}${permalink}` ).replace(/\/$/, ""); @@ -76,6 +78,23 @@ export default function MetadataWrapper(props: Props): ReactNode { lastUpdatedAt={metadata.lastUpdatedAt} /> )} + {isSamplePage && ( + tag.replace(/-/g, " "))} + /> + )} ); @@ -96,7 +115,7 @@ function ValidatedGuideDocPageMetadata({ }: { title: string; description: string; - lastUpdatedAt?: number; + lastUpdatedAt?: number | null; frontMatter: ReturnType["frontMatter"]; canonicalUrl: string; ogImageUrl: string; @@ -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/TOC/useFilteredToc.ts b/src/theme/DocItem/TOC/useFilteredToc.ts index 2d0bd321ab..a813d0f44e 100644 --- a/src/theme/DocItem/TOC/useFilteredToc.ts +++ b/src/theme/DocItem/TOC/useFilteredToc.ts @@ -34,7 +34,6 @@ function getFilteredToc(originalToc: readonly TOCItem[]): readonly TOCItem[] { const filteredToc: TOCItem[] = []; uniqueIds.forEach((id) => { - // eslint-disable-next-line no-undef const headingEl = markdownEl.querySelector(`#${CSS.escape(id)}`); if (headingEl) { diff --git a/src/theme/DocItem/index.tsx b/src/theme/DocItem/index.tsx index 4c27d8108e..b32f23df66 100644 --- a/src/theme/DocItem/index.tsx +++ b/src/theme/DocItem/index.tsx @@ -4,6 +4,7 @@ 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"; type Props = WrapperProps; @@ -11,6 +12,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 +28,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/Tag/index.tsx b/src/theme/Tag/index.tsx index 42e0de194f..fedfc4eb9a 100644 --- a/src/theme/Tag/index.tsx +++ b/src/theme/Tag/index.tsx @@ -3,10 +3,7 @@ import clsx from "clsx"; import Link from "@docusaurus/Link"; import type { Props as DocusaurusTagProps } from "@theme/Tag"; -export interface Props - extends Partial, - // eslint-disable-next-line no-undef - React.HTMLAttributes { +export interface Props extends Partial, React.HTMLAttributes { children?: React.ReactNode; to?: string; size?: "xs" | "default"; diff --git a/src/typescript/docMetadata.d.ts b/src/typescript/docMetadata.d.ts index d40e3f782d..f6219efc5d 100644 --- a/src/typescript/docMetadata.d.ts +++ b/src/typescript/docMetadata.d.ts @@ -3,15 +3,41 @@ import { DocsLanguage } from "@site/src/components/LanguageStore"; import { SeeAlsoItemType } from "@site/src/components/SeeAlso/types"; import { IconName } from "@site/src/typescript/iconName"; +export interface GalleryImage { + src: string; + alt?: string; +} + +export interface RelatedResourceFrontMatter { + type: "guide" | "documentation" | "video"; + documentation_type?: "docs" | "cloud"; + subtitle: string; + article_key?: string; + url?: string; +} + export interface CustomDocFrontMatter extends DocFrontMatter { supported_languages?: DocsLanguage[]; see_also?: SeeAlsoItemType[]; author?: string; icon?: IconName; - image?: string | { light: string; dark: string }; - publishedAt?: string; - proficiencyLevel?: string; + image?: string; + img_alt?: string; + published_at?: string; + proficiency_level?: string; keywords?: string[]; + gallery?: GalleryImage[]; + challenges_solutions_tags?: string[]; + feature_tags?: string[]; + tech_stack_tags?: string[]; + category?: string; + license?: string; + license_url?: string; + repository_url?: string; + demo_url?: string; + languages?: string[]; + external_url?: string; + related_resources?: RelatedResourceFrontMatter[]; } type CustomDocContextValue = Omit & { 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/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/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/authors.mdx b/templates/authors.mdx index c5bd3c277d..e0e7fb0382 100644 --- a/templates/authors.mdx +++ b/templates/authors.mdx @@ -12,10 +12,6 @@ see_also: import Admonition from "@theme/Admonition"; import Panel from "@site/src/components/Panel"; - - - - # Authors ## Add a new author 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 7ade1167af..59d715022a 100644 --- a/templates/featured-guides.mdx +++ b/templates/featured-guides.mdx @@ -16,10 +16,6 @@ see_also: import Admonition from "@theme/Admonition"; import Panel from "@site/src/components/Panel"; - - - - # Featured guides ## Mark a guide as featured @@ -50,7 +46,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" @@ -71,5 +67,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 b1bbadf41f..c5964d7e3b 100644 --- a/templates/frames.mdx +++ b/templates/frames.mdx @@ -10,10 +10,6 @@ import Panel from '@site/src/components/Panel'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; - - - - # Frames overview This guide shows how to use content frames in MDX: lightweight `ContentFrame` for small/medium chunks and `Panel` for larger sections with a built‑in heading and body. @@ -26,15 +22,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 @@ -167,41 +161,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/home.mdx b/templates/home.mdx index 4899092a28..8d174f3a7f 100644 --- a/templates/home.mdx +++ b/templates/home.mdx @@ -5,10 +5,6 @@ pagination_prev: null title: "Documentation Templates" --- - - - - # Documentation Templates Welcome to the documentation authoring toolkit. This section serves as a reference library for the standardized building blocks used throughout our documentation. diff --git a/templates/icon-gallery.mdx b/templates/icon-gallery.mdx index a96fa529c9..dd3391a16a 100644 --- a/templates/icon-gallery.mdx +++ b/templates/icon-gallery.mdx @@ -12,10 +12,6 @@ see_also: import IconGallery from "@site/src/components/IconGallery/IconGallery"; import Admonition from "@theme/Admonition"; - - - - # Icon Gallery This page showcases all available icons in the project along with their names. Click on any icon to copy its name to the clipboard. 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/introduction.mdx b/templates/introduction.mdx index 3cc39ff65c..ac6233ed75 100644 --- a/templates/introduction.mdx +++ b/templates/introduction.mdx @@ -8,10 +8,6 @@ import CardWithImage from "@site/src/components/Common/CardWithImage"; import CardWithImageHorizontal from "@site/src/components/Common/CardWithImageHorizontal"; import ColGrid from "@site/src/components/ColGrid"; - - - - # Start template Use RavenDB to ship AI functionalities faster diff --git a/templates/new-guides.mdx b/templates/new-guides.mdx index 17de6c17d0..9494184ae2 100644 --- a/templates/new-guides.mdx +++ b/templates/new-guides.mdx @@ -17,10 +17,6 @@ import Admonition from "@theme/Admonition"; import Panel from "@site/src/components/Panel"; import Link from "@docusaurus/Link"; - - - - # Adding new guides This template explains how to create new Guides. @@ -56,7 +52,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. @@ -104,11 +100,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 419a493843..ddce7cd89f 100644 --- a/templates/see-also.mdx +++ b/templates/see-also.mdx @@ -7,10 +7,6 @@ sidebar_label: See also import Admonition from "@theme/Admonition"; import SeeAlso from "@site/src/components/SeeAlso"; - - - - # See also The `SeeAlso` component is used to display a list of related articles or external resources at the end of a page. It is automatically rendered in the page footer when the `see_also` property is defined in the page's frontmatter. 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/tags.mdx b/templates/tags.mdx index 6f40e217b9..7853b2c5a2 100644 --- a/templates/tags.mdx +++ b/templates/tags.mdx @@ -16,10 +16,6 @@ see_also: import Admonition from "@theme/Admonition"; import Panel from "@site/src/components/Panel"; - - - - # Tags ## Add a new tag diff --git a/templates/themed-images.mdx b/templates/themed-images.mdx index 407d00bcbb..ae6b63c977 100644 --- a/templates/themed-images.mdx +++ b/templates/themed-images.mdx @@ -12,10 +12,6 @@ import ColGrid from "@site/src/components/ColGrid"; import lightImage from '@site/static/img/templates/light-image.png'; import darkImage from '@site/static/img/templates/dark-image.png'; - - - - # Themed Images overview This guide shows how to use `ThemedImage` component to display different images for light and dark themes, ensuring optimal visual appearance in both modes. @@ -30,7 +26,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'; @@ -42,7 +38,7 @@ import darkImage from '@site/static/img/templates/dark-image.png'; dark: darkImage, }} /> -```` +``` #### Live example @@ -98,96 +94,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 @@ -197,9 +104,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 @@ -214,12 +118,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 ---