10 Commits

Author SHA1 Message Date
69e5f5a639 feat(statusBar): add account name to status bar display
- Updated the status bar item to include an optional account name.
- Modified the status bar text to show the account name if available.
- Adjusted the interface to accommodate the new accountName property.
2025-12-18 12:22:35 +08:00
3f04933b3b fix: Standardize formatting in README.md for better readability 2025-12-17 16:15:08 +08:00
99c4117193 chore: Bump version to 0.0.4 and update release notes with improvements and fixes 2025-12-17 16:13:00 +08:00
cc8163be64 fix: Correct publisher name formatting in package.json 2025-12-17 16:09:08 +08:00
239ddbd362 feat: Add export functionality for issues and pull requests
- Implemented exportIssues and exportPullRequests functions to export data in markdown, CSV, and JSON formats.
- Created helper functions for generating markdown and CSV formats for issues and pull requests.
- Added showExportDialog for user interaction to select export options.
- Implemented saveToFile function to handle file saving.

feat: Implement favorites and recent repositories management

- Added functionality to add, remove, and check favorites for repositories.
- Implemented recent repositories tracking with a limit on the number of recent entries.
- Created a quick pick interface to show favorites and recent repositories.

feat: Introduce notifications management

- Implemented functions to fetch, mark as read, and manage notifications.
- Created a NotificationProvider class to display notifications in a tree view.
- Added functionality to show notifications in a modal with quick actions.

feat: Implement search functionality for repositories, issues, and pull requests

- Added searchRepositories, searchIssues, and searchPullRequests functions with filtering options.
- Implemented getLabels and getCollaborators functions for additional filtering capabilities.
- Created a showFilterQuickPick function for user interaction to select filters.

feat: Enhance status bar with shortcuts and dynamic updates

- Added default keyboard shortcuts for various commands.
- Implemented createStatusBarItem and updateStatusBar functions to manage status bar display.
- Created a showStatusMenu function for quick actions related to the extension.
2025-12-17 16:06:52 +08:00
4b67c7f14a Add wizgit marketplace icon image to resources 2025-12-17 11:15:27 +08:00
92d2e939e1 bump version to 0.0.3 and update release notes 2025-12-17 11:11:38 +08:00
067bad6eef add LICENSE file and update README with detailed features and usage instructions 2025-12-17 11:09:57 +08:00
0f2f70324b update release notes for version 0.0.2 with enhancements and bug fixes 2025-12-17 10:44:35 +08:00
0f8143e881 bump version to 0.0.2 and update default branch to master 2025-12-17 10:43:31 +08:00
12 changed files with 2668 additions and 208 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) [year] [fullname]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

179
README.md
View File

