Skip to content

Commit ae318d5

Browse files
Katari-8055pranjalisr
authored andcommitted
feat(blog): implement date-based sorting and minimal post preview (truncated text, date, read more link) (webpack#7890) (webpack#7911)
1 parent 3193f0f commit ae318d5

9 files changed

Lines changed: 212 additions & 31 deletions

File tree

src/components/Page/Page.jsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export default function Page(props) {
4444
}, [props.content]);
4545

4646
const { hash, pathname } = useLocation();
47+
const isBlogIndex = pathname === "/blog/";
4748

4849
useEffect(() => {
4950
let observer;
@@ -104,6 +105,9 @@ export default function Page(props) {
104105
<main id="main-content" className="page">
105106
<Markdown>
106107
<h1>{title}</h1>
108+
{rest.date && pathname.startsWith("/blog/") && !isBlogIndex && (
109+
<div className="blog-post-date">{rest.date}</div>
110+
)}
107111

108112
{rest.thirdParty ? (
109113
<div className="italic my-[20px]">
@@ -116,6 +120,27 @@ export default function Page(props) {
116120

117121
<div id="md-content">{contentRender}</div>
118122

123+
{rest.url === "/blog/" && (
124+
<div className="blog-list">
125+
{(props.pages || [])
126+
.filter((post) => post.url !== "/blog/")
127+
.map((post) => (
128+
<div key={post.url} className="blog-post-item">
129+
<h2>
130+
<Link to={post.url}>{post.title}</Link>
131+
</h2>
132+
{post.date && (
133+
<div className="blog-post-date">{post.date}</div>
134+
)}
135+
<p>{post.teaser}</p>
136+
<Link to={post.url} className="read-more">
137+
Read More &rarr;
138+
</Link>
139+
</div>
140+
))}
141+
</div>
142+
)}
143+
119144
{loadRelated && (
120145
<div className="print:hidden">
121146
<h2>Further Reading</h2>
@@ -131,7 +156,7 @@ export default function Page(props) {
131156

132157
<PageLinks page={rest} />
133158

134-
{(previous || next) && (
159+
{!isBlogIndex && (previous || next) && (
135160
<AdjacentPages previous={previous} next={next} />
136161
)}
137162

@@ -155,6 +180,7 @@ Page.propTypes = {
155180
related: PropTypes.array,
156181
previous: PropTypes.object,
157182
next: PropTypes.object,
183+
pages: PropTypes.array,
158184
content: PropTypes.oneOfType([
159185
PropTypes.shape({
160186
// eslint-disable-next-line unicorn/no-thenable

src/components/Page/Page.scss

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,87 @@
1212
padding: 1.5em;
1313
}
1414
}
15+
16+
.blog-list {
17+
margin-top: 2rem;
18+
19+
.blog-post-item {
20+
margin-bottom: 3rem;
21+
padding-bottom: 2rem;
22+
border-bottom: 1px solid #f2f2f2;
23+
24+
&:last-child {
25+
border-bottom: 0;
26+
}
27+
28+
h2 {
29+
margin-top: 0;
30+
margin-bottom: 0.5rem;
31+
font-size: 1.8rem;
32+
font-weight: 700;
33+
line-height: 1.2;
34+
35+
a {
36+
color: #2b3a42;
37+
text-decoration: none;
38+
39+
&:hover {
40+
color: #1d78c1;
41+
}
42+
}
43+
}
44+
45+
.blog-post-date {
46+
color: #666;
47+
font-size: 1.2rem;
48+
font-weight: 600;
49+
margin-top: 0.25rem;
50+
margin-bottom: 1rem;
51+
font-style: italic;
52+
}
53+
54+
p {
55+
color: #535353;
56+
line-height: 1.4;
57+
margin-bottom: 1rem;
58+
}
59+
60+
.read-more {
61+
color: #1d78c1;
62+
font-weight: 600;
63+
text-decoration: none;
64+
65+
&:hover {
66+
text-decoration: underline;
67+
}
68+
}
69+
}
70+
}
71+
72+
[data-theme="dark"] {
73+
.blog-list {
74+
.blog-post-item {
75+
border-bottom-color: #222;
76+
77+
h2 a {
78+
color: #9ab3c0 !important;
79+
80+
&:hover {
81+
color: #8dd6f9 !important;
82+
}
83+
}
84+
85+
.blog-post-date {
86+
color: #bbb;
87+
}
88+
89+
p {
90+
color: #dedede;
91+
}
92+
93+
.read-more {
94+
color: #8dd6f9;
95+
}
96+
}
97+
}
98+
}

src/components/Site/Site.jsx

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,29 @@ function Site(props) {
7676
}
7777

7878
return array
79-
.map(({ title, name, url, group, sort, anchors, children }) => ({
80-
title: title || name,
81-
content: title || name,
82-
url,
83-
group,
84-
sort,
85-
anchors,
86-
children: children ? _strip(children) : [],
87-
}))
79+
.map(
80+
({
81+
title,
82+
name,
83+
url,
84+
group,
85+
sort,
86+
anchors,
87+
children,
88+
date,
89+
teaser,
90+
}) => ({
91+
title: title || name,
92+
content: title || name,
93+
url,
94+
group,
95+
sort,
96+
anchors,
97+
date,
98+
teaser,
99+
children: children ? _strip(children) : [],
100+
}),
101+
)
88102
.filter(
89103
(page) =>
90104
page.title !== "printable.mdx" && !page.content.includes("Printable"),
@@ -316,14 +330,21 @@ function PageElement(props) {
316330
currentPage={currentPage}
317331
pages={sidebarPages}
318332
/>
319-
<Page {...page} content={content} previous={previous} next={next} />
333+
<Page
334+
{...page}
335+
content={content}
336+
previous={previous}
337+
next={next}
338+
pages={sidebarPages}
339+
/>
320340
</Fragment>
321341
);
322342
}
323343

324344
PageElement.propTypes = {
325345
currentPage: PropTypes.string,
326346
sidebarPages: PropTypes.array,
347+
pages: PropTypes.array,
327348
previous: PropTypes.object,
328349
next: PropTypes.object,
329350
page: PropTypes.object,

src/content/blog/2020-10-10-webpack-5-release.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
2-
title: Webpack 5 release (2020-10-10)
3-
sort: -202010100
2+
title: Webpack 5 release
3+
sort: 20201010
44
contributors:
55
- sokra
66
- chenxsan

src/content/blog/2020-12-08-roadmap-2021.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
2-
title: Roadmap 2021 (2020-12-08)
3-
sort: -202012080
2+
title: Roadmap 2021
3+
sort: 20201208
44
contributors:
55
- sokra
66
---

src/content/blog/webpack-5-105.mdx renamed to src/content/blog/2026-02-03-webpack-5-105.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
2-
title: Webpack 5.105 (2026-02-03)
2+
title: Webpack 5.105
3+
sort: 20260203
34
contributors:
45
- bjohansebas
56
---

src/content/blog/2026-04-02-roadmap-2026.mdx renamed to src/content/blog/2026-02-04-roadmap-2026.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
2-
title: Roadmap 2026 (2026-02-04)
3-
sort: 9
2+
title: Roadmap 2026
3+
sort: 20260204
44
contributors:
55
- evenstensberg
66
- bjohansebas
@@ -37,7 +37,7 @@ You can also join the discussion about [universal target](https://github.com/web
3737

3838
### Support for building TypeScript without loaders
3939

40-
Recently, in version [5.105](/blog/webpack-5-105/) we included support for **resolving the paths defined in the TypeScript configuration**, eliminating the need to use a plugin ([tsconfig-paths-webpack-plugin](https://www.npmjs.com/package/tsconfig-paths-webpack-plugin)). Now we want to further expand TypeScript support by **removing the need to use a loader** (the most common one being _ts-loader_) to transpile TypeScript directly in webpack, which would also **reduce your project’s dependencies**.
40+
Recently, in version [5.105](/blog/2026-02-03-webpack-5-105/) we included support for **resolving the paths defined in the TypeScript configuration**, eliminating the need to use a plugin ([tsconfig-paths-webpack-plugin](https://www.npmjs.com/package/tsconfig-paths-webpack-plugin)). Now we want to further expand TypeScript support by **removing the need to use a loader** (the most common one being _ts-loader_) to transpile TypeScript directly in webpack, which would also **reduce your project’s dependencies**.
4141

4242
### [webpack#536](https://github.com/webpack/webpack/issues/536) - Import HTML files and use them as entry points without the need for plugins
4343

src/content/blog/index.mdx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,3 @@ contributors:
88
Read here for announcements.
99

1010
See recent blog posts in the side bar.
11-
12-
## Popular posts
13-
14-
- [Webpack 5 release (2020-10-10)](/blog/2020-10-10-webpack-5-release/)

src/utilities/content-tree-enhancers.mjs

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,19 @@ import slug from "../../src/remark-plugins/remark-slug/index.mjs";
1111
import remarkRemoveHeadingId from "../remark-plugins/remark-remove-heading-id/index.mjs";
1212

1313
export const enhance = (tree, options) => {
14-
// delete `./` root directory on node
15-
const dir = path.normalize(options.dir).replaceAll(/^(\.\/)/gm, "");
14+
// Normalize everything to forward slashes for cross-platform consistency
15+
const dir = path
16+
.normalize(options.dir)
17+
.replaceAll("\\", "/")
18+
.replace(/^\.\//, "");
19+
20+
const normalizedPath = tree.path.replaceAll("\\", "/");
1621

17-
tree.url = tree.path
22+
tree.url = normalizedPath
1823
// delete `.mdx` extensions
1924
.replace(tree.extension, "")
2025
// delete source content directory
2126
.replace(dir, "")
22-
// Normalize url for Windows
23-
.replaceAll("\\", "/")
2427
// remove `index` for root urls
2528
.replace(/\/index$/, "")
2629
// replace empty strings with `/`
@@ -33,7 +36,7 @@ export const enhance = (tree, options) => {
3336
if (tree.type === "file") {
3437
const anchors = [];
3538
const content = fs.readFileSync(tree.path, "utf8");
36-
const { attributes } = frontMatter(content);
39+
const { attributes, body } = frontMatter(content);
3740

3841
// remove underscore from fetched files
3942
if (tree.name[0] === "_") {
@@ -57,10 +60,33 @@ export const enhance = (tree, options) => {
5760

5861
tree.anchors = anchors;
5962

63+
const dateRegex = /\((\d{4}-\d{2}-\d{2})\)/;
64+
const match = attributes.title ? attributes.title.match(dateRegex) : null;
65+
if (match) {
66+
[, tree.date] = match;
67+
} else {
68+
const fileMatch = tree.name.match(/^(\d{4}-\d{2}-\d{2})/);
69+
if (fileMatch) {
70+
[, tree.date] = fileMatch;
71+
}
72+
}
73+
74+
const isBlogItem = normalizedPath.includes("/blog/");
75+
if (isBlogItem) {
76+
const teaser = (body || "")
77+
.split("\n")
78+
.filter((line) => line.trim() && !line.trim().startsWith("#"))
79+
.slice(0, 3)
80+
.join(" ")
81+
.replaceAll(/\[([^\]]+)\]\([^)]+\)/g, "$1") // Strip markdown links but keep text
82+
.slice(0, 240);
83+
tree.teaser = `${teaser}...`;
84+
}
85+
6086
Object.assign(
6187
tree,
6288
{
63-
path: tree.path.replaceAll("\\", "/"),
89+
path: normalizedPath,
6490
},
6591
attributes,
6692
);
@@ -70,12 +96,39 @@ export const enhance = (tree, options) => {
7096
export const filter = () => true;
7197

7298
export const sort = (a, b) => {
99+
const aPath = (a.path || "").toLowerCase();
100+
const bPath = (b.path || "").toLowerCase();
101+
102+
// Blog specific sorting: Index at top, then newest first
103+
if (aPath.includes("/blog/") && bPath.includes("/blog/")) {
104+
if (a.name === "index.mdx" || a.url === "/blog/") return -1;
105+
if (b.name === "index.mdx" || b.url === "/blog/") return 1;
106+
107+
// Blog specific sorting: Index at top, then newest first by date
108+
if (a.date && b.date) {
109+
if (a.date > b.date) return -1;
110+
if (a.date < b.date) return 1;
111+
}
112+
113+
// Backup: Use manual sort property
114+
if (a.sort !== undefined && b.sort !== undefined) {
115+
return b.sort - a.sort;
116+
}
117+
}
118+
73119
const group1 = (a.group || "").toLowerCase();
74120
const group2 = (b.group || "").toLowerCase();
75121

76122
if (group1 < group2) return -1;
77123
if (group1 > group2) return 1;
78-
if (a.sort && b.sort) return a.sort - b.sort;
124+
125+
if (a.sort !== undefined && b.sort !== undefined) {
126+
return a.sort - b.sort;
127+
} else if (a.sort !== undefined) {
128+
return -1;
129+
} else if (b.sort !== undefined) {
130+
return 1;
131+
}
79132

80133
const aTitle = (a.title || "").toLowerCase();
81134
const bTitle = (b.title || "").toLowerCase();

0 commit comments

Comments
 (0)