Skip to content
Merged
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
61 changes: 43 additions & 18 deletions app/components/Readme.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,66 @@ defineProps<{
html: string
}>()

const router = useRouter()
const { copy } = useClipboard()

const handleCopy = async (e: MouseEvent) => {
const target = (e.target as HTMLElement).closest('[data-copy]')
// Combined click handler for:
// 1. Intercepting npmjs.com links to route internally
// 2. Copy button functionality for code blocks
function handleClick(event: MouseEvent) {
const target = event.target as HTMLElement | undefined
if (!target) return

const wrapper = target.closest('.readme-code-block')
if (!wrapper) return
// Handle copy button clicks
const copyTarget = target.closest('[data-copy]')
if (copyTarget) {
const wrapper = copyTarget.closest('.readme-code-block')
if (!wrapper) return

const pre = wrapper.querySelector('pre')
if (!pre?.textContent) return
const pre = wrapper.querySelector('pre')
if (!pre?.textContent) return

await copy(pre.textContent)
copy(pre.textContent)

const icon = target.querySelector('span')
if (!icon) return
const icon = copyTarget.querySelector('span')
if (!icon) return

const originalIcon = 'i-carbon:copy'
const successIcon = 'i-carbon:checkmark'
const originalIcon = 'i-carbon:copy'
const successIcon = 'i-carbon:checkmark'

icon.classList.remove(originalIcon)
icon.classList.add(successIcon)
icon.classList.remove(originalIcon)
icon.classList.add(successIcon)

setTimeout(() => {
icon.classList.remove(successIcon)
icon.classList.add(originalIcon)
}, 2000)
setTimeout(() => {
icon.classList.remove(successIcon)
icon.classList.add(originalIcon)
}, 2000)
return
}

// Handle npmjs.com link clicks - route internally
const anchor = target.closest('a')
if (!anchor) return

const href = anchor.getAttribute('href')
if (!href) return

const match = href.match(/^(?:https?:\/\/)?(?:www\.)?npmjs\.(?:com|org)(\/.+)$/)
if (!match || !match[1]) return

const route = router.resolve(match[1])
if (route) {
event.preventDefault()
router.push(route)
}
}
</script>

<template>
<article
class="readme prose prose-invert max-w-[70ch] lg:max-w-none"
v-html="html"
@click="handleCopy"
@click="handleClick"
/>
</template>

Expand Down
20 changes: 1 addition & 19 deletions app/pages/package/[...package].vue
Original file line number Diff line number Diff line change
Expand Up @@ -385,24 +385,6 @@ defineOgImageComponent('Package', {
stars: () => stars.value ?? 0,
primaryColor: '#60a5fa',
})

// We're using only @click because it catches touch events and enter hits
function handleClick(event: MouseEvent) {
const target = (event?.target as HTMLElement | undefined)?.closest('a')
if (!target) return

const href = target.getAttribute('href')
if (!href) return

const match = href.match(/^(?:https?:\/\/)?(?:www\.)?npmjs\.(?:com|org)(\/.+)$/)
if (!match || !match[1]) return

const route = router.resolve(match[1])
if (route) {
event.preventDefault()
router.push(route)
}
}
</script>

<template>
Expand Down Expand Up @@ -968,7 +950,7 @@ function handleClick(event: MouseEvent) {
</a>
</h2>
<!-- eslint-disable vue/no-v-html -- HTML is sanitized server-side -->
<Readme v-if="readmeData?.html" :html="readmeData.html" @click="handleClick" />
<Readme v-if="readmeData?.html" :html="readmeData.html" />
<p v-else class="text-fg-subtle italic">
{{ $t('package.readme.no_readme') }}
<a
Expand Down
Loading