@@ -4,7 +4,7 @@ import { join } from "@tauri-apps/api/path";
4
4
import { message } from "@tauri-apps/plugin-dialog" ;
5
5
import { fetch as tauriFetch } from "@tauri-apps/plugin-http" ;
6
6
import { openPath , openUrl } from "@tauri-apps/plugin-opener" ;
7
- import { BookText , ChevronDown , ChevronUp , FileText , HelpCircle , Mail , Share2Icon } from "lucide-react" ;
7
+ import { BookText , Check , ChevronDown , ChevronUp , Copy , FileText , HelpCircle , Mail , Share2Icon } from "lucide-react" ;
8
8
import { useState } from "react" ;
9
9
10
10
import { useHypr } from "@/contexts" ;
@@ -38,6 +38,7 @@ function ShareButtonInNote() {
38
38
const [ open , setOpen ] = useState ( false ) ;
39
39
const [ expandedId , setExpandedId ] = useState < string | null > ( null ) ;
40
40
const [ selectedObsidianFolder , setSelectedObsidianFolder ] = useState < string > ( "default" ) ;
41
+ const [ copySuccess , setCopySuccess ] = useState ( false ) ;
41
42
const hasEnhancedNote = ! ! session ?. enhanced_memo_html ;
42
43
43
44
const isObsidianConfigured = useQuery ( {
@@ -72,6 +73,15 @@ function ShareButtonInNote() {
72
73
staleTime : 5 * 60 * 1000 ,
73
74
} ) ;
74
75
76
+ const directActions : DirectAction [ ] = [
77
+ {
78
+ id : "copy" ,
79
+ title : "Copy Note" ,
80
+ icon : < Copy size = { 20 } /> ,
81
+ description : "" ,
82
+ } ,
83
+ ] ;
84
+
75
85
const exportOptions : ExportCard [ ] = [
76
86
{
77
87
id : "pdf" ,
@@ -124,6 +134,10 @@ function ShareButtonInNote() {
124
134
setOpen ( newOpen ) ;
125
135
setExpandedId ( null ) ;
126
136
137
+ if ( ! newOpen ) {
138
+ setCopySuccess ( false ) ;
139
+ }
140
+
127
141
if ( newOpen ) {
128
142
isObsidianConfigured . refetch ( ) . then ( ( configResult ) => {
129
143
if ( configResult . data ) {
@@ -143,7 +157,9 @@ function ShareButtonInNote() {
143
157
const start = performance . now ( ) ;
144
158
let result : ExportResult | null = null ;
145
159
146
- if ( optionId === "pdf" ) {
160
+ if ( optionId === "copy" ) {
161
+ result = await exportHandlers . copy ( session ) ;
162
+ } else if ( optionId === "pdf" ) {
147
163
result = await exportHandlers . pdf ( session ) ;
148
164
} else if ( optionId === "email" ) {
149
165
result = await exportHandlers . email ( session ) ;
@@ -187,16 +203,22 @@ function ShareButtonInNote() {
187
203
} ) ;
188
204
} ,
189
205
onSuccess : ( result ) => {
190
- if ( result ?. type === "pdf" && result . path ) {
206
+ if ( result ?. type === "copy" && result . success ) {
207
+ setCopySuccess ( true ) ;
208
+ // Reset after 2 seconds
209
+ setTimeout ( ( ) => setCopySuccess ( false ) , 2000 ) ;
210
+ } else if ( result ?. type === "pdf" && result . path ) {
191
211
openPath ( result . path ) ;
192
212
} else if ( result ?. type === "email" && result . url ) {
193
213
openUrl ( result . url ) ;
194
214
} else if ( result ?. type === "obsidian" && result . url ) {
195
215
openUrl ( result . url ) ;
196
216
}
197
217
} ,
198
- onSettled : ( ) => {
199
- setOpen ( false ) ;
218
+ onSettled : ( result ) => {
219
+ if ( result ?. type !== "copy" ) {
220
+ setOpen ( false ) ;
221
+ }
200
222
} ,
201
223
onError : ( error ) => {
202
224
console . error ( error ) ;
@@ -238,6 +260,35 @@ function ShareButtonInNote() {
238
260
</ p >
239
261
</ div >
240
262
< div className = "space-y-2" >
263
+ { /* Direct action buttons */ }
264
+ { directActions . map ( ( action ) => {
265
+ const isLoading = exportMutation . isPending && exportMutation . variables ?. optionId === action . id ;
266
+ const isSuccess = action . id === "copy" && copySuccess ;
267
+
268
+ return (
269
+ < div key = { action . id } className = "border rounded-lg overflow-hidden" >
270
+ < button
271
+ onClick = { ( ) => handleExport ( action . id ) }
272
+ disabled = { exportMutation . isPending }
273
+ className = "w-full flex items-center justify-between p-3 hover:bg-gray-50 transition-colors disabled:opacity-50"
274
+ >
275
+ < div className = "flex items-center space-x-3" >
276
+ < div className = { `text-gray-700 transition-colors ${ isSuccess ? "text-green-600" : "" } ` } >
277
+ { isSuccess ? < Check size = { 20 } /> : action . icon }
278
+ </ div >
279
+ < div className = "text-left" >
280
+ < span className = "font-medium text-sm block" > { action . title } </ span >
281
+ < span className = "text-xs text-gray-600" > { action . description } </ span >
282
+ </ div >
283
+ </ div >
284
+ { isLoading && < span className = "text-xs text-gray-500" > Copying...</ span > }
285
+ { isSuccess && < span className = "text-xs text-green-600" > Copied!</ span > }
286
+ </ button >
287
+ </ div >
288
+ ) ;
289
+ } ) }
290
+
291
+ { /* Expandable export options */ }
241
292
{ exportOptions . map ( ( option ) => {
242
293
const expanded = expandedId === option . id ;
243
294
@@ -315,6 +366,13 @@ function ShareButtonInNote() {
315
366
) ;
316
367
}
317
368
369
+ interface DirectAction {
370
+ id : "copy" ;
371
+ title : string ;
372
+ icon : React . ReactNode ;
373
+ description : string ;
374
+ }
375
+
318
376
interface ExportCard {
319
377
id : "pdf" | "email" | "obsidian" ;
320
378
title : string ;
@@ -324,9 +382,10 @@ interface ExportCard {
324
382
}
325
383
326
384
interface ExportResult {
327
- type : "pdf" | "email" | "obsidian" ;
385
+ type : "copy" | " pdf" | "email" | "obsidian" ;
328
386
path ?: string ;
329
387
url ?: string ;
388
+ success ?: boolean ;
330
389
}
331
390
332
391
interface ObsidianFolder {
@@ -335,6 +394,26 @@ interface ObsidianFolder {
335
394
}
336
395
337
396
const exportHandlers = {
397
+ copy : async ( session : Session ) : Promise < ExportResult > => {
398
+ try {
399
+ let textToCopy = "" ;
400
+
401
+ if ( session . enhanced_memo_html ) {
402
+ textToCopy = html2md ( session . enhanced_memo_html ) ;
403
+ } else if ( session . raw_memo_html ) {
404
+ textToCopy = html2md ( session . raw_memo_html ) ;
405
+ } else {
406
+ textToCopy = session . title || "No content available" ;
407
+ }
408
+
409
+ await navigator . clipboard . writeText ( textToCopy ) ;
410
+ return { type : "copy" , success : true } ;
411
+ } catch ( error ) {
412
+ console . error ( "Failed to copy to clipboard:" , error ) ;
413
+ throw new Error ( "Failed to copy note to clipboard" ) ;
414
+ }
415
+ } ,
416
+
338
417
pdf : async ( session : Session ) : Promise < ExportResult > => {
339
418
const path = await exportToPDF ( session ) ;
340
419
return { type : "pdf" , path } ;
0 commit comments