[Docs Site] Add PartialsUsage component (#23608)

* [Docs Site] Add PartialsUsage component

* naming and 1111 slugs
This commit is contained in:
Kian 2025-07-14 12:14:03 +01:00 committed by GitHub
parent fb579fa3ab
commit 76069eb2ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 228 additions and 88 deletions

View file

@ -1,8 +1,8 @@
---
import { z } from "astro:schema";
import { getComponentsUsage } from "~/util/components";
import { slug } from "github-slugger";
import UsageList from "./UsageList.astro";
import Details from "./Details.astro";
type Props = z.infer<typeof props>;
@ -22,70 +22,6 @@ const usage = await getComponentsUsage(component);
<code>{usage.pages.size}</code> pages.
</p>
<Details header={`Pages which use ${component}`}>
<p><strong>Pages</strong></p>
<ul>
{
[...usage.pages]
.filter((path) => path.startsWith("src/content/docs/"))
.sort()
.map((path) => {
const slugified =
"/" +
path
.replace("src/content/docs/", "")
.replace(".mdx", "")
.split("/")
.map((segment) => slug(segment))
.join("/") +
"/";
return (
<li>
<a
href={"https://developers.cloudflare.com" + slugified}
target="_blank"
>
{slugified}
</a>
<span>
-
<a
href={
"https://github.com/cloudflare/cloudflare-docs/blob/production/" +
path
}
target="_blank"
>
Source
</a>
</span>
</li>
);
})
}
</ul>
<p><strong>Partials</strong></p>
<ul>
{
[...usage.pages]
.filter((path) => path.startsWith("src/content/partials/"))
.sort()
.map((path) => {
return (
<li>
<a
href={
"https://github.com/cloudflare/cloudflare-docs/blob/production/" +
path
}
target="_blank"
>
{path}
</a>
</li>
);
})
}
</ul>
<UsageList usage={usage} />
</Details>
</>

View file

@ -7,12 +7,13 @@ type Props = z.infer<typeof props>;
const props = z.object({
header: z.string(),
open: z.boolean().optional(),
id: z.string().optional(),
});
const { header, open } = props.parse(Astro.props);
const { header, open, id } = props.parse(Astro.props);
---
<details open={open}>
<details open={open} id={id}>
<summary set:html={marked.parse(header)} />
<slot />
</details>

View file

@ -0,0 +1,41 @@
---
import { getPartialsUsage } from "~/util/components";
import Details from "./Details.astro";
import UsageList from "./UsageList.astro";
const partials = await getPartialsUsage();
---
<Details header="Usage" id="partials-container">
{
[...Object.entries(partials)]
.sort((a, b) => a[0].localeCompare(b[0]))
.map(([name, usage]) => (
<Details header={name} id={name}>
<UsageList usage={usage} />
</Details>
))
}
</Details>
<script>
const params = new URLSearchParams(window.location.search);
console.log(params);
const partial = params.get("partial");
if (partial) {
const container = document.querySelector<HTMLDetailsElement>(
"#partials-container",
);
const details = document.querySelector<HTMLDetailsElement>(
`#${CSS.escape(partial)}`,
);
if (container && details) {
container.open = true;
details.open = true;
details.scrollIntoView();
}
}
</script>

View file

@ -0,0 +1,93 @@
---
import { z } from "astro:schema";
import { slug } from "github-slugger";
const props = z.object({
usage: z.object({
count: z.number(),
pages: z.set(z.string()),
}),
});
const { usage } = props.parse(Astro.props);
---
<>
<p>
Used <strong>{usage.count}</strong> times.
</p>
<p>
<strong>Pages</strong>
</p>
<ul>
{
[...usage.pages]
.filter((path) => path.startsWith("src/content/docs/"))
.sort()
.map((path) => {
const slugified =
"/" +
path
.replace("src/content/docs/", "")
.replace(".mdx", "")
.split("/")
.map((segment) => {
if (segment === "1.1.1.1") {
return segment;
}
return slug(segment);
})
.join("/") +
"/";
return (
<li>
<a
href={"https://developers.cloudflare.com" + slugified}
target="_blank"
>
{slugified}
</a>
<span>
-
<a
href={
"https://github.com/cloudflare/cloudflare-docs/blob/production/" +
path
}
target="_blank"
>
Source
</a>
</span>
</li>
);
})
}
</ul>
<p>
<strong>Partials</strong>
</p>
<ul>
{
[...usage.pages]
.filter((path) => path.startsWith("src/content/partials/"))
.sort()
.map((path) => {
return (
<li>
<a
href={
"https://github.com/cloudflare/cloudflare-docs/blob/production/" +
path
}
target="_blank"
>
{path}
</a>
</li>
);
})
}
</ul>
</>

View file

@ -41,6 +41,7 @@ export { default as PagesBuildEnvironmentTools } from "./PagesBuildEnvironmentTo
export { default as PagesBuildPreset } from "./PagesBuildPreset.astro";
export { default as PagesBuildPresetsTable } from "./PagesBuildPresetsTable.astro";
export { default as PagesLanguageSupport } from "./PagesLanguageSupport.astro";
export { default as PartialsUsage } from "./PartialsUsage.astro";
export { default as Plan } from "./Plan.astro";
export { default as PlanInfo } from "./PlanInfo.astro";
export { default as ProductChangelog } from "./ProductChangelog.astro";

View file

@ -4,7 +4,7 @@ styleGuide:
component: Render
---
import { Code, Details, Type, MetaInfo } from "~/components";
import { Code, Details, Type, MetaInfo, PartialsUsage } from "~/components";
The `Render` component allows us to include a "partial", a reusable Markdown snippet, onto a page.
@ -15,40 +15,43 @@ It also accepts parameters that can be used as variables within the partial, so
```mdx live
import { Render } from "~/components";
<Render file="simple-props" params={{
name: "world",
}} />
<Render
file="simple-props"
params={{
name: "world",
}}
/>
```
### Inputs
- `file` <Type text="string" />
This should be the name of the partial, without the containing directory or file extension. For example, `/partials/style-guide/hello.mdx` would be `file="hello"`.
This should be the name of the partial, without the containing directory or file extension. For example, `/partials/style-guide/hello.mdx` would be `file="hello"`.
- `product` <Type text="string" /> <MetaInfo text="optional" />
By default, it will look for partials in the same product folder as the current page. You can use this to specify a different product.
By default, it will look for partials in the same product folder as the current page. You can use this to specify a different product.
:::caution
:::caution
When using the `Render` component inside partials, the original `product` is lost.
When using the `Render` component inside partials, the original `product` is lost.
For example, if there are three files:
For example, if there are three files:
1. `docs/fundamentals/index.mdx`
2. `partials/dns/thing.mdx`
3. `partials/dns/thing2.mdx`
1. `docs/fundamentals/index.mdx`
2. `partials/dns/thing.mdx`
3. `partials/dns/thing2.mdx`
`docs/fundamentals/index.mdx` uses `<Render file="thing" product="dns" />`
`docs/fundamentals/index.mdx` uses `<Render file="thing" product="dns" />`
`partials/dns/thing.mdx` must use `<Render file="thing2" product="dns" />` as `product` cannot be inferred.
`partials/dns/thing.mdx` must use `<Render file="thing2" product="dns" />` as `product` cannot be inferred.
:::
:::
- `params` <Type text="object" /> <MetaInfo text="optional" />
If you wish to substitute values inside your partial, you can use pass params which can be referenced in your partial. Refer to [properties](#properties).
If you wish to substitute values inside your partial, you can use pass params which can be referenced in your partial. Refer to [properties](#properties).
## Properties
@ -128,9 +131,12 @@ import linkRaw from "~/content/partials/style-guide/link-in-props.mdx?raw";
```mdx live
import { Render } from "~/components";
<Render file="link-in-props" params={{
link: "/style-guide/components/render/#links"
}} />
<Render
file="link-in-props"
params={{
link: "/style-guide/components/render/#links",
}}
/>
```
#### Images
@ -185,4 +191,8 @@ import { Render } from "~/components";
<hr />
<Render file="optional-props" params={{ product: "Thing Three" }} />
```
```
## Partials
<PartialsUsage />

View file

@ -12,6 +12,7 @@ import { visit } from "unist-util-visit";
type Usage = { count: number; pages: Set<string> };
let usages: Record<string, Usage>;
let partials: Record<string, Usage>;
export function getComponentsUsage(): Promise<Record<string, Usage>>;
export function getComponentsUsage(component: string): Promise<Usage>;
@ -60,3 +61,60 @@ export async function getComponentsUsage(
return usages;
}
export async function getPartialsUsage(): Promise<Record<string, Usage>> {
if (!partials) {
partials = {};
const entities = await readdir("./src/content/", {
recursive: true,
withFileTypes: true,
});
const files = entities.filter(
(entity) => entity.isFile() && entity.name.endsWith(".mdx"),
);
for (const file of files) {
const fullName = file.parentPath + "/" + file.name;
const content = await readFile(fullName, "utf8");
if (!content.includes("import")) continue;
const tree = fromMarkdown(content, {
extensions: [mdxjs()],
mdastExtensions: [mdxFromMarkdown()],
});
visit(tree, ["mdxJsxFlowElement", "mdxJsxTextElement"], function (node) {
const typed = node as MdxJsxFlowElement | MdxJsxTextElement;
if (!typed.name || typed.name[0] === typed.name[0].toLowerCase())
return;
if (typed.name === "Render") {
const file = typed.attributes.find(
(attr) => attr.type === "mdxJsxAttribute" && attr.name === "file",
)?.value;
let product = typed.attributes.find(
(attr) =>
attr.type === "mdxJsxAttribute" && attr.name === "product",
)?.value;
if (!product) {
product = fullName.split("/")[3];
}
const partialName = `${product}/${file}`;
partials[partialName] ||= { count: 0, pages: new Set() };
partials[partialName].count++;
partials[partialName].pages.add(fullName);
}
});
}
}
return partials;
}