|
| 1 | +<template> |
| 2 | + <div |
| 3 | + class="inline-flex items-stretch rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 shadow-sm overflow-hidden" |
| 4 | + > |
| 5 | + <button |
| 6 | + type="button" |
| 7 | + class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500" |
| 8 | + :aria-label="copied ? 'Copied' : 'Copy page as Markdown'" |
| 9 | + @click="copyPage" |
| 10 | + > |
| 11 | + <UIcon |
| 12 | + :name="copied ? 'i-lucide-check' : 'i-lucide-copy'" |
| 13 | + class="w-4 h-4 shrink-0" |
| 14 | + :class="copied ? 'text-green-600 dark:text-green-400' : ''" |
| 15 | + /> |
| 16 | + <span>{{ copied ? 'Copied' : 'Copy page' }}</span> |
| 17 | + </button> |
| 18 | + |
| 19 | + <UDropdownMenu |
| 20 | + :items="items" |
| 21 | + :content="{ align: 'end', side: 'bottom', sideOffset: 6 }" |
| 22 | + :ui="{ content: 'w-72' }" |
| 23 | + > |
| 24 | + <button |
| 25 | + type="button" |
| 26 | + class="inline-flex items-center px-2 border-l border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-200 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500" |
| 27 | + aria-label="More copy options" |
| 28 | + > |
| 29 | + <UIcon name="i-lucide-chevron-down" class="w-4 h-4" /> |
| 30 | + </button> |
| 31 | + |
| 32 | + <template #item-label="{ item }"> |
| 33 | + <div class="flex flex-col text-left"> |
| 34 | + <span class="text-sm font-medium text-gray-900 dark:text-white">{{ item.label }}</span> |
| 35 | + <span v-if="item.description" class="text-xs text-gray-500 dark:text-gray-400"> |
| 36 | + {{ item.description }} |
| 37 | + </span> |
| 38 | + </div> |
| 39 | + </template> |
| 40 | + </UDropdownMenu> |
| 41 | + </div> |
| 42 | +</template> |
| 43 | + |
| 44 | +<script setup lang="ts"> |
| 45 | +import { computed } from 'vue' |
| 46 | +
|
| 47 | +const route = useRoute() |
| 48 | +const config = useRuntimeConfig() |
| 49 | +const { copied, copyToClipboard } = useClipboardCopy() |
| 50 | +
|
| 51 | +const siteUrl = config.public.siteUrl as string |
| 52 | +
|
| 53 | +const mdPath = computed(() => `${route.path.replace(/\/$/, '')}.md`) |
| 54 | +const absoluteMdUrl = computed(() => `${siteUrl}${mdPath.value}`) |
| 55 | +
|
| 56 | +const llmPrompt = computed( |
| 57 | + () => `Read from ${absoluteMdUrl.value} so I can ask questions about it.` |
| 58 | +) |
| 59 | +
|
| 60 | +const chatGptUrl = computed( |
| 61 | + () => `https://chatgpt.com/?hints=search&q=${encodeURIComponent(llmPrompt.value)}` |
| 62 | +) |
| 63 | +
|
| 64 | +const claudeUrl = computed( |
| 65 | + () => `https://claude.ai/new?q=${encodeURIComponent(llmPrompt.value)}` |
| 66 | +) |
| 67 | +
|
| 68 | +async function fetchMarkdown(): Promise<string> { |
| 69 | + try { |
| 70 | + const response = await fetch(mdPath.value) |
| 71 | +
|
| 72 | + if (response.ok) { |
| 73 | + const text = await response.text() |
| 74 | +
|
| 75 | + if (text && !text.trimStart().startsWith('<')) { |
| 76 | + return text |
| 77 | + } |
| 78 | + } |
| 79 | + } catch { |
| 80 | + // Fall through to URL fallback (e.g. .md twin missing in dev) |
| 81 | + } |
| 82 | +
|
| 83 | + return absoluteMdUrl.value |
| 84 | +} |
| 85 | +
|
| 86 | +async function copyPage(): Promise<void> { |
| 87 | + const markdown = await fetchMarkdown() |
| 88 | + await copyToClipboard(markdown) |
| 89 | +} |
| 90 | +
|
| 91 | +const items = computed(() => [ |
| 92 | + [ |
| 93 | + { |
| 94 | + label: 'Copy page', |
| 95 | + description: 'Copy page as Markdown for LLMs', |
| 96 | + icon: 'i-lucide-file-text', |
| 97 | + onSelect: copyPage |
| 98 | + }, |
| 99 | + { |
| 100 | + label: 'View as Markdown', |
| 101 | + description: 'Open the raw Markdown in a new tab', |
| 102 | + icon: 'i-lucide-file-code', |
| 103 | + to: mdPath.value, |
| 104 | + target: '_blank' |
| 105 | + } |
| 106 | + ], |
| 107 | + [ |
| 108 | + { |
| 109 | + label: 'Open in ChatGPT', |
| 110 | + description: 'Ask ChatGPT about this page', |
| 111 | + icon: 'i-lucide-bot', |
| 112 | + to: chatGptUrl.value, |
| 113 | + target: '_blank' |
| 114 | + }, |
| 115 | + { |
| 116 | + label: 'Open in Claude', |
| 117 | + description: 'Ask Claude about this page', |
| 118 | + icon: 'i-lucide-sparkles', |
| 119 | + to: claudeUrl.value, |
| 120 | + target: '_blank' |
| 121 | + } |
| 122 | + ] |
| 123 | +]) |
| 124 | +</script> |
0 commit comments