Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
@jsr:registry=https://npm.jsr.io
# vite-plus preview build registry bridge (auto-added by vp)
registry=https://registry-bridge.viteplus.dev/
15 changes: 15 additions & 0 deletions app/components/Changelog/Card.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import type { IconClass } from '~/types'
import type { ReleaseData } from '~~/shared/types/changelog'
import { slugify } from '~~/shared/utils/html'

Expand All @@ -19,6 +20,14 @@ const navId = computed(() => `release-${slugify(release.title)}`)
function navigateToTitle() {
navigateTo(`#${navId.value}`)
}

const { providerIcon, viewOnProvider } = inject<{
providerIcon: MaybeRef<IconClass>
viewOnProvider: MaybeRef<string>
}>('changelog-provider-linkattr', {
providerIcon: 'i-lucide:code',
viewOnProvider: computed(() => $t('common.view_on.git_repo')),
})
Comment on lines +24 to +30

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using $t directly inside the <script setup> block will throw a ReferenceError: $t is not defined at runtime because global template helpers are not automatically bound to the script scope. Instead, use the useI18n composable to retrieve the t function.

const { t } = useI18n()

const { providerIcon, viewOnProvider } = inject<{
  providerIcon: MaybeRef<IconClass>
  viewOnProvider: MaybeRef<string>
}>('changelog-provider-linkattr', {
  providerIcon: 'i-lucide:code',
  viewOnProvider: computed(() => t('common.view_on.git_repo')),
})

