From 9b9860cb8b794ec84a9757ca4da170c2f92e7a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ju=CC=88rgen=20Rudigier?= Date: Fri, 3 Apr 2026 23:38:15 +0200 Subject: [PATCH 01/14] feat: bring back last-updated metadata and add filtered git history --- config.js | 1 + gulp/helpers/git-metadata.js | 76 +++++++++++++++++++ gulp/html.js | 34 +++++---- .../content/meta-info/_meta-info.scss | 69 ++++++++++++++++- .../content/meta-info/meta-info.hbs | 41 ++++++++-- 5 files changed, 194 insertions(+), 27 deletions(-) create mode 100644 gulp/helpers/git-metadata.js diff --git a/config.js b/config.js index 52961062..24a4a4d1 100644 --- a/config.js +++ b/config.js @@ -1,5 +1,6 @@ module.exports = { url: 'https://www.accessibility-developer-guide.com', + repoUrl: 'https://github.com/Access4all/adg', title: 'Accessibility Developer Guide', description: '', // TODO: Add twitter: '', // TODO: Add diff --git a/gulp/helpers/git-metadata.js b/gulp/helpers/git-metadata.js new file mode 100644 index 00000000..fb59d781 --- /dev/null +++ b/gulp/helpers/git-metadata.js @@ -0,0 +1,76 @@ +const childProcess = require('child_process') + +const excludedCommitIds = [ + 'ac195754a6e64604066dafe2f5ad373c2a949ac4', // May 2, 2018 Absolutise paths + '2c9c894097e3aaa65b4775f96c595b476c9b29b9', // May 16, 2018 JSONs + '6b45656eb96adfcf23c6f47dc3564cf64e84d77f', // May 16, 2018 Fix examples links for GitHub + 'f0de1b4faceb9623b881d993e9df44f3f27fa8f3', // May 31, 2018 Fix relative links + 'f03b52f2b54b1c0d0de23027f574202852d3d21a', // Jun 12, 2018 Cleanup + '077c23bfd14a84ba32faac7984231bfb6bfed089', // Jun 15, 2018 Cleanup + '45a0b144e22e3179466b515a70550a409b4ab42c', // Sep 22, 2021 chore: update changed date + 'f6c1a521625159db489d65f7f98030482e418eab', // Aug 20, 2021 feat: change toc placeholder + '8d2f1cf458bd1769c828cff79743d8878cf71276', // Jun 28, 2021 feat: change ToC insertion to manual mode + 'f84cdc3b77f12dc3170717f6025aeadf4c337bbb', // Jan 18, 2024 feat: replace http in urls with https + 'aac742ff1c64c608985a1be3777da79fa70bf292', // Jun 29, 2023 feat: remove manual changed date from markdown files + '34da02f9b8caf03abc590e28d5bae79f8fc89e08' // Jan 27, 2024 ADG-338 feat: unify card text endings +] + +const excludedCommitIdsSet = new Set( + excludedCommitIds.map(id => id.toLowerCase()) +) +const historyYearsLimit = 5 + +module.exports = ({ githubRepoUrl }) => { + const changedMetadata = {} + + return filePath => { + if (changedMetadata[filePath]) { + return changedMetadata[filePath] + } + + const historyStdout = childProcess.spawnSync( + 'git', + ['log', '--pretty=format:%H%x1f%ci%x1f%an%x1f%s%x1e', filePath], + { encoding: 'utf8' } + ).stdout + + const filteredHistoryEntries = historyStdout + .split('\x1e') + .map(item => item.trim()) + .filter(Boolean) + .map(item => { + const [commitId = '', changed = '', changedBy = '', message = ''] = + item.split('\x1f') + + return { + commitId, + changed, + changedBy, + commitUrl: `${githubRepoUrl}/commit/${commitId}`, + message + } + }) + .filter( + entry => + entry.commitId && + !excludedCommitIdsSet.has(entry.commitId.toLowerCase()) + ) + + const latestEntry = filteredHistoryEntries[0] || null + const cutoffDate = new Date() + cutoffDate.setFullYear(cutoffDate.getFullYear() - historyYearsLimit) + const historyEntries = filteredHistoryEntries.filter(entry => { + const changedDate = new Date(entry.changed) + return !Number.isNaN(changedDate.getTime()) && changedDate >= cutoffDate + }) + + const metadata = { + changed: latestEntry ? latestEntry.changed : '', + changedBy: latestEntry ? latestEntry.changedBy : '', + historyEntries + } + + changedMetadata[filePath] = metadata + return metadata + } +} diff --git a/gulp/html.js b/gulp/html.js index cfa15dc9..7e4768be 100644 --- a/gulp/html.js +++ b/gulp/html.js @@ -1,4 +1,3 @@ -const child_process = require('child_process') const gulp = require('gulp') const handlebars = require('gulp-hb') // const prettify = require('gulp-prettify') @@ -131,15 +130,16 @@ const flattenNavigation = items => return acc }, []) -// Cache changed dates -const changedDates = {} - module.exports = (config, cb) => { const datetime = importFresh('./helpers/datetime') const markdown = importFresh('./helpers/markdown')(config.rootDir) const metatags = importFresh('./helpers/metatags') const Feed = importFresh('./helpers/rss') const appConfig = importFresh('../config') + const githubRepoUrl = appConfig.repoUrl + const getGitMetadata = importFresh('./helpers/git-metadata')({ + githubRepoUrl + }) const files = [] const sitemap = [] @@ -261,19 +261,23 @@ module.exports = (config, cb) => { site_name: appConfig.title, url: `${appConfig.url}/${currentUrl}` } - const dateChanged = - changedDates[file.path] || - child_process.spawnSync( - 'git', - ['log', '-1', '--pretty=format:%ci', file.path], - { encoding: 'utf8' } - ).stdout - - changedDates[file.path] = dateChanged + const metadata = getGitMetadata(file.path) file.data = Object.assign({}, file.data, { changed: - dateChanged && dateChanged.length > 0 ? dateChanged : null, + metadata.changed && metadata.changed.length > 0 + ? metadata.changed + : null, + changedBy: + metadata.changedBy && metadata.changedBy.length > 0 + ? metadata.changedBy + : null, + historyEntries: + metadata.historyEntries && metadata.historyEntries.length > 0 + ? metadata.historyEntries + : [], + hasHistoryEntries: + metadata.historyEntries && metadata.historyEntries.length > 0, title: file.data.title, contents: file.contents, navigation: pageNavigation, @@ -291,7 +295,7 @@ module.exports = (config, cb) => { breadcrumb: breadcrumb.sort((a, b) => { return a.url.length - b.url.length }), - fileHistory: `https://github.com/Access4all/adg/commits/main/pages/${relPath}` + fileHistory: `${githubRepoUrl}/commits/main/pages/${relPath}` }) sitemap.push({ diff --git a/src/components/content/meta-info/_meta-info.scss b/src/components/content/meta-info/_meta-info.scss index 444366c6..259c4315 100644 --- a/src/components/content/meta-info/_meta-info.scss +++ b/src/components/content/meta-info/_meta-info.scss @@ -1,7 +1,10 @@ .meta-info { @include rem(padding-top, $gutter); + @include rem(gap, 0.75rem); display: flex; + flex-wrap: wrap; + align-items: center; font-size: 0.8rem; border-top: 1px solid var(--theme-main-color-inverse, $c-white); @@ -10,12 +13,70 @@ } } -.meta-info__title { - margin-right: 0.5em; -} - .meta-info__logo { display: block; width: 1.5em; height: 1.5em; } + +.meta-info__updated { + margin-right: auto; +} + +.meta-info__link { + display: inline-flex; + align-items: center; + gap: 0.35rem; +} + +.meta-info-history { + @include rem(margin-top, calc($gutter / 2)); + @include font-size(14px); +} + +.meta-info-history__summary { + list-style: none; + cursor: pointer; +} + +.meta-info-history__summary::-webkit-details-marker { + display: none; +} + +.meta-info-history__summary::before { + content: '▽'; + @include rem(margin-right, 0.35rem); + font-size: 0.75em; + line-height: 1; +} + +.meta-info-history[open] .meta-info-history__summary::before { + content: '△'; +} + +.meta-info-history__summary:focus-visible { + outline: 2px solid currentcolor; + outline-offset: 2px; +} + +.meta-info-history__list { + @include rem(margin-top, calc($gutter / 2)); + @include font-size(14px); +} + +.meta-info-history__item { + @include rem(margin-bottom, calc($gutter / 6)); +} + +.meta-info-history__date { + font-weight: 600; +} + +.meta-info-history__message { + @include rem(margin-left, 0.35rem); +} + +.meta-info-history__author { + @include rem(margin-left, 0.35rem); + color: var(--theme-color-dark, $c-black); +} diff --git a/src/components/content/meta-info/meta-info.hbs b/src/components/content/meta-info/meta-info.hbs index 2f708131..4cbc27a4 100644 --- a/src/components/content/meta-info/meta-info.hbs +++ b/src/components/content/meta-info/meta-info.hbs @@ -3,13 +3,38 @@ About this page

- - Page history: - - - - {{{ inlineSvg "src/assets/img/logo/github.svg" class="meta-info__logo" }}} - GitHub source - + + Last updated: {{ formatDate changed }} + {{#if changedBy}} + by + {{ changedBy }} + {{/if}} + + {{{ inlineSvg "src/assets/img/logo/github.svg" class="meta-info__logo" }}} + View full history on GitHub +

+{{#if hasHistoryEntries}} +
+ + Show recent relevant commits for this page + + Expands to a list of commit messages, dates, and authors. + + + +
+{{/if}} From 438aaa983856ab04572f9c52167eb87278a9919a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ju=CC=88rgen=20Rudigier?= Date: Wed, 15 Apr 2026 22:58:35 +0200 Subject: [PATCH 02/14] feat: add recently updated to home --- gulp/helpers/git-metadata.js | 33 +++++++- gulp/html.js | 59 ++++++++++++++- src/components/content/author/_author.scss | 14 ++++ src/components/content/author/author.hbs | 13 ++++ .../content/meta-info/_meta-info.scss | 9 ++- .../content/meta-info/meta-info.hbs | 11 ++- .../content/recent-pages/_recent-pages.scss | 75 +++++++++++++++++++ .../content/recent-pages/recent-pages.hbs | 24 ++++++ src/templates/layout.hbs | 3 + 9 files changed, 227 insertions(+), 14 deletions(-) create mode 100644 src/components/content/author/_author.scss create mode 100644 src/components/content/author/author.hbs create mode 100644 src/components/content/recent-pages/_recent-pages.scss create mode 100644 src/components/content/recent-pages/recent-pages.hbs diff --git a/gulp/helpers/git-metadata.js b/gulp/helpers/git-metadata.js index fb59d781..13122e7f 100644 --- a/gulp/helpers/git-metadata.js +++ b/gulp/helpers/git-metadata.js @@ -1,4 +1,5 @@ const childProcess = require('child_process') +const crypto = require('crypto') const excludedCommitIds = [ 'ac195754a6e64604066dafe2f5ad373c2a949ac4', // May 2, 2018 Absolutise paths @@ -19,6 +20,23 @@ const excludedCommitIdsSet = new Set( excludedCommitIds.map(id => id.toLowerCase()) ) const historyYearsLimit = 5 +const gravatarImageSize = 48 + +const getGravatarUrl = email => { + if (!email) { + return '' + } + + const normalizedEmail = String(email).trim().toLowerCase() + + if (!normalizedEmail) { + return '' + } + + const hash = crypto.createHash('sha256').update(normalizedEmail).digest('hex') + + return `https://gravatar.com/avatar/${hash}?s=${gravatarImageSize}&d=mp` +} module.exports = ({ githubRepoUrl }) => { const changedMetadata = {} @@ -30,7 +48,7 @@ module.exports = ({ githubRepoUrl }) => { const historyStdout = childProcess.spawnSync( 'git', - ['log', '--pretty=format:%H%x1f%ci%x1f%an%x1f%s%x1e', filePath], + ['log', '--pretty=format:%H%x1f%ci%x1f%an%x1f%ae%x1f%s%x1e', filePath], { encoding: 'utf8' } ).stdout @@ -39,13 +57,20 @@ module.exports = ({ githubRepoUrl }) => { .map(item => item.trim()) .filter(Boolean) .map(item => { - const [commitId = '', changed = '', changedBy = '', message = ''] = - item.split('\x1f') + const [ + commitId = '', + changed = '', + changedBy = '', + changedByEmail = '', + message = '' + ] = item.split('\x1f') return { commitId, changed, changedBy, + changedByEmail, + gravatarUrl: getGravatarUrl(changedByEmail), commitUrl: `${githubRepoUrl}/commit/${commitId}`, message } @@ -67,6 +92,8 @@ module.exports = ({ githubRepoUrl }) => { const metadata = { changed: latestEntry ? latestEntry.changed : '', changedBy: latestEntry ? latestEntry.changedBy : '', + changedByEmail: latestEntry ? latestEntry.changedByEmail : '', + gravatarUrl: latestEntry ? latestEntry.gravatarUrl : '', historyEntries } diff --git a/gulp/html.js b/gulp/html.js index 7e4768be..853b8d0f 100644 --- a/gulp/html.js +++ b/gulp/html.js @@ -23,6 +23,15 @@ const getUrl = (filePath, base) => { .replace(/\/$/, '') } +const getCurrentUrl = (filePath, base) => { + const relPath = path.relative(base, filePath) + const lastSeparatorIndex = relPath.lastIndexOf(path.sep) + + return ( + lastSeparatorIndex >= 0 ? relPath.substring(0, lastSeparatorIndex) : '' + ).replace(pathSeparatorRegExp, '/') +} + const getLayout = (layoutName, layouts) => { layoutName = layoutName || 'layout' @@ -130,6 +139,8 @@ const flattenNavigation = items => return acc }, []) +const recentUpdatesLimit = 8 + module.exports = (config, cb) => { const datetime = importFresh('./helpers/datetime') const markdown = importFresh('./helpers/markdown')(config.rootDir) @@ -140,6 +151,30 @@ module.exports = (config, cb) => { const getGitMetadata = importFresh('./helpers/git-metadata')({ githubRepoUrl }) + const getRecentlyUpdatedPages = currentFilePath => + files + .filter(file => !file.frontMatter.navigation_ignore) + .map(file => { + const metadata = getGitMetadata(file.path) + + return { + title: file.data.title, + lead: file.data.lead, + url: getCurrentUrl(file.path, config.base), + changed: metadata.changed, + changedBy: metadata.changedBy, + gravatarUrl: metadata.gravatarUrl + } + }) + .filter( + page => + page.title && + page.url && + page.changed && + page.url !== getCurrentUrl(currentFilePath, config.base) + ) + .sort((a, b) => new Date(b.changed) - new Date(a.changed)) + .slice(0, recentUpdatesLimit) const files = [] const sitemap = [] @@ -240,9 +275,7 @@ module.exports = (config, cb) => { try { const layout = getLayout(file.frontMatter.layout, layouts) const relPath = path.relative('./pages', file.path) - const currentUrl = relPath - .substring(0, relPath.lastIndexOf(path.sep)) - .replace(pathSeparatorRegExp, '/') + const currentUrl = getCurrentUrl(file.path, config.base) const prevNext = {} const breadcrumb = [] const subPages = [] @@ -272,6 +305,10 @@ module.exports = (config, cb) => { metadata.changedBy && metadata.changedBy.length > 0 ? metadata.changedBy : null, + gravatarUrl: + metadata.gravatarUrl && metadata.gravatarUrl.length > 0 + ? metadata.gravatarUrl + : null, historyEntries: metadata.historyEntries && metadata.historyEntries.length > 0 ? metadata.historyEntries @@ -291,6 +328,8 @@ module.exports = (config, cb) => { level: 1 })) : subPages, + recentlyUpdatedPages: + currentUrl === '' ? getRecentlyUpdatedPages(file.path) : [], metatags: metatags.generateTags(metatagsData), breadcrumb: breadcrumb.sort((a, b) => { return a.url.length - b.url.length @@ -327,6 +366,20 @@ module.exports = (config, cb) => { }, helpers: { formatDate: datetime.formatDate, + truncateText: function (text, maxLength) { + if (!text) { + return '' + } + + const normalizedText = String(text).trim().replace(/\s+/g, ' ') + const limit = Number(maxLength) + + if (!Number.isFinite(limit) || normalizedText.length <= limit) { + return normalizedText + } + + return `${normalizedText.slice(0, limit).trimEnd()}...` + }, eq: function (v1, v2, options) { if (v1 === v2) { return options.fn(this) diff --git a/src/components/content/author/_author.scss b/src/components/content/author/_author.scss new file mode 100644 index 00000000..ae2a1135 --- /dev/null +++ b/src/components/content/author/_author.scss @@ -0,0 +1,14 @@ +.author { + display: inline-flex; + align-items: center; + gap: 0.45rem; + font-size: 16px; +} + +.author__avatar { + display: block; + width: 24px; + height: 24px; + border-radius: 999px; + flex: 0 0 auto; +} diff --git a/src/components/content/author/author.hbs b/src/components/content/author/author.hbs new file mode 100644 index 00000000..a678ba7c --- /dev/null +++ b/src/components/content/author/author.hbs @@ -0,0 +1,13 @@ + + {{#if gravatarUrl}} + + {{/if}} + by {{name}} + diff --git a/src/components/content/meta-info/_meta-info.scss b/src/components/content/meta-info/_meta-info.scss index 259c4315..7e562133 100644 --- a/src/components/content/meta-info/_meta-info.scss +++ b/src/components/content/meta-info/_meta-info.scss @@ -5,7 +5,7 @@ display: flex; flex-wrap: wrap; align-items: center; - font-size: 0.8rem; + font-size: 16px; border-top: 1px solid var(--theme-main-color-inverse, $c-white); .theme-contribution & { @@ -20,6 +20,10 @@ } .meta-info__updated { + margin-right: 0; +} + +.meta-info__author { margin-right: auto; } @@ -27,6 +31,7 @@ display: inline-flex; align-items: center; gap: 0.35rem; + margin-left: auto; } .meta-info-history { @@ -46,7 +51,7 @@ .meta-info-history__summary::before { content: '▽'; @include rem(margin-right, 0.35rem); - font-size: 0.75em; + font-size: 16px; line-height: 1; } diff --git a/src/components/content/meta-info/meta-info.hbs b/src/components/content/meta-info/meta-info.hbs index 4cbc27a4..206e240d 100644 --- a/src/components/content/meta-info/meta-info.hbs +++ b/src/components/content/meta-info/meta-info.hbs @@ -2,19 +2,18 @@

About this page

-

+

Last updated: {{ formatDate changed }} - {{#if changedBy}} - by - {{ changedBy }} - {{/if}} + {{#if changedBy}} + {{> "content/author/author" name=changedBy gravatarUrl=gravatarUrl className="meta-info__author"}} + {{/if}} {{{ inlineSvg "src/assets/img/logo/github.svg" class="meta-info__logo" }}} View full history on GitHub -

+
{{#if hasHistoryEntries}}
diff --git a/src/components/content/recent-pages/_recent-pages.scss b/src/components/content/recent-pages/_recent-pages.scss new file mode 100644 index 00000000..cd6a8db7 --- /dev/null +++ b/src/components/content/recent-pages/_recent-pages.scss @@ -0,0 +1,75 @@ +.recent-pages { + @include rem(margin-top, $gutter); +} + +.recent-pages__list { + margin: 0; + padding: 0; + list-style: none; + display: grid; + gap: 0.9rem; +} + +.recent-pages__item { + margin: 0; +} + +.recent-pages__link { + @include rem(padding, 1rem 1.1rem); + + display: block; + background: var(--theme-main-color-inverse, $c-white); + border: 1px solid var(--theme-color-medium, $c-gray); + border-radius: 10px; + box-shadow: 0 4px 14px 0 rgba(0, 0, 0, 0.08); + color: inherit; + text-decoration: none; + transition: border-color 0.15s ease, box-shadow 0.15s ease, + transform 0.15s ease; + + &:hover, + &:focus { + border-color: var(--theme-color-dark); + box-shadow: 0 8px 24px 0 rgba(0, 0, 0, 0.12); + transform: translateY(-1px); + text-decoration: none; + } +} + +.recent-pages__date { + display: block; + margin-bottom: 0.45rem; + color: var(--theme-color-dark); + font-size: 0.78rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.06em; +} + +.recent-pages__title { + margin: 0; + font-size: 20px; + line-height: 1.25; +} + +.recent-pages__title-link { + color: var(--theme-color-dark); + text-decoration: none; +} + +.recent-pages__lead { + @include rem(margin-top, 0.35rem); + @include rem(margin-bottom, 0.6rem); + + max-width: 58rem; + color: var(--theme-color-dark); + font-size: 16px; + line-height: 1.5; +} + +.recent-pages__author { + @include rem(margin-bottom, 0.65rem); + color: var(--theme-color-dark); + + display: flex; +} diff --git a/src/components/content/recent-pages/recent-pages.hbs b/src/components/content/recent-pages/recent-pages.hbs new file mode 100644 index 00000000..fc78a602 --- /dev/null +++ b/src/components/content/recent-pages/recent-pages.hbs @@ -0,0 +1,24 @@ +
+
+

Recent changes

+
+ + +
diff --git a/src/templates/layout.hbs b/src/templates/layout.hbs index f517a814..468a9445 100644 --- a/src/templates/layout.hbs +++ b/src/templates/layout.hbs @@ -55,6 +55,9 @@ {{#if subPages}} {{> "content/cardmenu/cardmenu" menuitem=subPages}} {{/if}} + {{#if recentlyUpdatedPages}} + {{> "content/recent-pages/recent-pages" pages=recentlyUpdatedPages}} + {{/if}} {{> "content/prev_next/prev_next" prev=previousPage next=nextPage}} {{#notEq section 'welcome'}} From c14322113a671dcd15516d6340b169f6d91be9ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ju=CC=88rgen=20Rudigier?= Date: Wed, 15 Apr 2026 23:42:08 +0200 Subject: [PATCH 03/14] feat: improve style for recently updated components --- src/components/content/author/_author.scss | 12 +++-- src/components/content/author/author.hbs | 6 +-- .../content/meta-info/_meta-info.scss | 34 ++++++++------ .../content/meta-info/meta-info.hbs | 46 +++++++++---------- .../content/recent-pages/_recent-pages.scss | 21 +++------ 5 files changed, 61 insertions(+), 58 deletions(-) diff --git a/src/components/content/author/_author.scss b/src/components/content/author/_author.scss index ae2a1135..a8cf7efd 100644 --- a/src/components/content/author/_author.scss +++ b/src/components/content/author/_author.scss @@ -1,14 +1,20 @@ .author { display: inline-flex; align-items: center; - gap: 0.45rem; + flex-wrap: wrap; + gap: 8px; font-size: 16px; } .author__avatar { display: block; - width: 24px; - height: 24px; + width: 32px; + height: 32px; border-radius: 999px; + border: 1px solid rgba(0, 0, 0, 0.08); flex: 0 0 auto; } + +.author__name { + min-width: 0; +} diff --git a/src/components/content/author/author.hbs b/src/components/content/author/author.hbs index a678ba7c..68e88682 100644 --- a/src/components/content/author/author.hbs +++ b/src/components/content/author/author.hbs @@ -5,9 +5,9 @@ src="{{gravatarUrl}}" alt="" loading="lazy" - width="24" - height="24" + width="32" + height="32" > {{/if}} - by {{name}} + {{name}} diff --git a/src/components/content/meta-info/_meta-info.scss b/src/components/content/meta-info/_meta-info.scss index 7e562133..d04934ba 100644 --- a/src/components/content/meta-info/_meta-info.scss +++ b/src/components/content/meta-info/_meta-info.scss @@ -1,6 +1,6 @@ .meta-info { @include rem(padding-top, $gutter); - @include rem(gap, 0.75rem); + gap: 1em; display: flex; flex-wrap: wrap; @@ -11,16 +11,21 @@ .theme-contribution & { border-color: var(--theme-color-medium, $c-gray); } + + @include mobile-portrait { + align-items: flex-start; + } } .meta-info__logo { display: block; - width: 1.5em; - height: 1.5em; + width: 32px; + height: 32px; } .meta-info__updated { margin-right: 0; + font-weight: 500; } .meta-info__author { @@ -30,13 +35,12 @@ .meta-info__link { display: inline-flex; align-items: center; - gap: 0.35rem; - margin-left: auto; + gap: 8px; } .meta-info-history { - @include rem(margin-top, calc($gutter / 2)); - @include font-size(14px); + font-size: 14px; + width: 100%; } .meta-info-history__summary { @@ -50,8 +54,8 @@ .meta-info-history__summary::before { content: '▽'; - @include rem(margin-right, 0.35rem); - font-size: 16px; + margin-right: 0.5em; + font-size: 1em; line-height: 1; } @@ -65,12 +69,12 @@ } .meta-info-history__list { - @include rem(margin-top, calc($gutter / 2)); - @include font-size(14px); + margin-top: 0.5em; + font-size: 14x; } .meta-info-history__item { - @include rem(margin-bottom, calc($gutter / 6)); + margin-bottom: 0.5em; } .meta-info-history__date { @@ -78,10 +82,10 @@ } .meta-info-history__message { - @include rem(margin-left, 0.35rem); + margin-left: 0.5em; } .meta-info-history__author { - @include rem(margin-left, 0.35rem); - color: var(--theme-color-dark, $c-black); + margin-left: 0.5em; + font-style: italic; } diff --git a/src/components/content/meta-info/meta-info.hbs b/src/components/content/meta-info/meta-info.hbs index 206e240d..a21dde80 100644 --- a/src/components/content/meta-info/meta-info.hbs +++ b/src/components/content/meta-info/meta-info.hbs @@ -13,27 +13,27 @@ {{{ inlineSvg "src/assets/img/logo/github.svg" class="meta-info__logo" }}} View full history on GitHub + {{#if hasHistoryEntries}} +
+ + Show recent relevant commits for this page + + Expands to a list of commit messages, dates, and authors. + + + +
+ {{/if}} -{{#if hasHistoryEntries}} -
- - Show recent relevant commits for this page - - Expands to a list of commit messages, dates, and authors. - - - -
-{{/if}} diff --git a/src/components/content/recent-pages/_recent-pages.scss b/src/components/content/recent-pages/_recent-pages.scss index cd6a8db7..7fcc542d 100644 --- a/src/components/content/recent-pages/_recent-pages.scss +++ b/src/components/content/recent-pages/_recent-pages.scss @@ -7,7 +7,7 @@ padding: 0; list-style: none; display: grid; - gap: 0.9rem; + gap: 1em; } .recent-pages__item { @@ -15,7 +15,7 @@ } .recent-pages__link { - @include rem(padding, 1rem 1.1rem); + padding: 1em; display: block; background: var(--theme-main-color-inverse, $c-white); @@ -38,9 +38,8 @@ .recent-pages__date { display: block; - margin-bottom: 0.45rem; - color: var(--theme-color-dark); - font-size: 0.78rem; + margin-bottom: 4px; + font-size: 16px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; @@ -48,7 +47,7 @@ .recent-pages__title { margin: 0; - font-size: 20px; + font-size: 24px; line-height: 1.25; } @@ -58,18 +57,12 @@ } .recent-pages__lead { - @include rem(margin-top, 0.35rem); - @include rem(margin-bottom, 0.6rem); - - max-width: 58rem; - color: var(--theme-color-dark); + margin-top: 0.75em; + margin-bottom: 1.5em; font-size: 16px; line-height: 1.5; } .recent-pages__author { - @include rem(margin-bottom, 0.65rem); - color: var(--theme-color-dark); - display: flex; } From 89cb83a211e850cc8c689e365739739a83468bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ju=CC=88rgen=20Rudigier?= Date: Wed, 6 May 2026 22:45:29 +0200 Subject: [PATCH 04/14] chore: update exclud list for relevant commits --- gulp/helpers/git-metadata.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gulp/helpers/git-metadata.js b/gulp/helpers/git-metadata.js index 13122e7f..479eedc7 100644 --- a/gulp/helpers/git-metadata.js +++ b/gulp/helpers/git-metadata.js @@ -13,7 +13,9 @@ const excludedCommitIds = [ '8d2f1cf458bd1769c828cff79743d8878cf71276', // Jun 28, 2021 feat: change ToC insertion to manual mode 'f84cdc3b77f12dc3170717f6025aeadf4c337bbb', // Jan 18, 2024 feat: replace http in urls with https 'aac742ff1c64c608985a1be3777da79fa70bf292', // Jun 29, 2023 feat: remove manual changed date from markdown files - '34da02f9b8caf03abc590e28d5bae79f8fc89e08' // Jan 27, 2024 ADG-338 feat: unify card text endings + 'a3f8584dd30b0404cab50cb982b62a132e8e0582', // Jan 2, 2024 ADG-338 feat: rename contribution page + '34da02f9b8caf03abc590e28d5bae79f8fc89e08', // Jan 27, 2024 ADG-338 feat: unify card text endings + 'c8cac292ac104b3dcd406c61091a5491448af7ea' // Dec 4, 2025 ADG-434 fix: remove duplicate positions ] const excludedCommitIdsSet = new Set( From 1485916f2a31ea4f47c7b7d4eaf0bf6ce21f0e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ju=CC=88rgen=20Rudigier?= Date: Tue, 12 May 2026 16:26:44 +0200 Subject: [PATCH 05/14] feat: revert meta-info chnages - remove athor and relevant history --- gulp/helpers/git-metadata.js | 28 ++----- gulp/html.js | 18 +--- .../content/meta-info/_meta-info.scss | 82 ++----------------- .../content/meta-info/meta-info.hbs | 44 +++------- 4 files changed, 24 insertions(+), 148 deletions(-) diff --git a/gulp/helpers/git-metadata.js b/gulp/helpers/git-metadata.js index 479eedc7..cc3046a8 100644 --- a/gulp/helpers/git-metadata.js +++ b/gulp/helpers/git-metadata.js @@ -21,7 +21,6 @@ const excludedCommitIds = [ const excludedCommitIdsSet = new Set( excludedCommitIds.map(id => id.toLowerCase()) ) -const historyYearsLimit = 5 const gravatarImageSize = 48 const getGravatarUrl = email => { @@ -40,7 +39,7 @@ const getGravatarUrl = email => { return `https://gravatar.com/avatar/${hash}?s=${gravatarImageSize}&d=mp` } -module.exports = ({ githubRepoUrl }) => { +module.exports = () => { const changedMetadata = {} return filePath => { @@ -50,11 +49,11 @@ module.exports = ({ githubRepoUrl }) => { const historyStdout = childProcess.spawnSync( 'git', - ['log', '--pretty=format:%H%x1f%ci%x1f%an%x1f%ae%x1f%s%x1e', filePath], + ['log', '--pretty=format:%H%x1f%ci%x1f%an%x1f%ae%x1e', filePath], { encoding: 'utf8' } ).stdout - const filteredHistoryEntries = historyStdout + const latestEntry = historyStdout .split('\x1e') .map(item => item.trim()) .filter(Boolean) @@ -63,8 +62,7 @@ module.exports = ({ githubRepoUrl }) => { commitId = '', changed = '', changedBy = '', - changedByEmail = '', - message = '' + changedByEmail = '' ] = item.split('\x1f') return { @@ -72,31 +70,19 @@ module.exports = ({ githubRepoUrl }) => { changed, changedBy, changedByEmail, - gravatarUrl: getGravatarUrl(changedByEmail), - commitUrl: `${githubRepoUrl}/commit/${commitId}`, - message + gravatarUrl: getGravatarUrl(changedByEmail) } }) - .filter( + .find( entry => entry.commitId && !excludedCommitIdsSet.has(entry.commitId.toLowerCase()) ) - const latestEntry = filteredHistoryEntries[0] || null - const cutoffDate = new Date() - cutoffDate.setFullYear(cutoffDate.getFullYear() - historyYearsLimit) - const historyEntries = filteredHistoryEntries.filter(entry => { - const changedDate = new Date(entry.changed) - return !Number.isNaN(changedDate.getTime()) && changedDate >= cutoffDate - }) - const metadata = { changed: latestEntry ? latestEntry.changed : '', changedBy: latestEntry ? latestEntry.changedBy : '', - changedByEmail: latestEntry ? latestEntry.changedByEmail : '', - gravatarUrl: latestEntry ? latestEntry.gravatarUrl : '', - historyEntries + gravatarUrl: latestEntry ? latestEntry.gravatarUrl : '' } changedMetadata[filePath] = metadata diff --git a/gulp/html.js b/gulp/html.js index 853b8d0f..95652ffa 100644 --- a/gulp/html.js +++ b/gulp/html.js @@ -148,9 +148,7 @@ module.exports = (config, cb) => { const Feed = importFresh('./helpers/rss') const appConfig = importFresh('../config') const githubRepoUrl = appConfig.repoUrl - const getGitMetadata = importFresh('./helpers/git-metadata')({ - githubRepoUrl - }) + const getGitMetadata = importFresh('./helpers/git-metadata')() const getRecentlyUpdatedPages = currentFilePath => files .filter(file => !file.frontMatter.navigation_ignore) @@ -301,20 +299,6 @@ module.exports = (config, cb) => { metadata.changed && metadata.changed.length > 0 ? metadata.changed : null, - changedBy: - metadata.changedBy && metadata.changedBy.length > 0 - ? metadata.changedBy - : null, - gravatarUrl: - metadata.gravatarUrl && metadata.gravatarUrl.length > 0 - ? metadata.gravatarUrl - : null, - historyEntries: - metadata.historyEntries && metadata.historyEntries.length > 0 - ? metadata.historyEntries - : [], - hasHistoryEntries: - metadata.historyEntries && metadata.historyEntries.length > 0, title: file.data.title, contents: file.contents, navigation: pageNavigation, diff --git a/src/components/content/meta-info/_meta-info.scss b/src/components/content/meta-info/_meta-info.scss index d04934ba..444366c6 100644 --- a/src/components/content/meta-info/_meta-info.scss +++ b/src/components/content/meta-info/_meta-info.scss @@ -1,91 +1,21 @@ .meta-info { @include rem(padding-top, $gutter); - gap: 1em; display: flex; - flex-wrap: wrap; - align-items: center; - font-size: 16px; + font-size: 0.8rem; border-top: 1px solid var(--theme-main-color-inverse, $c-white); .theme-contribution & { border-color: var(--theme-color-medium, $c-gray); } - - @include mobile-portrait { - align-items: flex-start; - } -} - -.meta-info__logo { - display: block; - width: 32px; - height: 32px; -} - -.meta-info__updated { - margin-right: 0; - font-weight: 500; -} - -.meta-info__author { - margin-right: auto; -} - -.meta-info__link { - display: inline-flex; - align-items: center; - gap: 8px; -} - -.meta-info-history { - font-size: 14px; - width: 100%; -} - -.meta-info-history__summary { - list-style: none; - cursor: pointer; } -.meta-info-history__summary::-webkit-details-marker { - display: none; -} - -.meta-info-history__summary::before { - content: '▽'; +.meta-info__title { margin-right: 0.5em; - font-size: 1em; - line-height: 1; -} - -.meta-info-history[open] .meta-info-history__summary::before { - content: '△'; -} - -.meta-info-history__summary:focus-visible { - outline: 2px solid currentcolor; - outline-offset: 2px; -} - -.meta-info-history__list { - margin-top: 0.5em; - font-size: 14x; } -.meta-info-history__item { - margin-bottom: 0.5em; -} - -.meta-info-history__date { - font-weight: 600; -} - -.meta-info-history__message { - margin-left: 0.5em; -} - -.meta-info-history__author { - margin-left: 0.5em; - font-style: italic; +.meta-info__logo { + display: block; + width: 1.5em; + height: 1.5em; } diff --git a/src/components/content/meta-info/meta-info.hbs b/src/components/content/meta-info/meta-info.hbs index a21dde80..2f708131 100644 --- a/src/components/content/meta-info/meta-info.hbs +++ b/src/components/content/meta-info/meta-info.hbs @@ -2,38 +2,14 @@

About this page

-
- - Last updated: {{ formatDate changed }} +

+ + Page history: - {{#if changedBy}} - {{> "content/author/author" name=changedBy gravatarUrl=gravatarUrl className="meta-info__author"}} - {{/if}} - - {{{ inlineSvg "src/assets/img/logo/github.svg" class="meta-info__logo" }}} - View full history on GitHub - - {{#if hasHistoryEntries}} -

- - Show recent relevant commits for this page - - Expands to a list of commit messages, dates, and authors. - - - -
- {{/if}} -
+ + + {{{ inlineSvg "src/assets/img/logo/github.svg" class="meta-info__logo" }}} + GitHub source + + +

From 1b8d5d96668b352c09f46ee53867e112a552e36e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ju=CC=88rgen=20Rudigier?= Date: Tue, 12 May 2026 22:41:46 +0200 Subject: [PATCH 06/14] feat: add section pill to recent page teaser --- gulp/html.js | 5 +++ .../content/recent-pages/_recent-pages.scss | 33 ++++++++++++++++--- .../content/recent-pages/recent-pages.hbs | 7 +++- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/gulp/html.js b/gulp/html.js index c15c73b6..b9b7a634 100644 --- a/gulp/html.js +++ b/gulp/html.js @@ -153,11 +153,16 @@ export default (config, cb) => { .filter(file => !file.frontMatter.navigation_ignore) .map(file => { const metadata = getGitMetadata(file.path) + const section = file.data.section + const sectionTitle = + navigation.find(item => item.url === section)?.title || '' return { title: file.data.title, lead: file.data.lead, url: getCurrentUrl(file.path, config.base), + section, + sectionTitle, changed: metadata.changed, changedBy: metadata.changedBy, gravatarUrl: metadata.gravatarUrl diff --git a/src/components/content/recent-pages/_recent-pages.scss b/src/components/content/recent-pages/_recent-pages.scss index 7fcc542d..3026f9a7 100644 --- a/src/components/content/recent-pages/_recent-pages.scss +++ b/src/components/content/recent-pages/_recent-pages.scss @@ -24,7 +24,9 @@ box-shadow: 0 4px 14px 0 rgba(0, 0, 0, 0.08); color: inherit; text-decoration: none; - transition: border-color 0.15s ease, box-shadow 0.15s ease, + transition: + border-color 0.15s ease, + box-shadow 0.15s ease, transform 0.15s ease; &:hover, @@ -36,13 +38,34 @@ } } +.recent-pages__meta { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.35em 0.5em; + margin-bottom: 0.25em; +} + .recent-pages__date { - display: block; - margin-bottom: 4px; - font-size: 16px; + display: inline-block; + margin: 0; + font-size: 14px; font-weight: 500; text-transform: uppercase; - letter-spacing: 0.06em; + letter-spacing: 0.05em; +} + +.recent-pages__pill { + display: inline-block; + margin: 0; + padding: 0.1em 0.5em; + border-radius: 999px; + font-size: 12px; + font-weight: 500; + line-height: 1.3; + letter-spacing: 0.01em; + background-color: var(--theme-color-light); + color: var(--theme-color-dark); } .recent-pages__title { diff --git a/src/components/content/recent-pages/recent-pages.hbs b/src/components/content/recent-pages/recent-pages.hbs index fc78a602..b669dee8 100644 --- a/src/components/content/recent-pages/recent-pages.hbs +++ b/src/components/content/recent-pages/recent-pages.hbs @@ -7,7 +7,12 @@ {{#each pages}}
  • - {{ formatDate changed }} +
    + {{ formatDate changed }} + {{#if sectionTitle}} + {{sectionTitle}} + {{/if}} +

    {{title}}

    From 87e3e4079e80348bba9ec2fa025cb07c7476cda7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ju=CC=88rgen=20Rudigier?= Date: Thu, 14 May 2026 22:58:15 +0200 Subject: [PATCH 07/14] chore: add page-updates page --- gulp/helpers/datetime.js | 56 +++++-- gulp/helpers/git-metadata.js | 84 +++++++++- gulp/helpers/page-frontmatter.js | 65 ++++++++ gulp/html.js | 119 ++++++++++++-- gulpfile.js | 151 ++++++++++-------- package.json | 2 +- pages/contribution/page-updates/README.md | 9 ++ .../page-updates/_page-updates-table.scss | 103 ++++++++++++ .../page-updates/page-updates-table.hbs | 60 +++++++ .../content/recent-pages/_recent-pages.scss | 4 + .../content/recent-pages/recent-pages.hbs | 8 +- src/templates/layout.hbs | 5 +- 12 files changed, 566 insertions(+), 100 deletions(-) create mode 100644 gulp/helpers/page-frontmatter.js create mode 100644 pages/contribution/page-updates/README.md create mode 100644 src/components/content/page-updates/_page-updates-table.scss create mode 100644 src/components/content/page-updates/page-updates-table.hbs diff --git a/gulp/helpers/datetime.js b/gulp/helpers/datetime.js index 37e0aa8a..65a8c73c 100644 --- a/gulp/helpers/datetime.js +++ b/gulp/helpers/datetime.js @@ -3,24 +3,28 @@ * @param {string} date in form of '2018-07-01' * @returns {string} '1. July, 2018' */ -const formatDate = date => { - var monthNames = [ - 'January', - 'February', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - 'December' - ] +const monthNames = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' +] +const formatDate = date => { const dateTime = new Date(date) + if (Number.isNaN(dateTime.getTime())) { + return '' + } + const day = dateTime.getDate() const monthIndex = dateTime.getMonth() const year = dateTime.getFullYear() @@ -28,4 +32,24 @@ const formatDate = date => { return `${monthNames[monthIndex]} ${day}, ${year}` } -export { formatDate } +const formatDateTime = timestamp => { + const dateTime = new Date( + typeof timestamp === 'number' && timestamp < 1_000_000_000_000 + ? timestamp * 1000 + : timestamp + ) + + if (Number.isNaN(dateTime.getTime())) { + return '' + } + + const day = dateTime.getDate() + const monthIndex = dateTime.getMonth() + const year = dateTime.getFullYear() + const hours = String(dateTime.getHours()).padStart(2, '0') + const minutes = String(dateTime.getMinutes()).padStart(2, '0') + + return `${monthNames[monthIndex]} ${day}, ${year}, ${hours}:${minutes}` +} + +export { formatDate, formatDateTime } diff --git a/gulp/helpers/git-metadata.js b/gulp/helpers/git-metadata.js index e2887a10..e1dd2cf5 100644 --- a/gulp/helpers/git-metadata.js +++ b/gulp/helpers/git-metadata.js @@ -21,6 +21,23 @@ const excludedCommitIds = [ const excludedCommitIdsSet = new Set( excludedCommitIds.map(id => id.toLowerCase()) ) +const gitHistoryRef = (() => { + for (const ref of ['main', 'master', 'HEAD']) { + const result = childProcess.spawnSync( + 'git', + ['rev-parse', '--verify', ref], + { + encoding: 'utf8' + } + ) + + if (result.status === 0) { + return result.stdout.trim() + } + } + + return 'HEAD' +})() const gravatarImageSize = 48 const getGravatarUrl = email => { @@ -41,15 +58,65 @@ const getGravatarUrl = email => { export default () => { const changedMetadata = {} + const fileChangeStatsCache = new Map() + + const getFileChangeStats = (commitId, filePath) => { + if (!commitId || !filePath) { + return { linesAdded: 0, linesDeleted: 0 } + } + + const cacheKey = `${commitId}\x1f${filePath}` + + if (fileChangeStatsCache.has(cacheKey)) { + return fileChangeStatsCache.get(cacheKey) + } - return filePath => { + const numstatStdout = childProcess.spawnSync( + 'git', + [ + 'show', + '-m', + '--first-parent', + '--numstat', + '--format=tformat:', + commitId, + '--', + filePath + ], + { encoding: 'utf8' } + ).stdout + + const [numstatLine = ''] = numstatStdout + .split('\n') + .map(line => line.trim()) + .filter(Boolean) + const [added = '0', deleted = '0'] = numstatLine.split('\t') + const stats = { + linesAdded: added === '-' ? 0 : Number(added) || 0, + linesDeleted: deleted === '-' ? 0 : Number(deleted) || 0 + } + + fileChangeStatsCache.set(cacheKey, stats) + return stats + } + + const getGitMetadata = filePath => { if (changedMetadata[filePath]) { return changedMetadata[filePath] } const historyStdout = childProcess.spawnSync( 'git', - ['log', '--pretty=format:%H%x1f%ci%x1f%an%x1f%ae%x1e', filePath], + [ + 'log', + gitHistoryRef, + '--pretty=format:%H%x1f%ci%x1f%ct%x1f%an%x1f%ae%x1f%s%x1e', + '-m', + '--merges', + '--first-parent', + '--', + filePath + ], { encoding: 'utf8' } ).stdout @@ -61,15 +128,19 @@ export default () => { const [ commitId = '', changed = '', + changedTimestamp = '', changedBy = '', - changedByEmail = '' + changedByEmail = '', + commitMessage = '' ] = item.split('\x1f') return { commitId, changed, + changedTimestamp: Number(changedTimestamp), changedBy, changedByEmail, + commitMessage, gravatarUrl: getGravatarUrl(changedByEmail) } }) @@ -81,11 +152,16 @@ export default () => { const metadata = { changed: latestEntry ? latestEntry.changed : '', + changedTimestamp: latestEntry ? latestEntry.changedTimestamp : 0, changedBy: latestEntry ? latestEntry.changedBy : '', - gravatarUrl: latestEntry ? latestEntry.gravatarUrl : '' + gravatarUrl: latestEntry ? latestEntry.gravatarUrl : '', + commitId: latestEntry ? latestEntry.commitId : '', + commitMessage: latestEntry ? latestEntry.commitMessage : '' } changedMetadata[filePath] = metadata return metadata } + + return { getGitMetadata, getFileChangeStats } } diff --git a/gulp/helpers/page-frontmatter.js b/gulp/helpers/page-frontmatter.js new file mode 100644 index 00000000..dc5d3bb9 --- /dev/null +++ b/gulp/helpers/page-frontmatter.js @@ -0,0 +1,65 @@ +import fs from 'node:fs' +import path from 'node:path' +import frontMatter from 'front-matter' + +const pagesDirectory = './pages' + +const collectPageMarkdownFiles = (directory, markdownFiles = []) => { + for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { + const entryPath = path.join(directory, entry.name) + + if (entry.isDirectory()) { + if (entry.name === '_examples') { + continue + } + + collectPageMarkdownFiles(entryPath, markdownFiles) + continue + } + + if (entry.name.endsWith('.md')) { + markdownFiles.push(entryPath) + } + } + + return markdownFiles +} + +const getPageDirectory = (filePath, pagesRoot) => { + const relativePath = path.relative(pagesRoot, filePath) + const relativeDirectory = path.dirname(relativePath) + + return relativeDirectory === '.' ? '' : relativeDirectory.replace(/\\/g, '/') +} + +const getPagesWithFrontMatterFlag = (rootDir, flag) => { + const pagesRoot = path.join(rootDir, pagesDirectory) + + return collectPageMarkdownFiles(pagesRoot) + .map(filePath => { + const { attributes } = frontMatter(fs.readFileSync(filePath, 'utf8')) + + if (!attributes[flag]) { + return null + } + + return { + filePath, + directory: getPageDirectory(filePath, pagesRoot) + } + }) + .filter(Boolean) +} + +const getDevOnlyPageGlobs = rootDir => + getPagesWithFrontMatterFlag(rootDir, 'dev_only').map( + ({ filePath, directory }) => + `!./pages/${directory || path.basename(filePath, path.extname(filePath))}/**/*.md` + ) + +const getDevOnlyDistPaths = rootDir => + getPagesWithFrontMatterFlag(rootDir, 'dev_only').map( + ({ directory }) => `./dist/${directory}` + ) + +export { getDevOnlyDistPaths, getDevOnlyPageGlobs } diff --git a/gulp/html.js b/gulp/html.js index b9b7a634..178071cd 100644 --- a/gulp/html.js +++ b/gulp/html.js @@ -1,6 +1,7 @@ import fs from 'node:fs' import path from 'node:path' import { Readable } from 'node:stream' +import { deleteAsync } from 'del' import gulp from 'gulp' import handlebars from 'gulp-hb' import frontMatter from 'gulp-front-matter' @@ -10,11 +11,12 @@ import normalize from 'normalize-strings' import { SitemapStream, streamToPromise } from 'sitemap' import { JSDOM } from 'jsdom' import appConfig from '../config.js' -import { formatDate } from './helpers/datetime.js' +import { formatDate, formatDateTime } from './helpers/datetime.js' import markdownFactory from './helpers/markdown.js' import { generateTags } from './helpers/metatags.js' import Feed from './helpers/rss.js' import getGitMetadataFactory from './helpers/git-metadata.js' +import { getDevOnlyDistPaths } from './helpers/page-frontmatter.js' const pathSeparatorRegExp = new RegExp('\\' + path.sep, 'g') @@ -144,15 +146,70 @@ const flattenNavigation = items => const recentUpdatesLimit = 8 +const groupPageEntriesByMerge = pages => { + const mergesByCommitId = new Map() + + for (const page of pages) { + if (!page.commitId) { + continue + } + + const affectedPage = { + title: page.title, + url: page.url, + section: page.section, + sectionTitle: page.sectionTitle, + linesAdded: page.linesAdded, + linesDeleted: page.linesDeleted, + linesChanged: page.linesChanged + } + const existingMerge = mergesByCommitId.get(page.commitId) + + if (existingMerge) { + existingMerge.pages.push(affectedPage) + continue + } + + mergesByCommitId.set(page.commitId, { + changed: page.changed, + changedTimestamp: page.changedTimestamp, + changedBy: page.changedBy, + gravatarUrl: page.gravatarUrl, + commitId: page.commitId, + commitShortId: page.commitId.slice(0, 7), + commitMessage: page.commitMessage, + commitUrl: page.commitUrl, + pages: [affectedPage] + }) + } + + return Array.from(mergesByCommitId.values()) + .map(merge => ({ + ...merge, + pages: merge.pages.sort((a, b) => { + if (b.linesChanged !== a.linesChanged) { + return b.linesChanged - a.linesChanged + } + + return a.title.localeCompare(b.title) + }) + })) + .sort((a, b) => b.changedTimestamp - a.changedTimestamp) +} + export default (config, cb) => { + const devMode = Boolean(config.devMode) const markdown = markdownFactory(config.rootDir) - const getGitMetadata = getGitMetadataFactory() + const { getGitMetadata, getFileChangeStats } = getGitMetadataFactory() const githubRepoUrl = appConfig.repoUrl - const getRecentlyUpdatedPages = currentFilePath => + const getUpdatedPageEntries = currentFilePath => files .filter(file => !file.frontMatter.navigation_ignore) .map(file => { const metadata = getGitMetadata(file.path) + const { linesAdded, linesDeleted } = metadata.commitId + ? getFileChangeStats(metadata.commitId, file.path) + : { linesAdded: 0, linesDeleted: 0 } const section = file.data.section const sectionTitle = navigation.find(item => item.url === section)?.title || '' @@ -163,25 +220,35 @@ export default (config, cb) => { url: getCurrentUrl(file.path, config.base), section, sectionTitle, + linesAdded, + linesDeleted, + linesChanged: linesAdded + linesDeleted, changed: metadata.changed, + changedTimestamp: metadata.changedTimestamp, changedBy: metadata.changedBy, - gravatarUrl: metadata.gravatarUrl + gravatarUrl: metadata.gravatarUrl, + commitId: metadata.commitId, + commitMessage: metadata.commitMessage, + commitUrl: metadata.commitId + ? `${githubRepoUrl}/commit/${metadata.commitId}` + : '' } }) .filter( page => page.title && page.url && - page.changed && + page.changedTimestamp > 0 && page.url !== getCurrentUrl(currentFilePath, config.base) ) - .sort((a, b) => new Date(b.changed) - new Date(a.changed)) - .slice(0, recentUpdatesLimit) + .sort((a, b) => b.changedTimestamp - a.changedTimestamp) const files = [] const sitemap = [] const layouts = {} let navigation = [] + let updatedPageEntries = [] + let pageUpdatesOverviewFile = null // const config = { // src: './pages/**/*.md', @@ -262,6 +329,16 @@ export default (config, cb) => { .filter(page => !page.parent && page.parent !== null) .sort((a, b) => a.position - b.position) + const homeFile = files.find( + file => getCurrentUrl(file.path, config.base) === '' + ) + + updatedPageEntries = homeFile + ? getUpdatedPageEntries(homeFile.path) + : [] + pageUpdatesOverviewFile = + files.find(file => file.frontMatter.page_updates_overview) || null + // Return files back to stream files.forEach(this.push.bind(this)) @@ -297,6 +374,24 @@ export default (config, cb) => { url: `${appConfig.url}/${currentUrl}` } const metadata = getGitMetadata(file.path) + const recentlyUpdatedPages = + currentUrl === '' + ? updatedPageEntries.slice(0, recentUpdatesLimit) + : [] + const pageUpdatesList = + devMode && file.frontMatter.page_updates_overview + ? groupPageEntriesByMerge(updatedPageEntries) + : [] + const recentPagesOverviewLink = + devMode && currentUrl === '' && pageUpdatesOverviewFile + ? { + title: pageUpdatesOverviewFile.data.title, + url: getCurrentUrl( + pageUpdatesOverviewFile.path, + config.base + ) + } + : null file.data = Object.assign({}, file.data, { changed: @@ -316,8 +411,9 @@ export default (config, cb) => { level: 1 })) : subPages, - recentlyUpdatedPages: - currentUrl === '' ? getRecentlyUpdatedPages(file.path) : [], + recentlyUpdatedPages, + pageUpdatesList, + recentPagesOverviewLink, metatags: generateTags(metatagsData), breadcrumb: breadcrumb.sort((a, b) => { return a.url.length - b.url.length @@ -354,6 +450,7 @@ export default (config, cb) => { }, helpers: { formatDate, + formatDateTime, truncateText: function (text, maxLength) { if (!text) { return '' @@ -478,6 +575,10 @@ export default (config, cb) => { fs.writeFileSync(config.sitemap, xml) + if (!devMode) { + await deleteAsync(getDevOnlyDistPaths(config.rootDir), { force: true }) + } + cb() }) } diff --git a/gulpfile.js b/gulpfile.js index 070f2523..2dc9a59c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -13,6 +13,7 @@ import html from './gulp/html.js' import css from './gulp/css.js' import js from './gulp/javascript.js' import examples from './gulp/examples.js' +import { getDevOnlyPageGlobs } from './gulp/helpers/page-frontmatter.js' const browserSync = browserSyncFactory.create() @@ -24,27 +25,33 @@ function errorHandler(err) { ) } +const isDevMode = () => process.env.ADG_DEV === '1' + +const getHtmlConfig = () => ({ + src: [ + './pages/**/*.md', + '!./pages/**/_examples/**/*.md', + ...(isDevMode() ? [] : getDevOnlyPageGlobs(import.meta.dirname)) + ], + base: './pages', + host: 'https://www.accessibility-developer-guide.com', + sitemap: './dist/sitemap.xml', + feed: { + json: './dist/feed/feed.json', + atom: './dist/feed/atom.xml', + rss: './dist/feed/rss.xml' + }, + devMode: isDevMode(), + errorHandler, + rootDir: import.meta.dirname +}) + gulp.task('html', cb => - html( - { - src: ['./pages/**/*.md', '!./pages/**/_examples/**/*.md'], - base: './pages', - host: 'https://www.accessibility-developer-guide.com', - sitemap: './dist/sitemap.xml', - feed: { - json: './dist/feed/feed.json', - atom: './dist/feed/atom.xml', - rss: './dist/feed/rss.xml' - }, - errorHandler, - rootDir: import.meta.dirname - }, - () => { - browserSync.reload() + html(getHtmlConfig(), () => { + browserSync.reload() - cb() - } - ) + cb() + }) ) gulp.task('html:examples', cb => @@ -252,54 +259,62 @@ gulp.task('rebuild', gulp.series('clean', 'build')) gulp.task( 'default', - gulp.series('build', function serveAndWatch() { - browserSync.init({ - server: { - baseDir: './dist' - } - }) + gulp.series( + function enableDevMode(cb) { + process.env.ADG_DEV = '1' + cb() + }, + 'build', + function serveAndWatch() { + browserSync.init({ + server: { + baseDir: './dist' + } + }) - gulp.watch( - ['./src/assets/css/**/*.scss', './src/components/**/*.scss'], - gulp.series('css') - ) - gulp.watch(['./src/assets/js/**/*.js'], gulp.series('js')) - gulp.watch( - [ - './pages/**/*.md', - './src/templates/**/*.hbs', - './src/components/**/*.hbs', - './gulp/helpers/*', - // Example content which is embedded in HTML pages - './pages/**/_examples/**/*.html', - './pages/**/_examples/**/*.js', - './pages/**/_examples/**/*.css' - ], - gulp.series('html') - ) - gulp.watch(['./pages/**/_examples/**/*'], gulp.series('html:examples')) - gulp.watch( - [ - // demo - './pages/**/_examples/**/*.html', - '!./pages/**/_examples/**/index.html', - // content - './pages/{,**/}_media/**/*', - './pages/**/*.{png,jpg,mp3}', - // assets - './src/assets/img/**/*', - // static - './pages/{,**/}_static/**/*' - ], - gulp.series('media:copy') - ) - gulp.watch( - ['./pages/{,**/}_media/**/*', './pages/**/_examples/**/*.png'], - gulp.series('media:resize') - ) - gulp.watch( - ['./src/assets/img/icons/**/*.png', '!./src/assets/img/icons/*.png'], - gulp.series('sprite') - ) - }) + gulp.watch( + ['./src/assets/css/**/*.scss', './src/components/**/*.scss'], + gulp.series('css') + ) + gulp.watch(['./src/assets/js/**/*.js'], gulp.series('js')) + gulp.watch( + [ + './pages/**/*.md', + './src/templates/**/*.hbs', + './src/components/**/*.hbs', + './gulp/helpers/*', + './gulp/html.js', + // Example content which is embedded in HTML pages + './pages/**/_examples/**/*.html', + './pages/**/_examples/**/*.js', + './pages/**/_examples/**/*.css' + ], + gulp.series('html') + ) + gulp.watch(['./pages/**/_examples/**/*'], gulp.series('html:examples')) + gulp.watch( + [ + // demo + './pages/**/_examples/**/*.html', + '!./pages/**/_examples/**/index.html', + // content + './pages/{,**/}_media/**/*', + './pages/**/*.{png,jpg,mp3}', + // assets + './src/assets/img/**/*', + // static + './pages/{,**/}_static/**/*' + ], + gulp.series('media:copy') + ) + gulp.watch( + ['./pages/{,**/}_media/**/*', './pages/**/_examples/**/*.png'], + gulp.series('media:resize') + ) + gulp.watch( + ['./src/assets/img/icons/**/*.png', '!./src/assets/img/icons/*.png'], + gulp.series('sprite') + ) + } + ) ) diff --git a/package.json b/package.json index 5b9fb134..87207a6b 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "main": "index.js", "type": "module", "scripts": { - "start": "gulp --webpackWatch", + "start": "ADG_DEV=1 gulp --webpackWatch", "build": "gulp build", "rebuild": "gulp rebuild", "gulp": "gulp", diff --git a/pages/contribution/page-updates/README.md b/pages/contribution/page-updates/README.md new file mode 100644 index 00000000..0e7494d3 --- /dev/null +++ b/pages/contribution/page-updates/README.md @@ -0,0 +1,9 @@ +--- +navigation_ignore: true +dev_only: true +page_updates_overview: true +--- + +# Page updates + +**This development overview groups guide pages that appear in the site navigation by their most recent merge. Each merge shows when it landed, who authored the merge commit, and its commit ID and message, followed by the pages it changed.** diff --git a/src/components/content/page-updates/_page-updates-table.scss b/src/components/content/page-updates/_page-updates-table.scss new file mode 100644 index 00000000..efa5e141 --- /dev/null +++ b/src/components/content/page-updates/_page-updates-table.scss @@ -0,0 +1,103 @@ +.page-updates { + @include rem(margin-top, $gutter); +} + +.page-updates__list { + margin: 0; + padding: 0; + list-style: none; + display: grid; + gap: 1em; +} + +.page-updates__item { + margin: 0; +} + +.page-updates__merge { + padding: 1em; + border: 1px solid var(--theme-color-medium, $c-gray); + border-radius: 10px; + background: var(--theme-main-color-inverse, $c-white); + box-shadow: 0 4px 14px 0 rgba(0, 0, 0, 0.08); +} + +.page-updates__meta { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.5em 0.75em; +} + +.page-updates__date { + margin: 0; + font-size: 14px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.page-updates__commit code { + font-size: 0.9em; +} + +.page-updates__message { + margin: 0.75em 0 0; + font-size: 16px; + line-height: 1.5; +} + +.page-updates__pages { + margin: 0.75em 0 0; + padding: 0; + list-style: none; + border-top: 1px solid var(--theme-color-medium, $c-gray); +} + +.page-updates__page { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 0.5em 0.75em; + padding: 0.5em 0; + border-bottom: 1px solid var(--theme-color-medium, $c-gray); + + font-size: 12px; + &:last-child { + border-bottom: 0; + padding-bottom: 0; + } +} + +.page-updates__link { + flex: 1 1 auto; + min-width: 12em; +} + +.page-updates__changes { + margin-left: auto; + font-size: 0.85em; + font-variant-numeric: tabular-nums; + white-space: nowrap; +} + +.page-updates__section { + display: inline-block; + margin: 0; + padding: 0.1em 0.5em; + border-radius: 999px; + font-size: 12px; + font-weight: 500; + line-height: 1.3; + letter-spacing: 0.01em; + background-color: var(--theme-color-light); + color: var(--theme-color-dark); +} + +.page-updates__author { + display: flex; +} + +.page-updates__empty { + margin: 0; +} diff --git a/src/components/content/page-updates/page-updates-table.hbs b/src/components/content/page-updates/page-updates-table.hbs new file mode 100644 index 00000000..1f314f97 --- /dev/null +++ b/src/components/content/page-updates/page-updates-table.hbs @@ -0,0 +1,60 @@ +
    +

    + Merges sorted by date with affected pages +

    + + {{#if merges}} +
      + {{#each merges}} +
    1. +
      + + {{#if commitMessage}} +

      {{commitMessage}}

      + {{/if}} +
        + {{#each pages}} +
      • + {{#if sectionTitle}} + + Section: {{sectionTitle}} + + {{/if}} + {{title}} + + +{{linesAdded}} −{{linesDeleted}} + +
      • + {{/each}} +
      +
      +
    2. + {{/each}} +
    + {{else}} +

    No pages with merge history were found.

    + {{/if}} +
    diff --git a/src/components/content/recent-pages/_recent-pages.scss b/src/components/content/recent-pages/_recent-pages.scss index 3026f9a7..00055c4e 100644 --- a/src/components/content/recent-pages/_recent-pages.scss +++ b/src/components/content/recent-pages/_recent-pages.scss @@ -89,3 +89,7 @@ .recent-pages__author { display: flex; } + +.recent-pages__overview { + margin: 1em 0 0; +} diff --git a/src/components/content/recent-pages/recent-pages.hbs b/src/components/content/recent-pages/recent-pages.hbs index b669dee8..c7c1a871 100644 --- a/src/components/content/recent-pages/recent-pages.hbs +++ b/src/components/content/recent-pages/recent-pages.hbs @@ -8,7 +8,7 @@
  • - {{ formatDate changed }} + {{#if sectionTitle}} {{sectionTitle}} {{/if}} @@ -26,4 +26,10 @@
  • {{/each}} + + {{#if overviewLink}} +

    + {{overviewLink.title}} +

    + {{/if}} diff --git a/src/templates/layout.hbs b/src/templates/layout.hbs index 87650b62..a7bac6ac 100644 --- a/src/templates/layout.hbs +++ b/src/templates/layout.hbs @@ -55,8 +55,11 @@ {{#if subPages}} {{> "content/cardmenu/cardmenu" menuitem=subPages}} {{/if}} + {{#if pageUpdatesList}} + {{> "content/page-updates/page-updates-table" merges=pageUpdatesList}} + {{/if}} {{#if recentlyUpdatedPages}} - {{> "content/recent-pages/recent-pages" pages=recentlyUpdatedPages}} + {{> "content/recent-pages/recent-pages" pages=recentlyUpdatedPages overviewLink=recentPagesOverviewLink}} {{/if}} {{> "content/prev_next/prev_next" prev=previousPage next=nextPage}} From 7b7d4aec90bee7bb809bf69777d3f3014a551224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ju=CC=88rgen=20Rudigier?= Date: Thu, 14 May 2026 23:46:53 +0200 Subject: [PATCH 08/14] chore: update recent pages entries --- config/recent-pages.js | 37 +++ gulp/helpers/git-metadata.js | 105 ++++---- gulp/html.js | 231 +++++++++++++++--- src/assets/js/app/modules.js | 7 + .../content/PageUpdatesConfigurator.js | 174 +++++++++++++ .../page-updates/_page-updates-table.scss | 58 +++++ .../page-updates/page-updates-table.hbs | 50 +++- src/templates/layout.hbs | 2 +- 8 files changed, 585 insertions(+), 79 deletions(-) create mode 100644 config/recent-pages.js create mode 100644 src/assets/js/app/modules/content/PageUpdatesConfigurator.js diff --git a/config/recent-pages.js b/config/recent-pages.js new file mode 100644 index 00000000..ba7c7c45 --- /dev/null +++ b/config/recent-pages.js @@ -0,0 +1,37 @@ +export default [ + { + commit: '23924f765de3b11b4c09bdc6ea6459a166b75317', + pages: [ + 'examples/hiding-elements/skip-navigation-links', + 'knowledge/keyboard-only/skip-navigation-links' + ] + }, + { + commit: '92cf70a701b23d2dcedb74a8521de7e8cac761b0', + pages: ['introduction/about'] + }, + { + commit: '703b03c2ef4581b27f743e3136d626a3e36edd58', + pages: ['examples/widgets/accordion'] + }, + { + commit: '517667bf917d002ea45092af00df472130d23f90', + pages: ['setup/windows/vmware-on-macos'] + }, + { + commit: '27b9f04175342243fb7a0d557a1867aa4038be09', + pages: ['examples/widgets/tablists'] + }, + { + commit: '9bd1f018edeb81e9d9e49e87863a3f161e92f0ff', + pages: ['knowledge/legal/eaa'] + }, + { + commit: '3ffb178c4c4388af0082249d4e0c8cf5bbdbea0a', + pages: ['examples/tables/spanning-rows-cols'] + }, + { + commit: '62db125e30d39aa7248c141561699b979738ffc9', + pages: ['knowledge/screen-readers/mobile/reading-websites'] + } +] diff --git a/gulp/helpers/git-metadata.js b/gulp/helpers/git-metadata.js index e1dd2cf5..b83ef98d 100644 --- a/gulp/helpers/git-metadata.js +++ b/gulp/helpers/git-metadata.js @@ -58,8 +58,66 @@ const getGravatarUrl = email => { export default () => { const changedMetadata = {} + const fileMergeHistoryCache = {} const fileChangeStatsCache = new Map() + const parseGitHistory = historyStdout => + historyStdout + .split('\x1e') + .map(item => item.trim()) + .filter(Boolean) + .map(item => { + const [ + commitId = '', + changed = '', + changedTimestamp = '', + changedBy = '', + changedByEmail = '', + commitMessage = '' + ] = item.split('\x1f') + + return { + commitId, + changed, + changedTimestamp: Number(changedTimestamp), + changedBy, + changedByEmail, + commitMessage, + gravatarUrl: getGravatarUrl(changedByEmail) + } + }) + .filter( + entry => + entry.commitId && + !excludedCommitIdsSet.has(entry.commitId.toLowerCase()) + ) + + const getFileMergeHistory = filePath => { + if (fileMergeHistoryCache[filePath]) { + return fileMergeHistoryCache[filePath] + } + + const historyStdout = childProcess.spawnSync( + 'git', + [ + 'log', + gitHistoryRef, + '--pretty=format:%H%x1f%ci%x1f%ct%x1f%an%x1f%ae%x1f%s%x1e', + '-m', + '--merges', + '--first-parent', + '--', + filePath + ], + { encoding: 'utf8' } + ).stdout + + const history = parseGitHistory(historyStdout) + + fileMergeHistoryCache[filePath] = history + return history + } + const getFileChangeStats = (commitId, filePath) => { if (!commitId || !filePath) { return { linesAdded: 0, linesDeleted: 0 } @@ -105,50 +163,7 @@ export default () => { return changedMetadata[filePath] } - const historyStdout = childProcess.spawnSync( - 'git', - [ - 'log', - gitHistoryRef, - '--pretty=format:%H%x1f%ci%x1f%ct%x1f%an%x1f%ae%x1f%s%x1e', - '-m', - '--merges', - '--first-parent', - '--', - filePath - ], - { encoding: 'utf8' } - ).stdout - - const latestEntry = historyStdout - .split('\x1e') - .map(item => item.trim()) - .filter(Boolean) - .map(item => { - const [ - commitId = '', - changed = '', - changedTimestamp = '', - changedBy = '', - changedByEmail = '', - commitMessage = '' - ] = item.split('\x1f') - - return { - commitId, - changed, - changedTimestamp: Number(changedTimestamp), - changedBy, - changedByEmail, - commitMessage, - gravatarUrl: getGravatarUrl(changedByEmail) - } - }) - .find( - entry => - entry.commitId && - !excludedCommitIdsSet.has(entry.commitId.toLowerCase()) - ) + const latestEntry = getFileMergeHistory(filePath)[0] const metadata = { changed: latestEntry ? latestEntry.changed : '', @@ -163,5 +178,5 @@ export default () => { return metadata } - return { getGitMetadata, getFileChangeStats } + return { getGitMetadata, getFileChangeStats, getFileMergeHistory } } diff --git a/gulp/html.js b/gulp/html.js index 178071cd..611fd6bc 100644 --- a/gulp/html.js +++ b/gulp/html.js @@ -11,6 +11,7 @@ import normalize from 'normalize-strings' import { SitemapStream, streamToPromise } from 'sitemap' import { JSDOM } from 'jsdom' import appConfig from '../config.js' +import recentPagesConfig from '../config/recent-pages.js' import { formatDate, formatDateTime } from './helpers/datetime.js' import markdownFactory from './helpers/markdown.js' import { generateTags } from './helpers/metatags.js' @@ -144,43 +145,188 @@ const flattenNavigation = items => return acc }, []) -const recentUpdatesLimit = 8 +const normalizeRecentPagesConfig = recentPages => { + if (!Array.isArray(recentPages)) { + return { urlOnly: [], commits: [] } + } -const groupPageEntriesByMerge = pages => { - const mergesByCommitId = new Map() + const urlOnly = [] + const commits = [] - for (const page of pages) { - if (!page.commitId) { + for (const entry of recentPages) { + if (typeof entry === 'string') { + const url = entry.replace(/^\/+/, '').trim() + + if (url) { + urlOnly.push(url) + } + + continue + } + + if (!entry || typeof entry !== 'object') { continue } - const affectedPage = { - title: page.title, - url: page.url, - section: page.section, - sectionTitle: page.sectionTitle, - linesAdded: page.linesAdded, - linesDeleted: page.linesDeleted, - linesChanged: page.linesChanged + const commit = typeof entry.commit === 'string' ? entry.commit.trim() : '' + const pages = Array.isArray(entry.pages) + ? [ + ...new Set( + entry.pages + .filter(page => typeof page === 'string') + .map(page => page.replace(/^\/+/, '').trim()) + .filter(Boolean) + ) + ] + : [] + + if (commit && pages.length) { + commits.push({ commit, pages }) + } + } + + return { + urlOnly: [...new Set(urlOnly)], + commits + } +} + +const findMergeByCommit = (merges, commitRef) => { + if (!commitRef) { + return null + } + + return ( + merges.find( + merge => + merge.commitId === commitRef || merge.commitId.startsWith(commitRef) + ) || null + ) +} + +const applyMergeMetadata = (page, merge) => ({ + ...page, + changed: merge.changed, + changedTimestamp: merge.changedTimestamp, + changedBy: merge.changedBy, + gravatarUrl: merge.gravatarUrl, + commitId: merge.commitId, + commitMessage: merge.commitMessage, + commitUrl: merge.commitUrl +}) + +const getRecentlyUpdatedPages = (entries, recentPages, mergeOptions) => { + const { urlOnly, commits } = normalizeRecentPagesConfig(recentPages) + + if (!urlOnly.length && !commits.length) { + return entries + } + + if (commits.length) { + const entryByUrl = new Map(entries.map(entry => [entry.url, entry])) + const merges = groupPageEntriesByMerge(entries, mergeOptions) + const results = [] + + for (const item of commits) { + const merge = findMergeByCommit(merges, item.commit) + + for (const url of item.pages) { + const base = entryByUrl.get(url) + + if (!base) { + continue + } + + results.push(merge ? applyMergeMetadata(base, merge) : base) + } } - const existingMerge = mergesByCommitId.get(page.commitId) - if (existingMerge) { - existingMerge.pages.push(affectedPage) + return results + } + + const selectedUrlSet = new Set(urlOnly) + + return entries.filter(page => selectedUrlSet.has(page.url)) +} + +const markMergePageSelection = (merges, recentPages) => { + const { commits } = normalizeRecentPagesConfig(recentPages) + const selectedByCommit = new Map() + + for (const item of commits) { + const merge = findMergeByCommit(merges, item.commit) + + if (merge) { + selectedByCommit.set(merge.commitId, new Set(item.pages)) + } + } + + return merges.map(merge => ({ + ...merge, + pages: merge.pages.map(page => ({ + ...page, + isSelected: selectedByCommit.get(merge.commitId)?.has(page.url) ?? false + })) + })) +} + +const groupPageEntriesByMerge = (pages, mergeOptions = {}) => { + const { + getFileMergeHistory, + getFileChangeStats, + githubRepoUrl = '' + } = mergeOptions + const mergesByCommitId = new Map() + + for (const page of pages) { + if (!page.filePath || !getFileMergeHistory || !getFileChangeStats) { continue } - mergesByCommitId.set(page.commitId, { - changed: page.changed, - changedTimestamp: page.changedTimestamp, - changedBy: page.changedBy, - gravatarUrl: page.gravatarUrl, - commitId: page.commitId, - commitShortId: page.commitId.slice(0, 7), - commitMessage: page.commitMessage, - commitUrl: page.commitUrl, - pages: [affectedPage] - }) + for (const mergeEntry of getFileMergeHistory(page.filePath)) { + const { linesAdded, linesDeleted } = getFileChangeStats( + mergeEntry.commitId, + page.filePath + ) + + if (linesAdded === 0 && linesDeleted === 0) { + continue + } + + const affectedPage = { + title: page.title, + url: page.url, + section: page.section, + sectionTitle: page.sectionTitle, + linesAdded, + linesDeleted, + linesChanged: linesAdded + linesDeleted + } + const existingMerge = mergesByCommitId.get(mergeEntry.commitId) + + if (existingMerge) { + if (existingMerge.pages.some(entry => entry.url === page.url)) { + continue + } + + existingMerge.pages.push(affectedPage) + continue + } + + mergesByCommitId.set(mergeEntry.commitId, { + changed: mergeEntry.changed, + changedTimestamp: mergeEntry.changedTimestamp, + changedBy: mergeEntry.changedBy, + gravatarUrl: mergeEntry.gravatarUrl, + commitId: mergeEntry.commitId, + commitShortId: mergeEntry.commitId.slice(0, 7), + commitMessage: mergeEntry.commitMessage, + commitUrl: githubRepoUrl + ? `${githubRepoUrl}/commit/${mergeEntry.commitId}` + : '', + pages: [affectedPage] + }) + } } return Array.from(mergesByCommitId.values()) @@ -198,10 +344,20 @@ const groupPageEntriesByMerge = pages => { } export default (config, cb) => { + buildHtml(config, cb) +} + +const buildHtml = (config, cb) => { const devMode = Boolean(config.devMode) const markdown = markdownFactory(config.rootDir) - const { getGitMetadata, getFileChangeStats } = getGitMetadataFactory() + const { getGitMetadata, getFileChangeStats, getFileMergeHistory } = + getGitMetadataFactory() const githubRepoUrl = appConfig.repoUrl + const mergeOptions = { + getFileMergeHistory, + getFileChangeStats, + githubRepoUrl + } const getUpdatedPageEntries = currentFilePath => files .filter(file => !file.frontMatter.navigation_ignore) @@ -218,6 +374,7 @@ export default (config, cb) => { title: file.data.title, lead: file.data.lead, url: getCurrentUrl(file.path, config.base), + filePath: file.path, section, sectionTitle, linesAdded, @@ -376,12 +533,20 @@ export default (config, cb) => { const metadata = getGitMetadata(file.path) const recentlyUpdatedPages = currentUrl === '' - ? updatedPageEntries.slice(0, recentUpdatesLimit) + ? getRecentlyUpdatedPages( + updatedPageEntries, + recentPagesConfig, + mergeOptions + ) : [] - const pageUpdatesList = + const pageUpdatesConfigurator = devMode && file.frontMatter.page_updates_overview - ? groupPageEntriesByMerge(updatedPageEntries) - : [] + const pageUpdatesList = pageUpdatesConfigurator + ? markMergePageSelection( + groupPageEntriesByMerge(updatedPageEntries, mergeOptions), + recentPagesConfig + ) + : [] const recentPagesOverviewLink = devMode && currentUrl === '' && pageUpdatesOverviewFile ? { @@ -413,6 +578,7 @@ export default (config, cb) => { : subPages, recentlyUpdatedPages, pageUpdatesList, + pageUpdatesConfigurator, recentPagesOverviewLink, metatags: generateTags(metatagsData), breadcrumb: breadcrumb.sort((a, b) => { @@ -482,6 +648,7 @@ export default (config, cb) => { or: function () { return Array.prototype.slice.call(arguments, 0, -1).some(Boolean) }, + json: context => JSON.stringify(context).replace(/ { // every module should at least implement two methods @@ -38,6 +39,12 @@ export default () => { ModuleManager.connect(Panel, elem) }) + contextTrigger.add('.js-page-updates-configurator', function () { + var elem = this + + ModuleManager.connect(PageUpdatesConfigurator, elem) + }) + contextTrigger.validate('body') // console.log('Selecting components took: ', new Date() - time, 'ms') diff --git a/src/assets/js/app/modules/content/PageUpdatesConfigurator.js b/src/assets/js/app/modules/content/PageUpdatesConfigurator.js new file mode 100644 index 00000000..24d1dc7a --- /dev/null +++ b/src/assets/js/app/modules/content/PageUpdatesConfigurator.js @@ -0,0 +1,174 @@ +const formatJsString = value => + `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'` + +const buildRecentPagesSnippet = groups => { + if (!groups.length) { + return '' + } + + const lines = ['export default ['] + + groups.forEach((group, index) => { + lines.push(' {') + lines.push(` commit: ${formatJsString(group.commit)},`) + lines.push(' pages: [') + + group.pages.forEach((url, pageIndex) => { + const suffix = pageIndex < group.pages.length - 1 ? ',' : '' + lines.push(` ${formatJsString(url)}${suffix}`) + }) + + lines.push(' ]') + lines.push(` }${index < groups.length - 1 ? ',' : ''}`) + }) + + lines.push(']') + + return lines.join('\n') +} + +/** + * Page updates configurator + * + * @selector .js-page-updates-configurator + * @enabled true + */ +export default class PageUpdatesConfigurator { + constructor() { + PageUpdatesConfigurator.namespaceIndex += 1 + this.ns = `.PageUpdatesConfigurator${PageUpdatesConfigurator.namespaceIndex}` + this.el = null + this.abortController = null + this.checkboxes = [] + this.textarea = null + this.status = null + this.handleChange = this.handleChange.bind(this) + this.copySnippet = this.copySnippet.bind(this) + } + + init(element) { + this.el = element + this.abortController = new AbortController() + const { signal } = this.abortController + + this.checkboxes = Array.from( + element.querySelectorAll('.page-updates__page-checkbox') + ) + this.textarea = element.querySelector('.page-updates__textarea') + this.status = element.querySelector('.page-updates__status') + + element.addEventListener('change', this.handleChange, { signal }) + + const copyButton = element.querySelector('.page-updates__copy') + + if (copyButton) { + copyButton.addEventListener('click', this.copySnippet, { signal }) + } + + this.updateSnippet() + + return this + } + + destroy() { + this.abortController?.abort() + this.abortController = null + this.el = null + this.checkboxes = [] + this.textarea = null + this.status = null + } + + handleChange(event) { + if (event.target.matches('.page-updates__page-checkbox')) { + this.updateSnippet() + } + } + + getSelectedGroups() { + const groups = [] + const groupByCommit = new Map() + + for (const checkbox of this.checkboxes) { + if (!checkbox.checked) { + continue + } + + const { commit } = checkbox.dataset + const { value: url } = checkbox + + if (!commit || !url) { + continue + } + + if (!groupByCommit.has(commit)) { + groupByCommit.set(commit, []) + } + + groupByCommit.get(commit).push(url) + } + + for (const item of this.el.querySelectorAll('.page-updates__item')) { + const checkbox = item.querySelector('.page-updates__page-checkbox') + const commit = checkbox?.dataset.commit + + if (!commit || !groupByCommit.has(commit)) { + continue + } + + groups.push({ + commit, + pages: groupByCommit.get(commit).sort((a, b) => a.localeCompare(b)) + }) + } + + return groups + } + + updateSnippet() { + const snippet = buildRecentPagesSnippet(this.getSelectedGroups()) + + if (this.textarea) { + this.textarea.value = snippet + } + + if (!this.status) { + return + } + + if (!snippet) { + this.status.textContent = + 'No pages selected. Without a config in config/recent-pages.js, the home page uses the latest git updates.' + return + } + + this.status.textContent = '' + } + + async copySnippet() { + const snippet = this.textarea?.value || '' + + if (!snippet) { + if (this.status) { + this.status.textContent = 'Nothing to copy.' + } + + return + } + + try { + await navigator.clipboard.writeText(snippet) + + if (this.status) { + this.status.textContent = 'Copied to clipboard.' + } + } catch { + if (this.status) { + this.status.textContent = + 'Copy failed. Select and copy the snippet manually.' + } + } + } +} + +PageUpdatesConfigurator.namespaceIndex = 0 diff --git a/src/components/content/page-updates/_page-updates-table.scss b/src/components/content/page-updates/_page-updates-table.scss index efa5e141..01c0b335 100644 --- a/src/components/content/page-updates/_page-updates-table.scss +++ b/src/components/content/page-updates/_page-updates-table.scss @@ -2,6 +2,11 @@ @include rem(margin-top, $gutter); } +.page-updates__intro { + margin: 0 0 1em; + max-width: 48em; +} + .page-updates__list { margin: 0; padding: 0; @@ -69,6 +74,16 @@ } } +.page-updates__page-label { + display: inline-flex; + margin: 0; + cursor: pointer; +} + +.page-updates__page-checkbox { + margin: 0; +} + .page-updates__link { flex: 1 1 auto; min-width: 12em; @@ -101,3 +116,46 @@ .page-updates__empty { margin: 0; } + +.page-updates__output { + margin-top: 1em; + padding-top: 1em; + border-top: 1px solid var(--theme-color-medium, $c-gray); +} + +.page-updates__output-label { + display: block; + margin-bottom: 0.5em; + font-weight: 600; +} + +.page-updates__textarea { + width: 100%; + min-height: 8em; + padding: 0.75em; + border: 1px solid var(--theme-color-medium, $c-gray); + border-radius: 6px; + font-family: + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', + 'Courier New', monospace; + font-size: 14px; + line-height: 1.5; + resize: vertical; +} + +.page-updates__actions { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.75em; + margin-top: 0.75em; +} + +.page-updates__copy { + margin: 0; +} + +.page-updates__status { + margin: 0; + font-size: 14px; +} diff --git a/src/components/content/page-updates/page-updates-table.hbs b/src/components/content/page-updates/page-updates-table.hbs index 1f314f97..1d741c56 100644 --- a/src/components/content/page-updates/page-updates-table.hbs +++ b/src/components/content/page-updates/page-updates-table.hbs @@ -1,8 +1,17 @@ -
    +

    Merges sorted by date with affected pages

    + {{#if showConfigurator}} +

    + Select the pages that should appear in the home page recent changes list. Copy the generated configuration into config/recent-pages.js, then restart npm start or run npm run build so the home page picks up the changes. Author, date, and commit details come from the selected merge. +

    + {{/if}} + {{#if merges}}
      {{#each merges}} @@ -35,6 +44,19 @@
        {{#each pages}}
      • + {{#if ../../showConfigurator}} + + {{/if}} {{#if sectionTitle}} Section: {{sectionTitle}} @@ -57,4 +79,30 @@ {{else}}

        No pages with merge history were found.

        {{/if}} + + {{#if showConfigurator}} +
        + + +
        + +

        +
        +
        + {{/if}}
    diff --git a/src/templates/layout.hbs b/src/templates/layout.hbs index a7bac6ac..eacdd8fd 100644 --- a/src/templates/layout.hbs +++ b/src/templates/layout.hbs @@ -56,7 +56,7 @@ {{> "content/cardmenu/cardmenu" menuitem=subPages}} {{/if}} {{#if pageUpdatesList}} - {{> "content/page-updates/page-updates-table" merges=pageUpdatesList}} + {{> "content/page-updates/page-updates-table" merges=pageUpdatesList showConfigurator=pageUpdatesConfigurator}} {{/if}} {{#if recentlyUpdatedPages}} {{> "content/recent-pages/recent-pages" pages=recentlyUpdatedPages overviewLink=recentPagesOverviewLink}} From bfd618635a7f9d073690b4d5dd7344e978345fd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ju=CC=88rgen=20Rudigier?= Date: Thu, 14 May 2026 23:49:58 +0200 Subject: [PATCH 09/14] chore: hide meta pages from nav --- pages/introduction/license/README.md | 1 + pages/introduction/privacy-policy/README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/pages/introduction/license/README.md b/pages/introduction/license/README.md index 84ab6c63..d5b0d431 100644 --- a/pages/introduction/license/README.md +++ b/pages/introduction/license/README.md @@ -1,5 +1,6 @@ --- navigation_title: "License" +navigation_ignore: true position: 4 --- diff --git a/pages/introduction/privacy-policy/README.md b/pages/introduction/privacy-policy/README.md index b9afa5ce..0b8b708b 100644 --- a/pages/introduction/privacy-policy/README.md +++ b/pages/introduction/privacy-policy/README.md @@ -1,5 +1,6 @@ --- navigation_title: "Privacy Policy" +navigation_ignore: true position: 5 --- From 71ea44b92365587a59ed876362989a8f25e4acde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ju=CC=88rgen=20Rudigier?= Date: Thu, 14 May 2026 23:59:14 +0200 Subject: [PATCH 10/14] chore: clean-up unused code for git metadata --- gulp/helpers/git-metadata.js | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/gulp/helpers/git-metadata.js b/gulp/helpers/git-metadata.js index b83ef98d..e1ffbaa2 100644 --- a/gulp/helpers/git-metadata.js +++ b/gulp/helpers/git-metadata.js @@ -1,26 +1,6 @@ import childProcess from 'node:child_process' import crypto from 'node:crypto' -const excludedCommitIds = [ - 'ac195754a6e64604066dafe2f5ad373c2a949ac4', // May 2, 2018 Absolutise paths - '2c9c894097e3aaa65b4775f96c595b476c9b29b9', // May 16, 2018 JSONs - '6b45656eb96adfcf23c6f47dc3564cf64e84d77f', // May 16, 2018 Fix examples links for GitHub - 'f0de1b4faceb9623b881d993e9df44f3f27fa8f3', // May 31, 2018 Fix relative links - 'f03b52f2b54b1c0d0de23027f574202852d3d21a', // Jun 12, 2018 Cleanup - '077c23bfd14a84ba32faac7984231bfb6bfed089', // Jun 15, 2018 Cleanup - '45a0b144e22e3179466b515a70550a409b4ab42c', // Sep 22, 2021 chore: update changed date - 'f6c1a521625159db489d65f7f98030482e418eab', // Aug 20, 2021 feat: change toc placeholder - '8d2f1cf458bd1769c828cff79743d8878cf71276', // Jun 28, 2021 feat: change ToC insertion to manual mode - 'f84cdc3b77f12dc3170717f6025aeadf4c337bbb', // Jan 18, 2024 feat: replace http in urls with https - 'aac742ff1c64c608985a1be3777da79fa70bf292', // Jun 29, 2023 feat: remove manual changed date from markdown files - 'a3f8584dd30b0404cab50cb982b62a132e8e0582', // Jan 2, 2024 ADG-338 feat: rename contribution page - '34da02f9b8caf03abc590e28d5bae79f8fc89e08', // Jan 27, 2024 ADG-338 feat: unify card text endings - 'c8cac292ac104b3dcd406c61091a5491448af7ea' // Dec 4, 2025 ADG-434 fix: remove duplicate positions -] - -const excludedCommitIdsSet = new Set( - excludedCommitIds.map(id => id.toLowerCase()) -) const gitHistoryRef = (() => { for (const ref of ['main', 'master', 'HEAD']) { const result = childProcess.spawnSync( @@ -86,11 +66,7 @@ export default () => { gravatarUrl: getGravatarUrl(changedByEmail) } }) - .filter( - entry => - entry.commitId && - !excludedCommitIdsSet.has(entry.commitId.toLowerCase()) - ) + .filter(entry => entry.commitId) const getFileMergeHistory = filePath => { if (fileMergeHistoryCache[filePath]) { From cb215b4b88e8acecce5aff46ffab6b9a0163a066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ju=CC=88rgen=20Rudigier?= Date: Fri, 15 May 2026 09:00:57 +0200 Subject: [PATCH 11/14] chore: add page-revisions page --- gulp/html.js | 96 ++++++++++++++++++- pages/develop/README.md | 9 ++ pages/develop/page-revisions/README.md | 10 ++ .../recent-changes}/README.md | 5 +- src/components/base/theme/_theme.scss | 3 +- .../content/all-pages/_all-pages-table.scss | 77 +++++++++++++++ .../content/all-pages/all-pages-table.hbs | 74 ++++++++++++++ src/templates/layout.hbs | 3 + 8 files changed, 271 insertions(+), 6 deletions(-) create mode 100644 pages/develop/README.md create mode 100644 pages/develop/page-revisions/README.md rename pages/{contribution/page-updates => develop/recent-changes}/README.md (80%) create mode 100644 src/components/content/all-pages/_all-pages-table.scss create mode 100644 src/components/content/all-pages/all-pages-table.hbs diff --git a/gulp/html.js b/gulp/html.js index 611fd6bc..475ba1ad 100644 --- a/gulp/html.js +++ b/gulp/html.js @@ -215,6 +215,11 @@ const applyMergeMetadata = (page, merge) => ({ commitUrl: merge.commitUrl }) +const isGuideNavigationPage = file => + !file.frontMatter.navigation_ignore && + !file.frontMatter.page_updates_overview && + !file.frontMatter.all_pages_overview + const getRecentlyUpdatedPages = (entries, recentPages, mergeOptions) => { const { urlOnly, commits } = normalizeRecentPagesConfig(recentPages) @@ -270,6 +275,81 @@ const markMergePageSelection = (merges, recentPages) => { })) } +const MIN_MEANINGFUL_REVISION_LINES = 4 + +const isMeaningfulRevision = (linesAdded, linesDeleted) => + linesAdded >= MIN_MEANINGFUL_REVISION_LINES || + linesDeleted >= MIN_MEANINGFUL_REVISION_LINES + +const getMeaningfulPageRevision = (filePath, mergeOptions) => { + const { getFileMergeHistory, getFileChangeStats } = mergeOptions + + for (const mergeEntry of getFileMergeHistory(filePath)) { + const { linesAdded, linesDeleted } = getFileChangeStats( + mergeEntry.commitId, + filePath + ) + const linesChanged = linesAdded + linesDeleted + + if (isMeaningfulRevision(linesAdded, linesDeleted)) { + return { + changed: mergeEntry.changed, + changedTimestamp: mergeEntry.changedTimestamp, + changedBy: mergeEntry.changedBy, + gravatarUrl: mergeEntry.gravatarUrl, + commitId: mergeEntry.commitId, + commitShortId: mergeEntry.commitId.slice(0, 7), + commitMessage: mergeEntry.commitMessage, + linesAdded, + linesDeleted, + linesChanged + } + } + } + + return null +} + +const getAllPagesByRevision = ( + pageFiles, + navigationItems, + mergeOptions, + base +) => + pageFiles + .map(file => { + const url = getCurrentUrl(file.path, base) + const section = file.data.section + const sectionTitle = + navigationItems.find(item => item.url === section)?.title || '' + const revision = getMeaningfulPageRevision(file.path, mergeOptions) + + return { + title: file.data.title, + url, + section, + sectionTitle, + changed: revision?.changed || '', + changedTimestamp: revision?.changedTimestamp || 0, + changedBy: revision?.changedBy || '', + gravatarUrl: revision?.gravatarUrl || '', + commitId: revision?.commitId || '', + commitShortId: revision?.commitShortId || '', + commitMessage: revision?.commitMessage || '', + linesAdded: revision?.linesAdded || 0, + linesDeleted: revision?.linesDeleted || 0, + linesChanged: revision?.linesChanged || 0 + } + }) + .filter(page => page.title && page.url) + .sort((a, b) => { + if (b.changedTimestamp !== a.changedTimestamp) { + return b.changedTimestamp - a.changedTimestamp + } + + return a.title.localeCompare(b.title) + }) + const groupPageEntriesByMerge = (pages, mergeOptions = {}) => { const { getFileMergeHistory, @@ -289,7 +369,7 @@ const groupPageEntriesByMerge = (pages, mergeOptions = {}) => { page.filePath ) - if (linesAdded === 0 && linesDeleted === 0) { + if (!isMeaningfulRevision(linesAdded, linesDeleted)) { continue } @@ -360,7 +440,7 @@ const buildHtml = (config, cb) => { } const getUpdatedPageEntries = currentFilePath => files - .filter(file => !file.frontMatter.navigation_ignore) + .filter(isGuideNavigationPage) .map(file => { const metadata = getGitMetadata(file.path) const { linesAdded, linesDeleted } = metadata.commitId @@ -395,7 +475,7 @@ const buildHtml = (config, cb) => { page => page.title && page.url && - page.changedTimestamp > 0 && + page.changed && page.url !== getCurrentUrl(currentFilePath, config.base) ) .sort((a, b) => b.changedTimestamp - a.changedTimestamp) @@ -547,6 +627,15 @@ const buildHtml = (config, cb) => { recentPagesConfig ) : [] + const allPagesList = + devMode && file.frontMatter.all_pages_overview + ? getAllPagesByRevision( + files.filter(isGuideNavigationPage), + navigation, + mergeOptions, + config.base + ) + : [] const recentPagesOverviewLink = devMode && currentUrl === '' && pageUpdatesOverviewFile ? { @@ -579,6 +668,7 @@ const buildHtml = (config, cb) => { recentlyUpdatedPages, pageUpdatesList, pageUpdatesConfigurator, + allPagesList, recentPagesOverviewLink, metatags: generateTags(metatagsData), breadcrumb: breadcrumb.sort((a, b) => { diff --git a/pages/develop/README.md b/pages/develop/README.md new file mode 100644 index 00000000..8fca9105 --- /dev/null +++ b/pages/develop/README.md @@ -0,0 +1,9 @@ +--- +navigation_title: "Develop" +position: 6 +dev_only: true +--- + +# Develop + +**Development-only tools for maintaining the guide, including merge-based recent changes and a revision overview for every guide page.** diff --git a/pages/develop/page-revisions/README.md b/pages/develop/page-revisions/README.md new file mode 100644 index 00000000..5c6c2de8 --- /dev/null +++ b/pages/develop/page-revisions/README.md @@ -0,0 +1,10 @@ +--- +navigation_title: "Page revisions" +position: 2 +dev_only: true +all_pages_overview: true +--- + +# Page revisions + +**Development overview of every guide page in the site navigation, sorted by the date of its most recent merge that changed more than three lines.** diff --git a/pages/contribution/page-updates/README.md b/pages/develop/recent-changes/README.md similarity index 80% rename from pages/contribution/page-updates/README.md rename to pages/develop/recent-changes/README.md index 0e7494d3..b9fa5515 100644 --- a/pages/contribution/page-updates/README.md +++ b/pages/develop/recent-changes/README.md @@ -1,9 +1,10 @@ --- -navigation_ignore: true +navigation_title: "Recent changes" +position: 1 dev_only: true page_updates_overview: true --- -# Page updates +# Recent changes by merge **This development overview groups guide pages that appear in the site navigation by their most recent merge. Each merge shows when it landed, who authored the merge commit, and its commit ID and message, followed by the pages it changed.** diff --git a/src/components/base/theme/_theme.scss b/src/components/base/theme/_theme.scss index 12b14a26..b40f990c 100644 --- a/src/components/base/theme/_theme.scss +++ b/src/components/base/theme/_theme.scss @@ -27,7 +27,8 @@ --theme-color-light: #{$theme-1-light}; } -.theme-contribution { +.theme-contribution, +.theme-develop { --theme-color-dark: #{$theme-4-dark}; --theme-color-medium: #{$c-gray}; --theme-color-light: #{$theme-4-light}; diff --git a/src/components/content/all-pages/_all-pages-table.scss b/src/components/content/all-pages/_all-pages-table.scss new file mode 100644 index 00000000..9d87fa29 --- /dev/null +++ b/src/components/content/all-pages/_all-pages-table.scss @@ -0,0 +1,77 @@ +.all-pages { + @include rem(margin-top, $gutter); +} + +.all-pages__intro { + margin: 0 0 1em; + max-width: 48em; +} + +.all-pages__table-wrap { + overflow-x: auto; +} + +.all-pages__table { + width: 100%; + border-collapse: collapse; + font-size: 14px; + line-height: 1.5; +} + +.all-pages__table th, +.all-pages__table td { + padding: 0.75em; + border-bottom: 1px solid var(--theme-color-medium, $c-gray); + text-align: left; + vertical-align: top; +} + +.all-pages__table th { + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.all-pages__date { + display: block; + margin: 0; + font-weight: 600; + white-space: nowrap; +} + +.all-pages__section { + display: inline-block; + margin: 0; + padding: 0.1em 0.5em; + border-radius: 999px; + font-size: 12px; + font-weight: 500; + line-height: 1.3; + letter-spacing: 0.01em; + background-color: var(--theme-color-light); + color: var(--theme-color-dark); + white-space: nowrap; +} + +.all-pages__link { + display: inline-block; + min-width: 12em; +} + +.all-pages__changes { + font-variant-numeric: tabular-nums; + white-space: nowrap; +} + +.all-pages__author { + display: flex; +} + +.all-pages__empty, +.all-pages__empty-date, +.all-pages__empty-changes, +.all-pages__empty-author { + margin: 0; + color: var(--theme-color-dark); +} diff --git a/src/components/content/all-pages/all-pages-table.hbs b/src/components/content/all-pages/all-pages-table.hbs new file mode 100644 index 00000000..c399abc3 --- /dev/null +++ b/src/components/content/all-pages/all-pages-table.hbs @@ -0,0 +1,74 @@ +
    +

    + All guide pages sorted by last meaningful revision +

    + +

    + Each row shows the latest merge that changed more than three lines in that page file. Pages without such a revision appear at the end. +

    + + {{#if pages}} +
    + + + + + + + + + + + + + {{#each pages}} + + + + + + + + {{/each}} + +
    + Guide pages sorted by last revision date +
    Last revisionSectionPageChangesAuthor
    + {{#if changedTimestamp}} + + {{else}} + No substantial revision + {{/if}} + + {{#if sectionTitle}} + + Section: {{sectionTitle}} + + {{/if}} + + {{title}} + + {{#if linesChanged}} + + +{{linesAdded}} −{{linesDeleted}} + + {{else}} + + {{/if}} + + {{#if changedBy}} + {{> "content/author/author" name=changedBy gravatarUrl=gravatarUrl className="all-pages__author"}} + {{else}} + + {{/if}} +
    +
    + {{else}} +

    No pages were found.

    + {{/if}} +
    diff --git a/src/templates/layout.hbs b/src/templates/layout.hbs index eacdd8fd..a852408a 100644 --- a/src/templates/layout.hbs +++ b/src/templates/layout.hbs @@ -58,6 +58,9 @@ {{#if pageUpdatesList}} {{> "content/page-updates/page-updates-table" merges=pageUpdatesList showConfigurator=pageUpdatesConfigurator}} {{/if}} + {{#if allPagesList}} + {{> "content/all-pages/all-pages-table" pages=allPagesList}} + {{/if}} {{#if recentlyUpdatedPages}} {{> "content/recent-pages/recent-pages" pages=recentlyUpdatedPages overviewLink=recentPagesOverviewLink}} {{/if}} From c255bd8d6afcc451217646a4c2d02c71734bf170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ju=CC=88rgen=20Rudigier?= Date: Fri, 15 May 2026 09:56:37 +0200 Subject: [PATCH 12/14] chore: text adjustments --- pages/develop/page-revisions/README.md | 2 +- pages/develop/recent-changes/README.md | 2 +- src/components/content/page-updates/page-updates-table.hbs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/develop/page-revisions/README.md b/pages/develop/page-revisions/README.md index 5c6c2de8..3237b8c9 100644 --- a/pages/develop/page-revisions/README.md +++ b/pages/develop/page-revisions/README.md @@ -7,4 +7,4 @@ all_pages_overview: true # Page revisions -**Development overview of every guide page in the site navigation, sorted by the date of its most recent merge that changed more than three lines.** +**Development overview of all guide pages, sorted by the most recent merge.** \ No newline at end of file diff --git a/pages/develop/recent-changes/README.md b/pages/develop/recent-changes/README.md index b9fa5515..a3a3fb0d 100644 --- a/pages/develop/recent-changes/README.md +++ b/pages/develop/recent-changes/README.md @@ -7,4 +7,4 @@ page_updates_overview: true # Recent changes by merge -**This development overview groups guide pages that appear in the site navigation by their most recent merge. Each merge shows when it landed, who authored the merge commit, and its commit ID and message, followed by the pages it changed.** +**Overview of all merges to main and the guide pages changed in each merge, with pages sorted by number of changed lines.** diff --git a/src/components/content/page-updates/page-updates-table.hbs b/src/components/content/page-updates/page-updates-table.hbs index 1d741c56..068a385b 100644 --- a/src/components/content/page-updates/page-updates-table.hbs +++ b/src/components/content/page-updates/page-updates-table.hbs @@ -8,7 +8,7 @@ {{#if showConfigurator}}

    - Select the pages that should appear in the home page recent changes list. Copy the generated configuration into config/recent-pages.js, then restart npm start or run npm run build so the home page picks up the changes. Author, date, and commit details come from the selected merge. + Select the pages that should appear in the home page recent changes list. Copy the generated configuration below into config/recent-pages.js, then restart npm start or run npm run build so the home page picks up the changes. Author, date, and commit details come from the selected merge.

    {{/if}} From ddbf833a129353008d0f05d6436565689a37a9f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ju=CC=88rgen=20Rudigier?= Date: Fri, 15 May 2026 09:56:59 +0200 Subject: [PATCH 13/14] chore: improve caching of git history --- gulp/helpers/git-metadata.js | 9 ++++--- gulp/html.js | 48 +++++++++++++++++++++++++----------- src/templates/layout.hbs | 2 +- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/gulp/helpers/git-metadata.js b/gulp/helpers/git-metadata.js index e1ffbaa2..3d67558d 100644 --- a/gulp/helpers/git-metadata.js +++ b/gulp/helpers/git-metadata.js @@ -36,11 +36,12 @@ const getGravatarUrl = email => { return `https://gravatar.com/avatar/${hash}?s=${gravatarImageSize}&d=mp` } -export default () => { - const changedMetadata = {} - const fileMergeHistoryCache = {} - const fileChangeStatsCache = new Map() +// Persist across watch rebuilds; git history does not change on every markdown save. +const changedMetadata = {} +const fileMergeHistoryCache = {} +const fileChangeStatsCache = new Map() +export default () => { const parseGitHistory = historyStdout => historyStdout .split('\x1e') diff --git a/gulp/html.js b/gulp/html.js index 475ba1ad..30025244 100644 --- a/gulp/html.js +++ b/gulp/html.js @@ -275,6 +275,8 @@ const markMergePageSelection = (merges, recentPages) => { })) } +let devAllPagesListCache = null + const MIN_MEANINGFUL_REVISION_LINES = 4 const isMeaningfulRevision = (linesAdded, linesDeleted) => @@ -484,8 +486,18 @@ const buildHtml = (config, cb) => { const sitemap = [] const layouts = {} let navigation = [] - let updatedPageEntries = [] + let updatedPageEntries = null let pageUpdatesOverviewFile = null + let homeFile = null + + const ensureUpdatedPageEntries = currentFilePath => { + if (updatedPageEntries !== null) { + return updatedPageEntries + } + + updatedPageEntries = getUpdatedPageEntries(currentFilePath) + return updatedPageEntries + } // const config = { // src: './pages/**/*.md', @@ -566,13 +578,9 @@ const buildHtml = (config, cb) => { .filter(page => !page.parent && page.parent !== null) .sort((a, b) => a.position - b.position) - const homeFile = files.find( + homeFile = files.find( file => getCurrentUrl(file.path, config.base) === '' ) - - updatedPageEntries = homeFile - ? getUpdatedPageEntries(homeFile.path) - : [] pageUpdatesOverviewFile = files.find(file => file.frontMatter.page_updates_overview) || null @@ -610,11 +618,11 @@ const buildHtml = (config, cb) => { site_name: appConfig.title, url: `${appConfig.url}/${currentUrl}` } - const metadata = getGitMetadata(file.path) + const metadata = devMode ? null : getGitMetadata(file.path) const recentlyUpdatedPages = currentUrl === '' ? getRecentlyUpdatedPages( - updatedPageEntries, + ensureUpdatedPageEntries(homeFile?.path ?? file.path), recentPagesConfig, mergeOptions ) @@ -623,18 +631,22 @@ const buildHtml = (config, cb) => { devMode && file.frontMatter.page_updates_overview const pageUpdatesList = pageUpdatesConfigurator ? markMergePageSelection( - groupPageEntriesByMerge(updatedPageEntries, mergeOptions), + groupPageEntriesByMerge( + ensureUpdatedPageEntries(file.path), + mergeOptions + ), recentPagesConfig ) : [] const allPagesList = devMode && file.frontMatter.all_pages_overview - ? getAllPagesByRevision( + ? devAllPagesListCache || + (devAllPagesListCache = getAllPagesByRevision( files.filter(isGuideNavigationPage), navigation, mergeOptions, config.base - ) + )) : [] const recentPagesOverviewLink = devMode && currentUrl === '' && pageUpdatesOverviewFile @@ -647,11 +659,17 @@ const buildHtml = (config, cb) => { } : null + const pageChanged = + metadata?.changed && metadata.changed.length > 0 + ? metadata.changed + : null + const showPageMetaInfo = devMode + ? Boolean(currentUrl && file.data.section !== 'welcome') + : Boolean(pageChanged) + file.data = Object.assign({}, file.data, { - changed: - metadata.changed && metadata.changed.length > 0 - ? metadata.changed - : null, + changed: pageChanged, + showPageMetaInfo, title: file.data.title, contents: file.contents, navigation: pageNavigation, diff --git a/src/templates/layout.hbs b/src/templates/layout.hbs index a852408a..1fcd46eb 100644 --- a/src/templates/layout.hbs +++ b/src/templates/layout.hbs @@ -67,7 +67,7 @@ {{> "content/prev_next/prev_next" prev=previousPage next=nextPage}} {{#notEq section 'welcome'}} - {{#if changed}} + {{#if showPageMetaInfo}} From a4c8d823734106aee380c2a3e53b4889b4bb4ad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ju=CC=88rgen=20Rudigier?= Date: Fri, 15 May 2026 10:03:35 +0200 Subject: [PATCH 14/14] chore: introduce dev job in package.json --- package.json | 3 ++- src/components/content/page-updates/page-updates-table.hbs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 87207a6b..168b33e6 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "main": "index.js", "type": "module", "scripts": { - "start": "ADG_DEV=1 gulp --webpackWatch", + "dev": "ADG_DEV=1 gulp --webpackWatch", + "start": "gulp --webpackWatch", "build": "gulp build", "rebuild": "gulp rebuild", "gulp": "gulp", diff --git a/src/components/content/page-updates/page-updates-table.hbs b/src/components/content/page-updates/page-updates-table.hbs index 068a385b..5c253a28 100644 --- a/src/components/content/page-updates/page-updates-table.hbs +++ b/src/components/content/page-updates/page-updates-table.hbs @@ -8,7 +8,7 @@ {{#if showConfigurator}}

    - Select the pages that should appear in the home page recent changes list. Copy the generated configuration below into config/recent-pages.js, then restart npm start or run npm run build so the home page picks up the changes. Author, date, and commit details come from the selected merge. + Select the pages that should appear in the home page recent changes list. Copy the generated configuration below into config/recent-pages.js, then restart npm run dev or run npm run build so the home page picks up the changes. Author, date, and commit details come from the selected merge.

    {{/if}}