@@ -1,57 +1,182 @@
# WizGIT for VS Code
![Visual Studio Marketplace Version](https://img.shields.io/visual-studio-marketplace/v/your-publisher.wizgit-in-vscode?style=for-the-badge&label=Marketplace)
![Visual Studio Marketplace Version](https://img.shields.io/visual-studio-marketplace/v/terencecarrera.wizgit-in-vscode?style=for-the-badge&label=Marketplace)
![License](https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge)
Your intelligent coding companion for repository management, right inside Visual Studio Code. WizGIT simplifies repository creation and management with seamless API integration.
Your intelligent coding companion for comprehensive repository management, right inside Visual Studio Code. WizGIT provides seamless integration with your WizGit for repositories, issues, and pull requests.
## Features
* **Repository Creation:** Easily create new repositories on WizGit directly from VS Code using the WizGit API.
* **Secure Authentication:** Safely authenticate using your WizGit Personal Access Token.
* **Progress Tracking:** Visual progress indication during repository creation with detailed status updates.
* **Error Handling:** Comprehensive error reporting for troubleshooting API issues.
### 🏠 **Dedicated Activity Bar**
* **Custom WizGIT sidebar** with organized views for repositories, issues, and pull requests
* **Quick access** to all WizGIT functionality from the activity bar
### 📁 **Repository Management**
* **Create repositories:** Easily create new repositories on WizGit directly from VS Code
* **Clone repositories:** Clone existing repositories with integrated UI
* **Repository discovery:** Automatic workspace detection and repository listing
* **Refresh functionality:** Keep your repository list up-to-date
### 🐛 **Issue Management**
* **Create issues:** Create new issues with intelligent workspace auto-detection
* **Repository auto-detection:** Automatically populate organization and repository from your workspace
* **Branch selection:** Choose target branch from a list of available branches (local and remote)
* **Context integration:** Create issues from file explorer or editor context menu
### 🔀 **Pull Request Management**
* **Create pull requests:** Create PRs with comprehensive file change detection
* **File changes display:** View detailed file modifications and diffs
* **Smart branch selection:** Choose source and destination branches with intelligent defaults
* **Workspace integration:** Auto-detect repository information from your current workspace
* **Enhanced viewing experience:** Modern, responsive webview interface for viewing issues and PRs
* **Interactive comments section:** Beautiful comments display with hover effects and markdown support
### 🔐 **Authentication & Security**
* **Secure token storage:** Safely store and manage your WizGit Personal Access Token
* **Configuration management:** Easy setup and configuration clearing options
* **API endpoint flexibility:** Support for custom WizGit/Gitea instances
### ⚙️ **Smart Features**
* **Workspace detection:** Automatically identify Git repositories and extract owner/repo information
* **Branch enumeration:** List and select from available local and remote branches
* **Progress tracking:** Visual progress indication during operations
* **Error handling:** Comprehensive error reporting and troubleshooting
* **Status bar integration:** Display current account name and configuration status in VS Code status bar
* **Modern UI components:** Enhanced webviews with card-based layouts, smooth animations, and VS Code theme integration
## Requirements
* Visual Studio Code v1.74.0 or newer.
* A valid WizGit account with API access.
* Personal Access Token from your WizGit instance.
* Visual Studio Code v1.74.0 or newer.
* A valid WizGit account with API access.
* Personal Access Token from your WizGit instance.
## Usage
## Getting Started
1. Open the Command Palette (`Ctrl+Shift+P` or `Cmd+Shift+P`)
2. Run the command: `WizGIT: Create Repository`
3. Follow the prompts to enter:
- Your WizGit API endpoint (e.g., `https://wizgit.com/api/v1`)
- Your Personal Access Token
- Repository name
- Repository description (optional)
### Initial Setup
The extension will create the repository and notify you of the result.
1. **Configure WizGIT:** Open Command Palette (`Ctrl+Shift+P`) and run `WizGIT: Configure WizGIT`
2. **Enter your details:**
- WizGit API endpoint (e.g., `https://your-wizgit-instance.com/api/v1`)
- Personal Access Token from your WizGit instance
3. **Access the sidebar:** Click the WizGIT icon in the activity bar to open the dedicated sidebar
### Creating Repositories
1. In the WizGIT sidebar, go to the **Repositories** view
2. Click the "Create Repository" button () in the title bar
3. Fill in repository details and click "Create Repository"
### Managing Issues
1. In the **Issues** view, click "Create Issue" (🐛)
2. The extension will automatically detect your workspace repository
3. Choose the target branch from the dropdown list
4. Fill in issue title and description, then submit
### Creating Pull Requests
1. In the **Pull Requests** view, click "Create Pull Request" (🔀)
2. Select source and destination branches
3. Review the automatically detected file changes
4. Add title and description, then create the PR
### Alternative Access Methods
- **Command Palette:** All commands are available via `Ctrl+Shift+P`
- **Context Menus:** Right-click in file explorer or editor to create issues
- **Keyboard Shortcuts:** Assign custom shortcuts to frequently used commands
## Commands
* `WizGIT: Create Repository` - Creates a new repository on your WizGit instance
### Repository Commands
* `WizGIT: Configure WizGIT` - Set up API endpoint and authentication
* `WizGIT: Create Repository` - Create a new repository
* `WizGIT: Clone Repository` - Clone an existing repository
* `WizGIT: Clear Configuration` - Remove stored authentication data
### Issue & PR Commands
* `WizGIT: Create Issue` - Create a new issue with workspace auto-detection
* `WizGIT: Create Pull Request` - Create a pull request with file change detection
## Extension Settings
Currently, this extension does not contribute any settings. All configuration is done through the interactive prompts when creating a repository.
This extension contributes the following settings:
* `wizgit.apiEndpoint` - Your WizGIT API endpoint URL
* `wizgit.autoDetect` - Automatically detect WizGIT repositories in workspace (default: `true`)
* `wizgit.statusBar.enabled` - Show WizGIT information in status bar (default: `true`)
* `wizgit.notifications.enabled` - Enable WizGIT notifications (default: `true`)
* `wizgit.defaultBranch` - Default branch name for new repositories (default: `master`)
You can access these settings through VS Code's Settings UI or by adding them to your `settings.json`.
## Known Issues
* Network connectivity issues may cause repository creation to fail.
* Please report any bugs or feature requests on our [GitHub Issues](https://github.com/your-repo/wizgit-in-vscode/issues) page.
* Network connectivity issues may cause API operations to fail
* File change detection for pull requests may not work with all repository configurations
* Workspace auto-detection requires a valid Git repository with remote origin configured
* Please report any bugs or feature requests on our [Issues page](http://126.209.24.132/terence.carrera/wizgit-vscode-extension/issues)
## Release Notes
### 0.0.5 (Current)
* Bump version to 0.0.5
**UI/UX Enhancements:**
* **Enhanced comments section:** Complete redesign of comments display in issue and PR webviews with modern card-based layout
* **Improved visual design:** Added hover effects, smooth transitions, and better typography for comments
* **Account name display:** Status bar now shows the authenticated user's account name after "WizGIT: "
* **Better empty states:** Friendly messages when no comments exist with call-to-action prompts
* **Markdown support hints:** Added helpful text about markdown formatting in comment forms
* **Enhanced comment forms:** Improved styling with better focus states, padding, and visual feedback
**Bug Fixes & Improvements:**
* Fixed status bar initialization to properly display account information on extension startup
### 0.0.4
* Bump version to 0.0.4
* Fix issue with branch selection dropdown not populating correctly in some cases
* Improve error messages for API failures during issue and pull request creation
* Optimize repository detection logic for workspaces with multiple Git repositories
* Enhance UI responsiveness during long-running operations with better progress indicators
### 0.0.3
* Bump version to 0.0.3
### 0.0.2
Major feature expansion and UI improvements:
* **New dedicated activity bar** with custom WizGIT sidebar
* **Issue management** with workspace auto-detection and branch selection
* **Pull request creation** with file change detection and diff display
* **Repository cloning** functionality
* **Workspace integration** - automatic repository detection from Git remotes
* **Branch enumeration** - list and select from local/remote branches
* **Context menu integration** - create issues from explorer and editor
* **Enhanced configuration** - comprehensive settings and token management
* **Improved error handling** and user experience
* **Custom branding** with WizGIT logo and theming
### 0.0.1
Initial release of WizGIT.
* Core feature: Create repositories via WizGit API.
* Interactive prompts for API configuration.
* Progress tracking and error handling.
Initial release of WizGIT:
* Core repository creation via WizGit API
* Basic authentication and configuration
* Progress tracking and error handling
---
**Enjoy streamlined repository management with WizGIT!**
**Enjoy comprehensive repository management with WizGIT! 🪄✨**

5
package-lock.json generated
View File

@@ -1,12 +1,13 @@
{
"name": "wizgit-in-vscode",
"version": "0.0.1",
"version": "0.0.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "wizgit-in-vscode",
"version": "0.0.1",
"version": "0.0.5",
"license": "MIT",
"dependencies": {
"node-fetch": "^3.3.2"
},

View File

@@ -2,7 +2,14 @@
"name": "wizgit-in-vscode",
"displayName": "WizGIT in VS Code",
"description": "A VS Code extension to interact with WizGIT API for repository management.",
"version": "0.0.1",
"version": "0.0.5",
"publisher": "TerenceCarrera",
"icon": "resources/wizgit-marketplace-icon.png",
"license": "MIT",
"repository": {
"type": "git",
"url": "http://126.209.24.132/terence.carrera/wizgit-vscode-extension"
},
"engines": {
"vscode": "^1.74.0"
},
@@ -21,10 +28,7 @@
{
"id": "wizgit",
"title": "WizGIT",
"icon": {
"light": "./resources/wizgit-logo.svg",
"dark": "./resources/wizgit-logo-dark.svg"
}
"icon": "./resources/wizgit-logo.svg"
}
]
},
@@ -101,6 +105,48 @@
"title": "Open Pull Request",
"category": "WizGIT",
"icon": "$(link-external)"
},
{
"command": "wizgit-in-vscode.searchRepositories",
"title": "Search Repositories",
"category": "WizGIT",
"icon": "$(telescope)"
},
{
"command": "wizgit-in-vscode.searchIssues",
"title": "Search Issues",
"category": "WizGIT",
"icon": "$(search)"
},
{
"command": "wizgit-in-vscode.searchPullRequests",
"title": "Search Pull Requests",
"category": "WizGIT",
"icon": "$(search)"
},
{
"command": "wizgit-in-vscode.openRecentRepositories",
"title": "Open Recent Repositories",
"category": "WizGIT",
"icon": "$(history)"
},
{
"command": "wizgit-in-vscode.exportIssues",
"title": "Export Issues",
"category": "WizGIT",
"icon": "$(save)"
},
{
"command": "wizgit-in-vscode.exportPullRequests",
"title": "Export Pull Requests",
"category": "WizGIT",
"icon": "$(save)"
},
{
"command": "wizgit-in-vscode.showNotifications",
"title": "Show Notifications",
"category": "WizGIT",
"icon": "$(bell)"
}
],
"menus": {
@@ -155,6 +201,32 @@
}
]
},
"keybindings": [
{
"command": "wizgit-in-vscode.searchRepositories",
"key": "ctrl+shift+g r",
"mac": "cmd+shift+g r",
"when": "wizgit:enabled"
},
{
"command": "wizgit-in-vscode.searchIssues",
"key": "ctrl+shift+g i",
"mac": "cmd+shift+g i",
"when": "wizgit:enabled"
},
{
"command": "wizgit-in-vscode.searchPullRequests",
"key": "ctrl+shift+g p",
"mac": "cmd+shift+g p",
"when": "wizgit:enabled"
},
{
"command": "wizgit-in-vscode.openRecentRepositories",
"key": "ctrl+shift+g t",
"mac": "cmd+shift+g t",
"when": "wizgit:enabled"
}
],
"configuration": {
"title": "WizGIT",
"properties": {
@@ -181,7 +253,7 @@
"wizgit.defaultBranch": {
"type": "string",
"description": "Default branch name for new repositories",
"default": "main"
"default": "master"
}
}
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

File diff suppressed because it is too large Load Diff

229
src/features/export.ts Normal file
View File

@@ -0,0 +1,229 @@
import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
export interface ExportOptions {
format: 'markdown' | 'csv' | 'json';
includeDetails: boolean;
includeComments: boolean;
filename?: string;
}
/**
* Export issues to file
*/
export async function exportIssues(
issues: any[],
options: ExportOptions
): Promise<string | null> {
try {
let content = '';
if (options.format === 'markdown') {
content = generateMarkdownIssues(issues, options.includeDetails, options.includeComments);
} else if (options.format === 'csv') {
content = generateCSVIssues(issues);
} else if (options.format === 'json') {
content = JSON.stringify(issues, null, 2);
}
return content;
} catch (error) {
console.error('Export issues error:', error);
return null;
}
}
/**
* Export pull requests to file
*/
export async function exportPullRequests(
pullRequests: any[],
options: ExportOptions
): Promise<string | null> {
try {
let content = '';
if (options.format === 'markdown') {
content = generateMarkdownPullRequests(pullRequests, options.includeDetails);
} else if (options.format === 'csv') {
content = generateCSVPullRequests(pullRequests);
} else if (options.format === 'json') {
content = JSON.stringify(pullRequests, null, 2);
}
return content;
} catch (error) {
console.error('Export PRs error:', error);
return null;
}
}
/**
* Generate markdown format for issues
*/
function generateMarkdownIssues(
issues: any[],
includeDetails: boolean = false,
_includeComments: boolean = false
): string {
let markdown = '# Issues Export\n\n';
markdown += `Generated: ${new Date().toISOString()}\n\n`;
issues.forEach(issue => {
markdown += `## #${issue.number} - ${issue.title}\n\n`;
markdown += `**State:** ${issue.state}\n`;
markdown += `**Author:** ${issue.user?.login || 'Unknown'}\n`;
if (issue.assignees && issue.assignees.length > 0) {
markdown += `**Assignees:** ${issue.assignees.map((a: any) => a.login).join(', ')}\n`;
}
if (issue.labels && issue.labels.length > 0) {
markdown += `**Labels:** ${issue.labels.map((l: any) => l.name).join(', ')}\n`;
}
markdown += `**Created:** ${new Date(issue.created_at).toLocaleDateString()}\n`;
if (includeDetails && issue.body) {
markdown += `\n### Description\n\n${issue.body}\n`;
}
markdown += `\n[View on WizGIT](${issue.html_url})\n\n---\n\n`;
});
return markdown;
}
/**
* Generate markdown format for pull requests
*/
function generateMarkdownPullRequests(
pullRequests: any[],
includeDetails: boolean = false
): string {
let markdown = '# Pull Requests Export\n\n';
markdown += `Generated: ${new Date().toISOString()}\n\n`;
pullRequests.forEach(pr => {
markdown += `## #${pr.number} - ${pr.title}\n\n`;
markdown += `**State:** ${pr.state}\n`;
markdown += `**Author:** ${pr.user?.login || 'Unknown'}\n`;
markdown += `**Base:** ${pr.base?.ref || 'Unknown'} ← **Head:** ${pr.head?.ref || 'Unknown'}\n`;
markdown += `**Mergeable:** ${pr.mergeable ? 'Yes' : 'No'}\n`;
markdown += `**Created:** ${new Date(pr.created_at).toLocaleDateString()}\n`;
if (includeDetails && pr.body) {
markdown += `\n### Description\n\n${pr.body}\n`;
}
markdown += `\n[View on WizGIT](${pr.html_url})\n\n---\n\n`;
});
return markdown;
}
/**
* Generate CSV format for issues
*/
function generateCSVIssues(issues: any[]): string {
const headers = ['ID', 'Number', 'Title', 'State', 'Author', 'Assignees', 'Labels', 'Created', 'Updated'];
const rows = issues.map(issue => [
issue.id,
issue.number,
`"${issue.title}"`,
issue.state,
issue.user?.login || '',
issue.assignees?.map((a: any) => a.login).join(';') || '',
issue.labels?.map((l: any) => l.name).join(';') || '',
new Date(issue.created_at).toLocaleDateString(),
new Date(issue.updated_at).toLocaleDateString()
]);
return [headers.join(','), ...rows.map(r => r.join(','))].join('\n');
}
/**
* Generate CSV format for pull requests
*/
function generateCSVPullRequests(pullRequests: any[]): string {
const headers = ['ID', 'Number', 'Title', 'State', 'Author', 'Base', 'Head', 'Mergeable', 'Created'];
const rows = pullRequests.map(pr => [
pr.id,
pr.number,
`"${pr.title}"`,
pr.state,
pr.user?.login || '',
pr.base?.ref || '',
pr.head?.ref || '',
pr.mergeable ? 'Yes' : 'No',
new Date(pr.created_at).toLocaleDateString()
]);
return [headers.join(','), ...rows.map(r => r.join(','))].join('\n');
}
/**
* Show export dialog and save file
*/
export async function showExportDialog(dataType: 'issues' | 'pullRequests'): Promise<ExportOptions | null> {
const formatOptions = ['Markdown', 'CSV', 'JSON'];
const selectedFormat = await vscode.window.showQuickPick(
formatOptions,
{ placeHolder: 'Select export format' }
);
if (!selectedFormat) return null;
const includeDetailsResponse = await vscode.window.showQuickPick(
['Yes', 'No'],
{ placeHolder: 'Include full details/descriptions?' }
);
const includeDetails = includeDetailsResponse === 'Yes';
let _includeComments = false;
if (dataType === 'issues') {
const includeCommentsResponse = await vscode.window.showQuickPick(
['Yes', 'No'],
{ placeHolder: 'Include comments?' }
);
_includeComments = includeCommentsResponse === 'Yes';
}
return {
format: selectedFormat.toLowerCase() as 'markdown' | 'csv' | 'json',
includeDetails,
includeComments: _includeComments
};
}
/**
* Save content to file
*/
export async function saveToFile(
content: string,
defaultFilename: string,
_format: string
): Promise<string | null> {
const uri = await vscode.window.showSaveDialog({
defaultUri: vscode.Uri.file(defaultFilename),
filters: {
'All Files': ['*'],
'Markdown': ['md'],
'CSV': ['csv'],
'JSON': ['json']
}
});
if (!uri) return null;
try {
fs.writeFileSync(uri.fsPath, content);
vscode.window.showInformationMessage(`Exported to ${path.basename(uri.fsPath)}`);
return uri.fsPath;
} catch (error) {
vscode.window.showErrorMessage(`Failed to save file: ${error}`);
return null;
}
}

172
src/features/favorites.ts Normal file
View File

@@ -0,0 +1,172 @@
import * as vscode from 'vscode';
export interface RepoReference {
id: string;
name: string;
owner: string;
repo: string;
cloneUrl?: string;
htmlUrl?: string;
timestamp: number;
lastAccessed?: number;
}
const FAVORITES_KEY = 'wizgit.favorites';
const RECENT_KEY = 'wizgit.recent';
const MAX_RECENT = 10;
/**
* Add a repository to favorites
*/
export async function addFavorite(
context: vscode.ExtensionContext,
repoId: string,
name: string,
owner: string,
repo: string
): Promise<void> {
const favorites = getFavorites(context);
if (!favorites.find(f => f.id === repoId)) {
favorites.push({
id: repoId,
name,
owner,
repo,
timestamp: Date.now()
});
await context.globalState.update(FAVORITES_KEY, favorites);
}
}
/**
* Remove a repository from favorites
*/
export async function removeFavorite(
context: vscode.ExtensionContext,
repoId: string
): Promise<void> {
const favorites = getFavorites(context);
const filtered = favorites.filter(f => f.id !== repoId);
await context.globalState.update(FAVORITES_KEY, filtered);
}
/**
* Get all favorites
*/
export function getFavorites(context: vscode.ExtensionContext): RepoReference[] {
return context.globalState.get(FAVORITES_KEY, []);
}
/**
* Check if a repository is favorited
*/
export function isFavorite(context: vscode.ExtensionContext, repoId: string): boolean {
return getFavorites(context).some(f => f.id === repoId);
}
/**
* Add a repository to recent list
*/
export async function addToRecent(
context: vscode.ExtensionContext,
repoId: string,
name: string,
owner: string,
repo: string
): Promise<void> {
const recent = getRecent(context);
// Remove if already exists
const filtered = recent.filter(r => r.id !== repoId);
// Add to beginning
filtered.unshift({
id: repoId,
name,
owner,
repo,
timestamp: Date.now(),
lastAccessed: Date.now()
});
// Keep only last MAX_RECENT
filtered.splice(MAX_RECENT);
await context.globalState.update(RECENT_KEY, filtered);
}
/**
* Get recent repositories
*/
export function getRecent(context: vscode.ExtensionContext): RepoReference[] {
return context.globalState.get(RECENT_KEY, []);
}
/**
* Clear recent repositories
*/
export async function clearRecent(context: vscode.ExtensionContext): Promise<void> {
await context.globalState.update(RECENT_KEY, []);
}
/**
* Show favorites and recent repos in a quick pick
*/
export async function showRepositoryQuickPick(context: vscode.ExtensionContext): Promise<RepoReference | undefined> {
const favorites = getFavorites(context);
const recent = getRecent(context);
const items: vscode.QuickPickItem[] = [];
if (favorites.length > 0) {
items.push({ label: '$(star-full) Favorites', kind: vscode.QuickPickItemKind.Separator });
items.push(...favorites.map(f => ({
label: `$(repo) ${f.name}`,
description: `${f.owner}/${f.repo}`,
detail: `Added ${new Date(f.timestamp).toLocaleDateString()}`,
data: f
})));
}
if (recent.length > 0) {
items.push({ label: '$(history) Recent', kind: vscode.QuickPickItemKind.Separator });
items.push(...recent.map(r => ({
label: `$(repo) ${r.name}`,
description: `${r.owner}/${r.repo}`,
detail: `Accessed ${new Date(r.lastAccessed || r.timestamp).toLocaleDateString()}`,
data: r
})));
}
if (items.length === 0) {
vscode.window.showInformationMessage('No favorites or recent repositories yet');
return undefined;
}
const selected = await vscode.window.showQuickPick(items, {
placeHolder: 'Select a repository'
});
return selected ? (selected as any).data : undefined;
}
/**
* Toggle favorite status for a repository
*/
export async function toggleFavorite(
context: vscode.ExtensionContext,
repoId: string,
name: string,
owner: string,
repo: string
): Promise<boolean> {
if (isFavorite(context, repoId)) {
await removeFavorite(context, repoId);
return false;
} else {
await addFavorite(context, repoId, name, owner, repo);
return true;
}
}

View File

@@ -0,0 +1,231 @@
import * as vscode from 'vscode';
import fetch from 'node-fetch';
export interface NotificationData {
id: number;
type: string;
repository: {
name: string;
full_name: string;
};
subject: {
title: string;
type: 'Issue' | 'PullRequest' | 'Commit';
url: string;
};
reason: string;
unread: boolean;
updated_at: string;
url: string;
}
/**
* Fetch notifications for the current user
*/
export async function getNotifications(
apiEndpoint: string,
token: string,
page: number = 1,
limit: number = 30
): Promise<NotificationData[]> {
try {
const url = new URL(`${apiEndpoint}/notifications`);
url.searchParams.append('page', page.toString());
url.searchParams.append('limit', limit.toString());
const response = await fetch(url.toString(), {
headers: {
'Authorization': `token ${token}`,
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Failed to fetch notifications: ${response.statusText}`);
}
return (await response.json()) as NotificationData[];
} catch (error) {
console.error('Get notifications error:', error);
return [];
}
}
/**
* Fetch unread notifications only
*/
export async function getUnreadNotifications(
apiEndpoint: string,
token: string
): Promise<NotificationData[]> {
try {
const notifications = await getNotifications(apiEndpoint, token);
return notifications.filter(n => n.unread);
} catch (error) {
console.error('Get unread notifications error:', error);
return [];
}
}
/**
* Mark notification as read
*/
export async function markNotificationAsRead(
apiEndpoint: string,
token: string,
notificationId: number
): Promise<boolean> {
try {
const response = await fetch(`${apiEndpoint}/notifications/${notificationId}`, {
method: 'PATCH',
headers: {
'Authorization': `token ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ status: 'read' })
});
return response.ok;
} catch (error) {
console.error('Mark notification as read error:', error);
return false;
}
}
/**
* Mark all notifications as read
*/
export async function markAllNotificationsAsRead(
apiEndpoint: string,
token: string
): Promise<boolean> {
try {
const response = await fetch(`${apiEndpoint}/notifications`, {
method: 'PUT',
headers: {
'Authorization': `token ${token}`,
'Content-Type': 'application/json'
}
});
return response.ok;
} catch (error) {
console.error('Mark all notifications as read error:', error);
return false;
}
}
/**
* Get mentions (notifications mentioning the current user)
*/
export async function getMentions(
apiEndpoint: string,
token: string
): Promise<NotificationData[]> {
try {
const notifications = await getUnreadNotifications(apiEndpoint, token);
return notifications.filter(n =>
n.reason === 'mention' || n.reason === '@mention'
);
} catch (error) {
console.error('Get mentions error:', error);
return [];
}
}
/**
* Show notifications in a tree view
*/
export class NotificationProvider implements vscode.TreeDataProvider<NotificationItem> {
private _onDidChangeTreeData: vscode.EventEmitter<NotificationItem | undefined | null | void> =
new vscode.EventEmitter<NotificationItem | undefined | null | void>();
readonly onDidChangeTreeData: vscode.Event<NotificationItem | undefined | null | void> =
this._onDidChangeTreeData.event;
private notifications: NotificationData[] = [];
private apiEndpoint: string = '';
private token: string = '';
constructor(_context: vscode.ExtensionContext) { }
setCredentials(apiEndpoint: string, token: string): void {
this.apiEndpoint = apiEndpoint;
this.token = token;
}
async refresh(): Promise<void> {
if (this.apiEndpoint && this.token) {
this.notifications = await getUnreadNotifications(this.apiEndpoint, this.token);
this._onDidChangeTreeData.fire();
}
}
getTreeItem(element: NotificationItem): vscode.TreeItem {
return element;
}
async getChildren(): Promise<NotificationItem[]> {
if (this.notifications.length === 0) {
return [new NotificationItem('No unread notifications', vscode.TreeItemCollapsibleState.None)];
}
return this.notifications.map(n =>
new NotificationItem(
`${n.subject.title}`,
vscode.TreeItemCollapsibleState.None,
{
title: 'Open Notification',
command: 'wizgit.openNotification',
arguments: [n]
},
n
)
);
}
}
export class NotificationItem extends vscode.TreeItem {
constructor(
public readonly label: string,
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
public readonly command?: vscode.Command,
public readonly data?: NotificationData
) {
super(label, collapsibleState);
if (data) {
this.description = `${data.repository.name}`;
this.iconPath = this.getIconForType(data.subject.type);
this.tooltip = `${data.reason}: ${data.subject.title}`;
}
}
private getIconForType(type: string): vscode.ThemeIcon {
switch (type) {
case 'Issue':
return new vscode.ThemeIcon('issues');
case 'PullRequest':
return new vscode.ThemeIcon('git-pull-request');
case 'Commit':
return new vscode.ThemeIcon('git-commit');
default:
return new vscode.ThemeIcon('bell');
}
}
}
/**
* Show notifications in a modal with quick actions
*/
export async function showNotificationModal(notification: NotificationData): Promise<void> {
const actions = ['Open in Browser', 'Mark as Read', 'Cancel'];
const selected = await vscode.window.showInformationMessage(
`${notification.subject.title}\n\nFrom: ${notification.repository.name}\nReason: ${notification.reason}`,
...actions
);
if (selected === 'Open in Browser') {
vscode.env.openExternal(vscode.Uri.parse(notification.url));
}
}

