feat: implement changelog page (#7099)

* feat: implement leaderboard page

* feat: sample changelog files

* Update UI for changelog page

* Make changelog page noindex

---------

Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>
feat/collection
Arik Chakma 2 months ago committed by GitHub
parent 3166a02f23
commit 9948e89b84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      .astro/settings.json
  2. 2
      package.json
  3. 17
      pnpm-lock.yaml
  4. 37
      src/components/Changelog/ChangelogItem.astro
  5. 13
      src/components/MarkdownFile.astro
  6. 25
      src/data/changelogs/leaderboard-page.md
  7. 12
      src/data/changelogs/new-dashboard-page.md
  8. 69
      src/lib/changelog.ts
  9. 39
      src/pages/changelog.astro

@ -3,6 +3,6 @@
"enabled": false "enabled": false
}, },
"_variables": { "_variables": {
"lastUpdateCheck": 1727087951727 "lastUpdateCheck": 1727095669945
} }
} }

@ -50,6 +50,7 @@
"jose": "^5.6.3", "jose": "^5.6.3",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"lucide-react": "^0.419.0", "lucide-react": "^0.419.0",
"luxon": "^3.5.0",
"nanoid": "^5.0.7", "nanoid": "^5.0.7",
"nanostores": "^0.10.3", "nanostores": "^0.10.3",
"node-html-parser": "^6.1.13", "node-html-parser": "^6.1.13",
@ -80,6 +81,7 @@
"@tailwindcss/typography": "^0.5.13", "@tailwindcss/typography": "^0.5.13",
"@types/dom-to-image": "^2.6.7", "@types/dom-to-image": "^2.6.7",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"@types/luxon": "^3.4.2",
"@types/prismjs": "^1.26.4", "@types/prismjs": "^1.26.4",
"@types/react-calendar-heatmap": "^1.6.7", "@types/react-calendar-heatmap": "^1.6.7",
"@types/turndown": "^5.0.5", "@types/turndown": "^5.0.5",

@ -71,6 +71,9 @@ importers:
lucide-react: lucide-react:
specifier: ^0.419.0 specifier: ^0.419.0
version: 0.419.0(react@18.3.1) version: 0.419.0(react@18.3.1)
luxon:
specifier: ^3.5.0
version: 3.5.0
nanoid: nanoid:
specifier: ^5.0.7 specifier: ^5.0.7
version: 5.0.7 version: 5.0.7
@ -156,6 +159,9 @@ importers:
'@types/js-cookie': '@types/js-cookie':
specifier: ^3.0.6 specifier: ^3.0.6
version: 3.0.6 version: 3.0.6
'@types/luxon':
specifier: ^3.4.2
version: 3.4.2
'@types/prismjs': '@types/prismjs':
specifier: ^1.26.4 specifier: ^1.26.4
version: 1.26.4 version: 1.26.4
@ -1246,6 +1252,9 @@ packages:
'@types/js-cookie@3.0.6': '@types/js-cookie@3.0.6':
resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
'@types/luxon@3.4.2':
resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==}
'@types/mdast@4.0.4': '@types/mdast@4.0.4':
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
@ -2174,6 +2183,10 @@ packages:
peerDependencies: peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
luxon@3.5.0:
resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==}
engines: {node: '>=12'}
magic-string@0.30.11: magic-string@0.30.11:
resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==}
@ -4238,6 +4251,8 @@ snapshots:
'@types/js-cookie@3.0.6': {} '@types/js-cookie@3.0.6': {}
'@types/luxon@3.4.2': {}
'@types/mdast@4.0.4': '@types/mdast@4.0.4':
dependencies: dependencies:
'@types/unist': 3.0.3 '@types/unist': 3.0.3
@ -5237,6 +5252,8 @@ snapshots:
dependencies: dependencies:
react: 18.3.1 react: 18.3.1
luxon@3.5.0: {}
magic-string@0.30.11: magic-string@0.30.11:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/sourcemap-codec': 1.5.0

@ -0,0 +1,37 @@
---
import type { ChangelogFileType } from '../../lib/changelog';
import { DateTime } from 'luxon';
import MarkdownFile from '../MarkdownFile.astro';
interface Props {
changelog: ChangelogFileType;
}
const { changelog } = Astro.props;
const { frontmatter } = changelog;
const formattedDate = DateTime.fromISO(frontmatter.date).toFormat(
'dd LLL, yyyy',
);
---
<div class='relative'>
<span class='h-2 w-2 flex-shrink-0 rounded-full bg-gray-300 absolute top-2 -left-6'></span>
<div class='mb-3 flex items-center gap-2'>
<span class='flex-shrink-0 text-xs tracking-wide text-gray-400'>
{formattedDate}
</span>
<span class='truncate text-base font-medium'>
{changelog.frontmatter.title}
</span>
</div>
<div class='rounded-xl border bg-white p-6'>
<div
class='prose prose-h2:text-lg prose-h2:font-medium prose-h2:mt-3 prose-sm prose-p:mb-0 prose-blockquote:font-normal prose-blockquote:text-gray-500 prose-ul:my-0 prose-img:mt-0 prose-img:rounded-lg [&>blockquote>p]:mt-0 prose-ul:bg-gray-100 prose-ul:rounded-lg prose-ul:px-4 prose-ul:py-4 prose-ul:pl-7 [&>ul>li]:my-0 [&>ul>li]:mb-1 [&>ul]:mt-3'
>
<changelog.Content />
</div>
</div>
</div>

