@dogsbay/docs-layout 0.2.0-beta.98 → 0.2.0-beta.99
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 +52 -3
- package/src/json-ld.ts +3 -1
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.99",
|
|
4
4
|
"description": "Standard documentation layout components for Dogsbay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"./json-ld": "./src/json-ld.ts"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@dogsbay/primitives": "0.2.0-beta.
|
|
34
|
-
"@dogsbay/ui": "0.2.0-beta.
|
|
33
|
+
"@dogsbay/primitives": "0.2.0-beta.99",
|
|
34
|
+
"@dogsbay/ui": "0.2.0-beta.99"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"happy-dom": "^20.10.6",
|
package/src/DocsLayout.astro
CHANGED
|
@@ -47,7 +47,7 @@ import VersionSwitcher from "./VersionSwitcher.astro";
|
|
|
47
47
|
import LocaleSwitcher from "./LocaleSwitcher.astro";
|
|
48
48
|
import { filterNavByAxis } from "./nav-filter.js";
|
|
49
49
|
import type { LlmProviderName } from "./llm-actions.js";
|
|
50
|
-
import { jsonLdTypeFor, normalizeCustomJsonLd, buildArticleJsonLd } from "./json-ld.js";
|
|
50
|
+
import { jsonLdTypeFor, normalizeCustomJsonLd, buildArticleJsonLd, buildWebSiteJsonLd } from "./json-ld.js";
|
|
51
51
|
import { resolveTagKeywords } from "./tag-list-data.js";
|
|
52
52
|
|
|
53
53
|
interface NavItem {
|
|
@@ -623,6 +623,23 @@ const computedCanonical = canonicalUrl
|
|
|
623
623
|
* raw value if it will not parse, so a typo shows up as itself rather
|
|
624
624
|
* than as "Invalid Date".
|
|
625
625
|
*/
|
|
626
|
+
/**
|
|
627
|
+
* A value as a machine-readable ISO 8601 datetime, or "" when it is
|
|
628
|
+
* not a date at all.
|
|
629
|
+
*
|
|
630
|
+
* Unlike `_formatPostDate`, an unparseable value must NOT pass
|
|
631
|
+
* through: this feeds `article:published_time` / `modified_time`,
|
|
632
|
+
* where a malformed datetime is worse than an absent one — a consumer
|
|
633
|
+
* either ignores the whole article object or shows garbage.
|
|
634
|
+
* `lastUpdated` is documented as also accepting a version/tag string
|
|
635
|
+
* ("v2.1"), which must never reach these tags.
|
|
636
|
+
*/
|
|
637
|
+
function isoDate(value: string | undefined): string {
|
|
638
|
+
if (!value) return "";
|
|
639
|
+
const d = new Date(value);
|
|
640
|
+
return Number.isNaN(d.getTime()) ? "" : d.toISOString();
|
|
641
|
+
}
|
|
642
|
+
|
|
626
643
|
function _formatPostDate(iso: string): string {
|
|
627
644
|
const d = new Date(iso);
|
|
628
645
|
if (Number.isNaN(d.getTime())) return iso;
|
|
@@ -682,7 +699,14 @@ const tagKeywords = resolveTagKeywords(tags, tagLabels);
|
|
|
682
699
|
// Course; everything else → Article. Matches the conventions
|
|
683
700
|
// search engines key off for educational / tutorial / reference
|
|
684
701
|
// SERP rendering. See `json-ld.ts` for the full mapping table.
|
|
685
|
-
|
|
702
|
+
// Structured data is emitted for EVERY page, not only tagged articles.
|
|
703
|
+
// This used to also require `tagKeywords.length > 0`, so an untagged
|
|
704
|
+
// page — a homepage, a landing page, a section index — emitted none at
|
|
705
|
+
// all. An external agent audit reported "No JSON-LD structured data
|
|
706
|
+
// found on homepage" and it was right: the page most likely to be an
|
|
707
|
+
// agent's entry point was the one describing itself the least.
|
|
708
|
+
// Keywords are enrichment, not a precondition for identity.
|
|
709
|
+
const articleJsonLd = ogType === "article"
|
|
686
710
|
? buildArticleJsonLd({
|
|
687
711
|
type: jsonLdTypeFor(pageType),
|
|
688
712
|
title,
|
|
@@ -693,7 +717,12 @@ const articleJsonLd = (ogType === "article" && tagKeywords.length > 0)
|
|
|
693
717
|
url: computedCanonical,
|
|
694
718
|
headings,
|
|
695
719
|
})
|
|
696
|
-
:
|
|
720
|
+
: buildWebSiteJsonLd({
|
|
721
|
+
siteName,
|
|
722
|
+
title,
|
|
723
|
+
description: metaDescription,
|
|
724
|
+
url: computedCanonical,
|
|
725
|
+
});
|
|
697
726
|
|
|
698
727
|
// `customJsonLd` accepts either a single object or an array.
|
|
699
728
|
// Normalize to an array so we can iterate uniformly. Empty array
|
|
@@ -806,6 +835,26 @@ const siteIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
|
|
|
806
835
|
{ogType === "article" && tagKeywords.map((keyword) => (
|
|
807
836
|
<meta property="article:tag" content={keyword} />
|
|
808
837
|
))}
|
|
838
|
+
{/* Article dates. `og:type="article"` was declared with no times
|
|
839
|
+
at all, which is an incomplete article object — crawlers and
|
|
840
|
+
social cards had no publish date to show even though the page
|
|
841
|
+
renders one visibly.
|
|
842
|
+
|
|
843
|
+
`article:modified_time` falls back to `lastUpdated`, the prop
|
|
844
|
+
the docs chrome already uses for "Last updated", so a docs page
|
|
845
|
+
gets a modified time without the blog-only `updatedDate` being
|
|
846
|
+
set. Only emitted when the value parses as a date: a
|
|
847
|
+
frontmatter `lastUpdated: v2.1` is a version string, and a
|
|
848
|
+
malformed datetime is worse than an absent one. */}
|
|
849
|
+
{ogType === "article" && isoDate(publishedDate) && (
|
|
850
|
+
<meta property="article:published_time" content={isoDate(publishedDate)} />
|
|
851
|
+
)}
|
|
852
|
+
{ogType === "article" && isoDate(updatedDate ?? lastUpdated) && (
|
|
853
|
+
<meta
|
|
854
|
+
property="article:modified_time"
|
|
855
|
+
content={isoDate(updatedDate ?? lastUpdated)}
|
|
856
|
+
/>
|
|
857
|
+
)}
|
|
809
858
|
|
|
810
859
|
{/* JSON-LD primary block — Article / HowTo / TechArticle /
|
|
811
860
|
Course depending on `pageType`. Emits keywords as a
|
package/src/json-ld.ts
CHANGED
|
@@ -100,8 +100,10 @@ export function buildArticleJsonLd(
|
|
|
100
100
|
"@context": "https://schema.org",
|
|
101
101
|
"@type": type,
|
|
102
102
|
name: title,
|
|
103
|
-
keywords: keywords.join(", "),
|
|
104
103
|
};
|
|
104
|
+
// Only when there are any. An empty `keywords: ""` asserts the page
|
|
105
|
+
// has no topics, which is a different claim from staying silent.
|
|
106
|
+
if (keywords.length > 0) block.keywords = keywords.join(", ");
|
|
105
107
|
|
|
106
108
|
if (type === "Article" || type === "TechArticle") {
|
|
107
109
|
block.headline = title;
|