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() {