@ -1,5 +1,16 @@
---
interface Props {
class?: string;
}
const { class: className } = Astro.props;
---
<div <div
class='container prose-h2:text-balance prose-h3:text-balance prose-h4:text-balance prose-h5:text-balance prose prose-xl prose-h2:mb-3 prose-h2:mt-10 prose-h2:scroll-mt-5 prose-h2:text-3xl prose-h3:mt-2 prose-h3:scroll-mt-5 prose-h5:font-medium prose-blockquote:font-normal prose-code:bg-transparent prose-img:mt-1 prose-h2:sm:scroll-mt-10 prose-h3:sm:scroll-mt-10' class:list={[
'container prose prose-xl prose-h2:mb-3 prose-h2:mt-10 prose-h2:scroll-mt-5 prose-h2:text-balance prose-h2:text-3xl prose-h3:mt-2 prose-h3:scroll-mt-5 prose-h3:text-balance prose-h4:text-balance prose-h5:text-balance prose-h5:font-medium prose-blockquote:font-normal prose-code:bg-transparent prose-img:mt-1 prose-h2:sm:scroll-mt-10 prose-h3:sm:scroll-mt-10',
className,
]}
> >
<slot /> <slot />
</div> </div>

@ -0,0 +1,25 @@
---
title: 'New Dashboard, Leaderboards and Projects'
description: 'New leaderboard page showing the most active users'
seo:
title: 'Leaderboard Page - roadmap.sh'
description: ''
date: 2024-09-13
---
TL;DR: new dashboard, leaderboard page and projects page.
- New dashboard for logged-in users
- New leaderboard page
- Projects page listing all projects
- Ability to stop a started project
- Frontend and backend content improvements
- Bug fixes
![Leaderboard Page](https://assets.roadmap.sh/guest/personal-dashboard.png)
We just launched a dedicated dashboard for logged-in users to showing progress, projects, bookmarks and more. You can still access the old homepage by visiting [this page](https://roadmap.sh/home).
We also launched a new [leaderboard page](/leaderboard) showing the most active users, users who completed most projects and more.
There is also a [new projects page](/projects) where you can see all the projects you have been working on. You can also now stop a started project.

@ -0,0 +1,12 @@
---
title: 'New Dashboard Page'
description: 'We have added a new dashboard page to help you track your progress'
seo:
title: 'New Dashboard Page - roadmap.sh'
description: 'We have added a new dashboard page to help you track your progress'
date: 2024-09-12
---
We have revamped the dashboard page for logged-in users. The new dashboard page will help you track your progress and see your overall progress in a single view. We have also added a new progress bar to help you visualize your progress.
If you want to access the guest homepage, you check check it out [here](/home).

@ -0,0 +1,69 @@
import type { MarkdownFileType } from './file';
export interface ChangelogFrontmatter {
title: string;
description: string;
seo: {
title: string;
description: string;
};
date: string;
}
export type ChangelogFileType = MarkdownFileType<ChangelogFrontmatter> & {
id: string;
};
/**
* Generates id from the given changelog file
* @param filePath Markdown file path
*
* @returns unique changelog identifier
*/
function changelogPathToId(filePath: string): string {
const fileName = filePath.split('/').pop() || '';
return fileName.replace('.md', '');
}
/**
* Gets all the changelogs sorted by the publishing date
* @returns Promisifed guide files
*/
export async function getAllChangelogs(): Promise<ChangelogFileType[]> {
// @ts-ignore
const changelogs = import.meta.glob<ChangelogFileType>(
'/src/data/changelogs/*.md',
{
eager: true,
},
);
const changelogFiles = Object.values(changelogs) as ChangelogFileType[];
const enrichedChangelogs: ChangelogFileType[] = changelogFiles.map(
(changelogFile) => ({
...changelogFile,
id: changelogPathToId(changelogFile.file),
}),
);
return enrichedChangelogs.sort(
(a, b) =>
new Date(b.frontmatter.date).valueOf() -
new Date(a.frontmatter.date).valueOf(),
);
}
/**
* Gets the changelog by the given id
* @param id Changelog identifier
* @returns Promisified changelog file
*/
export async function getChangelogById(
id: string,
): Promise<ChangelogFileType | undefined> {
const allChangelogs = await getAllChangelogs();
return allChangelogs.find((changelog) => changelog.id === id);
}

@ -0,0 +1,39 @@
---
import SimplePageHeader from '../components/SimplePageHeader.astro';
import BaseLayout from '../layouts/BaseLayout.astro';
import { getAllChangelogs } from '../lib/changelog';
import ChangelogItem from '../components/Changelog/ChangelogItem.astro';
const allChangelogs = await getAllChangelogs();
---
<BaseLayout
title='Changelogs'
description='Changelogs for the updates and changes in the website'
permalink='/changelogs'
noIndex={true}
>
<div class='bg-gray-100'>
<div class='rounded-lg border-b bg-white text-left'>
<div class='mx-auto max-w-[500px] pt-12 pb-10 text-center'>
<h1 class='mb-3 text-4xl font-semibold'>Changelog</h1>
<p>Here is everything we have been shipping recently</p>
</div>
</div>
<div class='relative mx-auto max-w-[500px] pb-8 pt-4'>
<div class='space-y-6'>
<div
class='absolute inset-y-0 -left-5 w-px -translate-x-[0.5px] bg-gray-300'
>
</div>
{
allChangelogs.map((changelog) => (
<ChangelogItem changelog={changelog} />
))
}
</div>
</div>
</div>
</BaseLayout>
Loading…
Cancel
Save