soames-astro-theme 0.1.2 → 0.1.4
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
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Breadcrumb trail for single Knowledge Base articles (docs CPT). Presentational
|
|
2
|
+
// and static (no hydration): the crumb list is assembled in the docs template
|
|
3
|
+
// from docAncestors() + the /docs/ landing title. Crumbs with an `href` render
|
|
4
|
+
// as links; the last crumb (the current page) has no href and renders as plain
|
|
5
|
+
// text with aria-current="page". Separators are drawn in CSS (::before), so they
|
|
6
|
+
// aren't part of the accessible name or selectable text.
|
|
7
|
+
import React from "react";
|
|
8
|
+
import parse from "html-react-parser";
|
|
9
|
+
|
|
10
|
+
export interface Crumb {
|
|
11
|
+
label: string;
|
|
12
|
+
href?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface BreadcrumbsProps {
|
|
16
|
+
crumbs: Crumb[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const Breadcrumbs: React.FC<BreadcrumbsProps> = ({ crumbs }) => (
|
|
20
|
+
<nav className="soames-breadcrumbs-nav" aria-label="Breadcrumb">
|
|
21
|
+
<ol className="soames-breadcrumbs">
|
|
22
|
+
{crumbs.map((crumb, i) => {
|
|
23
|
+
const isCurrent = i === crumbs.length - 1;
|
|
24
|
+
return (
|
|
25
|
+
<li key={i} className="soames-breadcrumb-item">
|
|
26
|
+
{crumb.href && !isCurrent ? (
|
|
27
|
+
<a href={crumb.href}>{parse(crumb.label)}</a>
|
|
28
|
+
) : (
|
|
29
|
+
<span aria-current={isCurrent ? "page" : undefined}>
|
|
30
|
+
{parse(crumb.label)}
|
|
31
|
+
</span>
|
|
32
|
+
)}
|
|
33
|
+
</li>
|
|
34
|
+
);
|
|
35
|
+
})}
|
|
36
|
+
</ol>
|
|
37
|
+
</nav>
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export default Breadcrumbs;
|
package/src/lib/wp.ts
CHANGED
|
@@ -263,6 +263,25 @@ export function buildDocTree(docs: WpDoc[]): DocTreeNode[] {
|
|
|
263
263
|
return roots;
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
// Walk the parentDatabaseId chain up from a doc, returning its ancestors in
|
|
267
|
+
// root→parent order (excluding the doc itself). Used for breadcrumbs. Derives
|
|
268
|
+
// from the data, not the URI, because slugs don't always match titles and we
|
|
269
|
+
// need each ancestor's title as well as its uri. The depth cap guards against a
|
|
270
|
+
// malformed cycle in parentDatabaseId.
|
|
271
|
+
export function docAncestors(docs: WpDoc[], databaseId: number): WpDoc[] {
|
|
272
|
+
const byId = new Map<number, WpDoc>(docs.map((d) => [d.databaseId, d]));
|
|
273
|
+
const chain: WpDoc[] = [];
|
|
274
|
+
let current = byId.get(databaseId);
|
|
275
|
+
let guard = 0;
|
|
276
|
+
while (current && current.parentDatabaseId && guard++ < 50) {
|
|
277
|
+
const parent = byId.get(current.parentDatabaseId);
|
|
278
|
+
if (!parent) break;
|
|
279
|
+
chain.unshift(parent);
|
|
280
|
+
current = parent;
|
|
281
|
+
}
|
|
282
|
+
return chain;
|
|
283
|
+
}
|
|
284
|
+
|
|
266
285
|
export async function getGeneralSettings(): Promise<{ title: string; description: string }> {
|
|
267
286
|
const data = await wpQuery<{ generalSettings: { title: string; description: string } }>(
|
|
268
287
|
`{ generalSettings { title description } }`
|
|
@@ -7,7 +7,8 @@ import Base from "@theme/layouts/Base.astro";
|
|
|
7
7
|
import HeroHeader from "@theme/components/HeroHeader";
|
|
8
8
|
import Shortcodes from "@theme/components/shortcodes/Shortcodes";
|
|
9
9
|
import DocsSidebar from "@theme/components/DocsSidebar";
|
|
10
|
-
import
|
|
10
|
+
import Breadcrumbs from "@theme/components/Breadcrumbs";
|
|
11
|
+
import { getDocs, buildDocTree, getDocsPage, docAncestors } from "../../lib/wp";
|
|
11
12
|
|
|
12
13
|
export async function getStaticPaths() {
|
|
13
14
|
// Derive the [...slug] param from the doc's WP uri (e.g. /docs/getting-started/
|
|
@@ -31,18 +32,18 @@ export async function getStaticPaths() {
|
|
|
31
32
|
.filter(({ slug }) => slug.length > 0)
|
|
32
33
|
.map(({ doc, slug }) => ({
|
|
33
34
|
params: { slug },
|
|
34
|
-
props: { doc, tree, heroTitle, heroBg, heroOverlay },
|
|
35
|
+
props: { doc, docs, tree, heroTitle, heroBg, heroOverlay },
|
|
35
36
|
}));
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
let { doc, tree, heroTitle, heroBg, heroOverlay } = Astro.props;
|
|
39
|
+
let { doc, docs, tree, heroTitle, heroBg, heroOverlay } = Astro.props;
|
|
39
40
|
|
|
40
41
|
// Dev only: re-fetch on each request so WP doc edits show on refresh without
|
|
41
42
|
// restarting `astro dev` (getStaticPaths props are cached otherwise). Stripped
|
|
42
43
|
// from production builds (import.meta.env.DEV).
|
|
43
44
|
if (import.meta.env.DEV) {
|
|
44
45
|
const toSlug = (uri: string) => uri.replace(/^\/+|\/+$/g, "").replace(/^docs\/?/, "");
|
|
45
|
-
|
|
46
|
+
docs = await getDocs();
|
|
46
47
|
const fresh = docs.find((d) => toSlug(d.uri) === Astro.params.slug);
|
|
47
48
|
if (fresh) {
|
|
48
49
|
doc = fresh;
|
|
@@ -53,6 +54,15 @@ if (import.meta.env.DEV) {
|
|
|
53
54
|
heroBg = dp?.featuredImage?.sourceUrl ?? null;
|
|
54
55
|
heroOverlay = dp?.overlayOpacity ?? null;
|
|
55
56
|
}
|
|
57
|
+
|
|
58
|
+
// Breadcrumb trail: Home › <docs landing> › <ancestors…> › current (plain text).
|
|
59
|
+
// Ancestors come from the parentDatabaseId chain (data, not the URI).
|
|
60
|
+
const crumbs = [
|
|
61
|
+
{ label: "Home", href: "/" },
|
|
62
|
+
{ label: heroTitle, href: "/docs/" },
|
|
63
|
+
...docAncestors(docs, doc.databaseId).map((a) => ({ label: a.title, href: a.uri })),
|
|
64
|
+
{ label: doc.title },
|
|
65
|
+
];
|
|
56
66
|
---
|
|
57
67
|
<Base pageTitle={doc.title} description={doc.excerpt}>
|
|
58
68
|
<HeroHeader title={heroTitle} subhead="" backgroundImage={heroBg} overlayOpacity={heroOverlay} />
|
|
@@ -62,6 +72,7 @@ if (import.meta.env.DEV) {
|
|
|
62
72
|
<section id="soames-gatsby-content-container" class="soames-gatsby-blog-content">
|
|
63
73
|
<article class="blog-post soames-prose" itemscope itemtype="http://schema.org/TechArticle">
|
|
64
74
|
<header>
|
|
75
|
+
<Breadcrumbs crumbs={crumbs} />
|
|
65
76
|
<h1 itemprop="headline">{doc.title}</h1>
|
|
66
77
|
</header>
|
|
67
78
|
{doc.content && (
|
|
@@ -1108,9 +1108,23 @@ section.lazy-placeholder:after {
|
|
|
1108
1108
|
padding: 0;
|
|
1109
1109
|
}
|
|
1110
1110
|
|
|
1111
|
-
/*
|
|
1111
|
+
/* Own the vertical rhythm locally instead of inheriting the global `ul li`
|
|
1112
|
+
margin from wordpress-blocks.css. That global margin is cancelled on a
|
|
1113
|
+
submenu's first item by `.soames-docs-menu { margin: 0 }`, which left the
|
|
1114
|
+
first link under a parent with no gap above it. Reset it, then apply one
|
|
1115
|
+
uniform step between every item. */
|
|
1116
|
+
.soames-docs-menu-item {
|
|
1117
|
+
margin: 0;
|
|
1118
|
+
}
|
|
1119
|
+
.soames-docs-menu-item + .soames-docs-menu-item {
|
|
1120
|
+
margin-top: 1rem;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/* Nested levels indent under their parent, and sit a full step below the parent
|
|
1124
|
+
link so the first child matches the spacing between all other items. */
|
|
1112
1125
|
.soames-docs-submenu {
|
|
1113
1126
|
margin-left: 0.85rem;
|
|
1127
|
+
margin-top: 1rem;
|
|
1114
1128
|
}
|
|
1115
1129
|
|
|
1116
1130
|
.soames-docs-menu-item > a {
|
|
@@ -1139,6 +1153,53 @@ section.lazy-placeholder:after {
|
|
|
1139
1153
|
font-weight: 500;
|
|
1140
1154
|
}
|
|
1141
1155
|
|
|
1156
|
+
/* ── Article breadcrumbs (Breadcrumbs) ─────────────────────────────────────── */
|
|
1157
|
+
.soames-breadcrumbs-nav {
|
|
1158
|
+
font-family: 'Rubik', sans-serif;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
.soames-breadcrumbs {
|
|
1162
|
+
display: flex;
|
|
1163
|
+
flex-wrap: wrap;
|
|
1164
|
+
align-items: center;
|
|
1165
|
+
list-style: none;
|
|
1166
|
+
margin: 0 0 0.75rem;
|
|
1167
|
+
padding: 0;
|
|
1168
|
+
font-size: 0.9rem;
|
|
1169
|
+
line-height: 1.4;
|
|
1170
|
+
color: #6c6c6c;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
.soames-breadcrumb-item {
|
|
1174
|
+
display: inline-flex;
|
|
1175
|
+
align-items: center;
|
|
1176
|
+
margin: 0;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
/* Separator between crumbs, drawn in CSS so it isn't selectable or announced. */
|
|
1180
|
+
.soames-breadcrumb-item + .soames-breadcrumb-item::before {
|
|
1181
|
+
content: "›";
|
|
1182
|
+
margin: 0 0.5rem;
|
|
1183
|
+
color: #b3b0aa;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
.soames-breadcrumb-item a {
|
|
1187
|
+
color: #6c6c6c;
|
|
1188
|
+
text-decoration: none;
|
|
1189
|
+
transition: color 0.15s ease-in-out;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
.soames-breadcrumb-item a:hover,
|
|
1193
|
+
.soames-breadcrumb-item a:focus {
|
|
1194
|
+
color: #bc361b;
|
|
1195
|
+
text-decoration: underline;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
/* Current page: no link, reads a touch stronger than the ancestors. */
|
|
1199
|
+
.soames-breadcrumb-item [aria-current="page"] {
|
|
1200
|
+
color: #232323;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1142
1203
|
.soames-menu .navbar {
|
|
1143
1204
|
padding: .5rem 0;
|
|
1144
1205
|
background: #bc361b;
|