feat: add /releases page (#1688)

* feat: add `/releases` page

* remove solid

* generated on build instead

* update lockfile

* update links

* Update index.mdx

* Update build.ts

* fix slug creations

* Add nav links back

* update links valitor to 0.6.0

* use link card

* wip - generated frontmatter

* wip

* fix labels name

* improve logic

* WIP refactor

show link to GitHub
wip version side navigation
improve wording and navigation
move releases to references

* add entitify

* agora vai

* Update build.ts

* fix: dir name

* add (Rust)

* fix dirName build

* change if order

* remove dirName

* redo sidenav

* place /releases back on header

* wip mobile

* improve mobile nav

* Update ReleaseSidebar.astro

* refactor: sidebar nav + dropdown only

* fix width and redo button width

* revert to show sidebar list + hide toc

* fix labels

* fix sort

* fix split

* fix split

---------

Signed-off-by: Lorenzo Lewis <lorenzo_lewis@icloud.com>
Co-authored-by: Lorenzo Lewis <lorenzo_lewis@icloud.com>
Co-authored-by: Vitor Ayres <gitkey@virtuaires.com.br>
Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
Amr Bashir
2024-03-25 14:24:32 +02:00
committed by GitHub
parent 0e5f5bae16
commit 5c81737581
10 changed files with 620 additions and 1 deletions

View File

@@ -76,6 +76,9 @@ export default defineConfig({
components: {
SiteTitle: 'src/components/overrides/SiteTitle.astro',
Footer: 'src/components/overrides/Footer.astro',
MarkdownContent: 'starlight-blog/overrides/MarkdownContent.astro',
Sidebar: 'starlight-blog/overrides/Sidebar.astro',
Header: 'src/components/overrides/Header.astro',
ThemeSelect: 'src/components/overrides/ThemeSelect.astro',
},
head: [

View File

@@ -13,10 +13,11 @@
"dev": "astro dev",
"format": "prettier -w --cache --plugin prettier-plugin-astro .",
"build:references": "pnpm --filter js-api-generator run build",
"build:releases": "pnpm --filter releases-generator run build",
"build:config": "pnpm --filter config-generator run build",
"build:astro": "astro build",
"build:i18n": "pnpm --filter docs-i18n-tracker run build",
"build": "pnpm dev:setup && pnpm build:references && pnpm build:config && pnpm build:astro && pnpm build:i18n",
"build": "pnpm dev:setup && pnpm build:references && pnpm build:config && pnpm build:releases && pnpm build:astro && pnpm build:i18n",
"preview": "astro preview"
},
"dependencies": {
@@ -29,6 +30,7 @@
"prettier": "^3.2.5",
"prettier-plugin-astro": "^0.13.0",
"rehype-autolink-headings": "^7.1.0",
"semver": "^7.6.0",
"sharp": "^0.33.2",
"shiki": "^1.1.7",
"starlight-blog": "^0.5.0",

View File

@@ -0,0 +1,191 @@
import { writeFileSync, mkdirSync } from 'node:fs';
import { join } from 'node:path';
const note =
'\n# NOTE: This file is auto-generated in packages/releases-generator/build.ts\n# For corrections please edit it directly';
const packages = [
{
name: 'tauri',
url: 'https://raw.githubusercontent.com/tauri-apps/tauri/dev/core/tauri/CHANGELOG.md',
tag: 'https://github.com/tauri-apps/tauri/releases/tag',
},
{
name: '@tauri-apps/api',
url: 'https://raw.githubusercontent.com/tauri-apps/tauri/dev/tooling/api/CHANGELOG.md',
tag: 'https://github.com/tauri-apps/tauri/releases/tag',
},
{
name: 'tauri-cli',
url: 'https://raw.githubusercontent.com/tauri-apps/tauri/dev/tooling/cli/CHANGELOG.md',
tag: 'https://github.com/tauri-apps/tauri/releases/tag',
},
{
name: '@tauri-apps/cli',
url: 'https://raw.githubusercontent.com/tauri-apps/tauri/dev/tooling/cli/node/CHANGELOG.md',
tag: 'https://github.com/tauri-apps/tauri/releases/tag',
},
{
name: 'tauri-bundler',
url: 'https://raw.githubusercontent.com/tauri-apps/tauri/dev/tooling/bundler/CHANGELOG.md',
tag: 'https://github.com/tauri-apps/tauri/releases/tag',
},
{
name: 'wry',
url: 'https://raw.githubusercontent.com/tauri-apps/wry/dev/CHANGELOG.md',
tag: 'https://github.com/tauri-apps/wry/releases/tag',
},
{
name: 'tao',
url: 'https://raw.githubusercontent.com/tauri-apps/tao/dev/CHANGELOG.md',
tag: 'https://github.com/tauri-apps/tao/releases/tag',
},
];
const baseDir = '../../src/content/docs/releases';
let latestVersions: {
[key: string]: string;
} = {};
async function generator() {
for (const pkg of packages) {
const response = await fetch(pkg.url);
const responseText: string = await response.text();
const releases = responseText
.split('## \\[')
.filter((item) => !item.includes('# Changelog'))
.map((section) => {
const [version, ...c] = section.split('\n');
const contents = c.join('\n');
return {
version: version.replace('\\[', '').replace(']', ''),
notes: contents,
};
})
.filter(({ version }) => !version.includes('Not Published'));
mkdirSync(join(baseDir, pkg.name), { recursive: true });
//
/*
* Write files for each version
*/
let nextPage = '/releases';
let nextLabel = pkg.name;
const len = releases.length;
for (let i = 0; i < len; i++) {
/**
* Deal with next-prev labels
*/
const thisVersion = releases[i].version;
let prevLabel, prevPage;
let navFrontmatter;
if (i !== len - 1) {
prevLabel = releases[i + 1].version;
prevPage = `releases/${pkg.name}/v${releases[i + 1].version}`;
}
if (i === 0) {
// latest version
latestVersions[pkg.name] = `v${thisVersion}`;
navFrontmatter = [
`prev:`,
` link: '${prevPage}'`,
` label: '${prevLabel}'`,
`next: false`,
];
} else if (i === len - 1) {
// earliest version
navFrontmatter = [
`prev: false`,
`next:`,
` link: '${nextPage}'`,
` label: '${nextLabel}'`,
];
} else {
navFrontmatter = [
`prev:`,
` link: '${prevPage}'`,
` label: '${prevLabel}'`,
`next:`,
` link: '${nextPage}'`,
` label: '${nextLabel}'`,
];
}
//
const pageFrontmatter = [
note,
`title: '${pkg.name}@${thisVersion}'`,
`description: '${thisVersion}'`,
`slug: 'releases/${pkg.name}/v${thisVersion}'`,
`tableOfContents: false`,
`editUrl: 'https://github.com/tauri-apps/tauri-docs/packages/releases-generator/build.ts'`,
];
const frontmatter = ['---', ...pageFrontmatter, ...navFrontmatter, '---'].join('\n');
//
const indexLink = `[Return](/releases)`;
const viewInGitHub = `<a href="${pkg.tag}/${pkg.name}-v${thisVersion}">View on GitHub</a>`;
const linksDiv = `<div style="margin-bottom:3rem; display: flex; justify-content: space-between; align-items: center"><span>${indexLink}</span><span>${viewInGitHub}</span></div>`;
//
const sidebar = `\nimport ReleaseSidebar from '@components/list/ReleaseSidebar.astro';
\n\n<ReleaseSidebar slug="releases/${pkg.name}" packageName="${pkg.name}" />\n`;
writeFileSync(
join(baseDir, pkg.name, `v${thisVersion}.mdx`),
`${frontmatter}\n${sidebar}\n${linksDiv}\n${entitify(releases[i].notes)}`
);
// use in next iteration
nextPage = `releases/${pkg.name}/v${thisVersion}`;
nextLabel = `v${thisVersion}`;
}
}
// Generate index page
const extraNote =
'# To quickly preview changes, you can edit this file, them make sure you copy the changes over the source build.ts script\n';
const indexPage = [
'---',
note,
extraNote,
`title: 'Tauri Core Ecosystem Releases'`,
`editUrl: 'https://github.com/tauri-apps/tauri-docs/packages/releases-generator/build.ts'`,
'---',
].join('\n');
const indexPageContent = `import { LinkCard, CardGrid } from '@astrojs/starlight/components';\n
<CardGrid>
<LinkCard title="tauri" href="/releases/tauri/${latestVersions['tauri']}" />
<LinkCard title="@tauri-apps/api" href="/releases/@tauri-apps/api/${latestVersions['@tauri-apps/api']}" />
<LinkCard title="tauri-cli (Rust)" href="/releases/tauri-cli/${latestVersions['tauri-cli']}" />
<LinkCard title="@tauri-apps/cli (JavaScript)" href="/releases/@tauri-apps/cli/${latestVersions['@tauri-apps/cli']}" />
<LinkCard title="tauri-bundler" href="/releases/tauri-bundler/${latestVersions['tauri-bundler']}" />
<LinkCard title="wry" href="/releases/wry/${latestVersions['wry']}" />
<LinkCard title="tao" href="/releases/tao/${latestVersions['tao']}" />
</CardGrid>`;
writeFileSync(join(baseDir, 'index.mdx'), `${indexPage}\n${indexPageContent}`);
}
function entitify(str: string): string {
return str
.replace(/[&<>"']/g, function (entity) {
switch (entity) {
case '&':
return '&amp;';
case '<':
return '&lt;';
case '>':
return '&gt;';
case '"':
return '&quot;';
case "'":
return '&#39;';
default:
return entity;
}
})
.replace(/\$\{/g, '$\\{');
}
generator();

View File

@@ -0,0 +1,15 @@
{
"name": "releases-generator",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "tsm ./build.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"tsm": "^2.3.0"
}
}

9
pnpm-lock.yaml generated
View File

@@ -40,6 +40,9 @@ importers:
rehype-autolink-headings:
specifier: ^7.1.0
version: 7.1.0
semver:
specifier: ^7.6.0
version: 7.6.0
sharp:
specifier: ^0.33.2
version: 0.33.3
@@ -119,6 +122,12 @@ importers:
specifier: ^3.1.17
version: 3.1.18(typedoc@0.25.12)
packages/releases-generator:
dependencies:
tsm:
specifier: ^2.3.0
version: 2.3.0
packages:
/@ampproject/remapping@2.3.0:

View File

@@ -3,3 +3,4 @@ packages:
- 'packages/js-api-generator'
- 'packages/config-generator'
- 'packages/tauri-typedoc-theme'
- 'packages/releases-generator'

View File

@@ -0,0 +1,281 @@
---
import { stripLeadingAndTrailingSlashes } from 'node_modules/@astrojs/starlight/utils/path';
import { routes, type Route } from 'node_modules/@astrojs/starlight/utils/routing';
import { Icon } from '@astrojs/starlight/components';
import semver from 'semver';
interface Props {
/**
* Package name
*/
packageName: string;
/**
* Slug relative to /src - e.g "/zh-cn/features"
*
* Note: leading and trailing slashes are dropped "/features/" === "features"
*/
slug: string;
/**
* Filter out pages by title - case insensitive
*/
filterOutByTitle?: string[];
/**
* Filter out pages by file name - case insensitive
*/
filterOutByFileName?: string[];
}
let { slug } = Astro.props;
const { filterOutByTitle = [], filterOutByFileName = [], packageName } = Astro.props;
slug = stripLeadingAndTrailingSlashes(slug.toLowerCase());
let mainList: Route[] = [];
routes.forEach((page) => {
// `page.slug` is the slug of the current page being looped,
// `slug` is this component prop
if (page.slug.startsWith(slug + '/')) {
mainList.push(page);
}
});
/**
* Filter items if any filter is set
*/
if (filterOutByTitle.length > 0) {
mainList = mainList.filter(
(page) => !filterOutByTitle.some((val) => page.entry.data.title.includes(val))
);
}
if (filterOutByFileName.length > 0) {
mainList = mainList.filter(
(page) => !filterOutByFileName.some((val) => page.entry.id.includes(val))
);
}
function sortVersions(a: Route, b: Route): number {
const t1 = a.entry.data.title;
const t2 = b.entry.data.title;
const [package1, version1] = t1.split('@').filter(w => w.length > 0);
const [package2, version2] = t2.split('@').filter(w => w.length > 0);
if (package1 === package2) {
return semver.lt(version1, version2);
} else {
return package1.localeCompare(package2);
}
}
mainList.sort((a, b) => sortVersions(b, a));
---
<div class="parent">
<div class="sidenav">
<div class="sidenav-title">
<p>Changelogs: <br />{packageName}</p>
</div>
<div class="sidenav-content">
{
mainList.map((item) => (
<>
<a href={`/${item.slug}`}> {item.entry.data.description}</a>
</>
))
}
</div>
</div>
</div>
<mobile-release-list>
<nav aria-labelledby="mobile-release-list">
<details>
<summary id="mobile-release-list" class="sl-flex">
<div class="toggle sl-flex">
Release List:
<span class="display-current">{packageName}</span>
<Icon name={'right-caret'} class="caret" size="1rem" />
</div>
</summary>
<div class="dropdown">
<ul>
{
mainList.map((item) => (
<li>
<a href={`/${item.slug}`}> {item.entry.data.description}</a>
</li>
))
}
</ul>
</div>
</details>
</nav>
</mobile-release-list>
<script>
// Taken from starlight's mobile TOC
class MobileReleaseList extends HTMLElement {
constructor() {
super();
const details = this.querySelector('details');
if (!details) return;
const closeToC = () => {
details.open = false;
};
// Close the table of contents when a user clicks outside of it.
window.addEventListener('click', (e) => {
if (!details.contains(e.target as Node)) closeToC();
});
// Or when they press the escape key.
window.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && details.open) {
const hasFocus = details.contains(document.activeElement);
closeToC();
if (hasFocus) {
const summary = details.querySelector('summary');
if (summary) summary.focus();
}
}
});
}
}
customElements.define('mobile-release-list', MobileReleaseList);
</script>
<style>
.parent {
position: relative;
}
:root {
--release-navbar-width: 12rem;
--sl-content-width: 35rem !important;
}
.sidenav-title {
display: block;
padding-block: 0.5rem;
text-align: center;
background-color: var(--sl-color-gray-5);
border: 1px solid var(--sl-color-gray-6);
}
.sidenav {
display: none;
width: fit-content;
top: 0;
right: calc(var(--release-navbar-width) * -1);
box-shadow: var(--sl-shadow-md);
font-size: var(--sl-text-xs);
font-family: var(--__sl-font-mono);
background-color: var(--sl-color-bg-sidebar);
}
.sl-markdown-content {
margin: auto;
background-color: red;
}
@media (min-width: 72rem) {
.sidenav {
display: inline;
position: absolute;
}
}
.sidenav-content {
padding-inline: 16px 8px;
min-width: fit-content;
row-gap: 1em;
display: flex;
flex-direction: column;
max-height: 85vh;
overflow-y: auto;
overscroll-behavior: contain;
}
/* Mobile */
/* Taken from starlight's mobile TOC */
nav {
width: fit-content;
}
@media (min-width: 72rem) {
nav {
display: none;
}
}
summary {
padding-bottom: 1rem;
gap: 0.5rem;
align-items: center;
font-size: var(--sl-text-xs);
outline-offset: var(--sl-outline-offset-inside);
}
summary::marker,
summary::-webkit-details-marker {
display: none;
}
.toggle {
z-index: 3;
flex-shrink: 0;
gap: 1rem;
align-items: center;
justify-content: space-between;
border: 1px solid var(--sl-color-gray-5);
border-radius: 0.5rem;
padding-block: 0.5rem;
padding-inline-start: 0.75rem;
padding-inline-end: 0.5rem;
background-color: var(--sl-color-gray-6);
user-select: none;
cursor: pointer;
}
details[open] .toggle {
color: var(--sl-color-white);
border-color: var(--sl-color-accent);
}
details .toggle:hover {
color: var(--sl-color-white);
border-color: var(--sl-color-gray-2);
}
details[open] .caret {
transform: rotateZ(90deg);
}
.display-current {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
color: var(--sl-color-white);
}
.dropdown {
--border: 1px;
z-index: 2;
row-gap: 1em;
position: absolute;
border-top-left-radius: 0.5rem;
padding-inline: 0.75rem;
padding-top: 1em;
margin-top: -1em !important;
border: var(--border) solid var(--sl-color-gray-5);
max-height: calc(85vh - var(--sl-nav-height) - var(--sl-mobile-toc-height));
overflow-y: auto;
background-color: var(--sl-color-black);
box-shadow: var(--sl-shadow-md);
overscroll-behavior: contain;
background-color: var(--sl-color-bg-sidebar);
}
ul {
all: unset;
list-style: none;
}
li {
all: unset;
}
a {
display: block;
padding-block: 0.5rem;
line-height: 1.25;
}
</style>

View File

@@ -0,0 +1,111 @@
---
import type { Props } from '@astrojs/starlight/props';
import LanguageSelect from '@astrojs/starlight/components/LanguageSelect.astro';
import Search from '@astrojs/starlight/components/Search.astro';
import SiteTitle from './SiteTitle.astro';
import SocialIcons from '@astrojs/starlight/components/SocialIcons.astro';
import ThemeSelect from '@astrojs/starlight/components/ThemeSelect.astro';
---
<div class="header sl-flex">
<div class="sl-flex">
<SiteTitle {...Astro.props} />
</div>
<div class="sl-flex">
<Search {...Astro.props} />
</div>
<div class="sl-hidden md:sl-flex right-group">
<div class="sl-flex social-icons">
<SocialIcons {...Astro.props} />
</div>
<a href="/releases" class="sl-flex release-link action minimal"> Releases</a>
<ThemeSelect {...Astro.props} />
<LanguageSelect {...Astro.props} />
</div>
</div>
<style>
.header {
gap: var(--sl-nav-gap);
justify-content: space-between;
align-items: center;
height: 100%;
}
.right-group,
.social-icons {
gap: 1rem;
align-items: center;
}
.release-link::after,
.social-icons::after {
content: '';
height: 2rem;
border-inline-end: 1px solid var(--sl-color-gray-5);
}
@media (min-width: 50rem) {
:global(:root[data-has-sidebar]) {
--__sidebar-pad: calc(2 * var(--sl-nav-pad-x));
}
:global(:root:not([data-has-toc])) {
--__toc-width: 0rem;
}
.header {
--__sidebar-width: max(0rem, var(--sl-content-inline-start, 0rem) - var(--sl-nav-pad-x));
--__main-column-fr: calc(
(
100% + var(--__sidebar-pad, 0rem) - var(--__toc-width, var(--sl-sidebar-width)) -
(2 * var(--__toc-width, var(--sl-nav-pad-x))) - var(--sl-content-inline-start, 0rem) -
var(--sl-content-width)
) / 2
);
display: grid;
grid-template-columns:
/* 1 (site title): runs up until the main content columns left edge or the width of the title, whichever is the largest */
minmax(
calc(var(--__sidebar-width) + max(0rem, var(--__main-column-fr) - var(--sl-nav-gap))),
auto
)
/* 2 (search box): all free space that is available. */
1fr
/* 3 (right items): use the space that these need. */
auto;
align-content: center;
}
}
.action {
gap: 0.5em;
align-items: center;
padding: 0.5rem 1.125rem;
color: var(--sl-color-gray-1);
line-height: 1.1875;
text-decoration: none;
font-size: var(--sl-text-sm);
}
.action:hover {
color: var(--sl-color-gray-2);
}
.action.primary {
background: var(--sl-color-text-accent);
color: var(--sl-color-black);
}
.action.secondary {
border: 1px solid;
}
.action.minimal {
padding-inline: 0;
}
@media (min-width: 50rem) {
.action {
font-size: var(--sl-text-base);
padding: 1rem 1.25rem;
}
}
.release-link {
gap: 1rem;
}
</style>

View File

@@ -33,4 +33,8 @@ import { LinkCard, CardGrid } from '@astrojs/starlight/components';
title="Configuration Files"
href="/references/configuration-files"
/>
<LinkCard
title="Releases"
href="/releases"
/>
</CardGrid>

2
src/content/docs/releases/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*.md
*.mdx