You've already forked wizgit-vscode-extension
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 69e5f5a639 | |||
| 3f04933b3b | |||
| 99c4117193 | |||
| cc8163be64 | |||
| 239ddbd362 | |||
| 4b67c7f14a | |||
| 92d2e939e1 | |||
| 067bad6eef | |||
| 0f2f70324b | |||
| 0f8143e881 |
21
LICENSE
Normal file
21
LICENSE
Normal 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.
|
||||||
173
README.md
173
README.md
@@ -1,16 +1,54 @@
|
|||||||
# WizGIT for VS Code
|
# WizGIT for VS Code
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
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
|
## Features
|
||||||
|
|
||||||
* **Repository Creation:** Easily create new repositories on WizGit directly from VS Code using the WizGit API.
|
### 🏠 **Dedicated Activity Bar**
|
||||||
* **Secure Authentication:** Safely authenticate using your WizGit Personal Access Token.
|
|
||||||
* **Progress Tracking:** Visual progress indication during repository creation with detailed status updates.
|
* **Custom WizGIT sidebar** with organized views for repositories, issues, and pull requests
|
||||||
* **Error Handling:** Comprehensive error reporting for troubleshooting API issues.
|
* **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
|
## Requirements
|
||||||
|
|
||||||
@@ -18,40 +56,127 @@ Your intelligent coding companion for repository management, right inside Visual
|
|||||||
* A valid WizGit account with API access.
|
* A valid WizGit account with API access.
|
||||||
* Personal Access Token from your WizGit instance.
|
* Personal Access Token from your WizGit instance.
|
||||||
|
|
||||||
## Usage
|
## Getting Started
|
||||||
|
|
||||||
1. Open the Command Palette (`Ctrl+Shift+P` or `Cmd+Shift+P`)
|
### Initial Setup
|
||||||
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)
|
|
||||||
|
|
||||||
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
|
## 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
|
## 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
|
## Known Issues
|
||||||
|
|
||||||
* Network connectivity issues may cause repository creation to fail.
|
* Network connectivity issues may cause API operations to fail
|
||||||
* Please report any bugs or feature requests on our [GitHub Issues](https://github.com/your-repo/wizgit-in-vscode/issues) page.
|
* 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
|
## 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
|
### 0.0.1
|
||||||
|
|
||||||
Initial release of WizGIT.
|
Initial release of WizGIT:
|
||||||
* Core feature: Create repositories via WizGit API.
|
|
||||||
* Interactive prompts for API configuration.
|
* Core repository creation via WizGit API
|
||||||
* Progress tracking and error handling.
|
* 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
5
package-lock.json
generated
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "wizgit-in-vscode",
|
"name": "wizgit-in-vscode",
|
||||||
"version": "0.0.1",
|
"version": "0.0.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "wizgit-in-vscode",
|
"name": "wizgit-in-vscode",
|
||||||
"version": "0.0.1",
|
"version": "0.0.5",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"node-fetch": "^3.3.2"
|
"node-fetch": "^3.3.2"
|
||||||
},
|
},
|
||||||
|
|||||||
84
package.json
84
package.json
@@ -2,7 +2,14 @@
|
|||||||
"name": "wizgit-in-vscode",
|
"name": "wizgit-in-vscode",
|
||||||
"displayName": "WizGIT in VS Code",
|
"displayName": "WizGIT in VS Code",
|
||||||
"description": "A VS Code extension to interact with WizGIT API for repository management.",
|
"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": {
|
"engines": {
|
||||||
"vscode": "^1.74.0"
|
"vscode": "^1.74.0"
|
||||||
},
|
},
|
||||||
@@ -21,10 +28,7 @@
|
|||||||
{
|
{
|
||||||
"id": "wizgit",
|
"id": "wizgit",
|
||||||
"title": "WizGIT",
|
"title": "WizGIT",
|
||||||
"icon": {
|
"icon": "./resources/wizgit-logo.svg"
|
||||||
"light": "./resources/wizgit-logo.svg",
|
|
||||||
"dark": "./resources/wizgit-logo-dark.svg"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -101,6 +105,48 @@
|
|||||||
"title": "Open Pull Request",
|
"title": "Open Pull Request",
|
||||||
"category": "WizGIT",
|
"category": "WizGIT",
|
||||||
"icon": "$(link-external)"
|
"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": {
|
"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": {
|
"configuration": {
|
||||||
"title": "WizGIT",
|
"title": "WizGIT",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -181,7 +253,7 @@
|
|||||||
"wizgit.defaultBranch": {
|
"wizgit.defaultBranch": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Default branch name for new repositories",
|
"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 |
BIN
resources/wizgit-marketplace-icon.png
Normal file
BIN
resources/wizgit-marketplace-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
1472
src/extension.ts
1472
src/extension.ts
File diff suppressed because it is too large
Load Diff
229
src/features/export.ts
Normal file
229
src/features/export.ts
Normal 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
172
src/features/favorites.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
231
src/features/notifications.ts
Normal file
231
src/features/notifications.ts
Normal 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
265
src/features/search.ts
Normal 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
166
src/features/statusBar.ts
Normal 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);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user