Skip to content

Commit d6434d2

Browse files
authored
Merge pull request #20 from itk-dev/feature/fix-project-last-edited-dates
fix: show real per-project last-edited dates and add sort control
2 parents a9514b9 + 47fd14f commit d6434d2

4 files changed

Lines changed: 103 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: 93 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,48 @@ function formatDate(isoString) {
1726
})
1827
}
1928
29+
function sortKey(title) {
30+
// Strip HTML tags and any leading non-letter characters (emoji, punctuation,
31+
// whitespace) so decorative prefixes like "🔒 " don't affect ordering.
32+
return (title || '')
33+
.replace(/<[^>]*>/g, '')
34+
.replace(/^[^\p{L}]+/u, '')
35+
}
36+
2037
const features = computed(() => {
21-
return (frontmatter.value.features || []).map(f => ({
22-
...f,
23-
lastEdited: formatDate(projectDates[f.link]),
24-
}))
38+
const items = (frontmatter.value.features || []).map(f => {
39+
const iso = projectDates[f.link] || null
40+
return {
41+
...f,
42+
lastEditedIso: iso,
43+
lastEdited: formatDate(iso),
44+
}
45+
})
46+
47+
const sorted = [...items]
48+
switch (sort.value) {
49+
case 'edited-desc':
50+
case 'edited-asc': {
51+
const dir = sort.value === 'edited-desc' ? -1 : 1
52+
sorted.sort((a, b) => {
53+
// Items without a timestamp always sort last.
54+
if (!a.lastEditedIso && !b.lastEditedIso) return 0
55+
if (!a.lastEditedIso) return 1
56+
if (!b.lastEditedIso) return -1
57+
return dir * a.lastEditedIso.localeCompare(b.lastEditedIso)
58+
})
59+
break
60+
}
61+
case 'alpha-asc':
62+
case 'alpha-desc': {
63+
const dir = sort.value === 'alpha-asc' ? 1 : -1
64+
sorted.sort((a, b) =>
65+
dir * sortKey(a.title).localeCompare(sortKey(b.title), 'da')
66+
)
67+
break
68+
}
69+
}
70+
return sorted
2571
})
2672
2773
const grid = computed(() => {
@@ -37,6 +83,14 @@ const grid = computed(() => {
3783
<template>
3884
<div v-if="features.length" class="VPFeatures custom-features">
3985
<div class="container">
86+
<div class="sort-bar">
87+
<label class="sort-label" for="features-sort">Sort by</label>
88+
<select id="features-sort" v-model="sort" class="sort-select">
89+
<option v-for="opt in sortOptions" :key="opt.value" :value="opt.value">
90+
{{ opt.label }}
91+
</option>
92+
</select>
93+
</div>
4094
<div class="items">
4195
<div
4296
v-for="feature in features"
@@ -90,6 +144,40 @@ const grid = computed(() => {
90144
max-width: 1152px;
91145
}
92146
147+
.sort-bar {
148+
display: flex;
149+
align-items: center;
150+
justify-content: flex-end;
151+
gap: 8px;
152+
padding: 0 8px 12px;
153+
}
154+
155+
.sort-label {
156+
font-size: 13px;
157+
color: var(--vp-c-text-2);
158+
}
159+
160+
.sort-select {
161+
appearance: none;
162+
border: 1px solid var(--vp-c-divider);
163+
border-radius: 8px;
164+
background-color: var(--vp-c-bg-soft);
165+
color: var(--vp-c-text-1);
166+
font-size: 13px;
167+
padding: 6px 28px 6px 10px;
168+
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>");
169+
background-repeat: no-repeat;
170+
background-position: right 8px center;
171+
cursor: pointer;
172+
transition: border-color 0.25s;
173+
}
174+
175+
.sort-select:hover,
176+
.sort-select:focus {
177+
border-color: var(--vp-c-brand-1);
178+
outline: none;
179+
}
180+
93181
.items {
94182
display: flex;
95183
flex-wrap: wrap;

0 commit comments

Comments
 (0)