@dogsbay/docs-layout 0.2.0-beta.44 → 0.2.0-beta.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/DocsLayout.astro +11 -10
- package/src/json-ld.ts +77 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dogsbay/docs-layout",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.46",
|
|
4
4
|
"description": "Standard documentation layout components for Dogsbay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"./json-ld": "./src/json-ld.ts"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@dogsbay/ui": "0.2.0-beta.
|
|
33
|
-
"@dogsbay/primitives": "0.2.0-beta.
|
|
32
|
+
"@dogsbay/ui": "0.2.0-beta.46",
|
|
33
|
+
"@dogsbay/primitives": "0.2.0-beta.46"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"vitest": "^3.0.0"
|
package/src/DocsLayout.astro
CHANGED
|
@@ -44,7 +44,7 @@ import VersionSwitcher from "./VersionSwitcher.astro";
|
|
|
44
44
|
import LocaleSwitcher from "./LocaleSwitcher.astro";
|
|
45
45
|
import { filterNavByAxis } from "./nav-filter.js";
|
|
46
46
|
import type { LlmProviderName } from "./llm-actions.js";
|
|
47
|
-
import { jsonLdTypeFor, normalizeCustomJsonLd } from "./json-ld.js";
|
|
47
|
+
import { jsonLdTypeFor, normalizeCustomJsonLd, buildArticleJsonLd } from "./json-ld.js";
|
|
48
48
|
import { resolveTagKeywords } from "./tag-list-data.js";
|
|
49
49
|
|
|
50
50
|
interface NavItem {
|
|
@@ -548,15 +548,16 @@ const tagKeywords = resolveTagKeywords(tags, tagLabels);
|
|
|
548
548
|
// search engines key off for educational / tutorial / reference
|
|
549
549
|
// SERP rendering. See `json-ld.ts` for the full mapping table.
|
|
550
550
|
const articleJsonLd = (ogType === "article" && tagKeywords.length > 0)
|
|
551
|
-
? {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
keywords: tagKeywords
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
551
|
+
? buildArticleJsonLd({
|
|
552
|
+
type: jsonLdTypeFor(pageType),
|
|
553
|
+
title,
|
|
554
|
+
siteName,
|
|
555
|
+
keywords: tagKeywords,
|
|
556
|
+
description: metaDescription,
|
|
557
|
+
image: metaOgImage,
|
|
558
|
+
url: computedCanonical,
|
|
559
|
+
headings,
|
|
560
|
+
})
|
|
560
561
|
: undefined;
|
|
561
562
|
|
|
562
563
|
// `customJsonLd` accepts either a single object or an array.
|
package/src/json-ld.ts
CHANGED
|
@@ -53,3 +53,80 @@ export function normalizeCustomJsonLd(
|
|
|
53
53
|
if (Array.isArray(raw)) return raw;
|
|
54
54
|
return [raw];
|
|
55
55
|
}
|
|
56
|
+
|
|
57
|
+
export interface BuildArticleJsonLdOptions {
|
|
58
|
+
/** Already-resolved Schema.org `@type` (output of jsonLdTypeFor). */
|
|
59
|
+
type: string;
|
|
60
|
+
title: string;
|
|
61
|
+
/** Site name — used as the `provider` Organization for Course. */
|
|
62
|
+
siteName: string;
|
|
63
|
+
keywords: string[];
|
|
64
|
+
description?: string;
|
|
65
|
+
image?: string;
|
|
66
|
+
url?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Page headings — used to synthesize `step[]` for HowTo. Each
|
|
69
|
+
* H2 (or H3 if no H2s exist) becomes a HowToStep with a deep
|
|
70
|
+
* link to the heading id.
|
|
71
|
+
*/
|
|
72
|
+
headings?: ReadonlyArray<{ depth: number; slug: string; text: string }>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Build the JSON-LD payload for the page's primary structured-data
|
|
77
|
+
* block, shaped per `@type`. The earlier implementation emitted
|
|
78
|
+
* Article-shaped fields (`headline`) regardless of @type, which
|
|
79
|
+
* meant HowTo / Course pages failed Google's Rich Results
|
|
80
|
+
* requirements (HowTo needs `name` + `step`, Course needs `name` +
|
|
81
|
+
* `description` + `provider`). See
|
|
82
|
+
* `packages/cli/src/audit/rules/seo/json-ld-required-fields.ts`
|
|
83
|
+
* for the validator that catches this.
|
|
84
|
+
*
|
|
85
|
+
* Schema.org's `name` is a Thing-level field accepted by every
|
|
86
|
+
* @type, so we always emit it. `headline` is added on top for
|
|
87
|
+
* Article-family types (Article, TechArticle) because Google's
|
|
88
|
+
* Rich Results validator specifically requires it there. HowTo
|
|
89
|
+
* gets a synthesized `step[]` from the page's H2 headings (each
|
|
90
|
+
* H2 = one procedure step); Course gets a `provider` Organization
|
|
91
|
+
* built from `siteName`.
|
|
92
|
+
*/
|
|
93
|
+
export function buildArticleJsonLd(
|
|
94
|
+
opts: BuildArticleJsonLdOptions,
|
|
95
|
+
): Record<string, unknown> {
|
|
96
|
+
const { type, title, siteName, keywords, description, image, url, headings } =
|
|
97
|
+
opts;
|
|
98
|
+
|
|
99
|
+
const block: Record<string, unknown> = {
|
|
100
|
+
"@context": "https://schema.org",
|
|
101
|
+
"@type": type,
|
|
102
|
+
name: title,
|
|
103
|
+
keywords: keywords.join(", "),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
if (type === "Article" || type === "TechArticle") {
|
|
107
|
+
block.headline = title;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (description) block.description = description;
|
|
111
|
+
if (image) block.image = image;
|
|
112
|
+
if (url) block.url = url;
|
|
113
|
+
|
|
114
|
+
if (type === "HowTo") {
|
|
115
|
+
let stepHeadings = (headings ?? []).filter((h) => h.depth === 2);
|
|
116
|
+
if (stepHeadings.length === 0) {
|
|
117
|
+
stepHeadings = (headings ?? []).filter((h) => h.depth === 3);
|
|
118
|
+
}
|
|
119
|
+
block.step = stepHeadings.map((h) => ({
|
|
120
|
+
"@type": "HowToStep",
|
|
121
|
+
name: h.text,
|
|
122
|
+
url: url ? `${url}#${h.slug}` : `#${h.slug}`,
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (type === "Course") {
|
|
127
|
+
block.provider = { "@type": "Organization", name: siteName };
|
|
128
|
+
if (!block.description) block.description = title;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return block;
|
|
132
|
+
}
|