Skip to content

Commit 1d1008f

Browse files
yepzdkclaude
andcommitted
fix: show real per-project last-edited dates and add sort control
The CI runner used a shallow clone, so the projectDates data loader fell back to the same commit timestamp for every project card on the deployed site. Fetch full history in both workflows and add a sort control on the home page (last edited / alphabetically, asc/desc). Co-authored-by: Claude <noreply@anthropic.com>
1 parent a9514b9 commit 1d1008f

4 files changed

Lines changed: 99 additions & 5 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ jobs:
1919
runs-on: ubuntu-latest
2020
steps:
2121
- uses: actions/checkout@v4
22+
with:
23+
fetch-depth: 0
2224

2325
- uses: actions/setup-node@v4
2426
with:

.github/workflows/verify_build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ jobs:
1313
runs-on: ubuntu-latest
1414
steps:
1515
- uses: actions/checkout@v4
16+
with:
17+
fetch-depth: 0
1618

1719
- uses: actions/setup-node@v4
1820
with:

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

77
## [Unreleased]
88

9+
### Fixed — Project "Last edited" dates on the deployed site
10+
- GitHub Actions workflows (`deploy.yml`, `verify_build.yml`) now check out the full git history (`fetch-depth: 0`) so the `projectDates` data loader can read the per-project last-commit timestamp. Previously the shallow clone caused every project card to show the same date.
11+
12+
### Added — Sortable project cards on the home page
13+
- `HomeFeatures.vue` now renders a sort control with four options: `Last edited ↓ / ↑` and `Alphabetically ↓ / ↑`. Default sort is newest-first by last edited; projects without a known timestamp sort last.
14+
915
### Added — Shared mock banner
1016
- New shared mock banner under `docs/public/design-system/v1/mock-banner.{css,js}` — auto-injects a "this is a mock" strip across the top of every prototype, with optional `data-banner-text` override
1117
- CI check `npm run lint:mocks` (wired into `.github/workflows/verify_build.yml`) fails the build if a prototype HTML file under `docs/public/projects/` doesn't reference the shared banner; allowlist supported for deliberate exceptions (currently `deltag-aarhus`)

docs/.vitepress/theme/HomeFeatures.vue

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
<script setup>
2-
import { computed } from 'vue'
2+
import { computed, ref } from 'vue'
33
import { useData, withBase } from 'vitepress'
44
import { data as projectDates } from './projectDates.data.js'
55
66
const { frontmatter } = useData()
77
8+
const sortOptions = [
9+
{ value: 'edited-desc', label: 'Last edited ↓' },
10+
{ value: 'edited-asc', label: 'Last edited ↑' },
11+
{ value: 'alpha-asc', label: 'Alphabetically ↓' },
12+
{ value: 'alpha-desc', label: 'Alphabetically ↑' },
13+
]
14+
15+
const sort = ref('edited-desc')
16+
817
function formatDate(isoString) {
918
if (!isoString) return null
1019
return new Date(isoString).toLocaleString('da-DK', {
@@ -17,11 +26,44 @@ function formatDate(isoString) {
1726
})
1827
}
1928
29+
function stripTags(html) {
30+
return (html || '').replace(/<[^>]*>/g, '')
31+
}
32+
2033
const features = computed(() => {
21-
return (frontmatter.value.features || []).map(f => ({
22-
...f,
23-
lastEdited: formatDate(projectDates[f.link]),
24-
}))
34+
const items = (frontmatter.value.features || []).map(f => {
35+
const iso = projectDates[f.link] || null
36+
return {
37+
...f,
38+
lastEditedIso: iso,
39+
lastEdited: formatDate(iso),
40+
}
41+
})
42+
43+
const sorted = [...items]
44+
switch (sort.value) {
45+
case 'edited-desc':
46+
case 'edited-asc': {
47+
const dir = sort.value === 'edited-desc' ? -1 : 1
48+
sorted.sort((a, b) => {
49+
// Items without a timestamp always sort last.
50+
if (!a.lastEditedIso && !b.lastEditedIso) return 0
51+
if (!a.lastEditedIso) return 1
52+
if (!b.lastEditedIso) return -1
53+
return dir * a.lastEditedIso.localeCompare(b.lastEditedIso)
54+
})
55+
break
56+
}
57+
case 'alpha-asc':
58+
case 'alpha-desc': {
59+
const dir = sort.value === 'alpha-asc' ? 1 : -1
60+
sorted.sort((a, b) =>
61+
dir * stripTags(a.title).localeCompare(stripTags(b.title), 'da')
62+
)
63+
break
64+
}
65+
}
66+
return sorted
2567
})
2668
2769
const grid = computed(() => {
@@ -37,6 +79,14 @@ const grid = computed(() => {
3779
<template>
3880
<div v-if="features.length" class="VPFeatures custom-features">
3981
<div class="container">
82+
<div class="sort-bar">
83+
<label class="sort-label" for="features-sort">Sort by</label>
84+
<select id="features-sort" v-model="sort" class="sort-select">
85+
<option v-for="opt in sortOptions" :key="opt.value" :value="opt.value">
86+
{{ opt.label }}
87+
</option>
88+
</select>
89+
</div>
4090
<div class="items">
4191
<div
4292
v-for="feature in features"
@@ -90,6 +140,40 @@ const grid = computed(() => {
90140
max-width: 1152px;
91141
}
92142
143+
.sort-bar {
144+
display: flex;
145+
align-items: center;
146+
justify-content: flex-end;
147+
gap: 8px;
148+
padding: 0 8px 12px;
149+
}
150+
151+
.sort-label {
152+
font-size: 13px;
153+
color: var(--vp-c-text-2);
154+
}
155+
156+
.sort-select {
157+
appearance: none;
158+
border: 1px solid var(--vp-c-divider);
159+
border-radius: 8px;
160+
background-color: var(--vp-c-bg-soft);
161+
color: var(--vp-c-text-1);
162+
font-size: 13px;
163+
padding: 6px 28px 6px 10px;
164+
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='gray' d='M4 6l4 4 4-4z'/></svg>");
165+
background-repeat: no-repeat;
166+
background-position: right 8px center;
167+
cursor: pointer;
168+
transition: border-color 0.25s;
169+
}
170+
171+
.sort-select:hover,
172+
.sort-select:focus {
173+
border-color: var(--vp-c-brand-1);
174+
outline: none;
175+
}
176+
93177
.items {
94178
display: flex;
95179
flex-wrap: wrap;

0 commit comments

Comments
 (0)