Skip to content

Commit

Permalink
Merge pull request #11 from mamertofabian/repo-list
Browse files Browse the repository at this point in the history
Repo list
  • Loading branch information
aidrivencoder authored Dec 29, 2024
2 parents 49f485c + f5c669b commit 9cdd28d
Show file tree
Hide file tree
Showing 3 changed files with 222 additions and 14 deletions.
186 changes: 177 additions & 9 deletions src/lib/components/GitHubSettings.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
ChevronDown,
Check,
X,
Search,
Loader2,
} from "lucide-svelte";
import { onMount } from 'svelte';
import { GitHubService } from "../../services/GitHubService";
Expand All @@ -37,11 +39,108 @@
let isTokenValid: boolean | null = null;
let tokenValidationTimeout: number;
let validationError: string | null = null;
let repositories: Array<{
name: string;
description: string | null;
html_url: string;
private: boolean;
created_at: string;
updated_at: string;
language: string | null;
}> = [];
let isLoadingRepos = false;
let showRepoDropdown = false;
let repoSearchQuery = "";
let repoInputFocused = false;
let repoExists = false;
let selectedIndex = -1;
onMount(() => {
$: filteredRepos = repositories
.filter(repo =>
repo.name.toLowerCase().includes(repoSearchQuery.toLowerCase()) ||
(repo.description && repo.description.toLowerCase().includes(repoSearchQuery.toLowerCase()))
)
.slice(0, 10);
$: if (repoName) {
repoExists = repositories.some(repo => repo.name.toLowerCase() === repoName.toLowerCase());
}
async function loadRepositories() {
if (!githubToken || !repoOwner || !isTokenValid) return;
try {
isLoadingRepos = true;
const githubService = new GitHubService(githubToken);
repositories = await githubService.listUserRepositories(repoOwner);
} catch (error) {
console.error('Error loading repositories:', error);
repositories = [];
} finally {
isLoadingRepos = false;
}
}
function handleRepoInput() {
repoSearchQuery = repoName;
onInput();
}
function selectRepo(repo: typeof repositories[0]) {
repoName = repo.name;
showRepoDropdown = false;
repoSearchQuery = repo.name;
onInput();
}
function handleRepoKeydown(event: KeyboardEvent) {
if (!showRepoDropdown) return;
switch (event.key) {
case 'ArrowDown':
event.preventDefault();
selectedIndex = Math.min(selectedIndex + 1, filteredRepos.length - 1);
break;
case 'ArrowUp':
event.preventDefault();
selectedIndex = Math.max(selectedIndex - 1, -1);
break;
case 'Enter':
event.preventDefault();
if (selectedIndex >= 0 && filteredRepos[selectedIndex]) {
selectRepo(filteredRepos[selectedIndex]);
}
break;
case 'Escape':
event.preventDefault();
showRepoDropdown = false;
break;
}
}
function handleRepoFocus() {
repoInputFocused = true;
showRepoDropdown = true;
repoSearchQuery = repoName;
}
function handleRepoBlur() {
repoInputFocused = false;
// Delay hiding dropdown to allow click events to register
setTimeout(() => {
showRepoDropdown = false;
}, 200);
}
onMount(async () => {
chrome.storage.local.get(['showNewUserGuide'], (result) => {
showNewUserGuide = result.showNewUserGuide ?? true;
});
// If we have initial valid settings, validate and load repos
if (githubToken && repoOwner) {
await validateSettings();
}
});
function toggleNewUserGuide() {
Expand All @@ -63,6 +162,11 @@
const result = await githubService.validateTokenAndUser(repoOwner);
isTokenValid = result.isValid;
validationError = result.error || null;
// Load repositories after successful validation
if (result.isValid) {
await loadRepositories();
}
} catch (error) {
console.error('Error validating settings:', error);
isTokenValid = false;
Expand Down Expand Up @@ -238,14 +342,75 @@
{/if}
</span>
</Label>
<Input
type="text"
id="repoName"
bind:value={repoName}
on:input={onInput}
placeholder="repository-name"
class="bg-slate-800 border-slate-700 text-slate-200 placeholder:text-slate-500"
/>
<div class="relative">
<div class="relative">
<Input
type="text"
id="repoName"
bind:value={repoName}
on:input={handleRepoInput}
on:focus={handleRepoFocus}
on:blur={handleRepoBlur}
on:keydown={handleRepoKeydown}
placeholder="Search or enter repository name"
class="bg-slate-800 border-slate-700 text-slate-200 placeholder:text-slate-500 pr-10"
autocomplete="off"
/>
<div class="absolute right-3 top-1/2 -translate-y-1/2">
{#if isLoadingRepos}
<Loader2 class="h-4 w-4 text-slate-400 animate-spin" />
{:else}
<Search class="h-4 w-4 text-slate-400" />
{/if}
</div>
</div>
{#if showRepoDropdown && (filteredRepos.length > 0 || !repoExists)}
<div class="absolute z-50 w-full mt-1 bg-slate-800 border border-slate-700 rounded-md shadow-lg">
<ul class="py-1 max-h-60 overflow-auto">
{#each filteredRepos as repo, i}
<li>
<button
class="w-full px-3 py-2 text-left hover:bg-slate-700 text-slate-200 {selectedIndex === i ? 'bg-slate-700' : ''}"
on:click={() => selectRepo(repo)}
>
<div class="flex items-center justify-between">
<span class="font-medium">{repo.name}</span>
{#if repo.private}
<span class="text-xs text-slate-400">Private</span>
{/if}
</div>
{#if repo.description}
<p class="text-sm text-slate-400 truncate">{repo.description}</p>
{/if}
</button>
</li>
{/each}
{#if !repoExists}
<li class="px-3 py-2 text-sm text-slate-400">
{#if repoName.length > 0}
<p class="text-orange-400">💡If the repository "{repoName}" doesn't exist, it will be created automatically.</p>
<p class="text-emerald-400">✨ If it's a private repository, you can still enter it manually even if it's not visible in the list.</p>
{:else}
<p>Enter a repository name (new or private) or select from your public repositories</p>
{/if}
</li>
{/if}
</ul>
</div>
{/if}
</div>
{#if repoExists}
<p class="text-sm text-blue-400">
ℹ️ Using existing repository
</p>
{:else if repoName}
<p class="text-sm text-emerald-400">
✨ A new repository will be created if it doesn't exist yet.
</p>
<p class="text-sm text-orange-400">
⚠️ You can push to private repositories, but loading it into Bolt will fail.
</p>
{/if}
</div>

<div class="space-y-2">
Expand All @@ -262,6 +427,9 @@
class="bg-slate-800 border-slate-700 text-slate-200 placeholder:text-slate-500"
/>
</div>
<p class="text-sm text-slate-400">
💡 If the branch doesn't exist, it will be created automatically from the default branch.
</p>

<Button
type="submit"
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/ProjectsList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
}> = [];
let searchQuery = '';
let showPublicRepos = true;
let showPublicRepos = false;
let filteredProjects: Array<{
projectId?: string;
repoName: string;
Expand Down
48 changes: 44 additions & 4 deletions src/services/GitHubService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,16 +187,15 @@ export class GitHubService {
// Create a more informative README.md to initialize the repository
const readmeContent = `# ${repo}
## Feel free to delete this file and replace it with your own content.
## Repository Initialization Notice
This repository was automatically initialized by the Bolt to GitHub extension.
**Auto-Generated Repository**
- Created to ensure a valid Git repository structure
- Serves as an initial commit point for your project
If you did not intend to create this repository or have any questions,
please check your Bolt to GitHub extension settings.`;
- Serves as an initial commit point for your project`;

await this.pushFile({
owner,
Expand Down Expand Up @@ -305,4 +304,45 @@ please check your Bolt to GitHub extension settings.`;
throw new Error(`Failed to fetch public repositories: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}

async listUserRepositories(username: string): Promise<Array<{
name: string;
description: string | null;
html_url: string;
private: boolean;
created_at: string;
updated_at: string;
language: string | null;
}>> {
try {
// First try user's repositories
try {
const repos = await this.request('GET', `/users/${username}/repos?per_page=100&sort=updated`);
return repos.map((repo: any) => ({
name: repo.name,
description: repo.description,
html_url: repo.html_url,
private: repo.private,
created_at: repo.created_at,
updated_at: repo.updated_at,
language: repo.language
}));
} catch (error) {
// If user endpoint fails, try organization endpoint
const repos = await this.request('GET', `/orgs/${username}/repos?per_page=100&sort=updated`);
return repos.map((repo: any) => ({
name: repo.name,
description: repo.description,
html_url: repo.html_url,
private: repo.private,
created_at: repo.created_at,
updated_at: repo.updated_at,
language: repo.language
}));
}
} catch (error) {
console.error('Failed to fetch repositories:', error);
throw new Error(`Failed to fetch repositories: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
}

0 comments on commit 9cdd28d

Please sign in to comment.