diff --git a/src/css/About.scss b/src/css/About.scss index 16e90d9c..368c14c2 100644 --- a/src/css/About.scss +++ b/src/css/About.scss @@ -1,7 +1,6 @@ @import 'mobile'; @import 'variables'; -$text-width: 400px; $icon-size: 40px; $title-font-size: 40px; $build-version-font-size: 12px; @@ -37,28 +36,42 @@ $link-color: #56b0ff; font-size: $build-version-font-size; } - .contributors-header { - margin-bottom: 0; + h2 { + margin-bottom: $ui-large-margin; } - .contributor { - margin-block-start: 0.5em; - margin-block-end: 0.5em; + a { + color: $link-color; + } + + .changelog { + margin-left: $ui-large-margin; - &:last-child { - margin-block-end: 0; + >div { + margin: $ui-large-margin 0 $ui-large-margin $ui-large-margin; } } - a { - color: $link-color; - } + .contributors { + margin: 0 auto; + display: flex; + flex-wrap: wrap; + gap: 14px; + justify-content: space-evenly; + align-items: end; + font-size: 14px; - p { - max-width: $text-width; + a { + display: flex; + align-items: center; + flex-direction: column; + gap: 4px; - @include mobile { - max-width: 100%; + img { + width: 42px; + height: 42px; + border-radius: 50%; + } } } @@ -66,4 +79,10 @@ $link-color: #56b0ff; box-sizing: border-box; } } + + max-width: 700px; + + @include mobile { + max-width: 100%; + } } diff --git a/src/type-definitions/webpack-globals.d.ts b/src/type-definitions/webpack-globals.d.ts index 433aca49..f49f5d7b 100644 --- a/src/type-definitions/webpack-globals.d.ts +++ b/src/type-definitions/webpack-globals.d.ts @@ -1,3 +1,15 @@ declare const BUILD_DATE: string; declare const COMMIT_HASH: string; -declare const CONTRIBUTORS_LIST: string[]; +declare const CONTRIBUTORS_LIST: Contributor[]; +declare const CHANGELOG: ChangelogEntry[]; + +declare interface Contributor { + id: string, + name: string, +} + +declare interface ChangelogEntry { + id: string, + message: string, + date: string, +} diff --git a/src/ui/About.tsx b/src/ui/About.tsx index 4df57eb6..f42bfd12 100644 --- a/src/ui/About.tsx +++ b/src/ui/About.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import LiveSplitIcon from "../assets/icon.svg"; +import { renderMarkdown } from "../util/Markdown"; import "../css/About.scss"; @@ -13,6 +14,11 @@ interface Callbacks { openTimerView(): void, } +const changelogRenderSettings = { + softBreak: false, + escapeHtml: false, +}; + export class About extends React.Component { public render() { const renderedView = this.renderView(); @@ -21,6 +27,8 @@ export class About extends React.Component { } private renderView() { + const idealAvatarResolution = Math.round(devicePixelRatio * 42); + return (
@@ -42,14 +50,35 @@ export class About extends React.Component { View Source Code on GitHub

-

Contributors

- { - CONTRIBUTORS_LIST.map((contributor) => ( -

- {contributor} -

- )) - } +

Recent Changes

+
+ { + CHANGELOG.map((change) => ( + <> + + {change.date} + +
+ {renderMarkdown(change.message, changelogRenderSettings)} +
+ + )) + } +
+

Contributors

+
+ { + CONTRIBUTORS_LIST.map((contributor) => ( + + (e.target as any).remove()} + /> + {contributor.name} + + )) + } +
); diff --git a/src/util/Markdown.tsx b/src/util/Markdown.tsx index 51ada079..be84e0d1 100644 --- a/src/util/Markdown.tsx +++ b/src/util/Markdown.tsx @@ -22,13 +22,16 @@ export function replaceFlag(countryCode: string): JSX.Element { return {countryCode}; } -export function renderMarkdown(markdown: string): JSX.Element { +export function renderMarkdown(markdown: string, options: { + softBreak?: boolean, + escapeHtml?: boolean, +} = {}): JSX.Element { const markdownWithEmotes = replaceTwitchEmotes(markdown); const parsed = new CommonMarkParser().parse(markdownWithEmotes); const renderedMarkdown = new CommonMarkRenderer({ - escapeHtml: true, + escapeHtml: options.escapeHtml ?? true, linkTarget: "_blank", - softBreak: "br", + softBreak: options.softBreak === false ? undefined : "br", }).render(parsed); return ( diff --git a/webpack.config.js b/webpack.config.js index 2d17fc04..19e922c3 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -13,6 +13,36 @@ import fetch from "node-fetch"; import { fileURLToPath } from 'url'; import * as sass from "sass"; +function parseChangelog() { + return execSync("git log --grep \"^Changelog: \" -10") + .toString() + .split(/^commit /) + .slice(1) + .map((commit) => { + const changelogIndex = commit.indexOf(" Changelog: "); + if (changelogIndex === -1) { + return {}; + } + const dateIndex = commit.indexOf("Date: "); + if (dateIndex === -1) { + return {}; + } + const afterDate = commit.substring(dateIndex + 8); + const date = moment(new Date(afterDate.substring(0, afterDate.indexOf("\n")))).utc().format("YYYY-MM-DD"); + const id = commit.substring(0, commit.indexOf("\n")); + const message = commit + .substring(changelogIndex + 15) + .replaceAll("\n ", "\n") + .trim(); + return { + id, + message, + date, + }; + }) + .filter((changelog) => changelog.message); +} + export default async (env, argv) => { const getContributorsForRepo = async (repoName) => { const contributorsData = await fetch(`https://api.github.com/repos/LiveSplit/${repoName}/contributors`); @@ -40,10 +70,14 @@ export default async (env, argv) => { const contributorsList = Object.values(coreContributorsMap) .sort((user1, user2) => user2.contributions - user1.contributions) - .map((user) => user.login); + .map((user) => { + return { id: user.id, name: user.login }; + }); const commitHash = execSync("git rev-parse --short HEAD").toString(); const date = moment.utc().format("YYYY-MM-DD kk:mm:ss z"); + const changelog = parseChangelog(); + const basePath = path.dirname(fileURLToPath(import.meta.url)); const isProduction = argv.mode === "production"; @@ -97,6 +131,7 @@ export default async (env, argv) => { BUILD_DATE: JSON.stringify(date), COMMIT_HASH: JSON.stringify(commitHash), CONTRIBUTORS_LIST: JSON.stringify(contributorsList), + CHANGELOG: JSON.stringify(changelog), }), ...(isProduction ? [ new HtmlInlineScriptPlugin({