Skip to content

Commit 13eef59

Browse files
authored
Merge pull request #633 from microsoft/docs/changelog-pages-site
docs: add changelog site published to GitHub Pages
2 parents 493fc9b + 1e0aae3 commit 13eef59

8 files changed

Lines changed: 1040 additions & 0 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Publish Changelog Site
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- 'WHATS_NEW.md'
8+
- 'site/**'
9+
- '.github/workflows/publish-changelog.yml'
10+
workflow_dispatch:
11+
12+
permissions:
13+
contents: read
14+
15+
# Allow one publish at a time; queued runs wait so the live site always
16+
# reflects the newest commit rather than a cancelled mid-deploy state.
17+
concurrency:
18+
group: pages
19+
cancel-in-progress: false
20+
21+
jobs:
22+
build:
23+
runs-on: ubuntu-latest
24+
steps:
25+
- name: Checkout repository
26+
uses: actions/checkout@v4
27+
28+
- name: Set up Node.js
29+
uses: actions/setup-node@v4
30+
with:
31+
node-version: '20'
32+
33+
- name: Install dependencies
34+
working-directory: site
35+
run: npm install --no-audit --no-fund
36+
37+
- name: Build changelog site
38+
working-directory: site
39+
run: npm run build
40+
41+
- name: Configure GitHub Pages
42+
uses: actions/configure-pages@v5
43+
44+
- name: Upload Pages artifact
45+
uses: actions/upload-pages-artifact@v3
46+
with:
47+
path: site/dist
48+
49+
deploy:
50+
needs: build
51+
runs-on: ubuntu-latest
52+
permissions:
53+
pages: write
54+
id-token: write
55+
environment:
56+
name: github-pages
57+
url: ${{ steps.deployment.outputs.page_url }}
58+
steps:
59+
- name: Deploy to GitHub Pages
60+
id: deployment
61+
uses: actions/deploy-pages@v4

site/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
dist/

