From db85c7b441b311ec36ac532050bf06b2d327081c Mon Sep 17 00:00:00 2001 From: MatteoGabriele Date: Thu, 16 Apr 2026 18:55:46 +0200 Subject: [PATCH 1/6] fix: resolve readme copy functionality in Safari resolves #2151 --- app/pages/package/[[org]]/[name].vue | 1 + 1 file changed, 1 insertion(+) diff --git a/app/pages/package/[[org]]/[name].vue b/app/pages/package/[[org]]/[name].vue index b9740f799f..ac07e360da 100644 --- a/app/pages/package/[[org]]/[name].vue +++ b/app/pages/package/[[org]]/[name].vue @@ -99,6 +99,7 @@ const { const { copied: copiedReadme, copy: copyReadme } = useClipboard({ source: () => '', copiedDuring: 2000, + legacy: true, }) function prefetchReadmeMarkdown() { From f3f11b176e5be8bb107b0fe237ed5b2c2358588f Mon Sep 17 00:00:00 2001 From: MatteoGabriele Date: Thu, 16 Apr 2026 23:26:18 +0200 Subject: [PATCH 2/6] feat: use custom async clipboard --- app/composables/useClipboardAsync.ts | 37 ++++++++++++++++++++++++++++ app/pages/package/[[org]]/[name].vue | 25 ++++++++----------- 2 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 app/composables/useClipboardAsync.ts diff --git a/app/composables/useClipboardAsync.ts b/app/composables/useClipboardAsync.ts new file mode 100644 index 0000000000..57721f6d8d --- /dev/null +++ b/app/composables/useClipboardAsync.ts @@ -0,0 +1,37 @@ +import type { ShallowRef } from 'vue' + +type UseClipboardAsyncReturn = { + copy: () => void + copied: ShallowRef +} + +type UseClipboardAsyncOptions = { + copiedDuring: number +} + +export default function useClipboardAsync( + fn: () => Promise, + options?: UseClipboardAsyncOptions, +): UseClipboardAsyncReturn { + const copied = shallowRef(false) + const timeout = useTimeoutFn(() => (copied.value = false), options?.copiedDuring ?? 0, { + immediate: false, + }) + + function copy() { + const asyncClipboard = new ClipboardItem({ + 'text/plain': fn().then(text => { + return new Blob([text], { type: 'text/plain' }) + }), + }) + + copied.value = true + navigator.clipboard.write([asyncClipboard]) + timeout.start() + } + + return { + copy, + copied, + } +} diff --git a/app/pages/package/[[org]]/[name].vue b/app/pages/package/[[org]]/[name].vue index ac07e360da..a7cc7c4bd4 100644 --- a/app/pages/package/[[org]]/[name].vue +++ b/app/pages/package/[[org]]/[name].vue @@ -96,11 +96,15 @@ const { ) //copy README file as Markdown -const { copied: copiedReadme, copy: copyReadme } = useClipboard({ - source: () => '', - copiedDuring: 2000, - legacy: true, -}) +const { copied: copiedReadme, copy: copyReadme } = useClipboardAsync( + async () => { + await fetchReadmeMarkdown() + return readmeMarkdownData.value?.markdown ?? '' + }, + { + copiedDuring: 2000, + }, +) function prefetchReadmeMarkdown() { if (readmeMarkdownStatus.value === 'idle') { @@ -108,15 +112,6 @@ function prefetchReadmeMarkdown() { } } -async function copyReadmeHandler() { - await fetchReadmeMarkdown() - - const markdown = readmeMarkdownData.value?.markdown - if (!markdown) return - - await copyReadme(markdown) -} - // Track active TOC item based on scroll position const tocItems = computed(() => readmeData.value?.toc ?? []) const { activeId: activeTocId } = useActiveTocItem(tocItems) @@ -1020,7 +1015,7 @@ const showSkeleton = shallowRef(false)