Skip to content

Commit 6d7cd87

Browse files
committed
Verify hash links
1 parent f8a9f2a commit 6d7cd87

4 files changed

Lines changed: 72 additions & 29 deletions

File tree

.eleventy.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,45 @@ module.exports = function (eleventyConfig) {
5959
return dirName.replace(/^\d{4}-\d{2}-\d{2}-/, "");
6060
});
6161

62+
// Transform relative image paths to root-absolute paths
63+
eleventyConfig.addTransform("absoluteImagePaths", function (content, outputPath) {
64+
if (outputPath && outputPath.endsWith(".html")) {
65+
// Get the directory of the output file relative to _site
66+
// e.g., "_site/blog/my-post/index.html" -> "blog/my-post"
67+
// or "/full/path/_site/blog/my-post/index.html" -> "blog/my-post"
68+
let outputDir = outputPath;
69+
70+
// Handle both absolute and relative paths
71+
const siteIndex = outputDir.indexOf("_site/");
72+
if (siteIndex !== -1) {
73+
outputDir = outputDir.substring(siteIndex + 6); // Remove everything up to and including "_site/"
74+
}
75+
76+
// Remove the filename (e.g., "index.html")
77+
// If there's a slash, extract directory path. If no slash (root level), use empty string
78+
if (outputDir.includes("/")) {
79+
outputDir = outputDir.replace(/\/[^\/]*$/, "");
80+
} else {
81+
outputDir = ""; // Root level file
82+
}
83+
84+
// Replace relative image paths with absolute paths
85+
// Match src="./filename.ext"
86+
content = content.replace(/(<img[^>]+src=["'])\.\/([^"']+)(["'])/g, (match, prefix, path, suffix) => {
87+
// Convert ./file.png to /blog/my-post/file.png or /file.png (for root)
88+
const absolutePath = outputDir ? `/${outputDir}/${path}` : `/${path}`;
89+
return `${prefix}${absolutePath}${suffix}`;
90+
});
91+
92+
// Also handle relative paths in link hrefs for images
93+
content = content.replace(/(<a[^>]+href=["'])\.\/([^"']+\.(gif|png|jpg|jpeg|svg))(["'])/gi, (match, prefix, path, ext, suffix) => {
94+
const absolutePath = outputDir ? `/${outputDir}/${path}` : `/${path}`;
95+
return `${prefix}${absolutePath}${suffix}`;
96+
});
97+
}
98+
return content;
99+
});
100+
62101
// Watch for changes in CSS files for hot reload during development
63102
eleventyConfig.addWatchTarget("./static/**/*.css");
64103

.github/workflows/link-check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
uses: lycheeverse/lychee-action@v1
3535
with:
3636
# Check all HTML files in the built site
37-
args: --verbose --no-progress './_site/**/*.html'
37+
args: --verbose --no-progress --include-fragments --base 'https://rupertmckay.com' './_site/**/*.html'
3838
# Fail the workflow if broken links are found
3939
fail: true
4040
env:

lychee.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
# Maximum number of concurrent network requests
55
max_concurrency = 8
66

7-
# Check links in HTML
8-
include = ["./**/*.html"]
7+
# Check that fragment identifiers (hash links) exist on the target page
8+
include_fragments = true
99

1010
# Exclude patterns (regex)
1111
exclude = [
@@ -20,6 +20,9 @@ exclude = [
2020

2121
# Exclude mailto links (they often fail checks)
2222
"mailto:",
23+
24+
# Exclude npmjs.com (blocks automated requests with 403)
25+
"https://www.npmjs.com",
2326
]
2427

2528
# Accepted status codes for valid links

src/blog/2021-07-10-handling-optional-values-in-typescript/index.md

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ description: What are all these question marks in my code?
44
date: 2021-07-10
55
layout: layouts/post.njk
66
---
7+
78
Key takeaways:
89

910
- Prefer `??` to `||`
@@ -24,14 +25,14 @@ TypeScript supports optional properties via the following syntax:
2425
```ts
2526
// Works with 'interface'
2627
interface User {
27-
name: string;
28-
age?: number;
28+
name: string;
29+
age?: number;
2930
}
3031

3132
// and also with 'type'
3233
type User = {
33-
name: string;
34-
age?: number;
34+
name: string;
35+
age?: number;
3536
};
3637
```
3738

@@ -49,14 +50,14 @@ When defining a function that accepts the `User` type, the `age` type will be `n
4950
```ts
5051
// Lets find out if this user is old enough to drive
5152
function isOldEnoughToDrive(user: User) {
52-
// initially user.age is number | undefined
53-
if (user.age === undefined) {
54-
// But after an if check the type will be narrowed
55-
// In this example we return 'false' if 'age' isn't available.
56-
return false;
57-
}
58-
// But if age is available, then it is narrowed to a 'number' type.
59-
return user.age >= DRIVING_AGE;
53+
// initially user.age is number | undefined
54+
if (user.age === undefined) {
55+
// But after an if check the type will be narrowed
56+
// In this example we return 'false' if 'age' isn't available.
57+
return false;
58+
}
59+
// But if age is available, then it is narrowed to a 'number' type.
60+
return user.age >= DRIVING_AGE;
6061
}
6162

6263
isOldEnoughToDrive({ name: "Ash Ketchum", age: 10 }); // returns false
@@ -68,17 +69,17 @@ This behavior is _nearly_ equivalent to if we had:
6869

6970
```ts
7071
interface User {
71-
name: string;
72-
age: number | undefined;
72+
name: string;
73+
age: number | undefined;
7374
}
7475
```
7576

7677
The difference is whether the key must be explicitly provided by objects wanting to satisfy this interface:
7778

7879
```ts
7980
interface User {
80-
name: string;
81-
age: number | undefined;
81+
name: string;
82+
age: number | undefined;
8283
}
8384

8485
// Type error! "age" is a required property of "User"
@@ -106,7 +107,7 @@ The only difference is that `||` will fall back if the lefthand value is _anythi
106107

107108
I highly recommend forgetting about `||` for default fallbacks and using `??` instead.
108109

109-
You can enforce this with [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint)'s own [prefer-nullish-coalescing](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md) rule
110+
You can enforce this with [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint)'s own [prefer-nullish-coalescing](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.mdx) rule
110111

111112
## Optional Chaining (?.)
112113

@@ -116,15 +117,15 @@ What if this time we have a `Session` object, which has a `User` only if the cur
116117

117118
```ts
118119
interface Session {
119-
user?: User;
120+
user?: User;
120121
}
121122

122123
function getWelcomeMessage(session: Session) {
123-
const userName = session.user?.name;
124-
if (userName === undefined) {
125-
return "Hello Guest!";
126-
}
127-
return `Hello ${userName}!`;
124+
const userName = session.user?.name;
125+
if (userName === undefined) {
126+
return "Hello Guest!";
127+
}
128+
return `Hello ${userName}!`;
128129
}
129130
```
130131

@@ -142,12 +143,12 @@ We can use all these concepts together:
142143

143144
```ts
144145
interface Session {
145-
user?: User;
146+
user?: User;
146147
}
147148

148149
function getWelcomeMessage(session: Session) {
149-
const userName = session.user?.name ?? "GUEST";
150-
return `Hello ${userName}!`;
150+
const userName = session.user?.name ?? "GUEST";
151+
return `Hello ${userName}!`;
151152
}
152153
```
153154

0 commit comments

Comments
 (0)