Skip to content

Commit a658571

Browse files
committed
Make tags hierarchical
1 parent 562cbc6 commit a658571

3 files changed

Lines changed: 110 additions & 14 deletions

File tree

src/_data/tag-definitions.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,7 @@
562562
{
563563
"tag": "associated-newspapers",
564564
"title": "Associated Newspapers",
565+
"parent": "dmgt",
565566
"permalink": "/tags/associated-newspapers/",
566567
"aliases": [
567568
"associated-newspapers"
@@ -2162,6 +2163,7 @@
21622163
{
21632164
"tag": "daily-mail",
21642165
"title": "Daily Mail",
2166+
"parent": "associated-newspapers",
21652167
"permalink": "/tags/daily-mail/",
21662168
"aliases": [
21672169
"daily-mail"

src/_data/tagDefinitions.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ function createDefinition(data) {
3131
const tag = decodeValue(data.tag);
3232
const title = decodeValue(data.title) || `Tag: ${tag}`;
3333
const commentary = decodeValue(data.commentary);
34+
const parent = decodeValue(data.parent);
3435
const displayTitle = stripTagPrefix(title, tag);
3536
const permalink = ensureWrappedSlashes(data.permalink || `/tags/${tag}/`);
3637
const slug = permalink.replace(/^\/+|\/+$/g, "");
@@ -48,6 +49,7 @@ function createDefinition(data) {
4849
tag,
4950
title,
5051
commentary,
52+
parent: parent ? String(parent).trim() : null,
5153
displayTitle,
5254
heading: `Tag: ${displayTitle}`,
5355
permalink,
@@ -66,6 +68,23 @@ module.exports = function () {
6668
}
6769
}
6870

71+
for (const definition of definitions) {
72+
if (!definition.parent) {
73+
definition.parentTag = null;
74+
definition.parentPermalink = null;
75+
continue;
76+
}
77+
78+
const parentDefinition = byAlias[definition.parent];
79+
80+
if (!parentDefinition) {
81+
throw new Error(`Unknown parent tag alias "${definition.parent}" for tag "${definition.tag}"`);
82+
}
83+
84+
definition.parentTag = parentDefinition.tag;
85+
definition.parentPermalink = parentDefinition.permalink;
86+
}
87+
6988
return {
7089
all: definitions,
7190
byAlias

src/_data/tags.js

Lines changed: 89 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,123 @@
11
const getPosts = require("./posts.js");
22
const getTagDefinitions = require("./tagDefinitions.js");
33

4+
function createFallbackDefinition(rawTag) {
5+
return {
6+
tag: rawTag,
7+
title: `Tag: ${rawTag}`,
8+
commentary: null,
9+
parent: null,
10+
parentTag: null,
11+
parentPermalink: null,
12+
displayTitle: rawTag,
13+
heading: `Tag: ${rawTag}`,
14+
permalink: `/tags/${rawTag}/`,
15+
slug: `tags/${rawTag}`,
16+
aliases: [rawTag]
17+
};
18+
}
19+
20+
function uniquePosts(posts) {
21+
const seen = new Set();
22+
const result = [];
23+
24+
for (const post of posts) {
25+
if (seen.has(post.permalink)) {
26+
continue;
27+
}
28+
29+
seen.add(post.permalink);
30+
result.push(post);
31+
}
32+
33+
return result;
34+
}
35+
436
module.exports = function () {
537
const posts = getPosts();
6-
const { byAlias } = getTagDefinitions();
38+
const { all, byAlias } = getTagDefinitions();
739
const tagMap = new Map();
840

41+
for (const definition of all) {
42+
tagMap.set(definition.permalink, {
43+
...definition,
44+
directPosts: [],
45+
posts: [],
46+
rawTags: new Set(),
47+
childrenPermalinks: new Set()
48+
});
49+
}
50+
951
for (const post of posts) {
1052
for (const rawTag of post.tags || []) {
11-
const definition = byAlias[rawTag] || {
12-
tag: rawTag,
13-
title: `Tag: ${rawTag}`,
14-
displayTitle: rawTag,
15-
heading: `Tag: ${rawTag}`,
16-
permalink: `/tags/${rawTag}/`,
17-
slug: `tags/${rawTag}`,
18-
aliases: [rawTag]
19-
};
53+
const definition = byAlias[rawTag] || createFallbackDefinition(rawTag);
2054
const key = definition.permalink;
2155

2256
if (!tagMap.has(key)) {
2357
tagMap.set(key, {
2458
...definition,
59+
directPosts: [],
2560
posts: [],
2661
rawTags: new Set()
2762
});
2863
}
2964

3065
const tagEntry = tagMap.get(key);
3166

32-
if (!tagEntry.posts.some((entry) => entry.permalink === post.permalink)) {
33-
tagEntry.posts.push(post);
67+
if (!tagEntry.directPosts.some((entry) => entry.permalink === post.permalink)) {
68+
tagEntry.directPosts.push(post);
3469
}
3570

3671
tagEntry.rawTags.add(rawTag);
3772
}
3873
}
3974

75+
for (const tagEntry of tagMap.values()) {
76+
if (tagEntry.parentPermalink && tagMap.has(tagEntry.parentPermalink)) {
77+
tagMap.get(tagEntry.parentPermalink).childrenPermalinks.add(tagEntry.permalink);
78+
}
79+
}
80+
81+
const memo = new Map();
82+
83+
function collectPostsForTag(permalink, stack = []) {
84+
if (memo.has(permalink)) {
85+
return memo.get(permalink);
86+
}
87+
88+
if (stack.includes(permalink)) {
89+
throw new Error(`Tag hierarchy cycle detected: ${stack.concat(permalink).join(" -> ")}`);
90+
}
91+
92+
const tagEntry = tagMap.get(permalink);
93+
const nestedStack = stack.concat(permalink);
94+
let postsForTag = [...(tagEntry.directPosts || [])];
95+
96+
for (const childPermalink of tagEntry.childrenPermalinks || []) {
97+
postsForTag = postsForTag.concat(collectPostsForTag(childPermalink, nestedStack));
98+
}
99+
100+
const deduped = uniquePosts(postsForTag).sort((a, b) => new Date(b.date) - new Date(a.date));
101+
memo.set(permalink, deduped);
102+
return deduped;
103+
}
104+
40105
return Array.from(tagMap.values())
41106
.map((tag) => ({
42107
...tag,
108+
directCount: tag.directPosts.length,
109+
childTags: Array.from(tag.childrenPermalinks)
110+
.map((permalink) => tagMap.get(permalink))
111+
.filter(Boolean)
112+
.map((entry) => ({
113+
tag: entry.tag,
114+
displayTitle: entry.displayTitle,
115+
permalink: entry.permalink
116+
}))
117+
.sort((a, b) => a.displayTitle.localeCompare(b.displayTitle)),
43118
rawTags: Array.from(tag.rawTags).sort(),
44-
posts: tag.posts.sort((a, b) => new Date(b.date) - new Date(a.date)),
45-
count: tag.posts.length
119+
posts: collectPostsForTag(tag.permalink),
120+
count: collectPostsForTag(tag.permalink).length
46121
}))
47122
.sort((a, b) => a.displayTitle.localeCompare(b.displayTitle));
48123
};

0 commit comments

Comments
 (0)