Skip to content

Commit cade2c1

Browse files
authored
Add changelog page to website (#3242)
* Add changelog page to website * Add pages for individual changelog entries that can be linked to * Handle first case for individual page * Add some more changelog entries, improve some spacing
1 parent affeec8 commit cade2c1

File tree

8 files changed

+487
-2
lines changed

8 files changed

+487
-2
lines changed

apps/frontend/src/components/ui/NavTabs.vue

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,12 @@ function pickLink() {
7676
subpageSelected.value = false;
7777
for (let i = filteredLinks.value.length - 1; i >= 0; i--) {
7878
const link = filteredLinks.value[i];
79-
if (decodeURIComponent(route.path) === link.href) {
79+
if (props.query) {
80+
if (route.query[props.query] === link.href || (!route.query[props.query] && !link.href)) {
81+
index = i;
82+
break;
83+
}
84+
} else if (decodeURIComponent(route.path) === link.href) {
8085
index = i;
8186
break;
8287
} else if (
@@ -150,7 +155,7 @@ onMounted(() => {
150155
});
151156
152157
watch(
153-
() => route.path,
158+
() => [route.path, route.query],
154159
() => pickLink(),
155160
);
156161
</script>
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<script setup lang="ts">
2+
import { getChangelog } from "@modrinth/utils";
3+
import { ChangelogEntry } from "@modrinth/ui";
4+
import { ChevronLeftIcon } from "@modrinth/assets";
5+
6+
const route = useRoute();
7+
8+
const changelogEntry = computed(() =>
9+
route.params.date
10+
? getChangelog().find((x) => {
11+
if (x.product === route.params.product) {
12+
console.log("Found matching product!");
13+
14+
if (x.version && x.version === route.params.date) {
15+
console.log("Found matching version!");
16+
return x;
17+
} else if (x.date.unix() === Number(route.params.date as string)) {
18+
console.log("Found matching date!");
19+
return x;
20+
}
21+
}
22+
return undefined;
23+
})
24+
: undefined,
25+
);
26+
27+
const isFirst = computed(() => changelogEntry.value?.date === getChangelog()[0].date);
28+
29+
if (!changelogEntry.value) {
30+
createError({ statusCode: 404, statusMessage: "Version not found" });
31+
}
32+
</script>
33+
34+
<template>
35+
<div v-if="changelogEntry" class="page experimental-styles-within">
36+
<nuxt-link
37+
:to="`/news/changelog?filter=${changelogEntry.product}`"
38+
class="mb-4 flex w-fit items-center gap-2 text-link"
39+
>
40+
<ChevronLeftIcon /> View full changelog
41+
</nuxt-link>
42+
<div class="relative flex flex-col gap-4 pb-6">
43+
<div class="absolute flex h-full w-4 justify-center">
44+
<div class="timeline-indicator" :class="{ first: isFirst }" />
45+
</div>
46+
<ChangelogEntry :entry="changelogEntry" :first="isFirst" show-type class="relative z-10" />
47+
</div>
48+
</div>
49+
</template>
50+
51+
<style lang="scss" scoped>
52+
.page {
53+
padding: 0.5rem;
54+
margin-left: auto;
55+
margin-right: auto;
56+
max-width: 56rem;
57+
}
58+
59+
.timeline-indicator {
60+
background-image: linear-gradient(
61+
to bottom,
62+
var(--color-raised-bg) 66%,
63+
rgba(255, 255, 255, 0) 0%
64+
);
65+
background-size: 100% 30px;
66+
background-repeat: repeat-y;
67+
68+
height: calc(100% + 2rem);
69+
width: 4px;
70+
margin-top: -2rem;
71+
72+
mask-image: linear-gradient(
73+
to bottom,
74+
transparent 0%,
75+
black 8rem,
76+
black calc(100% - 8rem),
77+
transparent 100%
78+
);
79+
80+
&.first {
81+
margin-top: 1rem;
82+
83+
mask-image: linear-gradient(black calc(100% - 15rem), transparent 100%);
84+
}
85+
}
86+
</style>
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<script setup lang="ts">
2+
import { type Product, getChangelog } from "@modrinth/utils";
3+
import { ChangelogEntry } from "@modrinth/ui";
4+
import NavTabs from "~/components/ui/NavTabs.vue";
5+
6+
const route = useRoute();
7+
8+
const filter = ref<Product | undefined>(undefined);
9+
const allChangelogEntries = ref(getChangelog());
10+
11+
function updateFilter() {
12+
if (route.query.filter) {
13+
filter.value = route.query.filter as Product;
14+
} else {
15+
filter.value = undefined;
16+
}
17+
}
18+
19+
updateFilter();
20+
21+
watch(
22+
() => route.query,
23+
() => updateFilter(),
24+
);
25+
26+
const changelogEntries = computed(() =>
27+
allChangelogEntries.value.filter((x) => !filter.value || x.product === filter.value),
28+
);
29+
</script>
30+
31+
<template>
32+
<div class="page experimental-styles-within">
33+
<h1 class="m-0 text-3xl font-extrabold">Changelog</h1>
34+
<p class="my-3">Keep up-to-date on what's new with Modrinth.</p>
35+
<NavTabs
36+
:links="[
37+
{
38+
label: 'All',
39+
href: '',
40+
},
41+
{
42+
label: 'Website',
43+
href: 'web',
44+
},
45+
{
46+
label: 'Servers',
47+
href: 'servers',
48+
},
49+
{
50+
label: 'App',
51+
href: 'app',
52+
},
53+
]"
54+
query="filter"
55+
class="mb-4"
56+
/>
57+
<div class="relative flex flex-col gap-4 pb-6">
58+
<div class="absolute flex h-full w-4 justify-center">
59+
<div class="timeline-indicator" />
60+
</div>
61+
<ChangelogEntry
62+
v-for="(entry, index) in changelogEntries"
63+
:key="entry.date"
64+
:entry="entry"
65+
:first="index === 0"
66+
:show-type="filter === undefined"
67+
has-link
68+
class="relative z-10"
69+
/>
70+
</div>
71+
</div>
72+
</template>
73+
74+
<style lang="scss" scoped>
75+
.page {
76+
padding: 0.5rem;
77+
margin-left: auto;
78+
margin-right: auto;
79+
max-width: 56rem;
80+
}
81+
82+
.timeline-indicator {
83+
background-image: linear-gradient(
84+
to bottom,
85+
var(--color-raised-bg) 66%,
86+
rgba(255, 255, 255, 0) 0%
87+
);
88+
background-size: 100% 30px;
89+
background-repeat: repeat-y;
90+
margin-top: 1rem;
91+
92+
height: calc(100% - 1rem);
93+
width: 4px;
94+
95+
mask-image: linear-gradient(to bottom, black calc(100% - 15rem), transparent 100%);
96+
}
97+
</style>
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
<template>
2+
<div>
3+
<div class="flex items-center gap-4">
4+
<div
5+
class="h-4 w-4 rounded-full border-2 border-solid border-button-border"
6+
:class="recent || first ? 'bg-brand' : 'bg-button-bg'"
7+
/>
8+
<AutoLink
9+
:to="
10+
hasLink ? `/news/changelog/${entry.product}/${entry.version ?? entry.date.unix()}` : ''
11+
"
12+
class="flex items-center gap-2"
13+
:class="{ 'hover:underline': hasLink }"
14+
>
15+
<h2 class="flex items-center gap-2 m-0 text-xl font-extrabold text-contrast">
16+
<template v-if="showType">
17+
{{ formatMessage(messages[entry.product]) }}
18+
<div class="w-2 h-2 rounded-full bg-secondary" />
19+
</template>
20+
<span :class="{ 'text-primary font-bold': showType }">
21+
{{ entry.version ?? formattedDate }}
22+
</span>
23+
</h2>
24+
<div v-if="entry.version" v-tooltip="dateTooltip" :class="{ 'cursor-help': dateTooltip }">
25+
{{ formattedDate }}
26+
</div>
27+
</AutoLink>
28+
</div>
29+
<div class="ml-8 mt-3 rounded-2xl bg-bg-raised px-4 py-3">
30+
<div class="changelog-body" v-html="renderHighlightedString(entry.body)" />
31+
</div>
32+
</div>
33+
</template>
34+
35+
<script setup lang="ts">
36+
import type { VersionEntry } from '@modrinth/utils/changelog'
37+
import { renderHighlightedString } from '@modrinth/utils'
38+
import dayjs from 'dayjs'
39+
import { useVIntl, defineMessages } from '@vintl/vintl'
40+
import { computed, ref } from 'vue'
41+
import AutoLink from '../base/AutoLink.vue'
42+
43+
const { formatMessage } = useVIntl()
44+
45+
const props = withDefaults(
46+
defineProps<{
47+
entry: VersionEntry
48+
showType?: boolean
49+
first?: boolean
50+
hasLink?: boolean
51+
}>(),
52+
{
53+
showType: false,
54+
first: false,
55+
hasLink: false,
56+
},
57+
)
58+
59+
const currentDate = ref(dayjs())
60+
const recent = computed(() => props.entry.date.isAfter(currentDate.value.subtract(1, 'week')))
61+
const dateTooltip = computed(() => props.entry.date.format('MMMM D, YYYY [at] h:mm A'))
62+
const formattedDate = computed(() =>
63+
props.entry.version ? props.entry.date.fromNow() : props.entry.date.format('MMMM D, YYYY'),
64+
)
65+
66+
const messages = defineMessages({
67+
web: {
68+
id: 'changelog.product.web',
69+
defaultMessage: 'Website',
70+
},
71+
servers: {
72+
id: 'changelog.product.servers',
73+
defaultMessage: 'Servers',
74+
},
75+
app: {
76+
id: 'changelog.product.app',
77+
defaultMessage: 'App',
78+
},
79+
api: {
80+
id: 'changelog.product.api',
81+
defaultMessage: 'API',
82+
},
83+
})
84+
</script>
85+
<style lang="scss" scoped>
86+
:deep(.changelog-body) {
87+
line-height: 1.4;
88+
89+
h1,
90+
h2,
91+
h3,
92+
h4,
93+
h5,
94+
h6 {
95+
margin: 0;
96+
}
97+
98+
ul {
99+
padding-left: 1.25rem;
100+
margin: 0;
101+
}
102+
103+
a {
104+
color: var(--color-link);
105+
106+
&:hover,
107+
&:focus-visible {
108+
filter: brightness(1.2);
109+
text-decoration: underline;
110+
}
111+
}
112+
113+
code {
114+
background-color: var(--color-bg);
115+
font-size: var(--font-size-sm);
116+
padding: 0.125rem 0.25rem;
117+
border-radius: 4px;
118+
}
119+
120+
p {
121+
margin: 0;
122+
}
123+
124+
* + p {
125+
margin-top: 0.5rem;
126+
}
127+
128+
h3 + * {
129+
margin-top: 0.5rem;
130+
}
131+
132+
* + h3 {
133+
margin-top: 0.75rem;
134+
}
135+
136+
* + li {
137+
margin-top: 0.5rem;
138+
}
139+
140+
li ul li {
141+
margin-top: 0.25rem;
142+
}
143+
144+
img {
145+
max-width: 100%;
146+
border-radius: var(--radius-md);
147+
}
148+
}
149+
</style>

packages/ui/src/components/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ export { default as Toggle } from './base/Toggle.vue'
4040
export { default as AnimatedLogo } from './brand/AnimatedLogo.vue'
4141
export { default as TextLogo } from './brand/TextLogo.vue'
4242

43+
// Changelog
44+
export { default as ChangelogEntry } from './changelog/ChangelogEntry.vue'
45+
4346
// Charts
4447
export { default as Chart } from './chart/Chart.vue'
4548
export { default as CompactChart } from './chart/CompactChart.vue'

packages/ui/src/locales/en-US/index.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@
3232
"button.upload-image": {
3333
"defaultMessage": "Upload image"
3434
},
35+
"changelog.product.api": {
36+
"defaultMessage": "API"
37+
},
38+
"changelog.product.app": {
39+
"defaultMessage": "App"
40+
},
41+
"changelog.product.servers": {
42+
"defaultMessage": "Servers"
43+
},
44+
"changelog.product.web": {
45+
"defaultMessage": "Website"
46+
},
3547
"collection.label.private": {
3648
"defaultMessage": "Private"
3749
},

0 commit comments

Comments
 (0)