@@ -58,17 +58,58 @@ export class AnnotationManager {
5858 this . init ( ) ;
5959 }
6060
61- private init ( ) {
61+ private annotationsEnabled : boolean = true ;
62+
63+ private async init ( ) {
6264 logger . info ( 'ContentScript' , 'Initializing annotation manager' ) ;
6365 this . injectStyles ( ) ;
6466
67+ // Load annotation enabled setting (default: true)
68+ try {
69+ const { storage } = await import ( '../utils/storage' ) ;
70+ const enabled = await storage . getSetting < boolean > ( 'annotationsEnabled' , true ) ;
71+ this . annotationsEnabled = enabled ?? true ;
72+ logger . info ( 'ContentScript' , 'Annotation enabled setting loaded' , { enabled : this . annotationsEnabled } ) ;
73+ } catch ( error ) {
74+ logger . warn ( 'ContentScript' , 'Failed to load annotation setting, defaulting to enabled' , error as Error ) ;
75+ this . annotationsEnabled = true ;
76+ }
77+
6578 // Bind handlers once for cleanup
6679 this . mouseUpHandler = this . handleTextSelection . bind ( this ) ;
6780 this . messageHandler = this . handleMessage . bind ( this ) ;
6881
6982 document . addEventListener ( 'mouseup' , this . mouseUpHandler ) ;
7083 chrome . runtime . onMessage . addListener ( this . messageHandler ) ;
7184
85+ // Listen for toggle messages
86+ chrome . runtime . onMessage . addListener ( ( message , _sender , sendResponse ) => {
87+ if ( message . type === 'TOGGLE_ANNOTATIONS' ) {
88+ this . annotationsEnabled = message . enabled ?? ! this . annotationsEnabled ;
89+ logger . info ( 'ContentScript' , 'Annotations toggled' , { enabled : this . annotationsEnabled } ) ;
90+ if ( ! this . annotationsEnabled ) {
91+ this . hideAnnotationButton ( ) ;
92+ }
93+ sendResponse ( { enabled : this . annotationsEnabled } ) ;
94+ return true ;
95+ }
96+ if ( message . type === 'GET_ANNOTATIONS_ENABLED' ) {
97+ sendResponse ( { enabled : this . annotationsEnabled } ) ;
98+ return true ;
99+ }
100+ return false ;
101+ } ) ;
102+
103+ // Listen for keyboard shortcut (Alt+Shift+A)
104+ document . addEventListener ( 'keydown' , ( e ) => {
105+ const isMac = navigator . platform . toUpperCase ( ) . indexOf ( 'MAC' ) >= 0 ;
106+ const altKey = isMac ? e . altKey : e . altKey ;
107+ if ( altKey && e . shiftKey && e . key . toLowerCase ( ) === 'a' ) {
108+ e . preventDefault ( ) ;
109+ this . toggleAnnotations ( ) ;
110+ }
111+ } ) ;
112+
72113 // Delay initial annotation load to allow dynamic content to load (SPAs)
73114 // This is especially important for sites like pubky.app that load content asynchronously
74115 setTimeout ( ( ) => {
@@ -333,9 +374,26 @@ export class AnnotationManager {
333374 }
334375
335376 private handleTextSelection ( event : MouseEvent ) {
377+ // Don't process if annotations are disabled
378+ if ( ! this . annotationsEnabled ) {
379+ return ;
380+ }
381+
382+ // Don't hide button if clicking on the annotation button itself
383+ const target = event . target as HTMLElement ;
384+ if ( target ?. closest ( '.pubky-annotation-button' ) || target ?. closest ( '.pubky-annotation-modal' ) ) {
385+ return ;
386+ }
387+
336388 const selection = window . getSelection ( ) ;
337389 if ( ! selection || selection . isCollapsed ) {
338- this . hideAnnotationButton ( ) ;
390+ // Delay hiding to allow button click to register
391+ setTimeout ( ( ) => {
392+ const button = document . querySelector ( '.pubky-annotation-button' ) ;
393+ if ( button && ! button . contains ( document . activeElement ) ) {
394+ this . hideAnnotationButton ( ) ;
395+ }
396+ } , 100 ) ;
339397 return ;
340398 }
341399
@@ -366,10 +424,25 @@ export class AnnotationManager {
366424 ` ;
367425 button . style . left = `${ x - 80 } px` ;
368426 button . style . top = `${ y + 10 } px` ;
427+
428+ // Use both mousedown and click to ensure the modal opens
429+ // mousedown fires before mouseup, so it prevents the button from being hidden
369430 button . onmousedown = ( e ) => {
431+ e . preventDefault ( ) ;
370432 e . stopPropagation ( ) ;
433+ // Show modal immediately on mousedown to prevent button from being hidden
371434 this . showAnnotationModal ( ) ;
372435 } ;
436+
437+ // Also handle click as backup
438+ button . onclick = ( e ) => {
439+ e . preventDefault ( ) ;
440+ e . stopPropagation ( ) ;
441+ // If modal isn't already showing, show it
442+ if ( ! document . querySelector ( '.pubky-annotation-modal' ) ) {
443+ this . showAnnotationModal ( ) ;
444+ }
445+ } ;
373446
374447 document . body . appendChild ( button ) ;
375448 }
@@ -381,6 +454,32 @@ export class AnnotationManager {
381454 }
382455 }
383456
457+ private async toggleAnnotations ( ) {
458+ const newValue = ! this . annotationsEnabled ;
459+ this . annotationsEnabled = newValue ;
460+
461+ try {
462+ const { storage } = await import ( '../utils/storage' ) ;
463+ await storage . saveSetting ( 'annotationsEnabled' , newValue ) ;
464+ logger . info ( 'ContentScript' , 'Annotations toggled via keyboard' , { enabled : newValue } ) ;
465+
466+ // Show visual feedback
467+ chrome . runtime . sendMessage ( {
468+ type : MESSAGE_TYPES . SHOW_TOAST ,
469+ toastType : newValue ? 'success' : 'info' ,
470+ message : newValue ? 'Annotations enabled' : 'Annotations disabled' ,
471+ } ) . catch ( ( ) => {
472+ // Ignore if background script not available
473+ } ) ;
474+
475+ if ( ! newValue ) {
476+ this . hideAnnotationButton ( ) ;
477+ }
478+ } catch ( error ) {
479+ logger . error ( 'ContentScript' , 'Failed to toggle annotations' , error as Error ) ;
480+ }
481+ }
482+
384483 private showAnnotationModal ( ) {
385484 if ( ! this . currentSelection ) return ;
386485 this . hideAnnotationButton ( ) ;
@@ -789,6 +888,24 @@ export class AnnotationManager {
789888 }
790889 }
791890 sendResponse ( { success : true } ) ;
891+ } else if ( message . type === MESSAGE_TYPES . REMOVE_ANNOTATION ) {
892+ const annotationId = message . annotationId ;
893+ // Remove highlight from DOM
894+ const highlight = document . querySelector ( `[data-annotation-id="${ annotationId } "]` ) ;
895+ if ( highlight ) {
896+ // Unwrap the highlight span, restoring original text
897+ const parent = highlight . parentNode ;
898+ if ( parent ) {
899+ while ( highlight . firstChild ) {
900+ parent . insertBefore ( highlight . firstChild , highlight ) ;
901+ }
902+ parent . removeChild ( highlight ) ;
903+ logger . info ( 'ContentScript' , 'Annotation highlight removed from page' , { id : annotationId } ) ;
904+ }
905+ }
906+ // Remove from local annotations array
907+ this . annotations = this . annotations . filter ( ( a ) => a . id !== annotationId ) ;
908+ sendResponse ( { success : true } ) ;
792909 }
793910 return true ;
794911 }
0 commit comments