</script>
<template>
<section
Expand Down Expand Up @@ -48,6 +57,12 @@ function navigateToTitle() {
{{ $t('changelog.draft') }}
</TagStatic>
<div class="flex-1" aria-hidden="true"></div>
<LinkBase
:classicon="providerIcon"
:title="viewOnProvider"
:to="release.link"
class="size-[0.9em]"
/>
<ReadmeTocDropdown
v-if="release?.toc && release.toc.length > 1"
:toc="release.toc"
Expand Down
18 changes: 12 additions & 6 deletions app/components/Changelog/Markdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const { info, goToVersion, tpTarget, resolveVersionPending } = defineProps<{

const route = useRoute()

const { data, error } = await useLazyFetch(
const { data, error, pending } = useLazyFetch(
() => `/api/changelog/md/${info.provider}/${info.repo}/${info.path}`,
)

Expand All @@ -34,8 +34,11 @@ if (import.meta.client) {
}
// lc = lower case
const lcRequestedVersion = goToVersion.toLowerCase()
const isMatching = createHeadingVersionMatcher(lcRequestedVersion)

for (const item of toc) {
if (item.text.toLowerCase().includes(lcRequestedVersion)) {
const text = item.text.toLowerCase()
if (text.toLowerCase().includes(lcRequestedVersion) && isMatching(text)) {
navigateTo(`#${item.id}`)
return
}
Expand All @@ -49,9 +52,12 @@ if (import.meta.client) {
}
</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" class="pt-4"></Readme>
<ChangelogSkeleton v-if="pending" />
<template v-else-if="data?.html">
<Teleport v-if="data?.toc && data.toc.length > 1 && !!tpTarget" :to="tpTarget">
<ReadmeTocDropdown :toc="data.toc" class="justify-self-end" />
</Teleport>
<Readme :html="data.html" class="pt-4"></Readme>
</template>
<slot v-else-if="error" name="error"></slot>
</template>
13 changes: 8 additions & 5 deletions app/components/Changelog/Releases.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ const { info, requestedDate, goToVersion, resolveVersionPending } = defineProps<
resolveVersionPending?: boolean
}>()

const { data: releases, error } = await useLazyFetch<ReleaseData[]>(
() => `/api/changelog/releases/${info.provider}/${info.repo}`,
)
const {
data: releases,
error,
pending,
} = useLazyFetch<ReleaseData[]>(() => `/api/changelog/releases/${info.provider}/${info.repo}`)

const route = useRoute()

Expand All @@ -23,7 +25,7 @@ const matchingDateReleases = computed(() => {
if (!release.publishedAt) {
return
}
return requestedDate === toIsoDate(new Date(release.publishedAt))
return requestedDate === release.publishedAt.slice(0, 10)
})
})

Expand Down Expand Up @@ -75,7 +77,8 @@ if (import.meta.client) {
prefetchComponents('ChangelogCard')
</script>
<template>
<div class="flex flex-col gap-2 py-3" v-if="releases">
<ChangelogSkeleton v-if="pending" />
<div class="flex flex-col gap-2 py-3" v-else-if="releases">
<ChangelogCard v-for="release of releases" :release :key="release.id" />
</div>
<slot v-else-if="error" name="error"></slot>
Expand Down
11 changes: 11 additions & 0 deletions app/components/Changelog/Skeleton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<template>
<SkeletonBlock class="h-8 w-40 rounded" />
<ul class="ms-3 list-disc my-4 ps-6 marker:color-[--border-hover]">
<li class="mb-1" v-for="_n in 5">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Vue requires a :key attribute on v-for elements to track identity and prevent rendering issues or compiler/ESLint warnings.

    <li v-for="_n in 5" :key="_n" class="mb-1">

<SkeletonBlock class="h-7 w-full max-w-2xl rounded" />
</li>
</ul>

<SkeletonBlock class="h-5 w-5/6 max-w-2xl rounded" />
<SkeletonBlock class="h-5 w-3/4 max-w-2xl rounded" />
</template>
18 changes: 1 addition & 17 deletions app/components/OgImage/Package.takumi.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,6 @@ import { joinURL } from 'ufo'
import { useCharts } from '~/composables/useCharts'
import { createSmoothPath } from 'vue-data-ui/utils'

const REPO_PROVIDER_ICONS: Record<string, string> = {
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',
}

function sortJsDelivrNodes(nodes: JsDelivrFileNode[]) {
return [...nodes].sort((a, b) => {
if (a.type !== b.type) return a.type === 'directory' ? -1 : 1
Expand Down Expand Up @@ -76,10 +63,7 @@ const repositoryUrl = computed(() => {
})

const { repoRef, stars, refresh: refreshRepoMeta } = useRepoMeta(repositoryUrl)
const repoProviderIcon = computed(() => {
const provider = repoRef.value?.provider
return provider ? (REPO_PROVIDER_ICONS[provider] ?? 'i-lucide:code') : 'i-lucide:code'
})
const repoProviderIcon = useProviderIcon(() => repoRef.value?.provider, 'i-lucide:code')

const formattedStars = computed(() => (stars.value > 0 ? compactFormat.format(stars.value) : ''))

Expand Down
20 changes: 1 addition & 19 deletions app/components/Package/ExternalLinks.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script setup lang="ts">
import type { IconClass } from '~/types'
import type { CommandPaletteContextCommandInput } from '~/types/command-palette'

const props = defineProps<{
Expand All @@ -24,24 +23,7 @@ const homepageUrl = computed(() => {
return homepage
})

const PROVIDER_ICONS: Record<string, 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
}

const repoProviderIcon = computed((): IconClass => {
const provider = repoRef.value?.provider
if (!provider) return 'i-simple-icons:github'
return PROVIDER_ICONS[provider] ?? 'i-lucide:code'
})
const repoProviderIcon = useProviderIcon(() => repoRef.value?.provider)

const repositoryCommandLabel = computed(() => {
if (!repoRef.value) {
Expand Down
10 changes: 6 additions & 4 deletions app/components/Package/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import type { RouteLocationRaw } from 'vue-router'
import type { CommandPaletteContextCommandInput } from '~/types/command-palette'
import { SCROLL_TO_TOP_THRESHOLD } from '~/composables/useScrollToTop'
import { usePackageChangelog } from '~/composables/usePackageChangelog'

const props = defineProps<{
pkg?: Pick<SlimPackument, 'name' | 'versions' | 'dist-tags'> | null
Expand Down Expand Up @@ -163,12 +162,15 @@ const diffLink = computed((): RouteLocationRaw | null => {
return diffRoute(props.pkg.name, props.resolvedVersion, props.latestVersion.version)
})

const { data: changelog } = usePackageChangelog(packageName, requestedVersion)

const hasChangelog = usePackageHasChangelog(
packageName,
() => requestedVersion.value || props.resolvedVersion,
true,
)
const changelogLink = computed((): RouteLocationRaw | null => {
if (
// either changelog.value is available or current page is the changelog
!(changelog.value || props.page == 'changelog') ||
!(hasChangelog.value || props.page == 'changelog') ||
props.pkg == null ||
props.resolvedVersion == null
) {
Expand Down
8 changes: 2 additions & 6 deletions app/composables/useCommandPalettePackageCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,7 @@ export function useCommandPalettePackageCommands(
.includes(packageName)
}

const { data: changelog } = usePackageChangelog(
() => toValue(context)?.packageName,
() => toValue(context)?.resolvedVersion,
)
const hasChangelog = usePackageHasChangelogFromState()

useCommandPaletteContextCommands(
computed((): CommandPaletteContextCommandInput[] => {
Expand Down Expand Up @@ -119,8 +116,7 @@ export function useCommandPalettePackageCommands(
},
]

const uChangelog = changelog.value
if (uChangelog?.type === 'md' || uChangelog?.type === 'release') {
if (hasChangelog.value) {
commands.push({
id: 'package-changelog',
group: 'package',
Expand Down
29 changes: 27 additions & 2 deletions app/composables/usePackageChangelog.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,38 @@
import type { ChangelogInfo } from '~~/shared/types/changelog'

const KEY = 'changelog:has:info'

export function usePackageChangelog(
packageName: MaybeRefOrGetter<string | null | undefined>,
packageName: MaybeRefOrGetter<string>,
version?: MaybeRefOrGetter<string | null | undefined>,
) {
return useLazyFetch<ChangelogInfo | null>(() => {
const name = toValue(packageName)
if (!name) return 'data:application/json,null' // returns null
const ver = toValue(version)
return `/api/changelog/info/${name}/v/${ver || 'latest'}`
})
}

/**
* check whether the current package & version has changelogs
* @param setState with `useState` also set the state of `changelog:info` (currently only for packageHeader)
*/
export function usePackageHasChangelog(
packageName: MaybeRefOrGetter<string>,
version?: MaybeRefOrGetter<string | null | undefined>,
setState?: boolean,
) {
Comment on lines 5 to +24

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When props.pkg is initially null or loading, packageName can evaluate to an empty string (''). Without a guard, this triggers broken API requests to /api/changelog/info//v/latest. Restoring the !name guard and allowing nullable types prevents these invalid requests.

export function usePackageChangelog(
  packageName: MaybeRefOrGetter<string | null | undefined>,
  version?: MaybeRefOrGetter<string | null | undefined>,
) {
  return useLazyFetch<ChangelogInfo | null>(() => {
    const name = toValue(packageName)
    if (!name) return null
    const ver = toValue(version)
    return '/api/changelog/info/' + name + '/v/' + (ver || 'latest')
  })
}

/**
 * check whether the current package & version has changelogs
 * @param setState with `useState` also set the state of `changelog:info` (currently only for packageHeader)
 */
export function usePackageHasChangelog(
  packageName: MaybeRefOrGetter<string | null | undefined>,
  version?: MaybeRefOrGetter<string | null | undefined>,
  setState?: boolean,
) {

const { data } = usePackageChangelog(packageName, version)
const hasChangelog = computed(() => data.value?.type == 'md' || data.value?.type == 'release')
if (setState) {
useState(KEY, () => hasChangelog)
}
return hasChangelog
}

/**
* get whether current package has changelog via `useState` (is needed for command pallette)
*/
export function usePackageHasChangelogFromState() {
return useState<boolean>(KEY)
}
7 changes: 5 additions & 2 deletions app/composables/useProviderIcon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ const PROVIDER_ICONS: Record<ProviderId, IconClass> = {
radicle: 'i-lucide:network', // Radicle is a P2P network, using network icon
}

export function useProviderIcon(provider: MaybeRefOrGetter<ProviderId | null | undefined>) {
export function useProviderIcon(
provider: MaybeRefOrGetter<ProviderId | null | undefined>,
fallbackIcon: IconClass = 'i-simple-icons:github',
) {
return computed((): IconClass => {
const uProvider = toValue(provider)
if (!uProvider) return 'i-simple-icons:github'
if (!uProvider) return fallbackIcon
return PROVIDER_ICONS[uProvider] ?? 'i-lucide:code'
})
}
Loading
Loading