diff --git a/src/index.ts b/src/index.ts index eb4fd12..99a5537 100644 --- a/src/index.ts +++ b/src/index.ts @@ -78,6 +78,7 @@ async function bootstrap() { title: '', body: '', activeField: 'title', + cursor: 0 }, addCommentForm: { body: '', @@ -86,6 +87,7 @@ async function bootstrap() { title: '', body: '', activeField: 'title', + cursor: 0 }, addTimeForm: { timeInput: '', diff --git a/src/tui.ts b/src/tui.ts index d028563..064ef9e 100644 --- a/src/tui.ts +++ b/src/tui.ts @@ -435,6 +435,8 @@ export class TuiEngine { this.handleDetailsKeypress(str, key); } else if (this.state.screen === 'create-issue') { this.handleCreateIssueKeypress(str, key); + } else if (this.state.screen === 'confirm-cancel-create') { + this.handleConfirmCancelCreateKeypress(str, key); } else if (this.state.screen === 'add-comment') { this.handleAddCommentKeypress(str, key); } else if (this.state.screen === 'edit-issue') { @@ -883,6 +885,7 @@ export class TuiEngine { this.state.createIssueForm.title = ''; this.state.createIssueForm.body = ''; this.state.createIssueForm.activeField = 'title'; + this.state.createIssueForm.cursor = this.state.createIssueForm.title.length; this.render(); return; } @@ -931,6 +934,32 @@ export class TuiEngine { * Key handling for detail screen scrolling */ private handleDetailsKeypress(str: string, key: any) { + if (key && key.name === 'tab') { + this.state.focusedPane = this.state.focusedPane === 'settings' ? 'list' : 'settings'; + this.render(); + return; + } + + if (this.state.focusedPane === 'settings') { + if ((key && key.name === 'return') || str === '\r' || str === '\n') { + const intervals = [0, 15, 30, 60, 300]; + const currentIdx = intervals.indexOf(this.state.autoRefreshInterval || 0); + this.state.autoRefreshInterval = intervals[(currentIdx + 1) % intervals.length]; + this.state.config.autoRefreshInterval = this.state.autoRefreshInterval; + this.restartAutoRefresh(); + saveGlobalConfig(this.state.config); + this.render(); + return; + } + if ((key && key.name === 'escape') || str === '\u001b') { + this.state.focusedPane = 'list'; + this.render(); + return; + } + // Swallow other keys in settings mode + return; + } + if (key && key.name === 'up') { if (this.state.detailScrollOffset > 0) { this.state.detailScrollOffset--; @@ -981,6 +1010,7 @@ export class TuiEngine { this.state.editIssueForm.title = this.state.selectedIssue.title; this.state.editIssueForm.body = this.state.selectedIssue.body; this.state.editIssueForm.activeField = 'title'; + this.state.editIssueForm.cursor = this.state.editIssueForm.title.length; this.state.error = null; this.render(); } @@ -1068,6 +1098,8 @@ export class TuiEngine { this.renderAddTimeScreen(cols, rows); } else if (this.state.screen === 'confirm-state-change') { this.renderConfirmStateChangeScreen(cols, rows); + } else if (this.state.screen === 'confirm-cancel-create') { + this.renderConfirmCancelCreateScreen(cols, rows); } else if (this.state.screen === 'animating-close') { this.renderAnimationScreen(cols, rows, GRAVESTONE_FRAMES); } else if (this.state.screen === 'animating-reopen') { @@ -1568,7 +1600,7 @@ export class TuiEngine { // Paginate/Scroll calculations const metaHeight = labels ? 3 : 2; // Meta 1, optionally Meta 2 (Labels), and Separator - const displayHeight = rows - 5 - metaHeight; // Header: 1, Borders: 2, Meta lines, Help: 1, and -1 to avoid scrolling + const displayHeight = rows - 7 - metaHeight; // Header: 1, Borders: 3, Meta, Settings: 1, Help: 1, and -1 const maxScroll = Math.max(0, contentLines.length - displayHeight); // Bounds check scroll offset @@ -1589,6 +1621,22 @@ export class TuiEngine { } } + console.log(chalk.bold.hex('#4A90E2')('├' + '─'.repeat(cols - 2) + '┤')); + + const isSettings = this.state.focusedPane === 'settings'; + let refreshStr = 'Off'; + if (this.state.autoRefreshInterval) { + if (this.state.autoRefreshInterval >= 60) { + refreshStr = `${this.state.autoRefreshInterval / 60}m`; + } else { + refreshStr = `${this.state.autoRefreshInterval}s`; + } + } + const text = ` Auto-Refresh: ${refreshStr} `; + const coloredText = isSettings ? chalk.bgHex('#2E4E7E').white.bold(text) : chalk.gray(` Auto-Refresh: `) + chalk.bold(refreshStr); + const paddingLength = Math.max(0, cols - 2 - stripAnsi(text).length - 1); + console.log(borderCh + ' ' + coloredText + ' '.repeat(paddingLength) + borderCh); + console.log(chalk.bold.hex('#4A90E2')('└' + '─'.repeat(cols - 2) + '┘')); // Footer Scroll percentage @@ -1599,7 +1647,10 @@ export class TuiEngine { } const actionKey = issue.state === 'open' ? 'Close' : 'Reopen'; - const helpLine = chalk.gray(` [?] Help [Esc/Backspace] Back to List [C] Add Comment [A] Assign [E] Edit [T] Add Time [X] ${actionKey} [R] Reload [O] Settings ${scrollHelp}`); + let helpLine = chalk.gray(` [?] Help [Esc/Backspace] Back to List [C] Add Comment [A] Assign [E] Edit [T] Add Time [X] ${actionKey} [R] Reload [Tab] Menu ${scrollHelp}`); + if (isSettings) { + helpLine = chalk.gray(` [?] Help [Tab] Back to Details [Enter] Toggle Auto-Refresh [Esc] Quit menu`); + } process.stdout.write(helpLine); } @@ -1708,14 +1759,30 @@ export class TuiEngine { // Title Field let titleLabel = ' Title: '; let titleVisible = form.title; - if (titleVisible.length > cols - 13) { - titleVisible = titleVisible.substring(titleVisible.length - (cols - 13)); - } - let titleContent = titleVisible.padEnd(cols - 12); + let titleContent = ''; + if (form.activeField === 'title') { titleLabel = chalk.yellow(' Title: '); - titleContent = chalk.inverse(titleVisible + ' ') + ' '.repeat(Math.max(0, cols - 13 - titleVisible.length)); + const c = Math.min(form.cursor || 0, form.title.length); + + let startIdx = 0; + if (c > cols - 13) { + startIdx = c - (cols - 13) + 1; + } + + const visTitle = form.title.substring(startIdx); + const visCursor = c - startIdx; + + const visBefore = visTitle.substring(0, visCursor); + const visChar = visCursor < visTitle.length ? visTitle[visCursor] : ' '; + const visAfter = visTitle.substring(visCursor + 1).padEnd(Math.max(0, cols - 13 - visBefore.length - 1), ' ').substring(0, Math.max(0, cols - 13 - visBefore.length - 1)); + + titleContent = visBefore + chalk.inverse(visChar) + visAfter; + } else { + titleVisible = titleVisible.length > cols - 13 ? titleVisible.substring(titleVisible.length - (cols - 13)) : titleVisible; + titleContent = titleVisible.padEnd(cols - 12); } + console.log(borderCh + titleLabel + titleContent + borderCh); console.log(chalk.bold.hex('#4A90E2')('├' + '─'.repeat(cols - 2) + '┤')); @@ -1730,10 +1797,29 @@ export class TuiEngine { // Body Content (multiline) const errorOffset = this.state.error ? 2 : 0; const bodyRows = rows - 10 - errorOffset; // available space for body - const lines = form.body.split('\n'); - // Pagination for body if it gets too long - const startLine = Math.max(0, lines.length - bodyRows); + const lines = form.body.split('\n'); + let cursorLine = 0; + let cursorCol = 0; + let remaining = Math.min(form.cursor || 0, form.body.length); + + for (let i = 0; i < lines.length; i++) { + if (remaining <= lines[i].length) { + cursorLine = i; + cursorCol = remaining; + break; + } + remaining -= lines[i].length + 1; + } + + let startLine = Math.max(0, lines.length - bodyRows); + if (form.activeField === 'body') { + if (cursorLine < startLine) { + startLine = cursorLine; + } else if (cursorLine >= startLine + bodyRows) { + startLine = cursorLine - bodyRows + 1; + } + } for (let i = 0; i < bodyRows; i++) { const lineIdx = startLine + i; @@ -1742,18 +1828,34 @@ export class TuiEngine { lineContent = lines[lineIdx]; } - let visLine = lineContent; - if (visLine.length > cols - 5) { - visLine = visLine.substring(visLine.length - (cols - 5)); - } - - if (form.activeField === 'body' && lineIdx === lines.length - 1) { - visLine = visLine + chalk.inverse(' ') + ' '.repeat(Math.max(0, cols - 5 - stripAnsi(visLine).length)); + if (form.activeField === 'body' && lineIdx === cursorLine) { + let startCol = 0; + if (cursorCol > cols - 6) { + startCol = cursorCol - (cols - 6) + 1; + } + + const visLineStr = lineContent.substring(startCol); + const visCursorCol = cursorCol - startCol; + + const before = visLineStr.substring(0, visCursorCol); + const char = visCursorCol < visLineStr.length ? visLineStr[visCursorCol] : ' '; + const after = visLineStr.substring(visCursorCol + 1); + + let visLine = before + chalk.inverse(char) + after; + const plainLen = stripAnsi(visLine).length; + if (plainLen < cols - 4) { + visLine += ' '.repeat(cols - 4 - plainLen); + } + + console.log(borderCh + ' ' + visLine + ' ' + borderCh); } else { - visLine = visLine.padEnd(cols - 4); + let visLineStr = lineContent; + if (visLineStr.length > cols - 5) { + visLineStr = visLineStr.substring(visLineStr.length - (cols - 5)); + } + visLineStr = visLineStr.padEnd(cols - 4); + console.log(borderCh + ' ' + visLineStr + ' ' + borderCh); } - - console.log(borderCh + ' ' + visLine + ' ' + borderCh); } console.log(chalk.bold.hex('#4A90E2')('└' + '─'.repeat(cols - 2) + '┘')); @@ -1778,7 +1880,7 @@ export class TuiEngine { */ private async handleCreateIssueKeypress(str: string, key: any) { if (key && key.name === 'escape') { - this.state.screen = 'list'; + this.state.screen = 'confirm-cancel-create'; this.state.error = null; this.render(); return; @@ -1813,6 +1915,7 @@ export class TuiEngine { if (key && key.name === 'tab') { this.state.createIssueForm.activeField = this.state.createIssueForm.activeField === 'title' ? 'body' : 'title'; + this.state.createIssueForm.cursor = this.state.createIssueForm[this.state.createIssueForm.activeField].length; this.render(); return; } @@ -1820,10 +1923,59 @@ export class TuiEngine { // Text input handling const activeField = this.state.createIssueForm.activeField; let val = this.state.createIssueForm[activeField]; + let cursor = this.state.createIssueForm.cursor; + if (cursor === undefined || cursor > val.length) cursor = val.length; - if (key && key.name === 'backspace') { - if (val.length > 0) { - this.state.createIssueForm[activeField] = val.slice(0, -1); + if (key && key.name === 'left') { + this.state.createIssueForm.cursor = Math.max(0, cursor - 1); + this.render(); + return; + } + + if (key && key.name === 'right') { + this.state.createIssueForm.cursor = Math.min(val.length, cursor + 1); + this.render(); + return; + } + + if (key && (key.name === 'up' || key.name === 'down') && activeField === 'body') { + const lines = val.split(' +'); + let lineIdx = 0, col = 0, count = 0; + for (let i = 0; i < lines.length; i++) { + if (count + lines[i].length >= cursor) { + lineIdx = i; + col = cursor - count; + break; + } + count += lines[i].length + 1; + } + + if (key.name === 'up' && lineIdx > 0) { + const prevLine = lines[lineIdx - 1]; + const newCol = Math.min(col, prevLine.length); + let newCursor = 0; + for (let i = 0; i < lineIdx - 1; i++) newCursor += lines[i].length + 1; + this.state.createIssueForm.cursor = newCursor + newCol; + this.render(); + } else if (key.name === 'down' && lineIdx < lines.length - 1) { + const nextLine = lines[lineIdx + 1]; + const newCol = Math.min(col, nextLine.length); + let newCursor = 0; + for (let i = 0; i <= lineIdx; i++) newCursor += lines[i].length + 1; + this.state.createIssueForm.cursor = newCursor + newCol; + this.render(); + } + return; + } + + if (key && (key.name === 'backspace' || key.name === 'delete')) { + if (key.name === 'backspace' && cursor > 0) { + this.state.createIssueForm[activeField] = val.slice(0, cursor - 1) + val.slice(cursor); + this.state.createIssueForm.cursor = cursor - 1; + this.render(); + } else if (key.name === 'delete' && cursor < val.length) { + this.state.createIssueForm[activeField] = val.slice(0, cursor) + val.slice(cursor + 1); this.render(); } return; @@ -1831,11 +1983,13 @@ export class TuiEngine { if (key && key.name === 'return') { if (activeField === 'body') { - this.state.createIssueForm.body += '\n'; + this.state.createIssueForm.body = val.slice(0, cursor) + ' +' + val.slice(cursor); + this.state.createIssueForm.cursor = cursor + 1; this.render(); } else { - // In title, return switches to body this.state.createIssueForm.activeField = 'body'; + this.state.createIssueForm.cursor = this.state.createIssueForm.body.length; this.render(); } return; @@ -1843,7 +1997,8 @@ export class TuiEngine { if (str && str.length === 1 && !key.ctrl && !key.meta) { this.state.error = null; - this.state.createIssueForm[activeField] += str; + this.state.createIssueForm[activeField] = val.slice(0, cursor) + str + val.slice(cursor); + this.state.createIssueForm.cursor = cursor + 1; this.render(); } } @@ -2000,14 +2155,30 @@ export class TuiEngine { // Title Field let titleLabel = ' Title: '; let titleVisible = form.title; - if (titleVisible.length > cols - 13) { - titleVisible = titleVisible.substring(titleVisible.length - (cols - 13)); - } - let titleContent = titleVisible.padEnd(cols - 12); + let titleContent = ''; + if (form.activeField === 'title') { titleLabel = chalk.yellow(' Title: '); - titleContent = chalk.inverse(titleVisible + ' ') + ' '.repeat(Math.max(0, cols - 13 - titleVisible.length)); + const c = Math.min(form.cursor || 0, form.title.length); + + let startIdx = 0; + if (c > cols - 13) { + startIdx = c - (cols - 13) + 1; + } + + const visTitle = form.title.substring(startIdx); + const visCursor = c - startIdx; + + const visBefore = visTitle.substring(0, visCursor); + const visChar = visCursor < visTitle.length ? visTitle[visCursor] : ' '; + const visAfter = visTitle.substring(visCursor + 1).padEnd(Math.max(0, cols - 13 - visBefore.length - 1), ' ').substring(0, Math.max(0, cols - 13 - visBefore.length - 1)); + + titleContent = visBefore + chalk.inverse(visChar) + visAfter; + } else { + titleVisible = titleVisible.length > cols - 13 ? titleVisible.substring(titleVisible.length - (cols - 13)) : titleVisible; + titleContent = titleVisible.padEnd(cols - 12); } + console.log(borderCh + titleLabel + titleContent + borderCh); console.log(chalk.bold.hex('#4A90E2')('├' + '─'.repeat(cols - 2) + '┤')); @@ -2022,10 +2193,29 @@ export class TuiEngine { // Body Content (multiline) const errorOffset = this.state.error ? 2 : 0; const bodyRows = rows - 10 - errorOffset; // available space for body - const lines = form.body.split('\n'); - // Pagination for body if it gets too long - const startLine = Math.max(0, lines.length - bodyRows); + const lines = form.body.split('\n'); + let cursorLine = 0; + let cursorCol = 0; + let remaining = Math.min(form.cursor || 0, form.body.length); + + for (let i = 0; i < lines.length; i++) { + if (remaining <= lines[i].length) { + cursorLine = i; + cursorCol = remaining; + break; + } + remaining -= lines[i].length + 1; + } + + let startLine = Math.max(0, lines.length - bodyRows); + if (form.activeField === 'body') { + if (cursorLine < startLine) { + startLine = cursorLine; + } else if (cursorLine >= startLine + bodyRows) { + startLine = cursorLine - bodyRows + 1; + } + } for (let i = 0; i < bodyRows; i++) { const lineIdx = startLine + i; @@ -2034,18 +2224,34 @@ export class TuiEngine { lineContent = lines[lineIdx]; } - let visLine = lineContent; - if (visLine.length > cols - 5) { - visLine = visLine.substring(visLine.length - (cols - 5)); - } - - if (form.activeField === 'body' && lineIdx === lines.length - 1) { - visLine = visLine + chalk.inverse(' ') + ' '.repeat(Math.max(0, cols - 5 - stripAnsi(visLine).length)); + if (form.activeField === 'body' && lineIdx === cursorLine) { + let startCol = 0; + if (cursorCol > cols - 6) { + startCol = cursorCol - (cols - 6) + 1; + } + + const visLineStr = lineContent.substring(startCol); + const visCursorCol = cursorCol - startCol; + + const before = visLineStr.substring(0, visCursorCol); + const char = visCursorCol < visLineStr.length ? visLineStr[visCursorCol] : ' '; + const after = visLineStr.substring(visCursorCol + 1); + + let visLine = before + chalk.inverse(char) + after; + const plainLen = stripAnsi(visLine).length; + if (plainLen < cols - 4) { + visLine += ' '.repeat(cols - 4 - plainLen); + } + + console.log(borderCh + ' ' + visLine + ' ' + borderCh); } else { - visLine = visLine.padEnd(cols - 4); + let visLineStr = lineContent; + if (visLineStr.length > cols - 5) { + visLineStr = visLineStr.substring(visLineStr.length - (cols - 5)); + } + visLineStr = visLineStr.padEnd(cols - 4); + console.log(borderCh + ' ' + visLineStr + ' ' + borderCh); } - - console.log(borderCh + ' ' + visLine + ' ' + borderCh); } console.log(chalk.bold.hex('#4A90E2')('└' + '─'.repeat(cols - 2) + '┘')); @@ -2113,6 +2319,7 @@ export class TuiEngine { if (key && key.name === 'tab') { this.state.editIssueForm.activeField = this.state.editIssueForm.activeField === 'title' ? 'body' : 'title'; + this.state.editIssueForm.cursor = this.state.editIssueForm[this.state.editIssueForm.activeField].length; this.render(); return; } @@ -2120,10 +2327,59 @@ export class TuiEngine { // Text input handling const activeField = this.state.editIssueForm.activeField; let val = this.state.editIssueForm[activeField]; + let cursor = this.state.editIssueForm.cursor; + if (cursor === undefined || cursor > val.length) cursor = val.length; - if (key && key.name === 'backspace') { - if (val.length > 0) { - this.state.editIssueForm[activeField] = val.slice(0, -1); + if (key && key.name === 'left') { + this.state.editIssueForm.cursor = Math.max(0, cursor - 1); + this.render(); + return; + } + + if (key && key.name === 'right') { + this.state.editIssueForm.cursor = Math.min(val.length, cursor + 1); + this.render(); + return; + } + + if (key && (key.name === 'up' || key.name === 'down') && activeField === 'body') { + const lines = val.split(' +'); + let lineIdx = 0, col = 0, count = 0; + for (let i = 0; i < lines.length; i++) { + if (count + lines[i].length >= cursor) { + lineIdx = i; + col = cursor - count; + break; + } + count += lines[i].length + 1; + } + + if (key.name === 'up' && lineIdx > 0) { + const prevLine = lines[lineIdx - 1]; + const newCol = Math.min(col, prevLine.length); + let newCursor = 0; + for (let i = 0; i < lineIdx - 1; i++) newCursor += lines[i].length + 1; + this.state.editIssueForm.cursor = newCursor + newCol; + this.render(); + } else if (key.name === 'down' && lineIdx < lines.length - 1) { + const nextLine = lines[lineIdx + 1]; + const newCol = Math.min(col, nextLine.length); + let newCursor = 0; + for (let i = 0; i <= lineIdx; i++) newCursor += lines[i].length + 1; + this.state.editIssueForm.cursor = newCursor + newCol; + this.render(); + } + return; + } + + if (key && (key.name === 'backspace' || key.name === 'delete')) { + if (key.name === 'backspace' && cursor > 0) { + this.state.editIssueForm[activeField] = val.slice(0, cursor - 1) + val.slice(cursor); + this.state.editIssueForm.cursor = cursor - 1; + this.render(); + } else if (key.name === 'delete' && cursor < val.length) { + this.state.editIssueForm[activeField] = val.slice(0, cursor) + val.slice(cursor + 1); this.render(); } return; @@ -2131,11 +2387,13 @@ export class TuiEngine { if (key && key.name === 'return') { if (activeField === 'body') { - this.state.editIssueForm.body += '\n'; + this.state.editIssueForm.body = val.slice(0, cursor) + ' +' + val.slice(cursor); + this.state.editIssueForm.cursor = cursor + 1; this.render(); } else { - // In title, return switches to body this.state.editIssueForm.activeField = 'body'; + this.state.editIssueForm.cursor = this.state.editIssueForm.body.length; this.render(); } return; @@ -2143,7 +2401,8 @@ export class TuiEngine { if (str && str.length === 1 && !key.ctrl && !key.meta) { this.state.error = null; - this.state.editIssueForm[activeField] += str; + this.state.editIssueForm[activeField] = val.slice(0, cursor) + str + val.slice(cursor); + this.state.editIssueForm.cursor = cursor + 1; this.render(); } } @@ -2372,6 +2631,50 @@ export class TuiEngine { return; } } + + private renderConfirmCancelCreateScreen(cols: number, rows: number) { + const borderCh = chalk.bold.hex('#4A90E2')('│'); + + const title = ' Confirm Cancel '; + const leftLen = stripAnsi(title).length; + let leftHeader = chalk.bold.hex('#4A90E2')(title); + + console.log(leftHeader + ' '.repeat(Math.max(1, cols - leftLen - 1))); + console.log(chalk.bold.hex('#4A90E2')('┌' + '─'.repeat(cols - 2) + '┐')); + + const question = 'Are you sure you want to cancel creating this issue?'; + const promptPlain = ` ${question} [y/N] `; + + const paddingRows = Math.floor((rows - 8) / 2); + for (let i = 0; i < paddingRows; i++) console.log(borderCh + ' '.repeat(cols - 2) + borderCh); + + const padContentLeft = Math.max(0, Math.floor((cols - 2 - promptPlain.length) / 2)); + const contentLine = (' '.repeat(padContentLeft) + chalk.yellow.bold(question) + ' [y/N] ').padEnd(cols - 2 + (chalk.yellow.bold(question).length - question.length)); + console.log(borderCh + contentLine + borderCh); + + for (let i = 0; i < (rows - 8) - paddingRows; i++) console.log(borderCh + ' '.repeat(cols - 2) + borderCh); + + console.log(chalk.bold.hex('#4A90E2')('└' + '─'.repeat(cols - 2) + '┘')); + + const helpLinePlain = ' [y] Yes [n/Esc] No'; + process.stdout.write(chalk.gray(helpLinePlain)); + } + + private async handleConfirmCancelCreateKeypress(str: string, key: any) { + if ((key && (key.name === 'escape' || key.name === 'n')) || str === 'n' || str === 'N') { + this.state.screen = 'create-issue'; + this.state.error = null; + this.render(); + return; + } + + if (str === 'y' || str === 'Y') { + this.state.screen = 'list'; + this.state.error = null; + this.render(); + return; + } + } private renderSetAssigneesScreen(cols: number, rows: number) { const borderCh = chalk.bold.hex('#4A90E2')('│'); diff --git a/src/types.ts b/src/types.ts index 3f31a70..bf51e81 100644 --- a/src/types.ts +++ b/src/types.ts @@ -57,6 +57,7 @@ export interface CreateIssueForm { title: string; body: string; activeField: 'title' | 'body'; + cursor: number; } export interface AddCommentForm { @@ -67,6 +68,7 @@ export interface EditIssueForm { title: string; body: string; activeField: 'title' | 'body'; + cursor: number; } export interface AddTimeForm { @@ -81,7 +83,7 @@ export interface LabelForm { activeField: 'name' | 'color' | 'description' | 'exclusive'; } -export type ScreenType = 'launch' | 'setup' | 'repo-picker' | 'list' | 'details' | 'create-issue' | 'add-comment' | 'edit-issue' | 'add-time' | 'confirm-state-change' | 'animating-close' | 'animating-reopen' | 'set-assignees' | 'labels-list' | 'create-label' | 'edit-label' | 'help'; +export type ScreenType = 'launch' | 'setup' | 'repo-picker' | 'list' | 'details' | 'create-issue' | 'add-comment' | 'edit-issue' | 'add-time' | 'confirm-state-change' | 'confirm-cancel-create' | 'animating-close' | 'animating-reopen' | 'set-assignees' | 'labels-list' | 'create-label' | 'edit-label' | 'help'; export interface RepoItem {