265
src/features/search.ts Normal file
View File

@@ -0,0 +1,265 @@
import * as vscode from 'vscode';
import fetch from 'node-fetch';
export interface SearchOptions {
query: string;
state?: 'open' | 'closed';
labels?: string[];
assignee?: string;
sort?: 'created' | 'updated' | 'popularity';
order?: 'asc' | 'desc';
}
export interface FilterOptions {
state?: 'open' | 'closed';
labels?: string[];
assignee?: string;
}
/**
* Search repositories across WizGIT
*/
export async function searchRepositories(
apiEndpoint: string,
token: string,
query: string,
page: number = 1,
limit: number = 30
): Promise<any> {
try {
const url = new URL(`${apiEndpoint}/repos/search`);
url.searchParams.append('q', query);
url.searchParams.append('page', page.toString());
url.searchParams.append('limit', limit.toString());
const response = await fetch(url.toString(), {
headers: {
'Authorization': `token ${token}`,
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Search failed: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('Repository search error:', error);
throw error;
}
}
/**
* Search issues in a repository with filters
*/
export async function searchIssues(
apiEndpoint: string,
token: string,
owner: string,
repo: string,
options: SearchOptions & { page?: number; limit?: number }
): Promise<any> {
try {
const url = new URL(`${apiEndpoint}/repos/${owner}/${repo}/issues`);
url.searchParams.append('page', (options.page || 1).toString());
url.searchParams.append('limit', (options.limit || 30).toString());
if (options.query) {
url.searchParams.append('q', options.query);
}
if (options.state) {
url.searchParams.append('state', options.state);
}
if (options.sort) {
url.searchParams.append('sort', options.sort);
}
if (options.order) {
url.searchParams.append('order', options.order);
}
if (options.labels && options.labels.length > 0) {
url.searchParams.append('labels', options.labels.join(','));
}
if (options.assignee) {
url.searchParams.append('assignee', options.assignee);
}
const response = await fetch(url.toString(), {
headers: {
'Authorization': `token ${token}`,
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Search failed: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('Issue search error:', error);
throw error;
}
}
/**
* Search pull requests with filters
*/
export async function searchPullRequests(
apiEndpoint: string,
token: string,
owner: string,
repo: string,
options: SearchOptions & { page?: number; limit?: number }
): Promise<any> {
try {
const url = new URL(`${apiEndpoint}/repos/${owner}/${repo}/pulls`);
url.searchParams.append('page', (options.page || 1).toString());
url.searchParams.append('limit', (options.limit || 30).toString());
if (options.query) {
url.searchParams.append('q', options.query);
}
if (options.state) {
url.searchParams.append('state', options.state);
}
if (options.sort) {
url.searchParams.append('sort', options.sort);
}
if (options.order) {
url.searchParams.append('order', options.order);
}
if (options.assignee) {
url.searchParams.append('assignee', options.assignee);
}
const response = await fetch(url.toString(), {
headers: {
'Authorization': `token ${token}`,
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Search failed: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('PR search error:', error);
throw error;
}
}
/**
* Get available labels for filtering
*/
export async function getLabels(
apiEndpoint: string,
token: string,
owner: string,
repo: string
): Promise<any[]> {
try {
const response = await fetch(`${apiEndpoint}/repos/${owner}/${repo}/labels`, {
headers: {
'Authorization': `token ${token}`,
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Failed to fetch labels: ${response.statusText}`);
}
return (await response.json()) as any[];
} catch (error) {
console.error('Get labels error:', error);
return [];
}
}
/**
* Get collaborators for assignee filtering
*/
export async function getCollaborators(
apiEndpoint: string,
token: string,
owner: string,
repo: string
): Promise<any[]> {
try {
const response = await fetch(`${apiEndpoint}/repos/${owner}/${repo}/collaborators`, {
headers: {
'Authorization': `token ${token}`,
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Failed to fetch collaborators: ${response.statusText}`);
}
return (await response.json()) as any[];
} catch (error) {
console.error('Get collaborators error:', error);
return [];
}
}
/**
* Show a quick filter picker for issues/PRs
*/
export async function showFilterQuickPick(
apiEndpoint: string,
token: string,
owner: string,
repo: string,
type: 'issues' | 'pulls'
): Promise<FilterOptions | null> {
const filterOptions: FilterOptions = {};
// State filter
const stateSelection = await vscode.window.showQuickPick(
['All', 'Open', 'Closed'],
{ placeHolder: 'Filter by state (optional)' }
);
if (stateSelection && stateSelection !== 'All') {
filterOptions.state = stateSelection === 'Open' ? 'open' : 'closed';
}
// Assignee filter
const collaborators = await getCollaborators(apiEndpoint, token, owner, repo);
if (collaborators.length > 0) {
const assigneeItems = ['No filter', ...collaborators.map(c => c.login)];
const assigneeSelection = await vscode.window.showQuickPick(
assigneeItems,
{ placeHolder: 'Filter by assignee (optional)' }
);
if (assigneeSelection && assigneeSelection !== 'No filter') {
filterOptions.assignee = assigneeSelection;
}
}
// Labels filter (only for issues)
if (type === 'issues') {
const labels = await getLabels(apiEndpoint, token, owner, repo);
if (labels.length > 0) {
const labelItems = labels.map(l => l.name);
const labelSelection = await vscode.window.showQuickPick(
labelItems,
{ placeHolder: 'Filter by label (optional)', canPickMany: true }
);
if (labelSelection && labelSelection.length > 0) {
filterOptions.labels = labelSelection;
}
}
}
return Object.keys(filterOptions).length > 0 ? filterOptions : null;
}

166
src/features/statusBar.ts Normal file
View File

@@ -0,0 +1,166 @@
import * as vscode from 'vscode';
export interface ShortcutCommand {
name: string;
shortcut: string;
description: string;
}
export const DEFAULT_SHORTCUTS: ShortcutCommand[] = [
{
name: 'wizgit.searchRepositories',
shortcut: 'ctrl+shift+g r',
description: 'Search repositories'
},
{
name: 'wizgit.searchIssues',
shortcut: 'ctrl+shift+g i',
description: 'Search issues'
},
{
name: 'wizgit.searchPullRequests',
shortcut: 'ctrl+shift+g p',
description: 'Search pull requests'
},
{
name: 'wizgit.openRecentRepos',
shortcut: 'ctrl+shift+g t',
description: 'Open recent repositories'
}
];
/**
* Register keyboard shortcuts
*/
export async function registerKeybindings(context: vscode.ExtensionContext): Promise<void> {
// Shortcuts will be defined in package.json keybindings
// This function ensures the extension recognizes them
context.subscriptions.push(
vscode.commands.registerCommand('wizgit.openKeybindings', async () => {
await vscode.commands.executeCommand('workbench.action.openGlobalKeybindings', '@ext:terencecarrera.wizgit-in-vscode');
})
);
}
/**
* Create or update status bar item
*/
export function createStatusBarItem(context: vscode.ExtensionContext): vscode.StatusBarItem {
const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
statusBarItem.command = 'wizgit.showStatusMenu';
statusBarItem.tooltip = 'WizGIT Status';
updateStatusBar(statusBarItem, {
configured: false,
hasRepositories: false,
notificationCount: 0,
accountName: undefined
});
context.subscriptions.push(statusBarItem);
return statusBarItem;
}
export interface StatusBarData {
configured: boolean;
hasRepositories: boolean;
notificationCount: number;
accountName?: string;
}
/**
* Update status bar display
*/
export function updateStatusBar(statusBarItem: vscode.StatusBarItem, data: StatusBarData): void {
if (!data.configured) {
statusBarItem.text = '$(warning) WizGIT Not Configured';
statusBarItem.show();
return;
}
let text = '$(git-branch) WizGIT: ';
if (data.accountName) {
text += `${data.accountName} `;
}
if (data.hasRepositories) {
text += '✓';
}
if (data.notificationCount > 0) {
text += ` (${data.notificationCount})`;
}
statusBarItem.text = text;
statusBarItem.show();
}
export interface StatusMenuItem extends vscode.QuickPickItem {
id?: string;
}
/**
* Show status menu with quick actions
*/
export async function showStatusMenu(): Promise<string | undefined> {
const quickPick = vscode.window.createQuickPick<StatusMenuItem>();
const items: StatusMenuItem[] = [
{
label: '$(telescope) Search Repositories',
description: 'Find and navigate repositories',
id: 'search-repos'
},
{
label: '$(issues) Search Issues',
description: 'Find issues across repositories',
id: 'search-issues'
},
{
label: '$(git-pull-request) Search Pull Requests',
description: 'Find pull requests',
id: 'search-prs'
},
{
label: '$(history) Recent Repositories',
description: 'Open recently accessed repositories',
id: 'recent-repos'
},
{
label: '$(gear) Settings',
description: 'Configure WizGIT',
id: 'configure'
}
];
quickPick.items = items;
return new Promise((resolve) => {
quickPick.onDidChangeSelection((selection) => {
resolve(selection[0]?.id);
quickPick.hide();
});
quickPick.onDidHide(() => {
resolve(undefined);
quickPick.dispose();
});
quickPick.show();
});
}
/**
* Register status bar click handler
*/
export function registerStatusBarClickHandler(
context: vscode.ExtensionContext,
callback: (action: string) => Promise<void>
): void {
context.subscriptions.push(
vscode.commands.registerCommand('wizgit.showStatusMenu', async () => {
const action = await showStatusMenu();
if (action) {
await callback(action);
}
})
);
}