open and close issue
This commit is contained in:
parent
0702fea1ed
commit
41f8f18fcb
24
src/api.ts
24
src/api.ts
|
|
@ -448,6 +448,30 @@ export async function editIssue(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the state of an issue (open/closed).
|
||||||
|
*/
|
||||||
|
export async function changeIssueState(
|
||||||
|
config: Config,
|
||||||
|
issueNumber: number,
|
||||||
|
state: 'open' | 'closed'
|
||||||
|
): Promise<void> {
|
||||||
|
const client = createAxiosInstance(config);
|
||||||
|
try {
|
||||||
|
await client.patch(`/repos/${config.owner}/${config.repo}/issues/${issueNumber}`, {
|
||||||
|
state,
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.response) {
|
||||||
|
if (error.response.status === 401) {
|
||||||
|
throw new Error('Unauthorized: You must be logged in with a token to change issue state.');
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to change issue state: ${error.response.data?.message || error.message}`);
|
||||||
|
}
|
||||||
|
throw new Error(`Network Error: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds time to an issue.
|
* Adds time to an issue.
|
||||||
* @param time Time in seconds
|
* @param time Time in seconds
|
||||||
|
|
|
||||||
86
src/tui.ts
86
src/tui.ts
|
|
@ -1,7 +1,7 @@
|
||||||
import readline from 'readline';
|
import readline from 'readline';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { AppState, Issue, Comment } from './types.js';
|
import { AppState, Issue, Comment } from './types.js';
|
||||||
import { fetchIssues, fetchIssue, fetchIssueComments, validateConnection, normalizeUrl, authenticateAndFetchRepos, createIssue, createIssueComment, editIssue, addIssueTime } from './api.js';
|
import { fetchIssues, fetchIssue, fetchIssueComments, validateConnection, normalizeUrl, authenticateAndFetchRepos, createIssue, createIssueComment, editIssue, addIssueTime, changeIssueState } from './api.js';
|
||||||
import { saveGlobalConfig } from './config.js';
|
import { saveGlobalConfig } from './config.js';
|
||||||
|
|
||||||
// Setup readline for stdin keypress events
|
// Setup readline for stdin keypress events
|
||||||
|
|
@ -291,6 +291,8 @@ export class TuiEngine {
|
||||||
this.handleEditIssueKeypress(str, key);
|
this.handleEditIssueKeypress(str, key);
|
||||||
} else if (this.state.screen === 'add-time') {
|
} else if (this.state.screen === 'add-time') {
|
||||||
this.handleAddTimeKeypress(str, key);
|
this.handleAddTimeKeypress(str, key);
|
||||||
|
} else if (this.state.screen === 'confirm-state-change') {
|
||||||
|
this.handleConfirmStateChangeKeypress(str, key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -747,6 +749,15 @@ export class TuiEngine {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (str === 'x' || str === 'X') {
|
||||||
|
if (this.state.selectedIssue) {
|
||||||
|
this.state.screen = 'confirm-state-change';
|
||||||
|
this.state.error = null;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ((key && (key.name === 'escape' || key.name === 'backspace')) || str === '\u001b' || str === 'q' || str === 'Q') {
|
if ((key && (key.name === 'escape' || key.name === 'backspace')) || str === '\u001b' || str === 'q' || str === 'Q') {
|
||||||
this.state.screen = 'list';
|
this.state.screen = 'list';
|
||||||
this.state.selectedIssue = null;
|
this.state.selectedIssue = null;
|
||||||
|
|
@ -781,6 +792,8 @@ export class TuiEngine {
|
||||||
this.renderEditIssueScreen(cols, rows);
|
this.renderEditIssueScreen(cols, rows);
|
||||||
} else if (this.state.screen === 'add-time') {
|
} else if (this.state.screen === 'add-time') {
|
||||||
this.renderAddTimeScreen(cols, rows);
|
this.renderAddTimeScreen(cols, rows);
|
||||||
|
} else if (this.state.screen === 'confirm-state-change') {
|
||||||
|
this.renderConfirmStateChangeScreen(cols, rows);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1198,7 +1211,8 @@ export class TuiEngine {
|
||||||
scrollHelp += chalk.yellow(` (${pct}%)`);
|
scrollHelp += chalk.yellow(` (${pct}%)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const helpLine = chalk.gray(` [Esc/Backspace] Back to List [C] Add Comment [E] Edit [T] Add Time [R] Reload [O] Settings ${scrollHelp}`);
|
const actionKey = issue.state === 'open' ? 'Close' : 'Reopen';
|
||||||
|
const helpLine = chalk.gray(` [Esc/Backspace] Back to List [C] Add Comment [E] Edit [T] Add Time [X] ${actionKey} [R] Reload [O] Settings ${scrollHelp}`);
|
||||||
process.stdout.write(helpLine);
|
process.stdout.write(helpLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1880,5 +1894,73 @@ export class TuiEngine {
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderConfirmStateChangeScreen(cols: number, rows: number) {
|
||||||
|
const borderCh = chalk.bold.hex('#4A90E2')('│');
|
||||||
|
|
||||||
|
const issue = this.state.selectedIssue;
|
||||||
|
if (!issue) return;
|
||||||
|
|
||||||
|
const action = issue.state === 'open' ? 'Close' : 'Reopen';
|
||||||
|
const title = ` Confirm ${action} `;
|
||||||
|
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) + '┐'));
|
||||||
|
|
||||||
|
if (this.state.error) {
|
||||||
|
console.log(borderCh + chalk.red(` Error: ${this.state.error}`).padEnd(cols - 2) + borderCh);
|
||||||
|
console.log(chalk.bold.hex('#4A90E2')('├' + '─'.repeat(cols - 2) + '┤'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const question = `Are you sure you want to ${action.toLowerCase()} issue #${issue.number}?`;
|
||||||
|
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 contentLine = (' '.repeat(Math.max(0, Math.floor((cols - 2 - promptPlain.length) / 2))) + 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 handleConfirmStateChangeKeypress(str: string, key: any) {
|
||||||
|
if ((key && (key.name === 'escape' || key.name === 'n')) || str === 'n' || str === 'N') {
|
||||||
|
this.state.screen = 'details';
|
||||||
|
this.state.error = null;
|
||||||
|
this.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str === 'y' || str === 'Y') {
|
||||||
|
const issue = this.state.selectedIssue;
|
||||||
|
if (!issue) return;
|
||||||
|
|
||||||
|
this.state.error = null;
|
||||||
|
this.state.loading = true;
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const newState = issue.state === 'open' ? 'closed' : 'open';
|
||||||
|
await changeIssueState(this.state.config, issue.number, newState);
|
||||||
|
this.state.screen = 'details';
|
||||||
|
this.state.loading = false;
|
||||||
|
// Reload to show the updated state
|
||||||
|
this.reloadSingleIssue();
|
||||||
|
} catch (err: any) {
|
||||||
|
this.state.error = err.message;
|
||||||
|
this.state.loading = false;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ export interface AddTimeForm {
|
||||||
timeInput: string;
|
timeInput: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ScreenType = 'setup' | 'repo-picker' | 'list' | 'details' | 'create-issue' | 'add-comment' | 'edit-issue' | 'add-time';
|
export type ScreenType = 'setup' | 'repo-picker' | 'list' | 'details' | 'create-issue' | 'add-comment' | 'edit-issue' | 'add-time' | 'confirm-state-change';
|
||||||
|
|
||||||
|
|
||||||
export interface RepoItem {
|
export interface RepoItem {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue