@@ -47,6 +47,13 @@ const TITLE_INPUT_SELECTORS = [
4747
4848const BODY_INPUT_SELECTORS = [ 'textarea[aria-label="Markdown value"]' ]
4949
50+ // The dialog's metadata sidebar renders the currently-picked assignees/labels
51+ // as tokens once selected via their own pickers. Best-effort read: if none of
52+ // these match, we simply omit assignees/labels from the create payload rather
53+ // than failing the create.
54+ const ASSIGNEES_CONTAINER_SELECTORS = [ '[data-testid="assignees-select-menu"]' ]
55+ const LABELS_CONTAINER_SELECTORS = [ '[data-testid="labels-select-menu"]' ]
56+
5057// The board's native "+ Add item" omnibar row that (when typed into) spawns
5158// this dialog with its text as the initial title. Closing the dialog through
5259// our intercepted Create button skips GitHub's own handler, which normally
@@ -56,8 +63,9 @@ const OMNIBAR_INPUT_SELECTORS = ['[class*="omnibarInput"]']
5663// After a "Create more" cycle, GitHub's dialog considers its form dirty and,
5764// on Close, shows this native confirmation instead of closing directly. RGP's
5865// own Close click doesn't know about it, so the dialog appears stuck.
59- const CONFIRM_DISCARD_SELECTOR =
60- '[data-component="ConfirmationDialog"] button[data-variant="danger"]'
66+ const CONFIRM_DISCARD_DIALOG_SELECTOR = '[data-component="ConfirmationDialog"]'
67+ const CONFIRM_DISCARD_BUTTON_SELECTOR = 'button[data-variant="danger"]'
68+ const CONFIRM_DISCARD_TEXT_PATTERN = / d i s c a r d / i
6169
6270// The board re-renders once the new item lands (the background's
6371// create→attach→field-update chain, several seconds after dispatch), and that
@@ -119,6 +127,30 @@ function readCreateMore(dialog: Element): boolean {
119127 return dialog . querySelector < HTMLInputElement > ( CREATE_MORE_SELECTOR ) ?. checked ?? false
120128}
121129
130+ function readAssignees ( dialog : Element ) : string [ ] {
131+ for ( const sel of ASSIGNEES_CONTAINER_SELECTORS ) {
132+ const container = dialog . querySelector ( sel )
133+ if ( ! container ) continue
134+ const logins = Array . from ( container . querySelectorAll < HTMLImageElement > ( 'img[alt]' ) )
135+ . map ( ( img ) => img . alt . replace ( / ^ @ / , '' ) . trim ( ) )
136+ . filter ( Boolean )
137+ if ( logins . length ) return logins
138+ }
139+ return [ ]
140+ }
141+
142+ function readLabels ( dialog : Element ) : string [ ] {
143+ for ( const sel of LABELS_CONTAINER_SELECTORS ) {
144+ const container = dialog . querySelector ( sel )
145+ if ( ! container ) continue
146+ const names = Array . from ( container . querySelectorAll ( '[data-testid="label-token"]' ) )
147+ . map ( ( el ) => el . textContent ?. trim ( ) ?? '' )
148+ . filter ( Boolean )
149+ if ( names . length ) return names
150+ }
151+ return [ ]
152+ }
153+
122154function readRepo ( dialog : Element ) : { owner : string ; name : string } | null {
123155 for ( const sel of REPO_HEADING_SELECTORS ) {
124156 const el = dialog . querySelector ( sel )
@@ -164,6 +196,9 @@ function buildCreatePayload(
164196 }
165197 }
166198
199+ const assignees = readAssignees ( dialog )
200+ const labels = readLabels ( dialog )
201+
167202 return {
168203 projectId,
169204 repoOwner : repo . owner ,
@@ -173,6 +208,8 @@ function buildCreatePayload(
173208 createMore : readCreateMore ( dialog ) ,
174209 updates,
175210 fieldMeta,
211+ ...( assignees . length ? { assignees } : { } ) ,
212+ ...( labels . length ? { labels } : { } ) ,
176213 }
177214}
178215
@@ -186,23 +223,46 @@ export function setupCreateIssueFieldInjector(
186223 let rafId : number | null = null
187224 let intercepting = false
188225 let omnibarWatchUntil : number | null = null
226+ let omnibarSeedText : string | null = null
227+
228+ function readOmnibarValue ( ) : string {
229+ for ( const sel of OMNIBAR_INPUT_SELECTORS ) {
230+ const el = document . querySelector < HTMLInputElement > ( sel )
231+ if ( el ) return el . value
232+ }
233+ return ''
234+ }
189235
190236 function clearOmnibar ( ) : void {
191237 for ( const sel of OMNIBAR_INPUT_SELECTORS ) {
192238 document . querySelectorAll < HTMLInputElement > ( sel ) . forEach ( clearInput )
193239 }
194240 }
195241
242+ // Same as clearOmnibar, but only touches inputs whose value still matches
243+ // the draft text captured at dispatch time — a value the user has since
244+ // typed over is left alone instead of being wiped by the re-render watch.
245+ function clearOmnibarIfUnchanged ( seed : string ) : void {
246+ for ( const sel of OMNIBAR_INPUT_SELECTORS ) {
247+ document . querySelectorAll < HTMLInputElement > ( sel ) . forEach ( ( el ) => {
248+ if ( el . value === seed ) clearInput ( el )
249+ } )
250+ }
251+ }
252+
196253 // GitHub's "Discard changes?" confirmation (if our Close click triggers one)
197254 // mounts asynchronously — it isn't in the DOM yet on the same tick as the
198255 // click. Poll a few animation frames instead of checking once.
199256 async function dismissDiscardConfirm ( timeoutMs = 5000 ) : Promise < void > {
200257 const deadline = Date . now ( ) + timeoutMs
201258 while ( Date . now ( ) < deadline ) {
202- const btn = document . querySelector < HTMLButtonElement > ( CONFIRM_DISCARD_SELECTOR )
203- if ( btn ) {
204- btn . click ( )
205- return
259+ const dialog = document . querySelector < HTMLElement > ( CONFIRM_DISCARD_DIALOG_SELECTOR )
260+ if ( dialog && CONFIRM_DISCARD_TEXT_PATTERN . test ( dialog . textContent ?? '' ) ) {
261+ const btn = dialog . querySelector < HTMLButtonElement > ( CONFIRM_DISCARD_BUTTON_SELECTOR )
262+ if ( btn ) {
263+ btn . click ( )
264+ return
265+ }
206266 }
207267 await new Promise ( ( resolve ) => requestAnimationFrame ( resolve ) )
208268 }
@@ -241,13 +301,15 @@ export function setupCreateIssueFieldInjector(
241301
242302 if ( ! payload . createMore ) {
243303 createIssueFieldsStore . clearAll ( )
304+ omnibarSeedText = readOmnibarValue ( )
244305 dialog . querySelector < HTMLButtonElement > ( '[data-component="Dialog.CloseButton"]' ) ?. click ( )
245306 // Fallback in case some other field still marks the form dirty.
246307 await dismissDiscardConfirm ( )
247308 clearOmnibar ( )
248309 // The board re-renders once the new item lands (async, seconds later),
249310 // and that re-render restores the omnibar's original draft text once.
250- // check() re-clears it on every subsequent mutation until this expires.
311+ // check() re-clears it on every subsequent mutation until this expires,
312+ // but only if the user hasn't since typed a new draft into it.
251313 omnibarWatchUntil = Date . now ( ) + OMNIBAR_WATCH_MS
252314 }
253315 } catch {
@@ -309,8 +371,9 @@ export function setupCreateIssueFieldInjector(
309371 if ( omnibarWatchUntil !== null ) {
310372 if ( Date . now ( ) > omnibarWatchUntil ) {
311373 omnibarWatchUntil = null
312- } else {
313- clearOmnibar ( )
374+ omnibarSeedText = null
375+ } else if ( omnibarSeedText !== null ) {
376+ clearOmnibarIfUnchanged ( omnibarSeedText )
314377 }
315378 }
316379
0 commit comments