@@ -10,6 +10,7 @@ import {
1010} from '../utils/validation' ;
1111import { ANNOTATION_CONSTANTS , MESSAGE_TYPES , TIMING_CONSTANTS , UI_CONSTANTS } from '../utils/constants' ;
1212import { isHTMLButtonElement } from '../utils/type-guards' ;
13+ import DOMPurify from 'dompurify' ;
1314
1415/**
1516 * Annotation data structure
@@ -460,12 +461,14 @@ export class AnnotationManager {
460461
461462 const button = document . createElement ( 'button' ) ;
462463 button . className = 'pubky-annotation-button' ;
463- button . innerHTML = `
464+ // Use DOMPurify to sanitize HTML (defense-in-depth, even though this is static)
465+ const buttonHtml = `
464466 <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
465467 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" />
466468 </svg>
467469 Add Annotation
468470 ` ;
471+ button . innerHTML = DOMPurify . sanitize ( buttonHtml ) ;
469472
470473 // Position button to the right and slightly below the selection
471474 // Account for button width (approximately 140px) and add some padding
@@ -572,7 +575,8 @@ export class AnnotationManager {
572575 modal . className = 'pubky-annotation-modal' ;
573576 modal . onclick = ( e ) => e . stopPropagation ( ) ;
574577
575- modal . innerHTML = `
578+ // Sanitize modal HTML with DOMPurify
579+ const modalHtml = `
576580 <h3>Add Annotation</h3>
577581 <div class="selected-text">"${ this . escapeHtml ( this . currentSelection . text ) } "</div>
578582 <textarea placeholder="Add your comment..." autofocus maxlength="${ VALIDATION_LIMITS . COMMENT_MAX_LENGTH } "></textarea>
@@ -585,6 +589,7 @@ export class AnnotationManager {
585589 <button class="submit-btn">Post Annotation</button>
586590 </div>
587591 ` ;
592+ modal . innerHTML = DOMPurify . sanitize ( modalHtml ) ;
588593
589594 const textarea = modal . querySelector ( 'textarea' ) ! ;
590595 const cancelBtn = modal . querySelector ( '.cancel-btn' ) ! ;
@@ -920,18 +925,29 @@ export class AnnotationManager {
920925 private handleHighlightClick ( annotation : Annotation ) {
921926 logger . info ( 'ContentScript' , 'Highlight clicked' , { id : annotation . id } ) ;
922927
928+ // Highlight the clicked annotation on the page
923929 document . querySelectorAll ( `.${ this . activeHighlightClass } ` ) . forEach ( ( el ) => {
924930 el . classList . remove ( this . activeHighlightClass ) ;
925931 } ) ;
926932
927933 const highlight = document . querySelector ( `[data-annotation-id="${ annotation . id } "]` ) ;
928934 if ( highlight ) {
929935 highlight . classList . add ( this . activeHighlightClass ) ;
936+ // Scroll the highlight into view
937+ highlight . scrollIntoView ( { behavior : 'smooth' , block : 'center' } ) ;
930938 }
931939
940+ // Send message to background to open sidepanel
941+ // Must be synchronous to preserve user gesture context
932942 chrome . runtime . sendMessage ( {
933- type : 'SHOW_ANNOTATION ' ,
943+ type : 'OPEN_SIDE_PANEL_FOR_ANNOTATION ' ,
934944 annotationId : annotation . id ,
945+ } , ( ) => {
946+ if ( chrome . runtime . lastError ) {
947+ logger . warn ( 'ContentScript' , 'Failed to send open sidepanel message' , new Error ( chrome . runtime . lastError . message ) ) ;
948+ } else {
949+ logger . info ( 'ContentScript' , 'Sidepanel open requested' , { annotationId : annotation . id } ) ;
950+ }
935951 } ) ;
936952 }
937953
0 commit comments