Add repo filters, UI tabs and dev docs

Enhance UI and dev workflow: add main/right tabbed panels (Repositories/Local and Clone/Settings/Servers), repository owner filter pills (All/Personal/Organizations), org-grouped listing, repo cards, git output and empty-state styling. Introduce new state vars (activeMainTab, activeRightTab, repoOwnerFilter, currentUserLogin) and update event bindings to toggle tabs/filters and focus appropriate panels on actions. Add fetchCurrentUser to gitea-api and use it when loading repositories to distinguish personal vs organization repos (falls back to mock user on error). Update README with development/prerequisites and build instructions and rename npm script "tauri:dev" -> "dev" for running the Tauri dev process.
This commit is contained in:
2026-05-09 18:12:36 +12:00
parent 6b245c628c
commit ce7f83734a
5 changed files with 497 additions and 115 deletions
+37 -2
View File
@@ -30,9 +30,44 @@ It connects to any compatible self-hosted Gitea instance (including Gitpub) with
- Rust Git commands: clone, pull, push, status, branch
- Settings for git path, clone directory, and theme placeholder
## Run
## Development
### Prerequisites
- [Node.js](https://nodejs.org/) (npm comes with it)
- [Rust](https://www.rust-lang.org/tools/install) via `rustup` (required for the Tauri backend)
- Platform-specific Tauri dependencies (WebView2 on Windows, Xcode tools on macOS, etc.) — see the [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) for your OS
- **Git** installed and available on your `PATH` (the app shells out to `git` for clone, pull, push, and related commands)
### Start the app in dev mode
From the repository root:
```bash
npm install
npm run tauri:dev
npm run dev
```
This runs the Tauri dev process, which loads the static frontend from `frontend/` and hot-reloads the Rust side when you change `src-tauri/`. Edit HTML/CSS/JS under `frontend/` and refresh the window (or rely on your usual workflow) as needed.
### Production build
```bash
npm install
npm run tauri:build
```
Installers and bundles are emitted under `src-tauri/target/release/bundle/` (exact paths depend on the target platform).
### Useful commands
| Command | Purpose |
| --- | --- |
| `npm run dev` | Desktop app in development mode |
| `npm run tauri:build` | Release build and platform bundles |
| `npm run tauri -- <args>` | Forward arguments to the Tauri CLI (e.g. `npm run tauri -- info`) |
### Notes
- There is no separate Vite/webpack dev server; the UI is plain static files under `frontend/`.
- Point the app at a running Gitea-compatible instance during setup; API calls use `<your-server>/api/v1`.
+215 -1
View File
@@ -15,14 +15,21 @@
.rightbar {
background: rgba(13, 21, 34, 0.88);
border-right: 1px solid var(--border);
padding: 14px;
}
.sidebar {
padding: 16px;
overflow-y: auto;
}
.rightbar {
border-right: 0;
border-left: 1px solid var(--border);
padding: 0 12px 12px;
overflow-y: auto;
}
.main {
padding: 16px;
overflow: auto;
@@ -69,6 +76,54 @@
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
.repo-filter-pills {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.pill-btn {
padding: 4px 14px;
border-radius: 999px;
border: 1px solid var(--border);
background: transparent;
color: var(--text-muted);
font-size: 13px;
cursor: pointer;
transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.pill-btn:hover {
background: rgba(255, 255, 255, 0.06);
color: var(--text-main);
}
.pill-btn.active {
background: var(--accent);
border-color: var(--accent);
color: #fff;
}
.org-group {
gap: 8px;
}
.org-group-header {
display: flex;
align-items: center;
gap: 10px;
}
.org-badge {
font-size: 13px;
font-weight: 600;
color: var(--text-main);
background: rgba(255, 255, 255, 0.07);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: 2px 10px;
}
.muted {
color: var(--text-muted);
}
@@ -131,6 +186,165 @@
color: var(--danger);
}
/* ── Tabs ─────────────────────────────────────────── */
.tabs {
display: flex;
align-items: center;
gap: 2px;
border-bottom: 1px solid var(--border);
padding: 0 4px;
flex-shrink: 0;
}
.tab-btn {
background: transparent;
border: none;
border-bottom: 2px solid transparent;
border-radius: 0;
padding: 10px 14px;
color: var(--text-muted);
font-size: 13px;
cursor: pointer;
transition: color 0.15s ease, border-color 0.15s ease;
margin-bottom: -1px;
}
.tab-btn:hover {
transform: none;
color: var(--text-main);
border-color: transparent;
background: transparent;
}
.tab-btn.active {
color: var(--text-main);
border-bottom-color: var(--accent);
}
.tab-spacer {
flex: 1;
}
.tab-search {
width: 180px !important;
padding: 6px 10px !important;
font-size: 13px;
margin: 4px 4px 4px 0;
}
.main-tabs {
margin-bottom: 0;
}
.right-tabs {
position: sticky;
top: 0;
margin: 0 -12px;
padding: 0 12px;
background: rgba(13, 21, 34, 0.96);
z-index: 1;
}
/* ── Sidebar ──────────────────────────────────────── */
.sidebar-brand .title {
font-size: 16px;
}
.sidebar .server-chip {
overflow: hidden;
}
.sidebar .server-chip > div {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.sidebar-nav {
display: flex;
gap: 6px;
}
.sidebar-btn {
flex: 1;
font-size: 12px;
padding: 7px 8px;
text-align: center;
}
.sidebar-recents {
display: flex;
flex-direction: column;
gap: 6px;
flex: 1;
min-height: 0;
}
.recent-item {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
color: var(--text-muted);
padding: 3px 0;
font-size: 13px;
}
.recent-item:hover {
color: var(--text-main);
}
/* ── Git output ───────────────────────────────────── */
.git-output {
background: rgba(0, 0, 0, 0.35);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: 12px 14px;
font-size: 12px;
font-family: "Fira Code", "Cascadia Code", "Consolas", monospace;
color: var(--success);
overflow-x: auto;
white-space: pre-wrap;
word-break: break-word;
margin: 0;
line-height: 1.6;
}
.git-output-placeholder {
font-size: 13px;
font-style: italic;
margin: 0;
}
.commit-note {
font-size: 11px;
margin-top: 4px;
}
/* ── Local repo indicator ─────────────────────────── */
.selected-repo {
font-size: 13px;
padding: 7px 10px;
background: rgba(79, 157, 255, 0.08);
border: 1px solid rgba(79, 157, 255, 0.18);
border-radius: var(--radius-md);
word-break: break-all;
}
/* ── Empty state ──────────────────────────────────── */
.empty-state {
grid-column: 1 / -1;
text-align: center;
padding: 40px 16px;
}
.empty-state > div:first-child {
font-size: 15px;
color: var(--text-main);
margin-bottom: 6px;
}
/* ── Responsive ───────────────────────────────────── */
@media (max-width: 1180px) {
.layout {
grid-template-columns: 220px 1fr;
+233 -111
View File
@@ -1,4 +1,4 @@
import { fetchRepositories } from "./gitea-api.js";
import { fetchCurrentUser, fetchRepositories } from "./gitea-api.js";
import { getActiveServer, getState, setSettings, updateSettings, addRecentRepo } from "./state.js";
import {
runGitBranch,
@@ -12,15 +12,21 @@ import {
const appRoot = document.getElementById("app");
const mockRepos = [
{ id: 1, full_name: "gitpub-desktop/client-ui", private: false, clone_url: "https://example.com/client-ui.git", updated_at: "today" },
{ id: 2, full_name: "gitpub-desktop/git-core", private: true, clone_url: "https://example.com/git-core.git", updated_at: "yesterday" },
{ id: 3, full_name: "gitpub-desktop/docs", private: false, clone_url: "https://example.com/docs.git", updated_at: "2 days ago" },
{ id: 1, full_name: "alice/portfolio", private: false, clone_url: "https://example.com/portfolio.git", updated_at: "today", owner: { login: "alice", type: "User" } },
{ id: 2, full_name: "alice/dotfiles", private: true, clone_url: "https://example.com/dotfiles.git", updated_at: "yesterday", owner: { login: "alice", type: "User" } },
{ id: 3, full_name: "acme-corp/client-ui", private: false, clone_url: "https://example.com/client-ui.git", updated_at: "today", owner: { login: "acme-corp", type: "Organization" } },
{ id: 4, full_name: "acme-corp/api-server", private: true, clone_url: "https://example.com/api-server.git", updated_at: "3 days ago", owner: { login: "acme-corp", type: "Organization" } },
{ id: 5, full_name: "oss-collective/toolkit", private: false, clone_url: "https://example.com/toolkit.git", updated_at: "last week", owner: { login: "oss-collective", type: "Organization" } },
];
let repositories = [...mockRepos];
let currentUserLogin = ""; // authenticated user's login name
let serverTestResult = "";
let settingsNotice = "";
let gitOutput = "";
let activeRightTab = "clone"; // "clone" | "settings" | "servers"
let activeMainTab = "repos"; // "repos" | "local"
let repoOwnerFilter = "all"; // "all" | "personal" | "orgs"
function uid() {
return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
@@ -105,10 +111,54 @@ function welcomeView() {
bindServerFormEvents();
}
function isOrgRepo(repo) {
const ownerLogin = repo.owner?.login || repo.full_name.split("/")[0];
// When we know the authenticated user, compare against owner login.
// Fall back to owner.type for cases where user info wasn't fetched.
if (currentUserLogin) {
return ownerLogin.toLowerCase() !== currentUserLogin.toLowerCase();
}
return repo.owner?.type === "Organization" || repo.owner?.type === "organization";
}
function filteredRepositories() {
const searchTerm = getState().repoSearch.trim().toLowerCase();
if (!searchTerm) return repositories;
return repositories.filter((repo) => repo.full_name.toLowerCase().includes(searchTerm));
let result = repositories;
if (repoOwnerFilter === "personal") {
result = result.filter((repo) => !isOrgRepo(repo));
} else if (repoOwnerFilter === "orgs") {
result = result.filter((repo) => isOrgRepo(repo));
}
if (searchTerm) {
result = result.filter((repo) => repo.full_name.toLowerCase().includes(searchTerm));
}
return result;
}
function groupedByOrg(repos) {
const groups = {};
for (const repo of repos) {
const org = repo.owner?.login || "Unknown";
if (!groups[org]) groups[org] = [];
groups[org].push(repo);
}
return groups;
}
function repoCardTemplate(repo) {
return `
<article class="repo-card stack">
<div><strong>${escapeHtml(repo.full_name)}</strong></div>
<div class="muted">${repo.private ? "Private" : "Public"} · ${escapeHtml(repo.updated_at || "recently")}</div>
<div class="row">
<button class="clone-repo-btn" data-clone-url="${escapeHtml(repo.clone_url || "")}" type="button">Clone</button>
<button class="open-repo-btn" data-repo-name="${escapeHtml(repo.full_name)}" type="button">Open</button>
</div>
</article>
`;
}
function dashboardView() {
@@ -119,128 +169,171 @@ function dashboardView() {
appRoot.innerHTML = `
<div class="layout">
<aside class="sidebar stack">
<div>
<div class="sidebar-brand">
<h2 class="title">Gitpub Desktop</h2>
<p class="subtitle">Lightweight Gitea Git client</p>
</div>
<div class="server-chip">
<div class="label">Active backend</div>
<div>${escapeHtml(activeServer?.displayName || "None")}</div>
<div class="muted">${escapeHtml(activeServer?.serverUrl || "Not configured")}</div>
<div class="label">Active server</div>
<div>${escapeHtml(activeServer?.displayName || "No server selected")}</div>
<div class="muted">${escapeHtml(activeServer?.serverUrl || "Add a server to connect")}</div>
</div>
<button id="refresh-repos-btn" type="button">Refresh repositories</button>
<button id="open-settings-btn" type="button">Settings</button>
<div class="panel">
<div class="label">Recent local repositories</div>
<div class="sidebar-nav">
<button id="refresh-repos-btn" class="sidebar-btn" type="button">Refresh</button>
<button id="open-settings-btn" class="sidebar-btn" type="button">Servers</button>
</div>
<div class="sidebar-recents">
<div class="label">Recent repositories</div>
<ul class="list">
${state.settings.recentRepositories.length
? state.settings.recentRepositories.map((path) => `<li>${escapeHtml(path)}</li>`).join("")
: "<li class='muted'>No repositories opened yet.</li>"}
? state.settings.recentRepositories
.map((path) => `<li class="recent-item" title="${escapeHtml(path)}">${escapeHtml(path.split(/[/\\]/).filter(Boolean).pop() || path)}</li>`)
.join("")
: "<li class='muted'>No recent repositories.</li>"}
</ul>
</div>
</aside>
<main class="main stack">
<div class="app-header">
<div>
<h2 class="title">Repository Dashboard</h2>
<p class="subtitle">Browse, clone, and open repositories across multiple Gitea backends.</p>
</div>
<div class="row">
<input id="repo-search-input" placeholder="Search repositories..." value="${escapeHtml(state.repoSearch)}" />
</div>
<div class="tabs main-tabs">
<button class="tab-btn ${activeMainTab === "repos" ? "active" : ""}" data-main-tab="repos">Repositories</button>
<button class="tab-btn ${activeMainTab === "local" ? "active" : ""}" data-main-tab="local">Local Repo</button>
${activeMainTab === "repos"
? `<span class="tab-spacer"></span><input id="repo-search-input" class="tab-search" placeholder="Search…" value="${escapeHtml(state.repoSearch)}" />`
: ""}
</div>
<section class="panel stack">
<div class="section-header">
<h3 class="title">Repositories</h3>
<span class="muted">${visibleRepos.length} shown</span>
</div>
<div class="repo-grid">
${visibleRepos
.map(
(repo) => `
<article class="repo-card stack">
<div><strong>${escapeHtml(repo.full_name)}</strong></div>
<div class="muted">${repo.private ? "Private" : "Public"} • Updated ${escapeHtml(repo.updated_at || "recently")}</div>
<div class="row">
<button class="clone-repo-btn" data-clone-url="${escapeHtml(repo.clone_url || "")}" type="button">Clone</button>
<button class="open-repo-btn" data-repo-name="${escapeHtml(repo.full_name)}" type="button">Open</button>
${activeMainTab === "repos" ? `
<section class="panel stack">
<div class="section-header">
<h3 class="title">Repositories</h3>
<span class="muted">${visibleRepos.length} shown</span>
</div>
<div class="repo-filter-pills">
<button class="pill-btn ${repoOwnerFilter === "all" ? "active" : ""}" data-owner-filter="all">All</button>
<button class="pill-btn ${repoOwnerFilter === "personal" ? "active" : ""}" data-owner-filter="personal">Personal</button>
<button class="pill-btn ${repoOwnerFilter === "orgs" ? "active" : ""}" data-owner-filter="orgs">Organizations</button>
</div>
${repoOwnerFilter === "orgs"
? (() => {
const groups = groupedByOrg(visibleRepos);
const orgNames = Object.keys(groups).sort();
if (!orgNames.length) {
return `<div class="empty-state"><div>No organisation repositories found</div><div class="muted">Try refreshing or check your server connection.</div></div>`;
}
return orgNames.map((org) => `
<div class="org-group stack">
<div class="org-group-header">
<span class="org-badge">${escapeHtml(org)}</span>
<span class="muted">${groups[org].length} repo${groups[org].length !== 1 ? "s" : ""}</span>
</div>
<div class="repo-grid">
${groups[org].map((repo) => repoCardTemplate(repo)).join("")}
</div>
</div>
</article>
`
)
.join("")}
</div>
</section>
<section class="panel stack">
<h3 class="title">Repository View</h3>
<div class="row">
<input id="local-repo-path-input" placeholder="Local repository path" value="${escapeHtml(state.localRepoPathInput)}" />
<button id="open-local-repo-btn" type="button">Open Local Repo</button>
</div>
<div class="muted">Current repository: ${escapeHtml(state.selectedRepoName || "None selected")}</div>
<div class="row wrap">
<button id="git-status-btn" type="button">Status</button>
<button id="git-branch-btn" type="button">Branch</button>
<button id="git-pull-btn" type="button">Pull</button>
<button id="git-push-btn" type="button">Push</button>
</div>
<div>
<div class="label">Commit message (MVP placeholder)</div>
<textarea id="commit-message-input" placeholder="Write commit message...">${escapeHtml(state.commitMessage)}</textarea>
<button type="button" disabled title="Commit command will be added in next milestone">Commit (next milestone)</button>
</div>
<pre class="repo-card">${escapeHtml(gitOutput || "Run a git action to see output.")}</pre>
</section>
`).join("");
})()
: `<div class="repo-grid">
${visibleRepos.length
? visibleRepos.map((repo) => repoCardTemplate(repo)).join("")
: `<div class="empty-state">
<div>No repositories found</div>
<div class="muted">Try refreshing or check your server connection.</div>
</div>`}
</div>`}
</section>
` : `
<section class="panel stack">
<h3 class="title">Local Repository</h3>
<div class="row">
<input id="local-repo-path-input" placeholder="Path to local repository…" value="${escapeHtml(state.localRepoPathInput)}" />
<button id="open-local-repo-btn" type="button">Open</button>
</div>
${state.selectedRepoName
? `<div class="selected-repo"><span class="label">Active: </span>${escapeHtml(state.selectedRepoName)}</div>`
: ""}
<div class="row wrap">
<button id="git-status-btn" type="button">Status</button>
<button id="git-branch-btn" type="button">Branch</button>
<button id="git-pull-btn" type="button">Pull</button>
<button id="git-push-btn" type="button">Push</button>
</div>
<div>
<div class="label">Commit message</div>
<textarea id="commit-message-input" placeholder="Write commit message…">${escapeHtml(state.commitMessage)}</textarea>
<div class="muted commit-note">Commit support coming in the next milestone.</div>
</div>
${gitOutput
? `<pre class="git-output">${escapeHtml(gitOutput)}</pre>`
: `<p class="muted git-output-placeholder">Run a git command to see output here.</p>`}
</section>
`}
</main>
<aside class="rightbar stack">
<section class="panel stack">
<h3 class="title">Clone Repository</h3>
<input id="clone-url-input" placeholder="https://git.example.com/org/repo.git" value="${escapeHtml(state.cloneUrlInput)}" />
<input id="clone-destination-input" placeholder="/Users/me/code/repo" value="${escapeHtml(state.cloneDestinationInput || state.settings.defaultCloneDirectory)}" />
<button id="clone-btn" class="primary" type="button">Clone</button>
<span class="muted">Uses system Git executable from settings.</span>
</section>
<div class="tabs right-tabs">
<button class="tab-btn ${activeRightTab === "clone" ? "active" : ""}" data-right-tab="clone">Clone</button>
<button class="tab-btn ${activeRightTab === "settings" ? "active" : ""}" data-right-tab="settings">Settings</button>
<button class="tab-btn ${activeRightTab === "servers" ? "active" : ""}" data-right-tab="servers">Servers</button>
</div>
<section class="panel stack">
<h3 class="title">Settings</h3>
<div class="label">Theme</div>
<select id="theme-select">
<option value="dark" ${state.settings.theme === "dark" ? "selected" : ""}>Dark (default)</option>
</select>
<div class="label">Git executable path</div>
<input id="git-path-input" value="${escapeHtml(state.settings.gitExecutablePath)}" placeholder="git or /usr/bin/git" />
<div class="label">Default clone directory</div>
<input id="default-clone-dir-input" value="${escapeHtml(state.settings.defaultCloneDirectory)}" placeholder="/Users/me/code" />
<button id="save-basic-settings-btn" type="button">Save basic settings</button>
</section>
${activeRightTab === "clone" ? `
<section class="panel stack">
<div>
<div class="label">Repository URL</div>
<input id="clone-url-input" placeholder="https://git.example.com/org/repo.git" value="${escapeHtml(state.cloneUrlInput)}" />
</div>
<div>
<div class="label">Destination path</div>
<input id="clone-destination-input" placeholder="/Users/me/code/repo" value="${escapeHtml(state.cloneDestinationInput || state.settings.defaultCloneDirectory)}" />
</div>
<button id="clone-btn" class="primary" type="button">Clone</button>
<span class="muted">Uses system Git from settings.</span>
</section>
` : ""}
<section class="panel stack">
<h3 class="title">Server Management</h3>
<div class="stack">
${activeRightTab === "settings" ? `
<section class="panel stack">
<div>
<div class="label">Theme</div>
<select id="theme-select">
<option value="dark" ${state.settings.theme === "dark" ? "selected" : ""}>Dark (default)</option>
</select>
</div>
<div>
<div class="label">Git executable path</div>
<input id="git-path-input" value="${escapeHtml(state.settings.gitExecutablePath)}" placeholder="git or /usr/bin/git" />
</div>
<div>
<div class="label">Default clone directory</div>
<input id="default-clone-dir-input" value="${escapeHtml(state.settings.defaultCloneDirectory)}" placeholder="/Users/me/code" />
</div>
<button id="save-basic-settings-btn" class="primary" type="button">Save settings</button>
${settingsNotice ? `<div class="muted">${escapeHtml(settingsNotice)}</div>` : ""}
</section>
` : ""}
${activeRightTab === "servers" ? `
<section class="panel stack">
${state.settings.servers
.map(
(server) => `
<div class="server-chip stack">
<strong>${escapeHtml(server.displayName)}</strong>
<span class="muted">${escapeHtml(server.serverUrl)}</span>
<div class="row wrap">
<button class="set-default-server-btn" data-id="${server.id}" type="button">Set Default</button>
<button class="edit-server-btn" data-id="${server.id}" type="button">Edit</button>
<button class="delete-server-btn danger" data-id="${server.id}" type="button">Remove</button>
</div>
.map((server) => `
<div class="server-chip stack">
<strong>${escapeHtml(server.displayName)}</strong>
<span class="muted">${escapeHtml(server.serverUrl)}</span>
<div class="row wrap">
<button class="set-default-server-btn" data-id="${server.id}" type="button">Set default</button>
<button class="edit-server-btn" data-id="${server.id}" type="button">Edit</button>
<button class="delete-server-btn danger" data-id="${server.id}" type="button">Remove</button>
</div>
`
)
.join("")}
</div>
<button id="add-server-btn" type="button">Add new server</button>
<div id="server-form-slot" class="stack"></div>
<div class="muted">${escapeHtml(settingsNotice)}</div>
</section>
</div>
`).join("")}
<button id="add-server-btn" type="button">Add new server</button>
<div id="server-form-slot" class="stack"></div>
${settingsNotice ? `<div class="muted">${escapeHtml(settingsNotice)}</div>` : ""}
</section>
` : ""}
</aside>
</div>
`;
@@ -327,15 +420,21 @@ function openServerForm(serverId = null) {
async function loadRepositories() {
const activeServer = getActiveServer();
if (!activeServer) {
// Empty setup defaults to mock cards so UI can be tested immediately.
repositories = [...mockRepos];
currentUserLogin = "alice"; // mock user matches mock personal repos
return;
}
try {
repositories = await fetchRepositories(activeServer);
const [user, repos] = await Promise.all([
fetchCurrentUser(activeServer),
fetchRepositories(activeServer),
]);
currentUserLogin = user.login || "";
repositories = repos;
} catch (error) {
settingsNotice = `Using mock repositories. API fetch failed: ${error.message}`;
currentUserLogin = "alice";
repositories = [...mockRepos];
}
}
@@ -371,13 +470,35 @@ function bindDashboardEvents() {
});
document.getElementById("open-settings-btn")?.addEventListener("click", () => {
const panel = document.getElementById("server-form-slot");
if (panel) panel.scrollIntoView({ behavior: "smooth", block: "start" });
activeRightTab = "servers";
render();
});
document.querySelectorAll("[data-right-tab]").forEach((btn) => {
btn.addEventListener("click", () => {
activeRightTab = btn.dataset.rightTab;
render();
});
});
document.querySelectorAll("[data-main-tab]").forEach((btn) => {
btn.addEventListener("click", () => {
activeMainTab = btn.dataset.mainTab;
render();
});
});
document.querySelectorAll("[data-owner-filter]").forEach((btn) => {
btn.addEventListener("click", () => {
repoOwnerFilter = btn.dataset.ownerFilter;
render();
});
});
document.querySelectorAll(".open-repo-btn").forEach((button) => {
button.addEventListener("click", () => {
state.selectedRepoName = button.dataset.repoName || "Repository";
activeMainTab = "local";
render();
});
});
@@ -385,6 +506,7 @@ function bindDashboardEvents() {
document.querySelectorAll(".clone-repo-btn").forEach((button) => {
button.addEventListener("click", () => {
state.cloneUrlInput = button.dataset.cloneUrl || "";
activeRightTab = "clone";
render();
});
});
+11
View File
@@ -27,6 +27,17 @@ export function buildHeaders(serverConfig) {
return headers;
}
export async function fetchCurrentUser(serverConfig) {
const apiBase = buildApiBaseUrl(serverConfig.serverUrl);
const response = await fetch(`${apiBase}/user`, {
headers: buildHeaders(serverConfig),
});
if (!response.ok) {
throw new Error(`Gitea API error: ${response.status}`);
}
return response.json();
}
export async function fetchRepositories(serverConfig, page = 1, limit = 50) {
const apiBase = buildApiBaseUrl(serverConfig.serverUrl);
const url = `${apiBase}/user/repos?page=${page}&limit=${limit}&sort=updated`;
+1 -1
View File
@@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"tauri": "tauri",
"tauri:dev": "tauri dev",
"dev": "tauri dev",
"tauri:build": "tauri build"
},
"devDependencies": {