From cf3ada09096cef3c732a883ad60d06fbe4397ef6 Mon Sep 17 00:00:00 2001 From: Todd Riley Date: Thu, 25 Sep 2025 13:40:09 -0400 Subject: [PATCH 1/7] Re-enhance when submitted. --- .../github/GitHubIssueAppendEnhancer.tsx | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx b/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx index 0fee621..c4a4557 100644 --- a/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx +++ b/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx @@ -3,6 +3,7 @@ import OverType, { type OverTypeInstance } from 'overtype' import type React from 'react' import type { CommentEnhancer, CommentSpot, StrippedLocation } from '@/lib/enhancer' import { logger } from '@/lib/logger' +import { oncePerRefresh } from '@/lib/once-per-refresh' import { modifyDOM } from '../modifyDOM' import { commonGitHubOptions, prepareGitHubHighlighter } from './github-common' @@ -63,14 +64,39 @@ export class GitHubIssueAppendEnhancer implements CommentEnhancer { + document.addEventListener('click', (e) => { + const target = e.target + if (!target) return false + const btn = (e.target as HTMLElement).closest('button') + if (!btn) return false + if (btn.textContent.trim() === 'Comment' || btn.matches('button[data-variant="primary"]')) { + this.enhance(textArea, _spot) + return true + } + return false + }) + }) } tableUpperDecoration(spot: GitHubIssueAppendSpot): React.ReactNode { From d78e3a8aa73dc8ea4b7f0e9de101881f2636dc14 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 26 Sep 2025 08:47:11 -0700 Subject: [PATCH 2/7] move submit handler to be after deletion --- .../github/GitHubIssueAppendEnhancer.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx b/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx index c4a4557..687a624 100644 --- a/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx +++ b/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx @@ -67,13 +67,13 @@ export class GitHubIssueAppendEnhancer implements CommentEnhancer { document.addEventListener('click', (e) => { const target = e.target - if (!target) return false - const btn = (e.target as HTMLElement).closest('button') - if (!btn) return false - if (btn.textContent.trim() === 'Comment' || btn.matches('button[data-variant="primary"]')) { - this.enhance(textArea, _spot) - return true + if (target) { + const btn = (e.target as HTMLElement).closest('button') + if (btn) { + if (btn.textContent.trim() === 'Comment' || btn.matches('button[data-variant="primary"]')) { + this.enhance(textArea, _spot) + return true + } + } } return false }) From 9e46e004ce58bbb7fae30af137e40baeef59f557 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 26 Sep 2025 09:10:28 -0700 Subject: [PATCH 3/7] give enhancers a way to return an optional cleanup function --- src/lib/enhancer.ts | 8 +++++- src/lib/enhancers/CommentEnhancerMissing.tsx | 10 +++++--- .../enhancers/github/GitHubEditEnhancer.tsx | 13 +++++++--- .../github/GitHubIssueAppendEnhancer.tsx | 18 +++++++++---- .../github/GitHubIssueCreateEnhancer.tsx | 23 +++++++++++------ .../github/GitHubPrAppendEnhancer.tsx | 25 ++++++++++++------- .../github/GitHubPrCreateEnhancer.tsx | 23 +++++++++++------ src/lib/registries.ts | 5 ++-- 8 files changed, 85 insertions(+), 40 deletions(-) diff --git a/src/lib/enhancer.ts b/src/lib/enhancer.ts index 56dc6a4..9dacec8 100644 --- a/src/lib/enhancer.ts +++ b/src/lib/enhancer.ts @@ -40,9 +40,15 @@ export interface CommentEnhancer { /** * If `tryToEnhance` returns non-null, then this gets called. */ - enhance(textarea: HTMLTextAreaElement, spot: Spot): OverTypeInstance + enhance(textarea: HTMLTextAreaElement, spot: Spot): OvertypeWithCleanup /** Returns a ReactNode which will be displayed in the table row. */ tableUpperDecoration(spot: Spot): ReactNode /** The default title of a row */ tableTitle(spot: Spot): string } + +/** Allows enhancers to attach an optional cleanup field to the return value of `enhance()`. */ +export interface OvertypeWithCleanup { + instance: OverTypeInstance + cleanup?: () => void +} diff --git a/src/lib/enhancers/CommentEnhancerMissing.tsx b/src/lib/enhancers/CommentEnhancerMissing.tsx index b123e1c..38d4918 100644 --- a/src/lib/enhancers/CommentEnhancerMissing.tsx +++ b/src/lib/enhancers/CommentEnhancerMissing.tsx @@ -1,6 +1,10 @@ -import type { OverTypeInstance } from 'overtype' import type { ReactNode } from 'react' -import type { CommentEnhancer, CommentSpot, StrippedLocation } from '../enhancer' +import type { + CommentEnhancer, + CommentSpot, + OvertypeWithCleanup, + StrippedLocation, +} from '../enhancer' /** Used when an entry is in the table which we don't recognize. */ export class CommentEnhancerMissing implements CommentEnhancer { @@ -43,7 +47,7 @@ export class CommentEnhancerMissing implements CommentEnhancer { tryToEnhance(_textarea: HTMLTextAreaElement, _location: StrippedLocation): CommentSpot | null { throw new Error('Method not implemented.') } - enhance(_textarea: HTMLTextAreaElement, _spot: CommentSpot): OverTypeInstance { + enhance(_textarea: HTMLTextAreaElement, _spot: CommentSpot): OvertypeWithCleanup { throw new Error('Method not implemented.') } } diff --git a/src/lib/enhancers/github/GitHubEditEnhancer.tsx b/src/lib/enhancers/github/GitHubEditEnhancer.tsx index d571783..d8281df 100644 --- a/src/lib/enhancers/github/GitHubEditEnhancer.tsx +++ b/src/lib/enhancers/github/GitHubEditEnhancer.tsx @@ -1,6 +1,11 @@ -import OverType, { type OverTypeInstance } from 'overtype' +import OverType from 'overtype' import type React from 'react' -import type { CommentEnhancer, CommentSpot, StrippedLocation } from '@/lib/enhancer' +import type { + CommentEnhancer, + CommentSpot, + OvertypeWithCleanup, + StrippedLocation, +} from '@/lib/enhancer' import { logger } from '@/lib/logger' import { modifyDOM } from '../modifyDOM' import { commonGitHubOptions, prepareGitHubHighlighter } from './github-common' @@ -50,7 +55,7 @@ export class GitHubEditEnhancer implements CommentEnhancer { } } - enhance(textArea: HTMLTextAreaElement, spot: GitHubEditSpot): OverTypeInstance { + enhance(textArea: HTMLTextAreaElement, spot: GitHubEditSpot): OvertypeWithCleanup { prepareGitHubHighlighter() const overtypeContainer = modifyDOM(textArea) const overtype = new OverType(overtypeContainer, { @@ -60,7 +65,7 @@ export class GitHubEditEnhancer implements CommentEnhancer { if (!spot.isIssue) { // TODO: autoheight not working } - return overtype + return { instance: overtype } } tableUpperDecoration(_spot: GitHubEditSpot): React.ReactNode { diff --git a/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx b/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx index 687a624..ffc5582 100644 --- a/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx +++ b/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx @@ -1,7 +1,12 @@ import { IssueOpenedIcon } from '@primer/octicons-react' import OverType, { type OverTypeInstance } from 'overtype' import type React from 'react' -import type { CommentEnhancer, CommentSpot, StrippedLocation } from '@/lib/enhancer' +import type { + CommentEnhancer, + CommentSpot, + OvertypeWithCleanup, + StrippedLocation, +} from '@/lib/enhancer' import { logger } from '@/lib/logger' import { oncePerRefresh } from '@/lib/once-per-refresh' import { modifyDOM } from '../modifyDOM' @@ -66,12 +71,12 @@ export class GitHubIssueAppendEnhancer implements CommentEnhancer { spot: T enhancer: CommentEnhancer overtype: OverTypeInstance + cleanup?: () => void } export class EnhancerRegistry { @@ -71,9 +72,9 @@ export class EnhancerRegistry { try { const spot = enhancer.tryToEnhance(textarea, location) if (spot) { - const overtype = enhancer.enhance(textarea, spot) + const { instance: overtype, cleanup } = enhancer.enhance(textarea, spot) this.handleDelayedValueInjection(overtype) - return { enhancer, overtype, spot, textarea } + return { enhancer, overtype, spot, textarea, ...(cleanup && { cleanup }) } } } catch (error) { console.warn('Handler failed to identify textarea:', error) From f0df91f971687016cd8790904e910869bec57d9b Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 26 Sep 2025 09:16:34 -0700 Subject: [PATCH 4/7] Abandon the `registerSubmitHandler`, and rely only on reliable cleanup. --- .../github/GitHubIssueAppendEnhancer.tsx | 43 +++++-------------- src/lib/registries.ts | 3 ++ 2 files changed, 13 insertions(+), 33 deletions(-) diff --git a/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx b/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx index ffc5582..906b53d 100644 --- a/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx +++ b/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx @@ -1,5 +1,5 @@ import { IssueOpenedIcon } from '@primer/octicons-react' -import OverType, { type OverTypeInstance } from 'overtype' +import OverType from 'overtype' import type React from 'react' import type { CommentEnhancer, @@ -8,7 +8,6 @@ import type { StrippedLocation, } from '@/lib/enhancer' import { logger } from '@/lib/logger' -import { oncePerRefresh } from '@/lib/once-per-refresh' import { modifyDOM } from '../modifyDOM' import { commonGitHubOptions, prepareGitHubHighlighter } from './github-common' @@ -69,44 +68,22 @@ export class GitHubIssueAppendEnhancer implements CommentEnhancer { - document.addEventListener('click', (e) => { - const target = e.target - if (target) { - const btn = (e.target as HTMLElement).closest('button') - if (btn) { - if ( - btn.textContent.trim() === 'Comment' || - btn.matches('button[data-variant="primary"]') - ) { - this.enhance(textArea, _spot) - return true - } - } - } - return false - }) - }) + const cleanup = () => { + OverType.instances.delete(overtypeContainer) + ;(overtypeContainer as any).overTypeInstance = undefined + } + return { + cleanup, + instance, + } } tableUpperDecoration(spot: GitHubIssueAppendSpot): React.ReactNode { diff --git a/src/lib/registries.ts b/src/lib/registries.ts index 550d77b..be2d2aa 100644 --- a/src/lib/registries.ts +++ b/src/lib/registries.ts @@ -133,6 +133,9 @@ export class TextareaRegistry { unregisterDueToModification(textarea: HTMLTextAreaElement): void { const enhanced = this.textareas.get(textarea) if (enhanced) { + if (enhanced.cleanup) { + enhanced.cleanup() + } this.sendEvent('DESTROYED', enhanced) this.textareas.delete(textarea) } From 603b314965f66fa2cf14c600ad9515a92bed5303 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 26 Sep 2025 09:29:33 -0700 Subject: [PATCH 5/7] So for PR append, a single text box persists for each comment, it just gets cleared after submission. Listeningfor `input` doesn't work, but listening with `MutationObserver` does. --- .../github/GitHubPrAppendEnhancer.tsx | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/lib/enhancers/github/GitHubPrAppendEnhancer.tsx b/src/lib/enhancers/github/GitHubPrAppendEnhancer.tsx index 0c13dd3..2d2280d 100644 --- a/src/lib/enhancers/github/GitHubPrAppendEnhancer.tsx +++ b/src/lib/enhancers/github/GitHubPrAppendEnhancer.tsx @@ -61,13 +61,33 @@ export class GitHubPrAppendEnhancer implements CommentEnhancer { + if (textArea.value === '') { + console.log('input event fired') + instance.updatePreview() + } + }) + const observer = new MutationObserver(() => { + if (textArea.value === '') { + console.log('MutationObserver fired') + instance.updatePreview() + } + }) + observer.observe(textArea, { attributes: true, characterData: true }) + const cleanup = () => { + OverType.instances.delete(overtypeContainer) + ;(overtypeContainer as any).overTypeInstance = undefined + } return { - instance: new OverType(overtypeContainer, { - ...commonGitHubOptions, - minHeight: '102px', - padding: 'var(--base-size-8)', - placeholder: 'Add your comment here...', - })[0]!, + cleanup, + instance, } } From c20cbd3a7e76733422d9552af698b0e55358a293 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 26 Sep 2025 09:32:29 -0700 Subject: [PATCH 6/7] So for PR enhancer, we can get by with only a MutationObserver. --- .../enhancers/github/GitHubPrAppendEnhancer.tsx | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/lib/enhancers/github/GitHubPrAppendEnhancer.tsx b/src/lib/enhancers/github/GitHubPrAppendEnhancer.tsx index 2d2280d..de4d4c5 100644 --- a/src/lib/enhancers/github/GitHubPrAppendEnhancer.tsx +++ b/src/lib/enhancers/github/GitHubPrAppendEnhancer.tsx @@ -67,26 +67,13 @@ export class GitHubPrAppendEnhancer implements CommentEnhancer { + const listenForEmpty = new MutationObserver(() => { if (textArea.value === '') { - console.log('input event fired') instance.updatePreview() } }) - const observer = new MutationObserver(() => { - if (textArea.value === '') { - console.log('MutationObserver fired') - instance.updatePreview() - } - }) - observer.observe(textArea, { attributes: true, characterData: true }) - const cleanup = () => { - OverType.instances.delete(overtypeContainer) - ;(overtypeContainer as any).overTypeInstance = undefined - } + listenForEmpty.observe(textArea, { attributes: true, characterData: true }) return { - cleanup, instance, } } From 2655e2b42d51e336d786f8272d113c5a345bdfbf Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 26 Sep 2025 09:42:56 -0700 Subject: [PATCH 7/7] Same result with less code by doing cleanup on *every* overtype that gets destroyed (probably a good idea). --- src/lib/enhancer.ts | 8 +------ src/lib/enhancers/CommentEnhancerMissing.tsx | 10 +++----- .../enhancers/github/GitHubEditEnhancer.tsx | 13 ++++------- .../github/GitHubIssueAppendEnhancer.tsx | 21 ++++------------- .../github/GitHubIssueCreateEnhancer.tsx | 23 +++++++------------ .../github/GitHubPrAppendEnhancer.tsx | 19 +++++---------- .../github/GitHubPrCreateEnhancer.tsx | 23 +++++++------------ src/lib/registries.ts | 14 ++++++----- 8 files changed, 42 insertions(+), 89 deletions(-) diff --git a/src/lib/enhancer.ts b/src/lib/enhancer.ts index 9dacec8..56dc6a4 100644 --- a/src/lib/enhancer.ts +++ b/src/lib/enhancer.ts @@ -40,15 +40,9 @@ export interface CommentEnhancer { /** * If `tryToEnhance` returns non-null, then this gets called. */ - enhance(textarea: HTMLTextAreaElement, spot: Spot): OvertypeWithCleanup + enhance(textarea: HTMLTextAreaElement, spot: Spot): OverTypeInstance /** Returns a ReactNode which will be displayed in the table row. */ tableUpperDecoration(spot: Spot): ReactNode /** The default title of a row */ tableTitle(spot: Spot): string } - -/** Allows enhancers to attach an optional cleanup field to the return value of `enhance()`. */ -export interface OvertypeWithCleanup { - instance: OverTypeInstance - cleanup?: () => void -} diff --git a/src/lib/enhancers/CommentEnhancerMissing.tsx b/src/lib/enhancers/CommentEnhancerMissing.tsx index 38d4918..b123e1c 100644 --- a/src/lib/enhancers/CommentEnhancerMissing.tsx +++ b/src/lib/enhancers/CommentEnhancerMissing.tsx @@ -1,10 +1,6 @@ +import type { OverTypeInstance } from 'overtype' import type { ReactNode } from 'react' -import type { - CommentEnhancer, - CommentSpot, - OvertypeWithCleanup, - StrippedLocation, -} from '../enhancer' +import type { CommentEnhancer, CommentSpot, StrippedLocation } from '../enhancer' /** Used when an entry is in the table which we don't recognize. */ export class CommentEnhancerMissing implements CommentEnhancer { @@ -47,7 +43,7 @@ export class CommentEnhancerMissing implements CommentEnhancer { tryToEnhance(_textarea: HTMLTextAreaElement, _location: StrippedLocation): CommentSpot | null { throw new Error('Method not implemented.') } - enhance(_textarea: HTMLTextAreaElement, _spot: CommentSpot): OvertypeWithCleanup { + enhance(_textarea: HTMLTextAreaElement, _spot: CommentSpot): OverTypeInstance { throw new Error('Method not implemented.') } } diff --git a/src/lib/enhancers/github/GitHubEditEnhancer.tsx b/src/lib/enhancers/github/GitHubEditEnhancer.tsx index d8281df..d571783 100644 --- a/src/lib/enhancers/github/GitHubEditEnhancer.tsx +++ b/src/lib/enhancers/github/GitHubEditEnhancer.tsx @@ -1,11 +1,6 @@ -import OverType from 'overtype' +import OverType, { type OverTypeInstance } from 'overtype' import type React from 'react' -import type { - CommentEnhancer, - CommentSpot, - OvertypeWithCleanup, - StrippedLocation, -} from '@/lib/enhancer' +import type { CommentEnhancer, CommentSpot, StrippedLocation } from '@/lib/enhancer' import { logger } from '@/lib/logger' import { modifyDOM } from '../modifyDOM' import { commonGitHubOptions, prepareGitHubHighlighter } from './github-common' @@ -55,7 +50,7 @@ export class GitHubEditEnhancer implements CommentEnhancer { } } - enhance(textArea: HTMLTextAreaElement, spot: GitHubEditSpot): OvertypeWithCleanup { + enhance(textArea: HTMLTextAreaElement, spot: GitHubEditSpot): OverTypeInstance { prepareGitHubHighlighter() const overtypeContainer = modifyDOM(textArea) const overtype = new OverType(overtypeContainer, { @@ -65,7 +60,7 @@ export class GitHubEditEnhancer implements CommentEnhancer { if (!spot.isIssue) { // TODO: autoheight not working } - return { instance: overtype } + return overtype } tableUpperDecoration(_spot: GitHubEditSpot): React.ReactNode { diff --git a/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx b/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx index 906b53d..0fee621 100644 --- a/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx +++ b/src/lib/enhancers/github/GitHubIssueAppendEnhancer.tsx @@ -1,12 +1,7 @@ import { IssueOpenedIcon } from '@primer/octicons-react' -import OverType from 'overtype' +import OverType, { type OverTypeInstance } from 'overtype' import type React from 'react' -import type { - CommentEnhancer, - CommentSpot, - OvertypeWithCleanup, - StrippedLocation, -} from '@/lib/enhancer' +import type { CommentEnhancer, CommentSpot, StrippedLocation } from '@/lib/enhancer' import { logger } from '@/lib/logger' import { modifyDOM } from '../modifyDOM' import { commonGitHubOptions, prepareGitHubHighlighter } from './github-common' @@ -68,22 +63,14 @@ export class GitHubIssueAppendEnhancer implements CommentEnhancer { - OverType.instances.delete(overtypeContainer) - ;(overtypeContainer as any).overTypeInstance = undefined - } - return { - cleanup, - instance, - } } tableUpperDecoration(spot: GitHubIssueAppendSpot): React.ReactNode { diff --git a/src/lib/enhancers/github/GitHubIssueCreateEnhancer.tsx b/src/lib/enhancers/github/GitHubIssueCreateEnhancer.tsx index ec0390d..1d1aba0 100644 --- a/src/lib/enhancers/github/GitHubIssueCreateEnhancer.tsx +++ b/src/lib/enhancers/github/GitHubIssueCreateEnhancer.tsx @@ -1,10 +1,5 @@ -import OverType from 'overtype' -import type { - CommentEnhancer, - CommentSpot, - OvertypeWithCleanup, - StrippedLocation, -} from '../../enhancer' +import OverType, { type OverTypeInstance } from 'overtype' +import type { CommentEnhancer, CommentSpot, StrippedLocation } from '../../enhancer' import { logger } from '../../logger' import { modifyDOM } from '../modifyDOM' import { commonGitHubOptions, prepareGitHubHighlighter } from './github-common' @@ -55,16 +50,14 @@ export class GitHubIssueCreateEnhancer implements CommentEnhancer { if (textArea.value === '') { - instance.updatePreview() + overtype.updatePreview() } }) listenForEmpty.observe(textArea, { attributes: true, characterData: true }) - return { - instance, - } + return overtype } tableUpperDecoration(spot: GitHubPrAppendSpot): React.ReactNode { diff --git a/src/lib/enhancers/github/GitHubPrCreateEnhancer.tsx b/src/lib/enhancers/github/GitHubPrCreateEnhancer.tsx index 523e285..72e9bdc 100644 --- a/src/lib/enhancers/github/GitHubPrCreateEnhancer.tsx +++ b/src/lib/enhancers/github/GitHubPrCreateEnhancer.tsx @@ -1,10 +1,5 @@ -import OverType from 'overtype' -import type { - CommentEnhancer, - CommentSpot, - OvertypeWithCleanup, - StrippedLocation, -} from '../../enhancer' +import OverType, { type OverTypeInstance } from 'overtype' +import type { CommentEnhancer, CommentSpot, StrippedLocation } from '../../enhancer' import { logger } from '../../logger' import { modifyDOM } from '../modifyDOM' import { commonGitHubOptions, prepareGitHubHighlighter } from './github-common' @@ -65,16 +60,14 @@ export class GitHubPrCreateEnhancer implements CommentEnhancer { spot: T enhancer: CommentEnhancer overtype: OverTypeInstance - cleanup?: () => void } export class EnhancerRegistry { @@ -72,9 +71,9 @@ export class EnhancerRegistry { try { const spot = enhancer.tryToEnhance(textarea, location) if (spot) { - const { instance: overtype, cleanup } = enhancer.enhance(textarea, spot) + const overtype = enhancer.enhance(textarea, spot) this.handleDelayedValueInjection(overtype) - return { enhancer, overtype, spot, textarea, ...(cleanup && { cleanup }) } + return { enhancer, overtype, spot, textarea } } } catch (error) { console.warn('Handler failed to identify textarea:', error) @@ -130,12 +129,15 @@ export class TextareaRegistry { this.sendEvent('ENHANCED', enhanced) } + private cleanupOvertype(overtype: OverTypeInstance) { + const container = overtype.element + OverType.instances.delete(container) + ;(container as any).overTypeInstance = undefined + } unregisterDueToModification(textarea: HTMLTextAreaElement): void { const enhanced = this.textareas.get(textarea) if (enhanced) { - if (enhanced.cleanup) { - enhanced.cleanup() - } + this.cleanupOvertype(enhanced.overtype) this.sendEvent('DESTROYED', enhanced) this.textareas.delete(textarea) }