Skip to content

Commit 149f3a2

Browse files
RDoc-3729 claude-seo fixes part 2
1 parent fb27f8f commit 149f3a2

13 files changed

Lines changed: 204 additions & 29 deletions

File tree

eslint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ module.exports = [
7474
"@typescript-eslint/no-explicit-any": "off",
7575
"@typescript-eslint/prefer-namespace-keyword": "off",
7676

77-
"no-console": "warn",
77+
"no-console": ["warn", { allow: ["warn", "error"] }],
7878
"no-debugger": "error",
7979
"no-alert": "warn",
8080
"no-var": "error",

samples/the-ravens-library.mdx

Lines changed: 98 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,21 @@ featureTags: [azure_storage_queues_etl, document_refresh, include, vector_search
66
techStackTags: [csharp, aspire, azure_storage_queues, azure_functions]
77
category: "Ecommerce"
88
license: "MIT License"
9+
licenseUrl: "https://github.com/ravendb/samples-library/blob/main/LICENSE"
10+
repositoryUrl: "https://github.com/ravendb/samples-library"
11+
languages: ["C#"]
912
image: "/img/samples/library-of-ravens/cover.webp"
1013
imgAlt: "The Library of Ravens App Screenshot"
1114
gallery:
1215
- src: "/img/samples/library-of-ravens/01.webp"
1316
alt: "The Library of Ravens - Main Interface"
1417
- src: "/img/samples/library-of-ravens/02.webp"
1518
alt: "The Library of Ravens - Author Profile"
19+
- src: "/img/samples/library-of-ravens/03.webp"
20+
alt: "The Library of Ravens - User Profile"
1621
---
1722

18-
import { ActionsCard, RelatedResource, FeatureAccordion, SampleMetadata, SampleMetadataColumn, SampleLayout } from '@site/src/components/Samples';
23+
import { ActionsCard, RelatedResource, FeatureAccordion, SampleMetadataColumn, SampleLayout } from '@site/src/components/Samples';
1924
import Gallery from '@site/src/components/Common/Gallery';
2025

2126
export const sampleDetails = (
@@ -24,7 +29,6 @@ export const sampleDetails = (
2429
<ActionsCard
2530
githubUser="ravendb"
2631
githubRepo="samples-library"
27-
demoUrl="https://demo.ravendb.net"
2832
/>
2933
}
3034
relatedResources={
@@ -46,15 +50,6 @@ export const sampleDetails = (
4650
/>
4751
);
4852

49-
<SampleMetadata
50-
title={frontMatter.title}
51-
description={frontMatter.description}
52-
permalink="/samples/the-ravens-library"
53-
license="https://opensource.org/licenses/MIT"
54-
languages={["C#"]}
55-
repositoryUrl="https://github.com/ravendb/samples-library"
56-
/>
57-
5853
<SampleLayout details={sampleDetails} gallery={<Gallery images={frontMatter.gallery} />}>
5954

6055
## Overview
@@ -84,16 +79,16 @@ Built with [RavenDB](https://ravendb.net/), [Aspire](https://aspire.dev/), [Azur
8479
using var session = store.OpenAsyncSession();
8580

8681
var book = await session
87-
.Include&lt;Book&gt;(b => b.AuthorId)
88-
.Include&lt;Book&gt;(b => b.CategoryId)
89-
.LoadAsync&lt;Book&gt;("books/1-A");
82+
.Include<Book>(b => b.AuthorId)
83+
.Include<Book>(b => b.CategoryId)
84+
.LoadAsync<Book>("books/1-A");
9085

9186
// These are already loaded - no additional DB calls
92-
var author = await session.LoadAsync&lt;Author&gt;(book.AuthorId);
93-
var category = await session.LoadAsync&lt;Category&gt;(book.CategoryId);
87+
var author = await session.LoadAsync<Author>(book.AuthorId);
88+
var category = await session.LoadAsync<Category>(book.CategoryId);
9489

9590
// Example: Loading multiple books with their authors
96-
var books = await session.Query&lt;Book&gt;()
91+
var books = await session.Query<Book>()
9792
.Include(b => b.AuthorId)
9893
.Where(b => b.IsAvailable)
9994
.ToListAsync();
@@ -105,23 +100,106 @@ Built with [RavenDB](https://ravendb.net/), [Aspire](https://aspire.dev/), [Azur
105100
description="Semantic search powered by AI embeddings for intelligent content discovery."
106101
icon="vector-search"
107102
>
108-
Vector search enables semantic similarity matching, allowing users to find books based on meaning rather than just keywords.
103+
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.
104+
105+
Implementation example:
106+
107+
```csharp
108+
// Define a vector index for book embeddings
109+
public class Books_ByEmbedding : AbstractIndexCreationTask<Book>
110+
{
111+
public Books_ByEmbedding()
112+
{
113+
Map = books => from book in books
114+
select new
115+
{
116+
book.Title,
117+
book.Description,
118+
// Vector field for semantic search
119+
Embedding = CreateField("Embedding",
120+
book.Embedding,
121+
stored: false,
122+
indexing: FieldIndexing.Default)
123+
};
124+
}
125+
}
126+
127+
// Find similar books using vector search
128+
var similarBooks = await session
129+
.Query<Book, Books_ByEmbedding>()
130+
.VectorSearch(
131+
field: b => b.Embedding,
132+
queryVector: currentBook.Embedding,
133+
minimumSimilarity: 0.7f)
134+
.Take(5)
135+
.ToListAsync();
136+
```
109137
</FeatureAccordion>
110138

111139
<FeatureAccordion
112140
title="Azure Storage Queues ETL"
113141
description="Reliable data integration with Azure Storage Queues."
114142
icon="azure-queue-storage-etl"
115143
>
116-
ETL to Azure Storage Queues ensures that all library updates are reliably propagated to downstream systems.
144+
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.
145+
146+
Implementation example:
147+
148+
```csharp
149+
// RavenDB ETL Script for Azure Storage Queues
150+
// Configured in RavenDB Studio
151+
152+
// This script runs when documents are refreshed
153+
loadToAzureQueueStorage('expiring-loans', {
154+
BookId: id(this),
155+
Title: this.Title,
156+
BorrowerId: this.CurrentLoan.BorrowerId,
157+
DueDate: this.CurrentLoan.DueDate,
158+
BorrowerEmail: load(this.CurrentLoan.BorrowerId).Email
159+
});
160+
161+
// Azure Function triggered by the queue message
162+
[Function("ProcessExpiringLoan")]
163+
public async Task Run(
164+
[QueueTrigger("expiring-loans")] LoanNotification notification)
165+
{
166+
await _emailService.SendReminderAsync(
167+
notification.BorrowerEmail,
168+
notification.Title,
169+
notification.DueDate);
170+
}
171+
```
117172
</FeatureAccordion>
118173

119174
<FeatureAccordion
120175
title="Document Refresh"
121176
description="Automatic document updates based on external data sources."
122177
icon="document-refresh"
123178
>
124-
Document Refresh keeps book metadata up-to-date by automatically fetching the latest information from external APIs.
179+
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.
180+
181+
Implementation example:
182+
183+
```csharp
184+
// Set a document to refresh at a specific time
185+
public async Task SetBookReturnReminder(
186+
string bookId,
187+
DateTime returnDate)
188+
{
189+
using var session = store.OpenAsyncSession();
190+
var book = await session.LoadAsync<Book>(bookId);
191+
192+
// Set the @refresh metadata
193+
var metadata = session.Advanced.GetMetadataFor(book);
194+
metadata["@refresh"] = returnDate.AddDays(-1); // Day before due
195+
196+
await session.SaveChangesAsync();
197+
}
198+
199+
// The document will be re-indexed when refresh time arrives,
200+
// triggering any subscriptions or ETL processes watching for
201+
// books with approaching due dates
202+
```
125203
</FeatureAccordion>
126204

127205
## Technologies

src/components/IconGallery/IconGalleryCard.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export default function IconGalleryCard({ iconName }: IconGalleryCardProps) {
1515
setCopied(true);
1616
window.setTimeout(() => setCopied(false), 2000);
1717
} catch (err) {
18-
// eslint-disable-next-line no-console
1918
console.error("Failed to copy icon name to clipboard:", err);
2019
}
2120
};

src/components/Samples/Overview/Partials/FeatureAccordion.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ export default function FeatureAccordion({
6565
"overflow-hidden transition-all duration-200 ease-in-out",
6666
isExpanded ? "grid grid-rows-[1fr] opacity-100" : "grid grid-rows-[0fr] opacity-0"
6767
)}
68-
aria-hidden={!isExpanded}
6968
>
7069
<div className="min-h-0">
7170
<div className="border-t border-black/10 dark:border-white/10 mt-3 pt-3 [&>*:last-child]:!mb-0">

src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export default function SampleMetadataColumn({ className, actionsCard, relatedRe
9393
const techStackTagKeys = frontMatter.techStackTags;
9494
const category = frontMatter.category;
9595
const license = frontMatter.license;
96+
const licenseUrl = frontMatter.licenseUrl;
9697

9798
const challengesSolutionsTags = getTagsWithLabels(challengesSolutionsTagKeys, challengesSolutionsTagsData);
9899
const featureTags = getTagsWithLabels(featureTagKeys, featureTagsData);
@@ -120,7 +121,19 @@ export default function SampleMetadataColumn({ className, actionsCard, relatedRe
120121
<Heading as="h5" className="!mb-0 text-sm font-semibold">
121122
License
122123
</Heading>
123-
<p className="text-sm text-black dark:text-white !mb-0">{license}</p>
124+
<p className="text-sm text-black dark:text-white !mb-0">
125+
{licenseUrl ? (
126+
<a
127+
href={licenseUrl}
128+
target="_blank"
129+
rel="noopener noreferrer"
130+
>
131+
{license}
132+
</a>
133+
) : (
134+
license
135+
)}
136+
</p>
124137
</div>
125138
)}
126139

src/plugins/recent-guides-plugin.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ export default function recentGuidesPlugin(context, _options): Plugin {
5858
const fileContent = fs.readFileSync(tagsYmlPath, "utf8");
5959
predefinedTags = (yaml.load(fileContent) as any) || {};
6060
} catch (e) {
61-
// eslint-disable-next-line no-console
6261
console.error("Failed to load tags.yml", e);
6362
}
6463
}

src/plugins/recent-samples-plugin.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ export default function recentSamplesPlugin(context, _options): Plugin {
7070
const fileContent = fs.readFileSync(filePath, "utf8");
7171
tagsByCategory[category] = (yaml.load(fileContent) as any) || {};
7272
} catch (e) {
73-
// eslint-disable-next-line no-console
7473
console.error(`Failed to load tags/${file}`, e);
7574
}
7675
}

src/theme/DocItem/Authors/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ type Author = {
1818
function getAuthorData(authorKey: string): Author | null {
1919
const authorInfo = authorsData[authorKey];
2020
if (!authorInfo) {
21-
// eslint-disable-next-line no-console
2221
console.warn(`No author data found for key '${authorKey}' in authors.json`);
2322
return null;
2423
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React, { type ReactNode } from "react";
2+
import Head from "@docusaurus/Head";
3+
4+
export interface SampleMetadataProps {
5+
title: string;
6+
description: string;
7+
canonicalUrl: string;
8+
ogImageUrl: string;
9+
repositoryUrl?: string;
10+
licenseUrl?: string;
11+
languages?: string[];
12+
keywords?: string[];
13+
}
14+
15+
export default function SampleMetadata({
16+
title,
17+
description,
18+
canonicalUrl,
19+
repositoryUrl,
20+
licenseUrl,
21+
languages,
22+
keywords,
23+
}: SampleMetadataProps): ReactNode {
24+
const jsonLd = JSON.stringify({
25+
"@context": "https://schema.org",
26+
"@type": "SoftwareSourceCode",
27+
name: title,
28+
...(description ? { description } : {}),
29+
url: canonicalUrl,
30+
...(repositoryUrl ? { codeRepository: repositoryUrl } : {}),
31+
...(licenseUrl ? { license: licenseUrl } : {}),
32+
...(languages?.length ? { programmingLanguage: languages } : {}),
33+
...(keywords?.length ? { keywords } : {}),
34+
runtimePlatform: "RavenDB",
35+
publisher: {
36+
"@type": "Organization",
37+
name: "RavenDB",
38+
url: "https://ravendb.net",
39+
},
40+
isPartOf: {
41+
"@type": "CollectionPage",
42+
"@id": `${canonicalUrl.split("/samples/")[0]}/samples`,
43+
name: "RavenDB Code Samples",
44+
},
45+
});
46+
47+
return (
48+
<Head>
49+
<script type="application/ld+json">{jsonLd}</script>
50+
</Head>
51+
);
52+
}

src/theme/DocItem/Metadata/index.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useDoc } from "@docusaurus/plugin-content-docs/client";
66
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
77
import Head from "@docusaurus/Head";
88
import GuideMetadata from "./GuideMetadata";
9+
import SampleMetadata from "./SampleMetadata";
910

1011
type Props = WrapperProps<typeof MetadataType>;
1112

@@ -20,11 +21,13 @@ export default function MetadataWrapper(props: Props): ReactNode {
2021
const isGuide = source?.startsWith("@site/guides/") || source?.startsWith("guides/") || false;
2122
const isCloud = source?.startsWith("@site/cloud/") || source?.startsWith("cloud/") || false;
2223
const isTemplate = source?.startsWith("@site/templates/") || source?.startsWith("templates/") || false;
23-
const isVersionedDoc = !isGuide && !isCloud && !isTemplate;
24+
const isSample = source?.startsWith("@site/samples/") || source?.startsWith("samples/") || false;
25+
const isVersionedDoc = !isGuide && !isCloud && !isTemplate && !isSample;
2426

25-
// Exclude landing pages (e.g. guides/home.mdx) from guide-specific metadata
27+
// Exclude landing pages (e.g. guides/home.mdx, samples/home.mdx) from type-specific metadata
2628
const fileName = source?.split("/").pop();
2729
const isGuidePage = isGuide && fileName !== "home.mdx";
30+
const isSamplePage = isSample && fileName !== "home.mdx";
2831

2932
// Strip trailing slash from base URL to avoid double slashes
3033
const baseUrl = (siteConfig.url as string).replace(/\/$/, "");
@@ -69,6 +72,22 @@ export default function MetadataWrapper(props: Props): ReactNode {
6972
permalink={permalink}
7073
/>
7174
)}
75+
{isSamplePage && (
76+
<SampleMetadata
77+
title={title}
78+
description={description}
79+
canonicalUrl={canonicalUrl}
80+
ogImageUrl={ogImageUrl}
81+
repositoryUrl={frontMatter.repositoryUrl}
82+
licenseUrl={frontMatter.licenseUrl}
83+
languages={frontMatter.languages}
84+
keywords={[
85+
...(frontMatter.challengesSolutionsTags ?? []),
86+
...(frontMatter.featureTags ?? []),
87+
...(frontMatter.techStackTags ?? []),
88+
].map((tag) => tag.replace(/_/g, " "))}
89+
/>
90+
)}
7291
<Metadata {...props} />
7392
</>
7493
);

0 commit comments

Comments
 (0)