-
-
Notifications
You must be signed in to change notification settings - Fork 379
feat: adding gh changelog/releases to npmx #1233
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 65 commits
c0a23b4
4873b1c
92193b5
2a397bf
4cfbbfe
42802dc
9c130bd
a424459
e487292
6c7d817
ba81215
8d22cf2
f8b32d4
b345df1
f4c84ec
bc0fa2f
5e32d04
faf6b3b
14c50da
5cd51af
53cd46f
30d2663
8d5ee71
34faad0
55d45f3
9c36c60
95e3699
9f9195d
f2d5f2b
cab3cc6
342adca
1a8c342
fbbff33
08717bc
e07af6c
8225654
013654c
6cbe6e8
1d93405
f6ec86e
6198a71
2c057b9
ce34993
fd15403
04b2a99
746464f
750072c
6e6ee3a
0cab1e8
4de0348
5f4520f
81f68b6
6d0edfd
f9c0736
d48654a
a67f039
08828b8
5f1be8a
d71dfd9
503b4df
cfc8612
e2fc2d2
47431f5
c43514b
ef72002
886dd9f
40e8f2b
299b45c
2de6f9f
6915b33
89cae7e
bdcf1a7
133aa39
ce053be
8b5311c
eb96164
7c99b43
ddd4121
bb8b09f
fabc65d
443341b
c54c406
267880f
9427f0f
94f2571
43306af
41e395f
aaea9ee
327dd42
6d140ab
a9d09b3
96bf944
04b2c21
7da82ae
850e5c8
c1e91e9
609395e
314d49f
a9d9802
dcbbdac
f7377d5
732268d
0c441b2
435b569
f3c1759
c0f3c44
6757ae0
c77f335
fcacad8
21a7d5f
5180309
9d7aff1
2d9609c
5630b1c
a2e0c16
22ccc27
85e2cb0
172f758
acea1fd
246fc86
07c28a9
ee38fbd
f9475fd
b93bf44
78ba6f7
e70f094
ddddc63
9036e81
479a1c4
52151ba
2ff9374
b512aea
2a439b4
9caf56b
ee5e892
5e6ee46
3a86ab8
44fffff
4433716
22901bc
ef51c3d
200d861
d8f7ed0
66f2de2
9714514
3ff1c44
3cec9bd
edd7541
07eadba
d15e2e9
d35024f
41e68b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| <script setup lang="ts"> | ||
| import type { ReleaseData } from '~~/shared/types/changelog' | ||
|
|
||
| const { release } = defineProps<{ | ||
| release: ReleaseData | ||
| }>() | ||
| const formattedDate = computed(() => { | ||
| if (!release.publishedAt) { | ||
| return | ||
| } | ||
| return new Date(release.publishedAt).toISOString().split('T')[0] | ||
| }) | ||
|
|
||
| const cardId = computed(() => (release.publishedAt ? `date-${formattedDate.value}` : undefined)) | ||
|
|
||
| const navId = computed(() => `release-${encodeURI(release.title)}:`) | ||
|
|
||
| function navigateToTitle() { | ||
| navigateTo(`#${navId.value}`) | ||
| } | ||
| </script> | ||
| <template> | ||
| <section class="border border-border rounded-lg p-4 sm:p-6"> | ||
| <div class="flex gap-2 items-center" :id="cardId"> | ||
| <h2 class="text-1xl sm:text-2xl font-medium min-w-0 break-words py-2" :id="navId"> | ||
| <a | ||
| class="hover:decoration-accent hover:text-accent focus-visible:decoration-accent focus-visible:text-accent transition-colors duration-200" | ||
| :class="$style.linkTitle" | ||
| :href="`#${navId}`" | ||
| @click.prevent="navigateToTitle()" | ||
| > | ||
| {{ release.title }} | ||
| </a> | ||
| </h2> | ||
| <TagStatic v-if="release.prerelease" variant="default" class="h-unset"> | ||
| {{ $t('changelog.pre_release') }} | ||
| </TagStatic> | ||
| <TagStatic v-if="release.draft" variant="default" class="h-unset"> | ||
| {{ $t('changelog.draft') }} | ||
| </TagStatic> | ||
| <div class="flex-1" aria-hidden="true"></div> | ||
| <ReadmeTocDropdown | ||
| v-if="release?.toc && release.toc.length > 1" | ||
| :toc="release.toc" | ||
| class="ms-auto" | ||
| /> | ||
| <!-- :active-id="activeTocId" --> | ||
| </div> | ||
| <DateTime v-if="release.publishedAt" :datetime="release.publishedAt" date-style="medium" /> | ||
| <Readme v-if="release.html" :html="release.html"></Readme> | ||
| </section> | ||
| </template> | ||
|
|
||
| <style module> | ||
| .linkTitle::after { | ||
| content: '__'; | ||
| @apply inline i-lucide:link rtl-flip ms-1 opacity-0; | ||
WilcoSp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| .linkTitle:hover:after { | ||
WilcoSp marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| @apply opacity-100; | ||
| } | ||
| </style> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <script setup lang="ts"> | ||
| const { info, tpTarget } = defineProps<{ | ||
| info: ChangelogMarkdownInfo | ||
| tpTarget?: HTMLElement | null | ||
| }>() | ||
|
|
||
| const { data } = useLazyFetch(() => `/api/changelog/md/${info.provider}/${info.repo}/${info.path}`) | ||
| </script> | ||
| <template> | ||
| <Teleport v-if="data?.toc && data.toc.length > 1 && !!tpTarget" :to="tpTarget"> | ||
| <ReadmeTocDropdown :toc="data.toc" class="justify-self-end" /> | ||
| </Teleport> | ||
| <Readme v-if="data?.html" :html="data.html"></Readme> | ||
| </template> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| <script setup lang="ts"> | ||
| const { info, requestedDate } = defineProps<{ | ||
| info: ChangelogReleaseInfo | ||
| requestedDate?: string | ||
| }>() | ||
|
|
||
| const { data: releases } = useFetch<ReleaseData[]>( | ||
| () => `/api/changelog/releases/${info.provider}/${info.repo}`, | ||
| ) | ||
|
|
||
| const route = useRoute() | ||
| const router = useRouter() | ||
|
|
||
| watch( | ||
| [() => route.hash, () => requestedDate, releases], | ||
| ([hash, date, r]) => { | ||
| if (hash || !date || !r) { | ||
| return | ||
| } | ||
|
|
||
| router.push(`#date-${date}`) | ||
| }, | ||
| { | ||
| immediate: true, | ||
| }, | ||
| ) | ||
| </script> | ||
| <template> | ||
| <div class="flex flex-col gap-2 py-3" v-if="releases"> | ||
| <ClientOnly> | ||
| <ChangelogCard v-for="release of releases" :release :key="release.id" /> | ||
| </ClientOnly> | ||
| </div> | ||
| </template> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -150,7 +150,7 @@ function handleClick(event: MouseEvent) { | |
| @apply inline i-lucide:external-link rtl-flip ms-1 opacity-50; | ||
| } | ||
|
|
||
| .readme :deep(:is(h1, h2, h3, h4, h5, h6) a[href^='#']::after) { | ||
| .readme :deep(:is(h1, h2, h3, h4, h5, h6) a[href^='#']:not([content-none])::after) { | ||
|
||
| /* I don't know what kind of sorcery this is, but it ensures this icon can't wrap to a new line on its own. */ | ||
| content: '__'; | ||
| @apply inline i-lucide:link rtl-flip ms-1 opacity-0; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import type { ChangelogInfo } from '~~/shared/types/changelog' | ||
|
|
||
| export function usePackageChangelog( | ||
| packageName: MaybeRefOrGetter<string>, | ||
| version?: MaybeRefOrGetter<string | null | undefined>, | ||
| ) { | ||
| return useLazyFetch<ChangelogInfo>(() => { | ||
| const name = toValue(packageName) | ||
| const ver = toValue(version) | ||
| const base = `/api/changelog/info/${name}` | ||
| return ver ? `${base}/v/${ver}` : base | ||
| }) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import type { ProviderId } from '#imports' | ||
| import type { IconClass } from '~/types/icon' | ||
| import { computed, toValue } from 'vue' | ||
|
|
||
| const PROVIDER_ICONS: Record<ProviderId, IconClass> = { | ||
| github: 'i-simple-icons:github', | ||
| gitlab: 'i-simple-icons:gitlab', | ||
| bitbucket: 'i-simple-icons:bitbucket', | ||
| codeberg: 'i-simple-icons:codeberg', | ||
| gitea: 'i-simple-icons:gitea', | ||
| forgejo: 'i-simple-icons:forgejo', | ||
| gitee: 'i-simple-icons:gitee', | ||
| sourcehut: 'i-simple-icons:sourcehut', | ||
| tangled: 'i-custom:tangled', | ||
| radicle: 'i-lucide:network', // Radicle is a P2P network, using network icon | ||
| } | ||
|
|
||
| export function useProviderIcon(provider: MaybeRefOrGetter<ProviderId | null | undefined>) { | ||
| return computed((): IconClass => { | ||
| const uProvider = toValue(provider) | ||
| if (!uProvider) return 'i-simple-icons:github' | ||
| return PROVIDER_ICONS[uProvider] ?? 'i-lucide:code' | ||
| }) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| <script setup lang="ts"> | ||
| import { useProviderIcon } from '~/composables/useProviderIcon' | ||
|
|
||
| definePageMeta({ | ||
| name: 'changes', | ||
| path: '/package-changes/:path+', | ||
| alias: ['/package/changes/:path+', '/changes/:path+'], | ||
| scrollMargin: 150, | ||
| }) | ||
|
|
||
| /// routing | ||
|
|
||
| const route = useRoute('changes') | ||
| const router = useRouter() | ||
| // Parse package name, version, and file path from URL | ||
| // Patterns: | ||
| // /code/nuxt/v/4.2.0 → packageName: "nuxt", version: "4.2.0", filePath: null (show tree) | ||
| // /code/nuxt/v/4.2.0/src/index.ts → packageName: "nuxt", version: "4.2.0", filePath: "src/index.ts" | ||
| // /code/@nuxt/kit/v/1.0.0 → packageName: "@nuxt/kit", version: "1.0.0", filePath: null | ||
| const parsedRoute = computed(() => { | ||
| const segments = route.params.path | ||
|
|
||
| // Find the /v/ separator for version | ||
| const vIndex = segments.indexOf('v') | ||
| if (vIndex === -1 || vIndex >= segments.length - 1) { | ||
| // No version specified - redirect or error | ||
| return { | ||
| packageName: segments.join('/'), | ||
| version: null as string | null, | ||
| filePath: null as string | null, | ||
| } | ||
| } | ||
|
|
||
| const packageName = segments.slice(0, vIndex).join('/') | ||
| const afterVersion = segments.slice(vIndex + 1) | ||
| const version = afterVersion[0] ?? null | ||
| const filePath = afterVersion.length > 1 ? afterVersion.slice(1).join('/') : null | ||
|
|
||
| return { packageName, version, filePath } | ||
| }) | ||
|
|
||
| const packageName = computed(() => parsedRoute.value.packageName) | ||
| const version = computed(() => parsedRoute.value.version) | ||
| const filePath = computed(() => parsedRoute.value.filePath?.replace(/\/$/, '')) | ||
|
|
||
| const { data: pkg } = usePackage(packageName, version) | ||
|
|
||
| const versionUrlPattern = computed(() => { | ||
| const base = `/package-changes/${packageName.value}/v/{version}` | ||
| return filePath.value ? `${base}/${filePath.value}` : base | ||
| }) | ||
|
|
||
| const latestVersion = computed(() => pkg.value?.['dist-tags']?.latest ?? null) | ||
|
|
||
| watch( | ||
| [version, latestVersion, packageName], | ||
| ([v, latest, name]) => { | ||
| if (!v && latest && name) { | ||
| const pathSegments = [...name.split('/'), 'v', latest] | ||
| router.replace({ name: 'changes', params: { path: pathSegments as [string, ...string[]] } }) | ||
| } | ||
| }, | ||
| { immediate: true }, | ||
| ) | ||
|
|
||
| // getting info | ||
| const { data: changelog, pending } = usePackageChangelog(packageName, version) | ||
|
|
||
| const repoProviderIcon = useProviderIcon(() => changelog.value?.provider) | ||
| const tptoc = useTemplateRef('tptoc') | ||
|
|
||
| const versionDate = computed(() => { | ||
| if (!version.value) { | ||
| return | ||
| } | ||
| const time = pkg.value?.time[version.value] | ||
| if (time) { | ||
| return new Date(time).toISOString().split('T')[0] | ||
| } | ||
| }) | ||
|
|
||
| // defineOgImageComponent('Default', { | ||
| // title: () => `${pkg.value?.name ?? 'Package'} - Changelogs`, | ||
| // description: () => pkg.value?.license ?? '', | ||
| // primaryColor: '#60a5fa', | ||
| // }) | ||
| </script> | ||
| <template> | ||
| <main class="flex-1 flex flex-col"> | ||
| <header class="border-b border-border bg-bg sticky top-14 z-20"> | ||
| <div class="container pt-4 pb-3"> | ||
| <div class="flex items-center gap-3 mb-3 flex-wrap min-w-0"> | ||
| <h1 | ||
| class="font-mono text-lg sm:text-xl font-semibold text-fg hover:text-fg-muted transition-colors truncate" | ||
| > | ||
| <NuxtLink v-if="packageName" :to="packageRoute(packageName, version)"> | ||
| {{ packageName }} | ||
| </NuxtLink> | ||
| </h1> | ||
|
|
||
| <VersionSelector | ||
| v-if="(version || latestVersion) && pkg?.versions && pkg?.['dist-tags']" | ||
| :package-name="packageName" | ||
| :current-version="version ?? latestVersion!" | ||
| :versions="pkg.versions" | ||
| :dist-tags="pkg['dist-tags']" | ||
| :url-pattern="versionUrlPattern" | ||
| /> | ||
| <div class="flex-1"></div> | ||
| <LinkBase | ||
| v-if="changelog?.link" | ||
| :to="changelog?.link" | ||
| :classicon="repoProviderIcon" | ||
| :title="$t('common.view_on', { site: changelog.provider })" | ||
| > | ||
| {{ changelog.provider }} | ||
| </LinkBase> | ||
|
|
||
| <div v-if="changelog?.type == 'md'" ref="tptoc" class="w-14 h-8"> | ||
| <!-- prevents layout shift while loading --> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </header> | ||
| <section class="container w-full" v-if="!pending"> | ||
| <LazyChangelogReleases | ||
| v-if="changelog?.type == 'release'" | ||
| :info="changelog" | ||
| :requestedDate="versionDate" | ||
| /> | ||
| <LazyChangelogMarkdown | ||
| v-else-if="changelog?.type == 'md'" | ||
| :info="changelog" | ||
| :tpTarget="tptoc" | ||
| /> | ||
| <p class="mt-5" v-else>{{ $t('changelog.no_logs') }}</p> | ||
| </section> | ||
| </main> | ||
| </template> |
Uh oh!
There was an error while loading. Please reload this page.