mirror of
https://github.com/kwhittle/obsidian-github-tools.git
synced 2026-07-22 07:44:31 +00:00
Merge fix/community-plugin-review: fix all community plugin review issues
This commit is contained in:
commit
82296202b5
6 changed files with 203 additions and 126 deletions
45
.github/workflows/release.yml
vendored
Normal file
45
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Generate artifact attestations
|
||||
uses: actions/attest-build-provenance@v1
|
||||
with:
|
||||
subject-path: |
|
||||
main.js
|
||||
styles.css
|
||||
|
||||
- name: Create release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
tag="${GITHUB_REF#refs/tags/}"
|
||||
gh release create "$tag" \
|
||||
--title "$tag" \
|
||||
--generate-notes \
|
||||
main.js manifest.json styles.css
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import esbuild from "esbuild";
|
||||
import process from "process";
|
||||
import builtins from "builtin-modules";
|
||||
import { builtinModules } from "module";
|
||||
|
||||
const prod = process.argv[2] === "production";
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ const context = await esbuild.context({
|
|||
"@lezer/common",
|
||||
"@lezer/highlight",
|
||||
"@lezer/lr",
|
||||
...builtins,
|
||||
...builtinModules,
|
||||
],
|
||||
format: "cjs",
|
||||
target: "es2018",
|
||||
|
|
|
|||
263
main.ts
263
main.ts
|
|
@ -7,6 +7,7 @@ import {
|
|||
PluginSettingTab,
|
||||
Setting,
|
||||
WorkspaceLeaf,
|
||||
requestUrl,
|
||||
setIcon,
|
||||
} from "obsidian";
|
||||
import { exec } from "child_process";
|
||||
|
|
@ -68,9 +69,56 @@ interface GitStatus {
|
|||
changedFiles: ChangedFile[];
|
||||
commitsBehind: number;
|
||||
comparingTo: string;
|
||||
incomingFiles: string[]; // files that would change on pull
|
||||
incomingFiles: string[];
|
||||
}
|
||||
|
||||
// ── GitHub API types ─────────────────────────────────────────────────────────
|
||||
|
||||
interface GitHubUser {
|
||||
login: string;
|
||||
}
|
||||
|
||||
interface GitHubPRItem {
|
||||
number: number;
|
||||
title: string;
|
||||
html_url: string;
|
||||
user: GitHubUser;
|
||||
updated_at: string;
|
||||
draft: boolean;
|
||||
requested_reviewers: GitHubUser[];
|
||||
}
|
||||
|
||||
interface GitHubNewPR {
|
||||
html_url: string;
|
||||
}
|
||||
|
||||
interface GitHubApiError {
|
||||
message?: string;
|
||||
}
|
||||
|
||||
// ── Electron types ───────────────────────────────────────────────────────────
|
||||
|
||||
interface ElectronOpenDialogResult {
|
||||
canceled: boolean;
|
||||
filePaths: string[];
|
||||
}
|
||||
|
||||
interface ElectronDialog {
|
||||
showOpenDialog(options: { properties: string[] }): Promise<ElectronOpenDialogResult>;
|
||||
}
|
||||
|
||||
interface ElectronModule {
|
||||
remote?: { dialog: ElectronDialog };
|
||||
}
|
||||
|
||||
// ── Error helper ─────────────────────────────────────────────────────────────
|
||||
|
||||
function errorMessage(e: unknown): string {
|
||||
return e instanceof Error ? e.message : String(e);
|
||||
}
|
||||
|
||||
// ── Git helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
async function gitExec(repoPath: string, args: string): Promise<string> {
|
||||
const { stdout } = await execAsync(`git -C "${repoPath}" ${args}`);
|
||||
return stdout.trim();
|
||||
|
|
@ -101,7 +149,6 @@ async function detectRepoIdentity(repoPath: string): Promise<{ owner: string; re
|
|||
}
|
||||
|
||||
async function fetchGitStatus(repoPath: string, trackBranch: string): Promise<GitStatus> {
|
||||
// Fetch is best-effort — don't fail if offline or remote is unreachable
|
||||
try { await gitExec(repoPath, "fetch origin"); } catch { /* non-fatal */ }
|
||||
|
||||
const branch = await gitExec(repoPath, "branch --show-current");
|
||||
|
|
@ -113,13 +160,11 @@ async function fetchGitStatus(repoPath: string, trackBranch: string): Promise<Gi
|
|||
]);
|
||||
|
||||
const changedFiles = parseChangedFiles(porcelain);
|
||||
// Plain `git branch` prefixes the current branch with "* " — strip it
|
||||
const localBranches = branchesRaw
|
||||
.split("\n")
|
||||
.map((b) => b.replace(/^\*\s*/, "").trim())
|
||||
.filter(Boolean);
|
||||
|
||||
// rev-list and diff can fail if the branch doesn't exist on the remote yet
|
||||
let commitsBehind = 0;
|
||||
let incomingFiles: string[] = [];
|
||||
try {
|
||||
|
|
@ -205,24 +250,24 @@ interface PullRequest {
|
|||
requestedReviewers: string[];
|
||||
}
|
||||
|
||||
async function ghFetch(url: string, token: string): Promise<any> {
|
||||
const res = await fetch(url, {
|
||||
async function ghFetch<T>(url: string, token: string): Promise<T> {
|
||||
const res = await requestUrl({
|
||||
url,
|
||||
headers: {
|
||||
Authorization: `token ${token}`,
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
},
|
||||
});
|
||||
if (!res.ok) throw new Error(`GitHub API error: ${res.status} ${res.statusText}`);
|
||||
return res.json();
|
||||
return res.json as T;
|
||||
}
|
||||
|
||||
async function detectGitHubUsername(token: string): Promise<string> {
|
||||
const data = await ghFetch("https://api.github.com/user", token);
|
||||
const data = await ghFetch<GitHubUser>("https://api.github.com/user", token);
|
||||
return data.login;
|
||||
}
|
||||
|
||||
async function fetchOpenPRs(owner: string, repo: string, token: string): Promise<PullRequest[]> {
|
||||
const data: any[] = await ghFetch(
|
||||
const data = await ghFetch<GitHubPRItem[]>(
|
||||
`https://api.github.com/repos/${owner}/${repo}/pulls?state=open&per_page=50`,
|
||||
token
|
||||
);
|
||||
|
|
@ -233,7 +278,7 @@ async function fetchOpenPRs(owner: string, repo: string, token: string): Promise
|
|||
author: pr.user.login,
|
||||
updatedAt: pr.updated_at,
|
||||
draft: pr.draft ?? false,
|
||||
requestedReviewers: (pr.requested_reviewers ?? []).map((r: any) => r.login),
|
||||
requestedReviewers: (pr.requested_reviewers ?? []).map((r) => r.login),
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -241,7 +286,8 @@ async function createGitHubPR(
|
|||
owner: string, repo: string, token: string,
|
||||
head: string, base: string, title: string, body: string
|
||||
): Promise<string> {
|
||||
const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/pulls`, {
|
||||
const res = await requestUrl({
|
||||
url: `https://api.github.com/repos/${owner}/${repo}/pulls`,
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `token ${token}`,
|
||||
|
|
@ -249,12 +295,13 @@ async function createGitHubPR(
|
|||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ title, body, head, base }),
|
||||
throw: false,
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({}));
|
||||
throw new Error((err as any).message ?? `GitHub API error: ${res.status}`);
|
||||
if (res.status < 200 || res.status >= 300) {
|
||||
const err = res.json as GitHubApiError;
|
||||
throw new Error(err.message ?? `GitHub API error: ${res.status}`);
|
||||
}
|
||||
const data: any = await res.json();
|
||||
const data = res.json as GitHubNewPR;
|
||||
return data.html_url;
|
||||
}
|
||||
|
||||
|
|
@ -262,8 +309,11 @@ async function createGitHubPR(
|
|||
|
||||
async function showFolderPicker(): Promise<string | null> {
|
||||
try {
|
||||
const electron = (window as any).require("electron");
|
||||
const dialog = electron.remote?.dialog ?? (await import("@electron/remote" as any)).dialog;
|
||||
const requireFn = (window as Window & { require?: (m: string) => ElectronModule }).require;
|
||||
if (!requireFn) return null;
|
||||
const electron = requireFn("electron");
|
||||
const dialog = electron.remote?.dialog;
|
||||
if (!dialog) return null;
|
||||
const result = await dialog.showOpenDialog({ properties: ["openDirectory"] });
|
||||
if (!result.canceled && result.filePaths.length > 0) return result.filePaths[0];
|
||||
} catch (e) {
|
||||
|
|
@ -314,8 +364,8 @@ class GitHubRepoView extends ItemView {
|
|||
} else {
|
||||
this.prs = [];
|
||||
}
|
||||
} catch (e: any) {
|
||||
this.errorMsg = e.message;
|
||||
} catch (e: unknown) {
|
||||
this.errorMsg = errorMessage(e);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
this.render();
|
||||
|
|
@ -333,7 +383,7 @@ class GitHubRepoView extends ItemView {
|
|||
header.createEl("h4", { text: "GitHub Repo" });
|
||||
const refreshBtn = header.createEl("button", { cls: "gwt-btn-icon", text: "↻" });
|
||||
refreshBtn.setAttribute("aria-label", "Refresh");
|
||||
refreshBtn.addEventListener("click", () => this.refresh());
|
||||
refreshBtn.addEventListener("click", () => { void this.refresh(); });
|
||||
|
||||
if (this.loading) {
|
||||
root.createDiv("gwt-muted").setText("Loading…");
|
||||
|
|
@ -363,13 +413,12 @@ class GitHubRepoView extends ItemView {
|
|||
const section = root.createDiv("gwt-section");
|
||||
section.createEl("h5", { text: "Local Repo" });
|
||||
|
||||
// Action bar
|
||||
const actionBar = section.createDiv("gwt-action-bar");
|
||||
const actions: Array<{ icon: string; label: string; handler: () => void }> = [
|
||||
{ icon: "rotate-cw", label: "Fetch", handler: () => this.handleFetch() },
|
||||
{ icon: "download", label: "Pull", handler: () => this.handlePull() },
|
||||
{ icon: "upload", label: "Push", handler: () => this.handlePush() },
|
||||
{ icon: "git-pull-request", label: "Create PR", handler: () => this.handleCreatePR() },
|
||||
{ icon: "rotate-cw", label: "Fetch", handler: () => { void this.handleFetch(); } },
|
||||
{ icon: "download", label: "Pull", handler: () => { void this.handlePull(); } },
|
||||
{ icon: "upload", label: "Push", handler: () => { void this.handlePush(); } },
|
||||
{ icon: "git-pull-request", label: "Create PR", handler: () => { void this.handleCreatePR(); } },
|
||||
];
|
||||
for (const a of actions) {
|
||||
const btn = actionBar.createEl("button", { cls: "gwt-action-btn" });
|
||||
|
|
@ -379,7 +428,6 @@ class GitHubRepoView extends ItemView {
|
|||
btn.addEventListener("click", a.handler);
|
||||
}
|
||||
|
||||
// Branch selector
|
||||
const branchRow = section.createDiv("gwt-branch-row");
|
||||
branchRow.createSpan({ cls: "gwt-label", text: "Branch" });
|
||||
const select = branchRow.createEl("select", { cls: "gwt-branch-select" });
|
||||
|
|
@ -387,9 +435,8 @@ class GitHubRepoView extends ItemView {
|
|||
const opt = select.createEl("option", { text: b, value: b });
|
||||
if (b === branch) opt.selected = true;
|
||||
});
|
||||
select.addEventListener("change", () => this.handleBranchSwitch(select.value, select, branch));
|
||||
select.addEventListener("change", () => { void this.handleBranchSwitch(select.value, select, branch); });
|
||||
|
||||
// New branch buttons
|
||||
const newBranchRow = section.createDiv("gwt-new-branch-row");
|
||||
const newBtn = newBranchRow.createEl("button", { cls: "gwt-btn gwt-btn-sm", text: "+ New branch" });
|
||||
newBtn.addEventListener("click", () => {
|
||||
|
|
@ -405,14 +452,13 @@ class GitHubRepoView extends ItemView {
|
|||
}).open();
|
||||
});
|
||||
|
||||
// Uncommitted changes + expandable file list
|
||||
if (hasUncommittedChanges) {
|
||||
const details = section.createEl("details", { cls: "gwt-files-details" });
|
||||
const summary = details.createEl("summary", { cls: "gwt-files-summary gwt-warning" });
|
||||
summary.createSpan({ text: `⚠ ${changedFiles.length} uncommitted change${changedFiles.length !== 1 ? "s" : ""}` });
|
||||
const commitBtn = summary.createEl("button", { cls: "gwt-btn gwt-btn-sm gwt-commit-inline-btn", text: "Commit…" });
|
||||
commitBtn.addEventListener("click", (e) => {
|
||||
e.stopPropagation(); // don't toggle the <details>
|
||||
e.stopPropagation();
|
||||
this.handleCommit(changedFiles);
|
||||
});
|
||||
|
||||
|
|
@ -424,7 +470,6 @@ class GitHubRepoView extends ItemView {
|
|||
});
|
||||
}
|
||||
|
||||
// Staleness + pull
|
||||
if (commitsBehind > 0) {
|
||||
const behindRow = section.createDiv("gwt-behind-row");
|
||||
behindRow.createSpan({
|
||||
|
|
@ -432,7 +477,7 @@ class GitHubRepoView extends ItemView {
|
|||
text: `⬇ ${commitsBehind} commit${commitsBehind !== 1 ? "s" : ""} behind origin/${comparingTo}`,
|
||||
});
|
||||
const pullBtn = behindRow.createEl("button", { cls: "gwt-btn gwt-btn-pull", text: "Pull" });
|
||||
pullBtn.addEventListener("click", () => this.handlePull());
|
||||
pullBtn.addEventListener("click", () => { void this.handlePull(); });
|
||||
} else {
|
||||
section.createDiv("gwt-row gwt-ok").setText(`✓ Up to date with origin/${comparingTo}`);
|
||||
}
|
||||
|
|
@ -449,9 +494,8 @@ class GitHubRepoView extends ItemView {
|
|||
await runGitCheckout(this.plugin.settings.repoPath, newBranch);
|
||||
new Notice(`Switched to branch: ${newBranch}`);
|
||||
await this.refresh();
|
||||
} catch (e: any) {
|
||||
new Notice(`Could not switch branch: ${e.message}`, 8000);
|
||||
// Revert the dropdown to the original branch
|
||||
} catch (e: unknown) {
|
||||
new Notice(`Could not switch branch: ${errorMessage(e)}`, 8000);
|
||||
Array.from(select.options).forEach((opt) => {
|
||||
opt.selected = opt.value === prevBranch;
|
||||
});
|
||||
|
|
@ -463,8 +507,8 @@ class GitHubRepoView extends ItemView {
|
|||
await runGitCreateBranch(this.plugin.settings.repoPath, name, from);
|
||||
new Notice(`Created and switched to branch: ${name}`);
|
||||
await this.refresh();
|
||||
} catch (e: any) {
|
||||
new Notice(`Failed to create branch: ${e.message}`, 8000);
|
||||
} catch (e: unknown) {
|
||||
new Notice(`Failed to create branch: ${errorMessage(e)}`, 8000);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -473,8 +517,8 @@ class GitHubRepoView extends ItemView {
|
|||
await gitExec(this.plugin.settings.repoPath, "fetch origin");
|
||||
new Notice("Fetched.");
|
||||
await this.refresh();
|
||||
} catch (e: any) {
|
||||
new Notice(`Fetch failed: ${e.message}`, 6000);
|
||||
} catch (e: unknown) {
|
||||
new Notice(`Fetch failed: ${errorMessage(e)}`, 6000);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -487,8 +531,8 @@ class GitHubRepoView extends ItemView {
|
|||
await runGitPush(repoPath, branch, !pushed);
|
||||
new Notice("Pushed.");
|
||||
await this.refresh();
|
||||
} catch (e: any) {
|
||||
new Notice(`Push failed: ${e.message}`, 8000);
|
||||
} catch (e: unknown) {
|
||||
new Notice(`Push failed: ${errorMessage(e)}`, 8000);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -507,8 +551,8 @@ class GitHubRepoView extends ItemView {
|
|||
new Notice("Committed.");
|
||||
}
|
||||
await this.refresh();
|
||||
} catch (e: any) {
|
||||
new Notice(`Commit failed: ${e.message}`, 8000);
|
||||
} catch (e: unknown) {
|
||||
new Notice(`Commit failed: ${errorMessage(e)}`, 8000);
|
||||
}
|
||||
}).open();
|
||||
}
|
||||
|
|
@ -523,7 +567,6 @@ class GitHubRepoView extends ItemView {
|
|||
return;
|
||||
}
|
||||
|
||||
// Step 1: uncommitted changes → commit first, then continue
|
||||
if (this.gitStatus?.hasUncommittedChanges) {
|
||||
new CommitModal(this.app, this.gitStatus.changedFiles, async (files, message, push) => {
|
||||
try {
|
||||
|
|
@ -535,8 +578,8 @@ class GitHubRepoView extends ItemView {
|
|||
}
|
||||
await this.refresh();
|
||||
await this.proceedWithPR(branch, localBranches);
|
||||
} catch (e: any) {
|
||||
new Notice(`Commit failed: ${e.message}`, 8000);
|
||||
} catch (e: unknown) {
|
||||
new Notice(`Commit failed: ${errorMessage(e)}`, 8000);
|
||||
}
|
||||
}).open();
|
||||
return;
|
||||
|
|
@ -548,28 +591,26 @@ class GitHubRepoView extends ItemView {
|
|||
private async proceedWithPR(branch: string, localBranches: string[]): Promise<void> {
|
||||
const { repoPath, repoOwner, repoName, githubToken } = this.plugin.settings;
|
||||
|
||||
// Step 2: push branch if not on remote
|
||||
const pushed = await isBranchPushed(repoPath, branch);
|
||||
if (!pushed) {
|
||||
try {
|
||||
new Notice("Branch not yet on remote. Pushing…");
|
||||
await runGitPush(repoPath, branch, true);
|
||||
new Notice("Pushed.");
|
||||
} catch (e: any) {
|
||||
new Notice(`Push failed: ${e.message}`, 8000);
|
||||
} catch (e: unknown) {
|
||||
new Notice(`Push failed: ${errorMessage(e)}`, 8000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: open PR creation modal
|
||||
new CreatePRModal(this.app, branch, localBranches, async (title, body, base) => {
|
||||
try {
|
||||
const url = await createGitHubPR(repoOwner, repoName, githubToken, branch, base, title, body);
|
||||
window.open(url, "_blank");
|
||||
new Notice("PR created.");
|
||||
await this.refresh();
|
||||
} catch (e: any) {
|
||||
new Notice(`Failed to create PR: ${e.message}`, 8000);
|
||||
} catch (e: unknown) {
|
||||
new Notice(`Failed to create PR: ${errorMessage(e)}`, 8000);
|
||||
}
|
||||
}).open();
|
||||
}
|
||||
|
|
@ -666,15 +707,14 @@ class GitHubRepoView extends ItemView {
|
|||
);
|
||||
return;
|
||||
}
|
||||
// Local changes don't touch any incoming files — safe to pull
|
||||
}
|
||||
|
||||
try {
|
||||
await runGitPull(this.plugin.settings.repoPath);
|
||||
new Notice("Pull successful.");
|
||||
await this.refresh();
|
||||
} catch (e: any) {
|
||||
new Notice(`Pull failed: ${e.message}`, 8000);
|
||||
} catch (e: unknown) {
|
||||
new Notice(`Pull failed: ${errorMessage(e)}`, 8000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -692,7 +732,7 @@ export default class GitHubWikiPlugin extends Plugin {
|
|||
|
||||
this.registerView(VIEW_TYPE, (leaf) => new GitHubRepoView(leaf, this));
|
||||
|
||||
this.ribbonIcon = this.addRibbonIcon("github", "GitHub Repo", () => this.activateView());
|
||||
this.ribbonIcon = this.addRibbonIcon("github", "GitHub Repo", () => { void this.activateView(); });
|
||||
|
||||
this.addSettingTab(new GitHubRepoSettingTab(this.app, this));
|
||||
|
||||
|
|
@ -700,13 +740,15 @@ export default class GitHubWikiPlugin extends Plugin {
|
|||
this.statusBarItem = this.addStatusBarItem();
|
||||
this.statusBarItem.addClass("gwt-status-bar");
|
||||
this.statusBarItem.setText("GH ···");
|
||||
this.statusBarItem.addEventListener("click", () => this.activateView());
|
||||
this.statusBarItem.addEventListener("click", () => { void this.activateView(); });
|
||||
}
|
||||
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
await this.detectAndCacheIdentity();
|
||||
if (this.settings.autoOpenSidebar) this.activateView();
|
||||
this.startPolling();
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
void (async () => {
|
||||
await this.detectAndCacheIdentity();
|
||||
if (this.settings.autoOpenSidebar) await this.activateView();
|
||||
this.startPolling();
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -715,7 +757,7 @@ export default class GitHubWikiPlugin extends Plugin {
|
|||
}
|
||||
|
||||
async loadSettings(): Promise<void> {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData() as Partial<Settings>);
|
||||
if (!this.settings.seenPRActivity) this.settings.seenPRActivity = {};
|
||||
}
|
||||
|
||||
|
|
@ -751,10 +793,10 @@ export default class GitHubWikiPlugin extends Plugin {
|
|||
const { workspace } = this.app;
|
||||
let leaf = workspace.getLeavesOfType(VIEW_TYPE)[0];
|
||||
if (!leaf) {
|
||||
leaf = workspace.getRightLeaf(false)!;
|
||||
leaf = workspace.getRightLeaf(false) ?? workspace.getLeaf(false);
|
||||
await leaf.setViewState({ type: VIEW_TYPE, active: true });
|
||||
}
|
||||
workspace.revealLeaf(leaf);
|
||||
await workspace.revealLeaf(leaf);
|
||||
}
|
||||
|
||||
updateRibbonIcon(commitsBehind: number): void {
|
||||
|
|
@ -801,7 +843,7 @@ export default class GitHubWikiPlugin extends Plugin {
|
|||
|
||||
private startPolling(): void {
|
||||
const intervalMs = (this.settings.pollIntervalMinutes || 15) * 60 * 1000;
|
||||
this.pollTimer = window.setInterval(() => this.refreshView(), intervalMs);
|
||||
this.pollTimer = window.setInterval(() => { void this.refreshView(); }, intervalMs);
|
||||
}
|
||||
|
||||
private stopPolling(): void {
|
||||
|
|
@ -826,7 +868,7 @@ export default class GitHubWikiPlugin extends Plugin {
|
|||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) this.saveSettings();
|
||||
if (changed) void this.saveSettings();
|
||||
}
|
||||
|
||||
hasNewActivity(pr: PullRequest): boolean {
|
||||
|
|
@ -838,7 +880,7 @@ export default class GitHubWikiPlugin extends Plugin {
|
|||
|
||||
markSeen(pr: PullRequest): void {
|
||||
this.settings.seenPRActivity[pr.number] = pr.updatedAt;
|
||||
this.saveSettings();
|
||||
void this.saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -865,7 +907,6 @@ class CommitModal extends Modal {
|
|||
|
||||
contentEl.createEl("h3", { text: "Commit changes" });
|
||||
|
||||
// File list with checkboxes
|
||||
contentEl.createEl("label", { cls: "gwt-modal-label", text: "Stage files" });
|
||||
const selectRow = contentEl.createDiv("gwt-modal-select-all");
|
||||
const selectAll = selectRow.createEl("a", { text: "Select all" });
|
||||
|
|
@ -876,7 +917,7 @@ class CommitModal extends Modal {
|
|||
|
||||
this.changedFiles.forEach((f) => {
|
||||
const item = fileList.createDiv("gwt-modal-file-item");
|
||||
const cb = item.createEl("input", { type: "checkbox" }) as HTMLInputElement;
|
||||
const cb = item.createEl("input", { type: "checkbox" });
|
||||
cb.checked = true;
|
||||
checkboxes.push(cb);
|
||||
item.createSpan({ cls: `gwt-file-status gwt-status-${f.status.replace("?", "untracked")}`, text: f.status });
|
||||
|
|
@ -887,13 +928,11 @@ class CommitModal extends Modal {
|
|||
selectAll.addEventListener("click", () => checkboxes.forEach((cb) => (cb.checked = true)));
|
||||
deselectAll.addEventListener("click", () => checkboxes.forEach((cb) => (cb.checked = false)));
|
||||
|
||||
// Commit message
|
||||
contentEl.createEl("label", { cls: "gwt-modal-label", text: "Commit message" });
|
||||
const textarea = contentEl.createEl("textarea", { cls: "gwt-modal-textarea" }) as HTMLTextAreaElement;
|
||||
const textarea = contentEl.createEl("textarea", { cls: "gwt-modal-textarea" });
|
||||
textarea.rows = 3;
|
||||
textarea.placeholder = "feat: describe your changes";
|
||||
|
||||
// Buttons
|
||||
const btnRow = contentEl.createDiv("gwt-modal-btns");
|
||||
const commitBtn = btnRow.createEl("button", { cls: "mod-cta", text: "Commit" });
|
||||
const commitPushBtn = btnRow.createEl("button", { text: "Commit & Push" });
|
||||
|
|
@ -912,11 +951,11 @@ class CommitModal extends Modal {
|
|||
await this.onSubmit(selected, message, push);
|
||||
};
|
||||
|
||||
commitBtn.addEventListener("click", () => submit(false));
|
||||
commitPushBtn.addEventListener("click", () => submit(true));
|
||||
commitBtn.addEventListener("click", () => { void submit(false); });
|
||||
commitPushBtn.addEventListener("click", () => { void submit(true); });
|
||||
cancelBtn.addEventListener("click", () => this.close());
|
||||
|
||||
setTimeout(() => textarea.focus(), 50);
|
||||
window.setTimeout(() => textarea.focus(), 50);
|
||||
}
|
||||
|
||||
onClose(): void { this.contentEl.empty(); }
|
||||
|
|
@ -948,7 +987,6 @@ class CreatePRModal extends Modal {
|
|||
|
||||
contentEl.createEl("h3", { text: "Create pull request" });
|
||||
|
||||
// Base branch
|
||||
const baseRow = contentEl.createDiv("gwt-modal-row");
|
||||
baseRow.createEl("label", { cls: "gwt-modal-label", text: "Base branch" });
|
||||
const baseSelect = baseRow.createEl("select", { cls: "gwt-modal-select" });
|
||||
|
|
@ -962,7 +1000,6 @@ class CreatePRModal extends Modal {
|
|||
if (b === defaultBase) opt.selected = true;
|
||||
});
|
||||
|
||||
// From hint
|
||||
const hint = contentEl.createEl("p", { cls: "gwt-modal-from-hint" });
|
||||
const updateHint = () => {
|
||||
hint.setText(`${this.currentBranch} → ${baseSelect.value}`);
|
||||
|
|
@ -970,20 +1007,17 @@ class CreatePRModal extends Modal {
|
|||
updateHint();
|
||||
baseSelect.addEventListener("change", updateHint);
|
||||
|
||||
// PR title
|
||||
const titleRow = contentEl.createDiv("gwt-modal-row");
|
||||
titleRow.createEl("label", { cls: "gwt-modal-label", text: "Title" });
|
||||
const titleInput = titleRow.createEl("input", { cls: "gwt-modal-input", type: "text" }) as HTMLInputElement;
|
||||
const titleInput = titleRow.createEl("input", { cls: "gwt-modal-input", type: "text" });
|
||||
titleInput.value = branchToPRTitle(this.currentBranch);
|
||||
|
||||
// Description
|
||||
const bodyRow = contentEl.createDiv("gwt-modal-row");
|
||||
bodyRow.createEl("label", { cls: "gwt-modal-label", text: "Description (optional)" });
|
||||
const bodyTextarea = bodyRow.createEl("textarea", { cls: "gwt-modal-textarea" }) as HTMLTextAreaElement;
|
||||
const bodyTextarea = bodyRow.createEl("textarea", { cls: "gwt-modal-textarea" });
|
||||
bodyTextarea.rows = 4;
|
||||
bodyTextarea.placeholder = "Describe your changes…";
|
||||
|
||||
// Buttons
|
||||
const btnRow = contentEl.createDiv("gwt-modal-btns");
|
||||
const createBtn = btnRow.createEl("button", { cls: "mod-cta", text: "Create PR" });
|
||||
const cancelBtn = btnRow.createEl("button", { text: "Cancel" });
|
||||
|
|
@ -997,11 +1031,11 @@ class CreatePRModal extends Modal {
|
|||
await this.onSubmit(title, bodyTextarea.value.trim(), baseSelect.value);
|
||||
};
|
||||
|
||||
createBtn.addEventListener("click", submit);
|
||||
createBtn.addEventListener("click", () => { void submit(); });
|
||||
cancelBtn.addEventListener("click", () => this.close());
|
||||
titleInput.addEventListener("keydown", (e) => { if (e.key === "Enter") submit(); });
|
||||
titleInput.addEventListener("keydown", (e) => { if (e.key === "Enter") void submit(); });
|
||||
|
||||
setTimeout(() => titleInput.focus(), 50);
|
||||
window.setTimeout(() => titleInput.focus(), 50);
|
||||
}
|
||||
|
||||
onClose(): void { this.contentEl.empty(); }
|
||||
|
|
@ -1011,7 +1045,7 @@ class CreatePRModal extends Modal {
|
|||
|
||||
class NewBranchModal extends Modal {
|
||||
private currentBranch: string;
|
||||
private sourceBranch: string | null; // null = from current HEAD
|
||||
private sourceBranch: string | null;
|
||||
private availableBranches: string[];
|
||||
private onSubmit: (name: string, from: string | null) => Promise<void>;
|
||||
|
||||
|
|
@ -1040,7 +1074,6 @@ class NewBranchModal extends Modal {
|
|||
text: fromPicker ? "Create new branch from…" : "Create new branch",
|
||||
});
|
||||
|
||||
// Source branch picker (only shown for "from..." flow)
|
||||
if (fromPicker) {
|
||||
const row = contentEl.createDiv("gwt-modal-row");
|
||||
row.createEl("label", { cls: "gwt-modal-label", text: "Source branch" });
|
||||
|
|
@ -1057,13 +1090,11 @@ class NewBranchModal extends Modal {
|
|||
});
|
||||
}
|
||||
|
||||
// Branch name input
|
||||
const nameRow = contentEl.createDiv("gwt-modal-row");
|
||||
nameRow.createEl("label", { cls: "gwt-modal-label", text: "Branch name" });
|
||||
const input = nameRow.createEl("input", { cls: "gwt-modal-input", type: "text" });
|
||||
input.placeholder = "feature/my-branch";
|
||||
|
||||
// Buttons
|
||||
const btnRow = contentEl.createDiv("gwt-modal-btns");
|
||||
const createBtn = btnRow.createEl("button", { cls: "mod-cta", text: "Create branch" });
|
||||
const cancelBtn = btnRow.createEl("button", { text: "Cancel" });
|
||||
|
|
@ -1077,15 +1108,14 @@ class NewBranchModal extends Modal {
|
|||
await this.onSubmit(name, this.sourceBranch);
|
||||
};
|
||||
|
||||
createBtn.addEventListener("click", submit);
|
||||
createBtn.addEventListener("click", () => { void submit(); });
|
||||
cancelBtn.addEventListener("click", () => this.close());
|
||||
input.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") submit();
|
||||
if (e.key === "Enter") void submit();
|
||||
if (e.key === "Escape") this.close();
|
||||
});
|
||||
|
||||
// Focus the name input after a tick so the modal is fully rendered
|
||||
setTimeout(() => input.focus(), 50);
|
||||
window.setTimeout(() => input.focus(), 50);
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
|
|
@ -1097,6 +1127,8 @@ class NewBranchModal extends Modal {
|
|||
|
||||
class GitHubRepoSettingTab extends PluginSettingTab {
|
||||
plugin: GitHubWikiPlugin;
|
||||
private detectedRepoEl: HTMLElement | null = null;
|
||||
private detectedUserEl: HTMLElement | null = null;
|
||||
|
||||
constructor(app: App, plugin: GitHubWikiPlugin) {
|
||||
super(app, plugin);
|
||||
|
|
@ -1106,9 +1138,8 @@ class GitHubRepoSettingTab extends PluginSettingTab {
|
|||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
containerEl.createEl("h2", { text: "GitHub Repo Tools" });
|
||||
|
||||
containerEl.createEl("h3", { text: "Connection" });
|
||||
new Setting(containerEl).setName("Connection").setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Local repo path")
|
||||
|
|
@ -1123,7 +1154,7 @@ class GitHubRepoSettingTab extends PluginSettingTab {
|
|||
this.plugin.settings.repoName = "";
|
||||
await this.plugin.saveSettings();
|
||||
await this.plugin.detectAndCacheIdentity();
|
||||
this.display();
|
||||
this.updateDetectedInfo();
|
||||
})
|
||||
)
|
||||
.addButton((btn) =>
|
||||
|
|
@ -1135,7 +1166,7 @@ class GitHubRepoSettingTab extends PluginSettingTab {
|
|||
this.plugin.settings.repoName = "";
|
||||
await this.plugin.saveSettings();
|
||||
await this.plugin.detectAndCacheIdentity();
|
||||
this.display();
|
||||
this.updateDetectedInfo();
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -1151,20 +1182,22 @@ class GitHubRepoSettingTab extends PluginSettingTab {
|
|||
this.plugin.settings.githubUsername = "";
|
||||
await this.plugin.saveSettings();
|
||||
await this.plugin.detectAndCacheIdentity();
|
||||
this.display();
|
||||
this.updateDetectedInfo();
|
||||
});
|
||||
t.inputEl.type = "password";
|
||||
});
|
||||
|
||||
const s = this.plugin.settings;
|
||||
if (s.repoOwner && s.repoName) {
|
||||
containerEl.createEl("p", { cls: "setting-item-description", text: `Detected repo: ${s.repoOwner}/${s.repoName}` });
|
||||
}
|
||||
if (s.githubUsername) {
|
||||
containerEl.createEl("p", { cls: "setting-item-description", text: `Detected GitHub user: @${s.githubUsername}` });
|
||||
}
|
||||
this.detectedRepoEl = containerEl.createEl("p", {
|
||||
cls: "setting-item-description",
|
||||
text: s.repoOwner && s.repoName ? `Detected repo: ${s.repoOwner}/${s.repoName}` : "",
|
||||
});
|
||||
this.detectedUserEl = containerEl.createEl("p", {
|
||||
cls: "setting-item-description",
|
||||
text: s.githubUsername ? `Detected GitHub user: @${s.githubUsername}` : "",
|
||||
});
|
||||
|
||||
containerEl.createEl("h3", { text: "Features" });
|
||||
new Setting(containerEl).setName("Features").setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Staleness check")
|
||||
|
|
@ -1186,7 +1219,7 @@ class GitHubRepoSettingTab extends PluginSettingTab {
|
|||
.setDesc("Show a section for PRs where you are a requested reviewer")
|
||||
.addToggle((t) => t.setValue(s.enableReviewRequests).onChange(async (v) => { this.plugin.settings.enableReviewRequests = v; await this.plugin.saveSettings(); }));
|
||||
|
||||
containerEl.createEl("h3", { text: "Display" });
|
||||
new Setting(containerEl).setName("Display").setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Status bar item")
|
||||
|
|
@ -1203,7 +1236,7 @@ class GitHubRepoSettingTab extends PluginSettingTab {
|
|||
.setDesc("Include draft pull requests in the PR list")
|
||||
.addToggle((t) => t.setValue(s.showDraftPRs).onChange(async (v) => { this.plugin.settings.showDraftPRs = v; await this.plugin.saveSettings(); }));
|
||||
|
||||
containerEl.createEl("h3", { text: "Advanced" });
|
||||
new Setting(containerEl).setName("Advanced").setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Poll interval (minutes)")
|
||||
|
|
@ -1235,4 +1268,18 @@ class GitHubRepoSettingTab extends PluginSettingTab {
|
|||
})
|
||||
);
|
||||
}
|
||||
|
||||
private updateDetectedInfo(): void {
|
||||
const s = this.plugin.settings;
|
||||
if (this.detectedRepoEl) {
|
||||
this.detectedRepoEl.setText(
|
||||
s.repoOwner && s.repoName ? `Detected repo: ${s.repoOwner}/${s.repoName}` : ""
|
||||
);
|
||||
}
|
||||
if (this.detectedUserEl) {
|
||||
this.detectedUserEl.setText(
|
||||
s.githubUsername ? `Detected GitHub user: @${s.githubUsername}` : ""
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "github-repo-tools",
|
||||
"name": "GitHub Repo Tools",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"minAppVersion": "1.7.2",
|
||||
"description": "Monitor a GitHub repo — staleness alerts, PR dashboard, and activity notifications.",
|
||||
"author": "Kyle Whittle",
|
||||
"authorUrl": "https://github.com/kry-kylewhittle",
|
||||
|
|
|
|||
14
package-lock.json
generated
14
package-lock.json
generated
|
|
@ -10,7 +10,6 @@
|
|||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"builtin-modules": "^3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"obsidian": "latest",
|
||||
"tslib": "2.4.0",
|
||||
|
|
@ -457,19 +456,6 @@
|
|||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/builtin-modules": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
|
||||
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/crelt": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"builtin-modules": "^3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"obsidian": "latest",
|
||||
"tslib": "2.4.0",
|
||||
|
|
|
|||
Loading…
Reference in a new issue