1+ import React , { useState , useRef } from "react" ;
2+ import { Button } from "@/components/ui/button" ;
3+ import { FileText , Plus } from "lucide-react" ;
4+ import { cn } from "@/lib/utils" ;
5+ import { useNoteStore } from "@/hooks/useNoteStore" ;
6+
7+ interface SelectableTextProps {
8+ children : React . ReactNode ;
9+ className ?: string ;
10+ source ?: string ; // Source identifier for the content
11+ onTabSwitch ?: ( ) => void ; // Callback to switch to notes tab
12+ }
13+
14+ export function SelectableText ( {
15+ children,
16+ className,
17+ source = "Chat Message" ,
18+ onTabSwitch
19+ } : SelectableTextProps ) {
20+ const [ selectedText , setSelectedText ] = useState < string > ( "" ) ;
21+ const [ showActions , setShowActions ] = useState ( false ) ;
22+ const [ selectionPosition , setSelectionPosition ] = useState ( { x : 0 , y : 0 } ) ;
23+ const containerRef = useRef < HTMLDivElement > ( null ) ;
24+
25+ const handleTextSelection = ( ) => {
26+ const selection = window . getSelection ( ) ;
27+ console . log ( 'Text selection event:' , { selection, text : selection ?. toString ( ) } ) ;
28+
29+ if ( selection && selection . toString ( ) . trim ( ) ) {
30+ const selectedString = selection . toString ( ) . trim ( ) ;
31+ setSelectedText ( selectedString ) ;
32+
33+ // Get selection position for floating actions
34+ const range = selection . getRangeAt ( 0 ) ;
35+ const rect = range . getBoundingClientRect ( ) ;
36+
37+ // Get the viewport dimensions
38+ const viewportWidth = window . innerWidth ;
39+ const viewportHeight = window . innerHeight ;
40+ const buttonWidth = 220 ; // Approximate width of the button container
41+ const buttonHeight = 60 ; // Approximate height of the button container
42+
43+ // Calculate position relative to viewport
44+ let x = rect . left + rect . width / 2 ;
45+ let y = rect . top - buttonHeight - 15 ; // Position above the selection with margin
46+
47+ // Ensure horizontal position stays within viewport bounds
48+ const minX = 10 ; // Minimum margin from left edge
49+ const maxX = viewportWidth - buttonWidth - 10 ; // Maximum position considering button width
50+
51+ if ( x - buttonWidth / 2 < minX ) {
52+ x = minX + buttonWidth / 2 ;
53+ } else if ( x - buttonWidth / 2 > maxX ) {
54+ x = maxX + buttonWidth / 2 ;
55+ }
56+
57+ // Ensure vertical position is visible
58+ const minY = 10 ; // Minimum margin from top
59+ const maxY = viewportHeight - buttonHeight - 10 ; // Maximum position considering button height
60+
61+ if ( y < minY ) {
62+ // If no space above, position below the selection
63+ y = rect . bottom + 15 ;
64+ if ( y > maxY ) {
65+ // If still no space below, position at max allowed position
66+ y = maxY ;
67+ }
68+ } else if ( y > maxY ) {
69+ y = maxY ;
70+ }
71+
72+ const position = { x, y } ;
73+
74+ console . log ( 'Setting position:' , position , 'viewport:' , { viewportWidth, viewportHeight } , 'selection rect:' , rect ) ;
75+ setSelectionPosition ( position ) ;
76+ setShowActions ( true ) ;
77+
78+ // Debug: Log the state changes
79+ console . log ( 'showActions set to true, selectedText:' , selectedString ) ;
80+ } else {
81+ setSelectedText ( "" ) ;
82+ setShowActions ( false ) ;
83+ }
84+ } ;
85+
86+ const handleAddToNote = ( ) => {
87+ if ( ! selectedText ) return ;
88+
89+ console . log ( 'Adding to note:' , selectedText ) ;
90+
91+ try {
92+ const { currentNoteId, getCurrentNote, createNoteFromContent, addContentToNote } = useNoteStore . getState ( ) ;
93+
94+ console . log ( 'Current note ID:' , currentNoteId ) ;
95+
96+ if ( currentNoteId && getCurrentNote ( ) ) {
97+ // Add to existing note
98+ console . log ( 'Adding to existing note' ) ;
99+ addContentToNote ( currentNoteId , selectedText , source ) ;
100+ } else {
101+ // Create new note
102+ console . log ( 'Creating new note' ) ;
103+ const newNote = createNoteFromContent ( selectedText , source ) ;
104+ console . log ( 'Created note:' , newNote ) ;
105+ }
106+
107+ // Switch to notes tab to show the result
108+ onTabSwitch ?.( ) ;
109+
110+ // Clear selection and hide actions
111+ window . getSelection ( ) ?. removeAllRanges ( ) ;
112+ setSelectedText ( "" ) ;
113+ setShowActions ( false ) ;
114+ } catch ( error ) {
115+ console . error ( 'Error adding to note:' , error ) ;
116+ }
117+ } ;
118+
119+ const handleCreateNewNote = ( ) => {
120+ if ( ! selectedText ) return ;
121+
122+ const { createNoteFromContent } = useNoteStore . getState ( ) ;
123+ createNoteFromContent ( selectedText , source ) ;
124+
125+ // Switch to notes tab to show the result
126+ onTabSwitch ?.( ) ;
127+
128+ // Clear selection and hide actions
129+ window . getSelection ( ) ?. removeAllRanges ( ) ;
130+ setSelectedText ( "" ) ;
131+ setShowActions ( false ) ;
132+ } ;
133+
134+ const handleClickOutside = ( e : React . MouseEvent ) => {
135+ if ( ! containerRef . current ?. contains ( e . target as Node ) ) {
136+ setShowActions ( false ) ;
137+ setSelectedText ( "" ) ;
138+ }
139+ } ;
140+
141+ return (
142+ < >
143+ < div
144+ ref = { containerRef }
145+ className = { cn ( "relative" , className ) }
146+ onMouseUp = { handleTextSelection }
147+ onMouseDown = { ( ) => setShowActions ( false ) }
148+ >
149+ { children }
150+ </ div >
151+
152+ { /* Floating Action Buttons */ }
153+ { ( ( ) => {
154+ console . log ( 'Render check - showActions:' , showActions , 'selectedText:' , selectedText ) ;
155+ return showActions && selectedText ;
156+ } ) ( ) && (
157+ < >
158+ { /* Backdrop to handle clicks outside */ }
159+ < div
160+ className = "fixed inset-0 bg-black/10"
161+ style = { { zIndex : 999998 } }
162+ onClick = { handleClickOutside }
163+ />
164+
165+ { /* Action Buttons */ }
166+ < div
167+ className = "fixed flex items-center gap-2 rounded-lg shadow-2xl p-3"
168+ style = { {
169+ left : `${ selectionPosition . x } px` ,
170+ top : `${ selectionPosition . y } px` ,
171+ transform : 'translateX(-50%)' ,
172+ backgroundColor : '#1e293b' , // Dark background
173+ border : '2px solid #3b82f6' , // Blue border
174+ boxShadow : '0 25px 50px -12px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(255, 255, 255, 0.05)' ,
175+ zIndex : 999999 , // Very high z-index to appear above panels
176+ minWidth : '220px' ,
177+ maxWidth : '300px' ,
178+ } }
179+ >
180+ < Button
181+ size = "sm"
182+ onClick = { handleAddToNote }
183+ title = "Add to current note or create new note"
184+ style = { {
185+ height : '32px' ,
186+ padding : '0 12px' ,
187+ fontSize : '12px' ,
188+ fontWeight : '500' ,
189+ backgroundColor : '#3b82f6' ,
190+ border : 'none' ,
191+ color : 'white' ,
192+ borderRadius : '6px' ,
193+ display : 'flex' ,
194+ alignItems : 'center' ,
195+ gap : '4px'
196+ } }
197+ >
198+ < FileText className = "w-3 h-3" />
199+ Add to Note
200+ </ Button >
201+
202+ < Button
203+ size = "sm"
204+ onClick = { handleCreateNewNote }
205+ title = "Create new note with this text"
206+ style = { {
207+ height : '32px' ,
208+ padding : '0 12px' ,
209+ fontSize : '12px' ,
210+ fontWeight : '500' ,
211+ backgroundColor : 'transparent' ,
212+ border : '1px solid #3b82f6' ,
213+ color : '#60a5fa' ,
214+ borderRadius : '6px' ,
215+ display : 'flex' ,
216+ alignItems : 'center' ,
217+ gap : '4px'
218+ } }
219+ >
220+ < Plus className = "w-3 h-3" />
221+ New Note
222+ </ Button >
223+ </ div >
224+ </ >
225+ ) }
226+ </ >
227+ ) ;
228+ }
0 commit comments