@half-built/astro 0.8.0 → 0.10.0
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/README.md
CHANGED
|
@@ -57,20 +57,37 @@ consumer config itself.
|
|
|
57
57
|
|
|
58
58
|
## WhenPublished and PostLink
|
|
59
59
|
|
|
60
|
-
`content/WhenPublished.astro` renders its children only when the post
|
|
61
|
-
at `slug` is visible, so a published post can tease one still in
|
|
62
|
-
draft and the passage appears on its own when the target ships.
|
|
63
60
|
`content/PostLink.astro` links to a post by slug and resolves the
|
|
64
61
|
href at build time through `postPath`, so a re-dated post does not
|
|
65
|
-
strand the links pointing at it.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
62
|
+
strand the links pointing at it. While the target is hidden (a draft, with the
|
|
63
|
+
draft gate off) it renders its text alone, with no element and no
|
|
64
|
+
styling, and becomes a link on its own when the post ships. That makes
|
|
65
|
+
it the right tool for a draft's name in published prose.
|
|
66
|
+
|
|
67
|
+
`content/WhenPublished.astro` renders its children only when the post
|
|
68
|
+
at `slug` is visible. Use it when a whole phrase only makes sense once
|
|
69
|
+
the post exists ("its own post", "see it here"). An optional `fallback`
|
|
70
|
+
slot renders while the target is hidden:
|
|
71
|
+
|
|
72
|
+
```mdx
|
|
73
|
+
<WhenPublished slug="my-draft">
|
|
74
|
+
It has <PostLink slug="my-draft">its own post</PostLink> now.
|
|
75
|
+
<Fragment slot="fallback">A post on it is coming.</Fragment>
|
|
76
|
+
</WhenPublished>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Both take the consumer's collection as the `posts` prop (the full
|
|
80
|
+
collection, drafts included, so an unknown slug can throw instead of
|
|
81
|
+
hiding as "still a draft") and the consumer's draft gate as
|
|
82
|
+
`showDrafts`. `showDrafts` defaults to false, so a preview build must
|
|
83
|
+
pass its own draft gate through to `PostLink` to keep draft links
|
|
84
|
+
live. `PostLink` also accepts an optional `tip` carried as
|
|
85
|
+
`data-tooltip` for the link-tip island; a hidden `PostLink` drops it. A
|
|
86
|
+
consumer wraps each in a one-line site component that injects
|
|
87
|
+
`getCollection` and its own gate, the way the blog does. The resolvers
|
|
88
|
+
(`postLinkHref`, `resolvePostHref`, `forwardLinkVisible`) live in
|
|
89
|
+
`lib/drafts.ts` for consumers that want the logic without the
|
|
90
|
+
components.
|
|
74
91
|
|
|
75
92
|
## Derived excerpts
|
|
76
93
|
|
package/package.json
CHANGED
|
@@ -3,7 +3,6 @@ import type { SitemapGroup, EcosystemEntry } from "./models";
|
|
|
3
3
|
import { BP_PHONE_MAX } from "../scripts/core/breakpoints";
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
6
|
-
siteName: string;
|
|
7
6
|
legalHolder: string;
|
|
8
7
|
sitemap: SitemapGroup[];
|
|
9
8
|
ecosystem: EcosystemEntry[];
|
|
@@ -12,7 +11,6 @@ interface Props {
|
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
const {
|
|
15
|
-
siteName,
|
|
16
14
|
legalHolder,
|
|
17
15
|
sitemap,
|
|
18
16
|
ecosystem,
|
|
@@ -28,17 +26,15 @@ const year = new Date().getFullYear();
|
|
|
28
26
|
<div class="shell">
|
|
29
27
|
<div class="bottom-footer-info">
|
|
30
28
|
<div class="site-info">
|
|
31
|
-
{/*
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
the
|
|
35
|
-
so a narrow screen wraps
|
|
29
|
+
{/* The copyright line alone (owner call 2026-09-14). The
|
|
30
|
+
site-name home link that used to sit under it retired.
|
|
31
|
+
The masthead and the Ecosystem group's bold entry already
|
|
32
|
+
say where you are, and the notice is complete without it.
|
|
33
|
+
The segments stay unbreakable so a narrow screen wraps
|
|
34
|
+
only after the year. */}
|
|
36
35
|
<slot name="site-info">
|
|
37
|
-
<span class="site-info-
|
|
38
|
-
|
|
39
|
-
<span class="site-info-seg">{legalHolder}</span>
|
|
40
|
-
</span>
|
|
41
|
-
<span class="site-info-line"><a href="/">{siteName}</a></span>
|
|
36
|
+
<span class="site-info-seg">Copyright © {year}</span>{" "}
|
|
37
|
+
<span class="site-info-seg">{legalHolder}</span>
|
|
42
38
|
</slot>
|
|
43
39
|
</div>
|
|
44
40
|
</div>
|
|
@@ -128,18 +124,9 @@ const year = new Date().getFullYear();
|
|
|
128
124
|
font-size: var(--font-size-sm);
|
|
129
125
|
}
|
|
130
126
|
|
|
131
|
-
/*
|
|
132
|
-
(owner report 2026-08-25). */
|
|
127
|
+
/* Centered like the sitemap below (owner report 2026-08-25). */
|
|
133
128
|
.site-info { text-align: center; }
|
|
134
|
-
.site-info-line { display: block; }
|
|
135
|
-
|
|
136
|
-
/* Underlined, unlike the sitemap links below: this one sits inside
|
|
137
|
-
running text (the copyright block), where the same ink at 0.8
|
|
138
|
-
opacity is not a visible link mark (WCAG 1.4.1, axe
|
|
139
|
-
link-in-text-block; the line break is all that frames it). */
|
|
140
|
-
.site-info a { text-decoration: underline; opacity: 0.8; transition: var(--transition); color: var(--ink); }
|
|
141
129
|
.site-info-seg { white-space: nowrap; }
|
|
142
|
-
.site-info a:hover { opacity: 1; }
|
|
143
130
|
|
|
144
131
|
/* Sitemap columns under the copyright line, centered like it (owner
|
|
145
132
|
feedback 2026-08-01). position: relative lifts the content above the
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
---
|
|
2
|
-
import {
|
|
2
|
+
import { postLinkHref, type Linkable } from "../../lib/drafts";
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
5
5
|
slug: string;
|
|
6
6
|
posts: Linkable[];
|
|
7
|
+
showDrafts?: boolean;
|
|
7
8
|
tip?: string;
|
|
8
9
|
class?: string;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
const { slug, posts, tip, class: className } = Astro.props;
|
|
12
|
-
const href =
|
|
12
|
+
const { slug, posts, showDrafts = false, tip, class: className } = Astro.props;
|
|
13
|
+
const href = postLinkHref(slug, posts, showDrafts);
|
|
13
14
|
---
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
{href ? (
|
|
17
|
+
<a href={href} data-tooltip={tip} class={className}>
|
|
18
|
+
<slot />
|
|
19
|
+
</a>
|
|
20
|
+
) : (
|
|
21
|
+
<slot />
|
|
22
|
+
)}
|
package/src/lib/drafts.ts
CHANGED
|
@@ -43,17 +43,21 @@ export function forwardLinkVisible(
|
|
|
43
43
|
/* Post links by slug. A hand-typed post URL bakes in the target's date,
|
|
44
44
|
so a re-dated draft silently breaks every earlier link to it. The
|
|
45
45
|
PostLink content component resolves the slug through postPath() at
|
|
46
|
-
build time instead.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
build time instead. postLinkHref() is its policy: a visible target
|
|
47
|
+
gets its href, a hidden one gets null and the component renders its
|
|
48
|
+
text with no link (owner decision 2026-09-26), so a draft's name can
|
|
49
|
+
sit in published prose and becomes a link on its own when the draft
|
|
50
|
+
ships. resolvePostHref() still resolves drafts: consumers use it for
|
|
51
|
+
lookups keyed by href, and Astro renders MDX slot children eagerly,
|
|
52
|
+
so a PostLink inside a hidden WhenPublished still runs. The
|
|
53
|
+
deploy-build guard against a link to a draft stays a consumer test
|
|
54
|
+
(no draft slug in any built page). Unknown slugs throw for the same
|
|
55
|
+
reason forwardLinkVisible's do. */
|
|
52
56
|
|
|
53
57
|
import { postPath } from "./slug";
|
|
54
58
|
|
|
55
59
|
export interface Linkable {
|
|
56
|
-
data: { slug: string; date: Date };
|
|
60
|
+
data: { slug: string; date: Date; draft?: boolean };
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
export function resolvePostHref(slug: string, posts: Linkable[]): string {
|
|
@@ -61,3 +65,14 @@ export function resolvePostHref(slug: string, posts: Linkable[]): string {
|
|
|
61
65
|
if (!target) throw new Error(`PostLink: no post has slug "${slug}"`);
|
|
62
66
|
return postPath(target);
|
|
63
67
|
}
|
|
68
|
+
|
|
69
|
+
export function postLinkHref(
|
|
70
|
+
slug: string,
|
|
71
|
+
posts: Linkable[],
|
|
72
|
+
showDrafts: boolean,
|
|
73
|
+
): string | null {
|
|
74
|
+
const target = posts.find((p) => p.data.slug === slug);
|
|
75
|
+
if (!target) throw new Error(`PostLink: no post has slug "${slug}"`);
|
|
76
|
+
if (target.data.draft && !showDrafts) return null;
|
|
77
|
+
return resolvePostHref(slug, posts);
|
|
78
|
+
}
|