1+ <template >
2+ <UModal
3+ v-model:open =" isOpen"
4+ title =" Installation Instructions"
5+ description =" Run these two commands in order"
6+ >
7+ <template #body >
8+ <div class =" space-y-4" >
9+ <!-- Clipboard Not Supported Warning -->
10+ <UAlert
11+ v-if =" !isClipboardSupported"
12+ color =" warning"
13+ variant =" soft"
14+ icon =" i-heroicons-exclamation-triangle"
15+ title =" Clipboard Not Available"
16+ description =" Your browser doesn't support automatic copying. Please copy the commands manually using Ctrl+C (Cmd+C on Mac)."
17+ />
18+
19+ <!-- Step 1: Add Marketplace -->
20+ <div class =" space-y-2" >
21+ <div class =" flex items-center gap-2 text-sm font-medium" >
22+ <UBadge color =" primary" variant =" soft" size =" sm" >1</UBadge >
23+ <span >Add Marketplace</span >
24+ </div >
25+ <div class =" relative" >
26+ <pre class =" bg-default border border-default rounded-lg p-3 text-sm overflow-x-auto" ><code >{{ marketplaceCommand }}</code ></pre >
27+ <UButton
28+ @click =" copyCommand(marketplaceCommand, 'marketplace')"
29+ :variant =" copiedStates.marketplace ? 'soft' : 'ghost'"
30+ :color =" copiedStates.marketplace ? 'success' : 'neutral'"
31+ size =" xs"
32+ :icon =" copiedStates.marketplace ? 'i-heroicons-check' : 'i-heroicons-clipboard-document'"
33+ class =" absolute top-2 right-2"
34+ :title =" copiedStates.marketplace ? 'Copied!' : 'Copy command'"
35+ />
36+ </div >
37+ </div >
38+
39+ <!-- Step 2: Install Plugin -->
40+ <div class =" space-y-2" >
41+ <div class =" flex items-center gap-2 text-sm font-medium" >
42+ <UBadge color =" primary" variant =" soft" size =" sm" >2</UBadge >
43+ <span >Install Plugin</span >
44+ </div >
45+ <div class =" relative" >
46+ <pre class =" bg-default border border-default rounded-lg p-3 text-sm overflow-x-auto" ><code >{{ installCommand }}</code ></pre >
47+ <UButton
48+ @click =" copyCommand(installCommand, 'install')"
49+ :variant =" copiedStates.install ? 'soft' : 'ghost'"
50+ :color =" copiedStates.install ? 'success' : 'neutral'"
51+ size =" xs"
52+ :icon =" copiedStates.install ? 'i-heroicons-check' : 'i-heroicons-clipboard-document'"
53+ class =" absolute top-2 right-2"
54+ :title =" copiedStates.install ? 'Copied!' : 'Copy command'"
55+ />
56+ </div >
57+ </div >
58+
59+ <!-- Copy All Button -->
60+ <div class =" pt-2 border-t border-default" >
61+ <UButton
62+ @click =" copyAllCommands"
63+ :variant =" copiedStates.all ? 'soft' : 'outline'"
64+ :color =" copiedStates.all ? 'success' : 'primary'"
65+ block
66+ :icon =" copiedStates.all ? 'i-heroicons-check-circle' : 'i-heroicons-clipboard-document-list'"
67+ >
68+ {{ copiedStates.all ? 'All Commands Copied!' : 'Copy All Commands' }}
69+ </UButton >
70+ </div >
71+
72+ <!-- Manual Copy Tip -->
73+ <div class =" text-sm text-muted flex items-start gap-2" >
74+ <UIcon name =" i-heroicons-information-circle" class =" shrink-0 mt-0.5" />
75+ <span >Tip: You can also select and copy the commands manually (Ctrl+C / Cmd+C)</span >
76+ </div >
77+ </div >
78+ </template >
79+
80+ <template #footer =" { close } " >
81+ <div class =" flex justify-end" >
82+ <UButton
83+ label =" Close"
84+ color =" neutral"
85+ variant =" outline"
86+ @click =" close"
87+ />
88+ </div >
89+ </template >
90+ </UModal >
91+ </template >
92+
93+ <script setup lang="ts">
94+ interface Props {
95+ pluginName: string
96+ open? : boolean
97+ }
98+
99+ interface Emits {
100+ (e : ' update:open' , value : boolean ): void
101+ }
102+
103+ const props = withDefaults (defineProps <Props >(), {
104+ open: false
105+ })
106+
107+ const emit = defineEmits <Emits >()
108+
109+ const isOpen = computed ({
110+ get : () => props .open ,
111+ set : (value ) => emit (' update:open' , value )
112+ })
113+
114+ const marketplaceCommand = ' /plugin marketplace add pleaseai/claude-code-plugins'
115+ const installCommand = computed (() => ` /plugin install ${props .pluginName }@pleaseai ` )
116+
117+ const copiedStates = reactive ({
118+ marketplace: false ,
119+ install: false ,
120+ all: false
121+ })
122+
123+ // Check if clipboard API is supported
124+ const isClipboardSupported = computed (() => {
125+ return typeof navigator !== ' undefined' &&
126+ ' clipboard' in navigator &&
127+ typeof navigator .clipboard ?.writeText === ' function'
128+ })
129+
130+ const copyCommand = async (command : string , type : keyof typeof copiedStates ) => {
131+ try {
132+ // Check if clipboard API is available
133+ if (! isClipboardSupported .value ) {
134+ throw new Error (' Clipboard API not available' )
135+ }
136+
137+ await navigator .clipboard .writeText (command )
138+ copiedStates [type ] = true
139+
140+ setTimeout (() => {
141+ copiedStates [type ] = false
142+ }, 2000 )
143+ } catch (err ) {
144+ // Log error with full context for debugging
145+ console .error (' Failed to copy command to clipboard:' , {
146+ command ,
147+ type ,
148+ error: err instanceof Error ? err .message : String (err ),
149+ clipboardAvailable: isClipboardSupported .value ,
150+ isSecureContext: typeof window !== ' undefined' ? window .isSecureContext : false
151+ })
152+
153+ // Show user-facing error notification
154+ const toast = useToast ()
155+ toast .add ({
156+ title: ' Copy Failed' ,
157+ description: ' Could not copy to clipboard. Please copy the command manually.' ,
158+ color: ' error' ,
159+ icon: ' i-heroicons-exclamation-triangle'
160+ })
161+
162+ // DO NOT set success state on failure
163+ copiedStates [type ] = false
164+ }
165+ }
166+
167+ const copyAllCommands = async () => {
168+ const allCommands = ` ${marketplaceCommand }\n ${installCommand .value } `
169+
170+ try {
171+ // Check if clipboard API is available
172+ if (! isClipboardSupported .value ) {
173+ throw new Error (' Clipboard API not available' )
174+ }
175+
176+ await navigator .clipboard .writeText (allCommands )
177+ copiedStates .all = true
178+
179+ setTimeout (() => {
180+ copiedStates .all = false
181+ }, 2000 )
182+ } catch (err ) {
183+ // Log error with full context for debugging
184+ console .error (' Failed to copy all commands to clipboard:' , {
185+ marketplaceCommand ,
186+ installCommand: installCommand .value ,
187+ error: err instanceof Error ? err .message : String (err ),
188+ clipboardAvailable: isClipboardSupported .value ,
189+ isSecureContext: typeof window !== ' undefined' ? window .isSecureContext : false
190+ })
191+
192+ // Show user-facing error notification
193+ const toast = useToast ()
194+ toast .add ({
195+ title: ' Copy Failed' ,
196+ description: ' Could not copy commands to clipboard. Please copy them manually from above.' ,
197+ color: ' error' ,
198+ icon: ' i-heroicons-exclamation-triangle'
199+ })
200+
201+ // DO NOT set success state on failure
202+ copiedStates .all = false
203+ }
204+ }
205+
206+ // Reset copied states when modal is closed
207+ watch (isOpen , (newValue ) => {
208+ if (! newValue ) {
209+ copiedStates .marketplace = false
210+ copiedStates .install = false
211+ copiedStates .all = false
212+ }
213+ })
214+ </script >
0 commit comments