Social Annotations in the Atmosphere
15
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: use selectionchange event for selection tracking

Replace mouseup/touchend/keyup listeners with selectionchange event
for more reliable selection tracking. Add 150ms debounce.

+36 -59
+28 -36
packages/core/src/content/base.ts
··· 52 52 53 53 const pageAnnotations = allAnnotations.filter( 54 54 (ann: Annotation) => { 55 - if (!ann || !ann.value || !ann.value.target) { 56 - return false; 57 - } 58 - return normalizeUrl(ann.value.target.url) === url; 59 - } 55 + if (!ann || !ann.value || !ann.value.target) { 56 + return false; 57 + } 58 + return normalizeUrl(ann.value.target.url) === url; 59 + } 60 60 ); 61 61 62 62 console.log(`[content] Found ${pageAnnotations.length} annotations in cache for ${url}`); ··· 76 76 } 77 77 } 78 78 79 - protected shouldHandleSelection(event: MouseEvent): boolean { 80 - return true; 81 - } 82 - 83 79 private setupSelectionTracking() { 84 - ['mouseup', 'touchend', 'keyup'].forEach(event => { 85 - document.addEventListener(event, (e) => { 86 - // Small delay for touchend/keyup to let selection settle 87 - setTimeout(() => { 88 - if (!this.shouldHandleSelection(e as MouseEvent)) { 89 - return; 90 - } 80 + let debounceTimer: ReturnType<typeof setTimeout> | null = null; 91 81 92 - const selection = window.getSelection(); 93 - if (selection && selection.toString().trim().length > 0) { 94 - const text = selection.toString().trim(); 95 - // Heuristic for root element - could be configurable 96 - const root = document.querySelector('main') || document.querySelector('article') || document.body; 82 + document.addEventListener('selectionchange', () => { 83 + if (debounceTimer) clearTimeout(debounceTimer); 97 84 98 - try { 99 - const selectors = this.adapter.generateSelectors(selection, root); 100 - this.currentSelection = { text, selectors }; 101 - console.log('[content] Text selected:', text); 102 - this.adapter.notifySelectionChange(this.currentSelection); 103 - } catch (err) { 104 - console.error('[content] Failed to generate selectors:', err); 105 - } 106 - } else { 107 - if (this.currentSelection) { 108 - this.currentSelection = null; 109 - this.adapter.notifySelectionChange(null); 110 - } 85 + debounceTimer = setTimeout(() => { 86 + const selection = window.getSelection(); 87 + if (selection && selection.toString().trim().length > 0) { 88 + const text = selection.toString().trim(); 89 + const root = document.querySelector('main') || document.querySelector('article') || document.body; 90 + 91 + try { 92 + const selectors = this.adapter.generateSelectors(selection, root); 93 + this.currentSelection = { text, selectors }; 94 + console.log('[content] Text selected:', text); 95 + this.adapter.notifySelectionChange(this.currentSelection); 96 + } catch (err) { 97 + console.error('[content] Failed to generate selectors:', err); 111 98 } 112 - }, 10); 113 - }); 99 + } else { 100 + if (this.currentSelection) { 101 + this.currentSelection = null; 102 + this.adapter.notifySelectionChange(null); 103 + } 104 + } 105 + }, 150); 114 106 }); 115 107 } 116 108
-7
packages/core/src/content/proxy.ts
··· 55 55 onAnnotate: options.onAnnotate 56 56 }); 57 57 } 58 - 59 - protected shouldHandleSelection(event: MouseEvent): boolean { 60 - if (event.target instanceof Node && this.uiManager.contains(event.target)) { 61 - return false; 62 - } 63 - return true; 64 - } 65 58 }
+8 -16
packages/core/src/content/ui.ts
··· 13 13 showButton(rect: DOMRect, text: string, selectors: any[]) { 14 14 this.removeButton(); 15 15 16 - // Use a timeout to ensure UI settles and to decouple from the event loop slightly 17 - setTimeout(() => { 18 - this.activeBtn = createMobileAnnotateButton( 19 - rect.left + rect.width / 2, 20 - rect.bottom, 21 - () => { 22 - // Trigger annotate (opens sidebar or focuses it) 23 - this.options.onAnnotate({ text, selectors }); 24 - this.removeButton(); 25 - } 26 - ); 27 - }, 0); 16 + this.activeBtn = createMobileAnnotateButton( 17 + rect.left + rect.width / 2, 18 + rect.bottom, 19 + () => { 20 + this.options.onAnnotate({ text, selectors }); 21 + this.removeButton(); 22 + } 23 + ); 28 24 } 29 25 30 26 removeButton() { ··· 32 28 this.activeBtn.remove(); 33 29 this.activeBtn = null; 34 30 } 35 - } 36 - 37 - contains(element: Node): boolean { 38 - return this.activeBtn ? this.activeBtn.contains(element) : false; 39 31 } 40 32 }