escape confirmation on create issue page

This commit is contained in:
Isaac Johnson 2026-06-04 16:18:12 -05:00
parent dcc2bbaa82
commit 759954f328
3 changed files with 359 additions and 52 deletions

View File

@ -78,6 +78,7 @@ async function bootstrap() {
title: '', title: '',
body: '', body: '',
activeField: 'title', activeField: 'title',
cursor: 0
}, },
addCommentForm: { addCommentForm: {
body: '', body: '',
@ -86,6 +87,7 @@ async function bootstrap() {
title: '', title: '',
body: '', body: '',
activeField: 'title', activeField: 'title',
cursor: 0
}, },
addTimeForm: { addTimeForm: {
timeInput: '', timeInput: '',

View File

@ -435,6 +435,8 @@ export class TuiEngine {
this.handleDetailsKeypress(str, key); this.handleDetailsKeypress(str, key);
} else if (this.state.screen === 'create-issue') { } else if (this.state.screen === 'create-issue') {
this.handleCreateIssueKeypress(str, key); this.handleCreateIssueKeypress(str, key);
} else if (this.state.screen === 'confirm-cancel-create') {
this.handleConfirmCancelCreateKeypress(str, key);
} else if (this.state.screen === 'add-comment') { } else if (this.state.screen === 'add-comment') {
this.handleAddCommentKeypress(str, key); this.handleAddCommentKeypress(str, key);
} else if (this.state.screen === 'edit-issue') { } else if (this.state.screen === 'edit-issue') {
@ -883,6 +885,7 @@ export class TuiEngine {
this.state.createIssueForm.title = ''; this.state.createIssueForm.title = '';
this.state.createIssueForm.body = ''; this.state.createIssueForm.body = '';
this.state.createIssueForm.activeField = 'title'; this.state.createIssueForm.activeField = 'title';
this.state.createIssueForm.cursor = this.state.createIssueForm.title.length;
this.render(); this.render();
return; return;
} }
@ -931,6 +934,32 @@ export class TuiEngine {
* Key handling for detail screen scrolling * Key handling for detail screen scrolling
*/ */
private handleDetailsKeypress(str: string, key: any) { 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 (key && key.name === 'up') {
if (this.state.detailScrollOffset > 0) { if (this.state.detailScrollOffset > 0) {
this.state.detailScrollOffset--; this.state.detailScrollOffset--;
@ -981,6 +1010,7 @@ export class TuiEngine {
this.state.editIssueForm.title = this.state.selectedIssue.title; this.state.editIssueForm.title = this.state.selectedIssue.title;
this.state.editIssueForm.body = this.state.selectedIssue.body; this.state.editIssueForm.body = this.state.selectedIssue.body;
this.state.editIssueForm.activeField = 'title'; this.state.editIssueForm.activeField = 'title';
this.state.editIssueForm.cursor = this.state.editIssueForm.title.length;
this.state.error = null; this.state.error = null;
this.render(); this.render();
} }
@ -1068,6 +1098,8 @@ export class TuiEngine {
this.renderAddTimeScreen(cols, rows); this.renderAddTimeScreen(cols, rows);
} else if (this.state.screen === 'confirm-state-change') { } else if (this.state.screen === 'confirm-state-change') {
this.renderConfirmStateChangeScreen(cols, rows); this.renderConfirmStateChangeScreen(cols, rows);
} else if (this.state.screen === 'confirm-cancel-create') {
this.renderConfirmCancelCreateScreen(cols, rows);
} else if (this.state.screen === 'animating-close') { } else if (this.state.screen === 'animating-close') {
this.renderAnimationScreen(cols, rows, GRAVESTONE_FRAMES); this.renderAnimationScreen(cols, rows, GRAVESTONE_FRAMES);
} else if (this.state.screen === 'animating-reopen') { } else if (this.state.screen === 'animating-reopen') {
@ -1568,7 +1600,7 @@ export class TuiEngine {
// Paginate/Scroll calculations // Paginate/Scroll calculations
const metaHeight = labels ? 3 : 2; // Meta 1, optionally Meta 2 (Labels), and Separator 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); const maxScroll = Math.max(0, contentLines.length - displayHeight);
// Bounds check scroll offset // 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) + '┘')); console.log(chalk.bold.hex('#4A90E2')('└' + '─'.repeat(cols - 2) + '┘'));
// Footer Scroll percentage // Footer Scroll percentage
@ -1599,7 +1647,10 @@ export class TuiEngine {
} }
const actionKey = issue.state === 'open' ? 'Close' : 'Reopen'; 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); process.stdout.write(helpLine);
} }
@ -1708,14 +1759,30 @@ export class TuiEngine {
// Title Field // Title Field
let titleLabel = ' Title: '; let titleLabel = ' Title: ';
let titleVisible = form.title; let titleVisible = form.title;
if (titleVisible.length > cols - 13) { let titleContent = '';
titleVisible = titleVisible.substring(titleVisible.length - (cols - 13));
}
let titleContent = titleVisible.padEnd(cols - 12);
if (form.activeField === 'title') { if (form.activeField === 'title') {
titleLabel = chalk.yellow(' 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(borderCh + titleLabel + titleContent + borderCh);
console.log(chalk.bold.hex('#4A90E2')('├' + '─'.repeat(cols - 2) + '┤')); console.log(chalk.bold.hex('#4A90E2')('├' + '─'.repeat(cols - 2) + '┤'));
@ -1730,10 +1797,29 @@ export class TuiEngine {
// Body Content (multiline) // Body Content (multiline)
const errorOffset = this.state.error ? 2 : 0; const errorOffset = this.state.error ? 2 : 0;
const bodyRows = rows - 10 - errorOffset; // available space for body const bodyRows = rows - 10 - errorOffset; // available space for body
const lines = form.body.split('\n');
// Pagination for body if it gets too long const lines = form.body.split('\n');
const startLine = Math.max(0, lines.length - bodyRows); 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++) { for (let i = 0; i < bodyRows; i++) {
const lineIdx = startLine + i; const lineIdx = startLine + i;
@ -1742,18 +1828,34 @@ export class TuiEngine {
lineContent = lines[lineIdx]; lineContent = lines[lineIdx];
} }
let visLine = lineContent; if (form.activeField === 'body' && lineIdx === cursorLine) {
if (visLine.length > cols - 5) { let startCol = 0;
visLine = visLine.substring(visLine.length - (cols - 5)); if (cursorCol > cols - 6) {
startCol = cursorCol - (cols - 6) + 1;
} }
if (form.activeField === 'body' && lineIdx === lines.length - 1) { const visLineStr = lineContent.substring(startCol);
visLine = visLine + chalk.inverse(' ') + ' '.repeat(Math.max(0, cols - 5 - stripAnsi(visLine).length)); const visCursorCol = cursorCol - startCol;
} else {
visLine = visLine.padEnd(cols - 4); 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); console.log(borderCh + ' ' + visLine + ' ' + borderCh);
} else {
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(chalk.bold.hex('#4A90E2')('└' + '─'.repeat(cols - 2) + '┘')); console.log(chalk.bold.hex('#4A90E2')('└' + '─'.repeat(cols - 2) + '┘'));
@ -1778,7 +1880,7 @@ export class TuiEngine {
*/ */
private async handleCreateIssueKeypress(str: string, key: any) { private async handleCreateIssueKeypress(str: string, key: any) {
if (key && key.name === 'escape') { if (key && key.name === 'escape') {
this.state.screen = 'list'; this.state.screen = 'confirm-cancel-create';
this.state.error = null; this.state.error = null;
this.render(); this.render();
return; return;
@ -1813,6 +1915,7 @@ export class TuiEngine {
if (key && key.name === 'tab') { if (key && key.name === 'tab') {
this.state.createIssueForm.activeField = this.state.createIssueForm.activeField === 'title' ? 'body' : 'title'; 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(); this.render();
return; return;
} }
@ -1820,10 +1923,59 @@ export class TuiEngine {
// Text input handling // Text input handling
const activeField = this.state.createIssueForm.activeField; const activeField = this.state.createIssueForm.activeField;
let val = 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 (key && key.name === 'left') {
if (val.length > 0) { this.state.createIssueForm.cursor = Math.max(0, cursor - 1);
this.state.createIssueForm[activeField] = val.slice(0, -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(); this.render();
} }
return; return;
@ -1831,11 +1983,13 @@ export class TuiEngine {
if (key && key.name === 'return') { if (key && key.name === 'return') {
if (activeField === 'body') { 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(); this.render();
} else { } else {
// In title, return switches to body
this.state.createIssueForm.activeField = 'body'; this.state.createIssueForm.activeField = 'body';
this.state.createIssueForm.cursor = this.state.createIssueForm.body.length;
this.render(); this.render();
} }
return; return;
@ -1843,7 +1997,8 @@ export class TuiEngine {
if (str && str.length === 1 && !key.ctrl && !key.meta) { if (str && str.length === 1 && !key.ctrl && !key.meta) {
this.state.error = null; 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(); this.render();
} }
} }
@ -2000,14 +2155,30 @@ export class TuiEngine {
// Title Field // Title Field
let titleLabel = ' Title: '; let titleLabel = ' Title: ';
let titleVisible = form.title; let titleVisible = form.title;
if (titleVisible.length > cols - 13) { let titleContent = '';
titleVisible = titleVisible.substring(titleVisible.length - (cols - 13));
}
let titleContent = titleVisible.padEnd(cols - 12);
if (form.activeField === 'title') { if (form.activeField === 'title') {
titleLabel = chalk.yellow(' 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(borderCh + titleLabel + titleContent + borderCh);
console.log(chalk.bold.hex('#4A90E2')('├' + '─'.repeat(cols - 2) + '┤')); console.log(chalk.bold.hex('#4A90E2')('├' + '─'.repeat(cols - 2) + '┤'));
@ -2022,10 +2193,29 @@ export class TuiEngine {
// Body Content (multiline) // Body Content (multiline)
const errorOffset = this.state.error ? 2 : 0; const errorOffset = this.state.error ? 2 : 0;
const bodyRows = rows - 10 - errorOffset; // available space for body const bodyRows = rows - 10 - errorOffset; // available space for body
const lines = form.body.split('\n');
// Pagination for body if it gets too long const lines = form.body.split('\n');
const startLine = Math.max(0, lines.length - bodyRows); 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++) { for (let i = 0; i < bodyRows; i++) {
const lineIdx = startLine + i; const lineIdx = startLine + i;
@ -2034,18 +2224,34 @@ export class TuiEngine {
lineContent = lines[lineIdx]; lineContent = lines[lineIdx];
} }
let visLine = lineContent; if (form.activeField === 'body' && lineIdx === cursorLine) {
if (visLine.length > cols - 5) { let startCol = 0;
visLine = visLine.substring(visLine.length - (cols - 5)); if (cursorCol > cols - 6) {
startCol = cursorCol - (cols - 6) + 1;
} }
if (form.activeField === 'body' && lineIdx === lines.length - 1) { const visLineStr = lineContent.substring(startCol);
visLine = visLine + chalk.inverse(' ') + ' '.repeat(Math.max(0, cols - 5 - stripAnsi(visLine).length)); const visCursorCol = cursorCol - startCol;
} else {
visLine = visLine.padEnd(cols - 4); 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); console.log(borderCh + ' ' + visLine + ' ' + borderCh);
} else {
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(chalk.bold.hex('#4A90E2')('└' + '─'.repeat(cols - 2) + '┘')); console.log(chalk.bold.hex('#4A90E2')('└' + '─'.repeat(cols - 2) + '┘'));
@ -2113,6 +2319,7 @@ export class TuiEngine {
if (key && key.name === 'tab') { if (key && key.name === 'tab') {
this.state.editIssueForm.activeField = this.state.editIssueForm.activeField === 'title' ? 'body' : 'title'; 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(); this.render();
return; return;
} }
@ -2120,10 +2327,59 @@ export class TuiEngine {
// Text input handling // Text input handling
const activeField = this.state.editIssueForm.activeField; const activeField = this.state.editIssueForm.activeField;
let val = 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 (key && key.name === 'left') {
if (val.length > 0) { this.state.editIssueForm.cursor = Math.max(0, cursor - 1);
this.state.editIssueForm[activeField] = val.slice(0, -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(); this.render();
} }
return; return;
@ -2131,11 +2387,13 @@ export class TuiEngine {
if (key && key.name === 'return') { if (key && key.name === 'return') {
if (activeField === 'body') { 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(); this.render();
} else { } else {
// In title, return switches to body
this.state.editIssueForm.activeField = 'body'; this.state.editIssueForm.activeField = 'body';
this.state.editIssueForm.cursor = this.state.editIssueForm.body.length;
this.render(); this.render();
} }
return; return;
@ -2143,7 +2401,8 @@ export class TuiEngine {
if (str && str.length === 1 && !key.ctrl && !key.meta) { if (str && str.length === 1 && !key.ctrl && !key.meta) {
this.state.error = null; 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(); this.render();
} }
} }
@ -2372,6 +2631,50 @@ export class TuiEngine {
return; 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) { private renderSetAssigneesScreen(cols: number, rows: number) {
const borderCh = chalk.bold.hex('#4A90E2')('│'); const borderCh = chalk.bold.hex('#4A90E2')('│');

View File

@ -57,6 +57,7 @@ export interface CreateIssueForm {
title: string; title: string;
body: string; body: string;
activeField: 'title' | 'body'; activeField: 'title' | 'body';
cursor: number;
} }
export interface AddCommentForm { export interface AddCommentForm {
@ -67,6 +68,7 @@ export interface EditIssueForm {
title: string; title: string;
body: string; body: string;
activeField: 'title' | 'body'; activeField: 'title' | 'body';
cursor: number;
} }
export interface AddTimeForm { export interface AddTimeForm {
@ -81,7 +83,7 @@ export interface LabelForm {
activeField: 'name' | 'color' | 'description' | 'exclusive'; 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 { export interface RepoItem {