site/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Changelog site
2+
3+
A static changelog page generated from [`WHATS_NEW.md`](../WHATS_NEW.md) and published to
4+
GitHub Pages by [`publish-changelog.yml`](../.github/workflows/publish-changelog.yml).
5+
6+
`WHATS_NEW.md` stays the single source of truth — edit releases there, never in this folder.
7+
8+
## Local development
9+
10+
```bash
11+
cd site
12+
npm install
13+
npm run build # writes site/dist
14+
npx http-server dist -p 8099 -c-1
15+
```
16+
17+
`site/dist` and `site/node_modules` are git-ignored; the site is built in CI on every push.
18+
19+
## How it works
20+
21+
- `build.mjs` splits `WHATS_NEW.md` on each `## Version <version> - <date>` heading, renders
22+
each release body with [marked](https://marked.js.org/), and emits a single `index.html`
23+
with a version sidebar.
24+
- `assets/styles.css` and `assets/app.js` are copied verbatim into `dist`. The script adds
25+
filtering (press `/` to focus), scroll-spy nav highlighting, and a light/dark toggle.
26+
- `### Added` / `Changed` / `Fixed` / `Removed` / `Deprecated` / `Security` headings inside a
27+
release become summary chips; other headings render normally.
28+
- A `.nojekyll` marker is emitted so Pages serves the output as-is.
29+
30+
## One-time repository setup
31+
32+
In **Settings → Pages**, set **Source** to **GitHub Actions**. The workflow then deploys on
33+
every push to `main` that touches `WHATS_NEW.md` or `site/`, and via **Run workflow**.

site/assets/app.js

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
(function () {
2+
'use strict';
3+
4+
var THEME_KEY = 'foundry-toolkit-changelog-theme';
5+
var root = document.documentElement;
6+
var toggle = document.getElementById('theme-toggle');
7+
var icon = toggle ? toggle.querySelector('[data-theme-icon]') : null;
8+
var search = document.getElementById('search');
9+
var results = document.getElementById('results');
10+
var navEmpty = document.getElementById('nav-empty');
11+
var contentEmpty = document.getElementById('content-empty');
12+
var releases = Array.prototype.slice.call(document.querySelectorAll('.release'));
13+
var navItems = Array.prototype.slice.call(document.querySelectorAll('.nav__item'));
14+
15+
// Theme
16+
17+
function applyTheme(theme) {
18+
root.setAttribute('data-theme', theme);
19+
if (icon) {
20+
icon.textContent = theme === 'dark' ? '☀' : '☾';
21+
}
22+
if (toggle) {
23+
toggle.setAttribute('aria-label', theme === 'dark' ? 'Switch to light theme' : 'Switch to dark theme');
24+
}
25+
}
26+
27+
function readStoredTheme() {
28+
try {
29+
return window.localStorage.getItem(THEME_KEY);
30+
} catch (error) {
31+
return null;
32+
}
33+
}
34+
35+
var stored = readStoredTheme();
36+
var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
37+
applyTheme(stored || (prefersDark ? 'dark' : 'light'));
38+
39+
if (toggle) {
40+
toggle.addEventListener('click', function () {
41+
var next = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
42+
applyTheme(next);
43+
try {
44+
window.localStorage.setItem(THEME_KEY, next);
45+
} catch (error) {
46+
/* Storage can be blocked; the theme still applies for this page view. */
47+
}
48+
});
49+
}
50+
51+
// Filtering
52+
53+
var searchIndex = releases.map(function (release) {
54+
return {
55+
element: release,
56+
id: release.id,
57+
text: (release.textContent || '').toLowerCase(),
58+
};
59+
});
60+
61+
function filter(query) {
62+
var normalized = query.trim().toLowerCase();
63+
var matchedIds = {};
64+
var matches = 0;
65+
66+
searchIndex.forEach(function (entry) {
67+
var isMatch = normalized === '' || entry.text.indexOf(normalized) !== -1;
68+
entry.element.hidden = !isMatch;
69+
if (isMatch) {
70+
matchedIds[entry.id] = true;
71+
matches += 1;
72+
}
73+
});
74+
75+
navItems.forEach(function (item) {
76+
item.hidden = !matchedIds[item.getAttribute('data-target')];
77+
});
78+
79+
document.querySelectorAll('.nav__group').forEach(function (group) {
80+
var visible = group.querySelectorAll('.nav__item:not([hidden])').length;
81+
group.hidden = visible === 0;
82+
});
83+
84+
if (navEmpty) {
85+
navEmpty.hidden = matches !== 0;
86+
}
87+
if (contentEmpty) {
88+
contentEmpty.hidden = matches !== 0;
89+
}
90+
if (results) {
91+
results.hidden = normalized === '';
92+
results.textContent =
93+
matches === 1 ? '1 matching release' : matches + ' matching releases';
94+
}
95+
}
96+
97+
if (search) {
98+
search.addEventListener('input', function () {
99+
filter(search.value);
100+
});
101+
102+
// Restore a filter that survived a reload (e.g. browser form restoration).
103+
if (search.value) {
104+
filter(search.value);
105+
}
106+
}
107+
108+
document.addEventListener('keydown', function (event) {
109+
if (event.key === '/' && search && document.activeElement !== search) {
110+
event.preventDefault();
111+
search.focus();
112+
}
113+
});
114+
115+
// Active release highlighting
116+
117+
function setActive(id) {
118+
navItems.forEach(function (item) {
119+
item.classList.toggle('is-active', item.getAttribute('data-target') === id);
120+
});
121+
}
122+
123+
if ('IntersectionObserver' in window && releases.length > 0) {
124+
var visible = new Set();
125+
var observer = new IntersectionObserver(
126+
function (entries) {
127+
entries.forEach(function (entry) {
128+
if (entry.isIntersecting) {
129+
visible.add(entry.target.id);
130+
} else {
131+
visible.delete(entry.target.id);
132+
}
133+
});
134+
135+
for (var i = 0; i < releases.length; i += 1) {
136+
if (visible.has(releases[i].id)) {
137+
setActive(releases[i].id);
138+
return;
139+
}
140+
}
141+
},
142+
{ rootMargin: '-96px 0px -60% 0px', threshold: 0 },
143+
);
144+
145+
releases.forEach(function (release) {
146+
observer.observe(release);
147+
});
148+
}
149+
150+
if (window.location.hash) {
151+
setActive(window.location.hash.slice(1));
152+
} else if (releases.length > 0) {
153+
setActive(releases[0].id);
154+
}
155+
})();

0 commit comments

Comments
 (0)