Compare commits
10 Commits
be8a2649f8
...
c2cdf23b13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2cdf23b13 | ||
|
|
52f2d39ae6 | ||
|
|
b9530abd8b | ||
|
|
f2ec8c38ac | ||
|
|
44ba496666 | ||
|
|
87e8c4feee | ||
|
|
61ea6fab6b | ||
|
|
6e0405b192 | ||
|
|
0a768d1701 | ||
|
|
21a7e972f2 |
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
@@ -21,7 +21,7 @@
|
||||
"type": "npm",
|
||||
"script": "watch:esbuild",
|
||||
"group": "build",
|
||||
"problemMatcher": "$esbuild-watch",
|
||||
"problemMatcher": [],
|
||||
"isBackground": true,
|
||||
"label": "npm: watch:esbuild",
|
||||
"presentation": {
|
||||
|
||||
90
Development Checklist.md
Normal file
90
Development Checklist.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# ✅ Prompt Catalog Development Checklist
|
||||
|
||||
## 🔧 Phase 1: Planning & Setup
|
||||
|
||||
- [ ] Review and finalize requirements from `Prompt Catalog Features.md`
|
||||
- [ ] Choose JavaScript framework (React, Vue, etc.)
|
||||
- [ ] Set up Supabase project
|
||||
- [ ] Create `prompts` table
|
||||
- [ ] Create `users` table (future)
|
||||
- [ ] Create `user_prompts` table (future)
|
||||
- [ ] Define JSON structure for import/export
|
||||
- [ ] Choose hosting platform (Vercel, Netlify, etc.)
|
||||
|
||||
---
|
||||
|
||||
## 🧱 Phase 2: Database & API
|
||||
|
||||
- [ ] Define and implement Supabase schema
|
||||
- [ ] Set up Supabase RLS rules (if applicable)
|
||||
- [ ] Connect frontend to Supabase using client API
|
||||
|
||||
---
|
||||
|
||||
## 🖼 Phase 3: Front-End Interface
|
||||
|
||||
- [ ] Build static UI from `Front End Interface.png`
|
||||
- [ ] Sidebar navigation (System / Task)
|
||||
- [ ] Search bar with filters
|
||||
- [ ] Prompt list display
|
||||
- [ ] Prompt detail view
|
||||
- [ ] Tags display and interaction
|
||||
- [ ] Integrate UI with Supabase for live data
|
||||
- [ ] Implement CRUD operations for prompts
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Phase 4: Search & Tagging
|
||||
|
||||
- [ ] Implement keyword and full-text search
|
||||
- [ ] Add filter by:
|
||||
- [ ] Type (System, Task)
|
||||
- [ ] Tags (multi-select)
|
||||
- [ ] Create tag suggestion/autocomplete
|
||||
|
||||
---
|
||||
|
||||
## 🤖 Phase 5: AI Integration
|
||||
|
||||
- [ ] Set up API key management (e.g., OpenAI, Together, Ollama)
|
||||
- [ ] Add prompt suggestion UI for user input
|
||||
- [ ] Integrate with AI API to return prompt suggestions
|
||||
|
||||
---
|
||||
|
||||
## 📦 Phase 6: Import/Export
|
||||
|
||||
- [ ] Implement prompt export to JSON
|
||||
- [ ] Implement prompt import from JSON with validation
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Phase 7: Authentication & User Features (Future)
|
||||
|
||||
- [ ] Add Supabase Auth for login/register
|
||||
- [ ] Create user profile UI
|
||||
- [ ] Track user-owned prompts
|
||||
- [ ] Enable user favorites system
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Phase 8: Deployment & QA
|
||||
|
||||
- [ ] Deploy frontend to hosting platform
|
||||
- [ ] Set up Supabase production environment
|
||||
- [ ] QA Testing:
|
||||
- [ ] UI functionality
|
||||
- [ ] Prompt CRUD operations
|
||||
- [ ] Search and filtering
|
||||
- [ ] Import/export behavior
|
||||
- [ ] Write usage documentation
|
||||
|
||||
---
|
||||
|
||||
## 🌱 Phase 9: Post-MVP Enhancements
|
||||
|
||||
- [ ] Add prompt rating system
|
||||
- [ ] Implement version history tracking
|
||||
- [ ] Add social sharing (links, embed)
|
||||
- [ ] Provide external API for prompt access
|
||||
- [ ] Improve AI integration with context-aware suggestions
|
||||
141
README.md
141
README.md
@@ -1,71 +1,94 @@
|
||||
# vscode-project-roadmap README
|
||||
# VSCode Project Roadmap
|
||||
|
||||
This is the README for your extension "vscode-project-roadmap". After writing up a brief description, we recommend including the following sections.
|
||||
|
||||
## Features
|
||||
|
||||
Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file.
|
||||
|
||||
For example if there is an image subfolder under your extension project workspace:
|
||||
|
||||
\!\[feature X\]\(images/feature-x.png\)
|
||||
|
||||
> Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow.
|
||||
|
||||
## Requirements
|
||||
|
||||
If you have any requirements or dependencies, add a section describing those and how to install and configure them.
|
||||
|
||||
## Extension Settings
|
||||
|
||||
Include if your extension adds any VS Code settings through the `contributes.configuration` extension point.
|
||||
|
||||
For example:
|
||||
|
||||
This extension contributes the following settings:
|
||||
|
||||
* `myExtension.enable`: Enable/disable this extension.
|
||||
* `myExtension.thing`: Set to `blah` to do something.
|
||||
|
||||
## Known Issues
|
||||
|
||||
Calling out known issues can help limit users opening duplicate issues against your extension.
|
||||
|
||||
## Release Notes
|
||||
|
||||
Users appreciate release notes as you update your extension.
|
||||
|
||||
### 1.0.0
|
||||
|
||||
Initial release of ...
|
||||
|
||||
### 1.0.1
|
||||
|
||||
Fixed issue #.
|
||||
|
||||
### 1.1.0
|
||||
|
||||
Added features X, Y, and Z.
|
||||
A Visual Studio Code extension to visualize and track your project's progress by phase using a Markdown checklist file. Perfect for teams and solo developers who want a clear, interactive roadmap directly in their editor.
|
||||
|
||||
---
|
||||
|
||||
## Following extension guidelines
|
||||
## Features
|
||||
|
||||
Ensure that you've read through the extensions guidelines and follow the best practices for creating your extension.
|
||||
- **Roadmap Sidebar:** View your project phases and tasks as a collapsible tree in the VS Code sidebar.
|
||||
- **Markdown Integration:** Uses a customizable Markdown checklist file (default: `Development Checklist.md`) for easy editing and version control.
|
||||
- **Checkbox Sync:** Toggle tasks as complete/incomplete directly from the tree view—updates your Markdown file instantly.
|
||||
- **Progress Indicators:** See completion bars and checkmarks for each phase at a glance.
|
||||
- **Webview Overview:** Open a tab with a clean, read-only summary of your roadmap and progress.
|
||||
- **Customizable Filename:** Change the checklist file via settings to fit any workflow.
|
||||
|
||||
* [Extension Guidelines](https://code.visualstudio.com/api/references/extension-guidelines)
|
||||
---
|
||||
|
||||
## Working with Markdown
|
||||
## Screenshot
|
||||
|
||||
You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts:
|
||||

|
||||
|
||||
* Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux).
|
||||
* Toggle preview (`Shift+Cmd+V` on macOS or `Shift+Ctrl+V` on Windows and Linux).
|
||||
* Press `Ctrl+Space` (Windows, Linux, macOS) to see a list of Markdown snippets.
|
||||
---
|
||||
|
||||
## For more information
|
||||
## Requirements
|
||||
|
||||
* [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown)
|
||||
* [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/)
|
||||
- Visual Studio Code v1.101.0 or higher
|
||||
|
||||
**Enjoy!**
|
||||
---
|
||||
|
||||
## Extension Settings
|
||||
|
||||
This extension contributes the following settings:
|
||||
|
||||
| Setting | Description | Default |
|
||||
|-------------------------------|----------------------------------------------------------|----------------------------|
|
||||
| `roadmapChecklist.filename` | The relative path to the Markdown file used for roadmap. | `Development Checklist.md` |
|
||||
|
||||
You can change this in your workspace settings to use a different checklist file.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
1. **Prepare your checklist:**
|
||||
Use a Markdown file with phases as headings (`## Phase Name`) and tasks as checkboxes:
|
||||
|
||||
```markdown
|
||||
## Phase 1: Planning
|
||||
- [ ] Define requirements
|
||||
- [x] Set up repository
|
||||
|
||||
## Phase 2: Development
|
||||
- [ ] Implement feature X
|
||||
- [ ] Write tests
|
||||
|
||||
```
|
||||
|
||||
2. **Open the Roadmap Sidebar:**
|
||||
Click the "Roadmap" icon in the Activity Bar to view and interact with your checklist.
|
||||
3. **Toggle tasks:**
|
||||
Click checkboxes in the tree to mark tasks as complete/incomplete.
|
||||
4. **View overview:**
|
||||
Right-click a phase or use the command palette to open the roadmap overview tab.
|
||||
|
||||
---
|
||||
|
||||
## Known Issues
|
||||
|
||||
- Only supports checklists in Markdown files with - [ ] or - [x] syntax.
|
||||
- Nested tasks are supported via indentation, but only one level deep is recommended for clarity.
|
||||
|
||||
---
|
||||
|
||||
## Release Notes
|
||||
|
||||
0.0.1
|
||||
Initial release: Roadmap sidebar, checklist sync, overview tab, and settings.
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
Pull requests and suggestions are welcome! Please open an issue to discuss your ideas.
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- [Extension Guidelines](https://code.visualstudio.com/api/ux-guidelines/overview)
|
||||
- [Markdown Syntax Reference](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github)
|
||||
|
||||
---
|
||||
|
||||
**Enjoy tracking your project roadmap in VS Code!**
|
||||
|
||||
BIN
media/roadmap-screenshot.png
Normal file
BIN
media/roadmap-screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 280 KiB |
3
media/roadmap.svg
Normal file
3
media/roadmap.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M2 2h12v12H2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 116 B |
49
package.json
49
package.json
@@ -1,7 +1,12 @@
|
||||
{
|
||||
"name": "vscode-project-roadmap",
|
||||
"displayName": "VSCode Project Roadmap",
|
||||
"publisher": "solo-web-works",
|
||||
"description": "Extension to track a project by phase using a Markdown checklist file.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.keithsolomon.net/Solo_Web_Works/VSCode_Project_Roadmap"
|
||||
},
|
||||
"version": "0.0.1",
|
||||
"engines": {
|
||||
"vscode": "^1.101.0"
|
||||
@@ -9,15 +14,51 @@
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [],
|
||||
"activationEvents": [
|
||||
"onStartupFinished",
|
||||
"workspaceContains:Development Checklist.md"
|
||||
],
|
||||
"main": "./dist/extension.js",
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "vscode-project-roadmap.helloWorld",
|
||||
"title": "Hello World"
|
||||
"command": "roadmapChecklist.openTab",
|
||||
"title": "Roadmap: Open Tab"
|
||||
}
|
||||
]
|
||||
],
|
||||
"configuration": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Development Checklist.md",
|
||||
"description": "Filename of the markdown checklist to use for the roadmap view.",
|
||||
"scope": "workspace",
|
||||
"properties": {
|
||||
"roadmapChecklist.filename": {
|
||||
"type": "string",
|
||||
"default": "Development Checklist.md",
|
||||
"description": "The relative path to the markdown file used for the roadmap checklist."
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"viewsContainers": {
|
||||
"activitybar": [
|
||||
{
|
||||
"id": "roadmapSidebar",
|
||||
"title": "Roadmap",
|
||||
"icon": "media/roadmap.svg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"views": {
|
||||
"roadmapSidebar": [
|
||||
{
|
||||
"id": "roadmapChecklist",
|
||||
"name": "Checklist",
|
||||
"icon": "media/roadmap.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "npm run package",
|
||||
|
||||
102
src/extension.ts
102
src/extension.ts
@@ -1,26 +1,94 @@
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import * as vscode from 'vscode';
|
||||
import { RoadmapTreeProvider, RoadmapItem } from './roadmapTree';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
// This method is called when your extension is activated
|
||||
// Your extension is activated the very first time the command is executed
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
export function activate(this: any, context: vscode.ExtensionContext) {
|
||||
const output = vscode.window.createOutputChannel('Roadmap Checklist');
|
||||
const config = vscode.workspace.getConfiguration('roadmapChecklist');
|
||||
const checklistFilename = config.get<string>('filename') || 'Development Checklist.md';
|
||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
||||
|
||||
// Use the console to output diagnostic information (console.log) and errors (console.error)
|
||||
// This line of code will only be executed once when your extension is activated
|
||||
console.log('Congratulations, your extension "vscode-project-roadmap" is now active!');
|
||||
if (workspaceFolder) {
|
||||
const filePath = path.join(workspaceFolder.uri.fsPath, checklistFilename);
|
||||
this.checklistPath = filePath;
|
||||
|
||||
// The command has been defined in the package.json file
|
||||
// Now provide the implementation of the command with registerCommand
|
||||
// The commandId parameter must match the command field in package.json
|
||||
const disposable = vscode.commands.registerCommand('vscode-project-roadmap.helloWorld', () => {
|
||||
// The code you place here will be executed every time your command is executed
|
||||
// Display a message box to the user
|
||||
vscode.window.showInformationMessage('Hello World from VSCode Project Roadmap!');
|
||||
console.log(`Using checklist file: ${this.checklistPath}`);
|
||||
output.appendLine(`Using checklist file: ${this.checklistPath}`);
|
||||
|
||||
if (!fs.existsSync(this.checklistPath)) {
|
||||
vscode.window.showWarningMessage(`Checklist file not found: ${this.checklistPath}`);
|
||||
} else {
|
||||
output.appendLine('Checklist file found, refreshing...');
|
||||
setTimeout(() => this.refresh(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
const configSet = `Roadmap Checklist filename set to: ${checklistFilename}`;
|
||||
output.appendLine(configSet);
|
||||
|
||||
const checklistPath = vscode.workspace.workspaceFolders?.[0]
|
||||
? vscode.Uri.joinPath(vscode.workspace.workspaceFolders[0].uri, checklistFilename).fsPath
|
||||
: '';
|
||||
|
||||
const message = `Using checklist file: ${this.checklistPath}`;
|
||||
console.log(message);
|
||||
output.appendLine(message);
|
||||
output.show(); // Optional, only if you want it to be visible
|
||||
|
||||
// if (fs.existsSync(checklistPath)) {
|
||||
// console.log('[Extension] Roadmap checklist file found:', checklistPath);
|
||||
// } else {
|
||||
// vscode.window.showWarningMessage(`Roadmap checklist file not found: ${checklistPath}`);
|
||||
// }
|
||||
|
||||
const roadmapProvider = new RoadmapTreeProvider(checklistPath, context);
|
||||
vscode.window.registerTreeDataProvider('roadmapChecklist', roadmapProvider);
|
||||
const treeView = vscode.window.createTreeView('roadmapChecklist', {
|
||||
treeDataProvider: roadmapProvider
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('roadmap.toggleCheckbox', async (item: RoadmapItem) => {
|
||||
const doc = vscode.workspace.textDocuments.find(d => d.uri.fsPath === checklistPath);
|
||||
if (!doc) { return; }
|
||||
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
const rawText = doc.getText();
|
||||
|
||||
// Normalize line endings and split into lines
|
||||
const lines = rawText.split(/\r?\n/);
|
||||
const index = lines.findIndex(line =>
|
||||
line.trim().match(/^[-*]\s+\[[ xX]\]/) &&
|
||||
line.includes(item.label)
|
||||
);
|
||||
if (index === -1) { return; }
|
||||
|
||||
const line = lines[index];
|
||||
const toggledLine = line.replace(/\[(x| )\]/i, item.checked ? '[ ]' : '[x]');
|
||||
lines[index] = toggledLine;
|
||||
|
||||
// Ensure exactly one trailing newline
|
||||
const finalText = lines.join('\n').replace(/\n+$/, '') + '\n';
|
||||
|
||||
const fullRange = new vscode.Range(
|
||||
doc.positionAt(0),
|
||||
doc.positionAt(rawText.length)
|
||||
);
|
||||
|
||||
edit.replace(doc.uri, fullRange, finalText);
|
||||
|
||||
await vscode.workspace.applyEdit(edit);
|
||||
await doc.save(); // ✅ Triggers refresh via onDidSave
|
||||
}),
|
||||
vscode.commands.registerCommand('roadmapChecklist.reveal', async (item: RoadmapItem) => {
|
||||
treeView.reveal(item, { expand: true });
|
||||
})
|
||||
);
|
||||
|
||||
function escapeRegex(str: string) {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
}
|
||||
|
||||
// This method is called when your extension is deactivated
|
||||
export function deactivate() {}
|
||||
|
||||
300
src/roadmapTree.ts
Normal file
300
src/roadmapTree.ts
Normal file
@@ -0,0 +1,300 @@
|
||||
// roadmapTree.ts
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
|
||||
export class RoadmapItem extends vscode.TreeItem {
|
||||
public children: RoadmapItem[] = [];
|
||||
public readonly checked: boolean;
|
||||
|
||||
constructor(
|
||||
public readonly label: string,
|
||||
public collapsibleState: vscode.TreeItemCollapsibleState,
|
||||
checked: boolean = false
|
||||
) {
|
||||
super(label, collapsibleState);
|
||||
this.checked = checked;
|
||||
|
||||
if (collapsibleState === vscode.TreeItemCollapsibleState.None) {
|
||||
// this is a task
|
||||
this.description = checked ? '✅ Done' : '';
|
||||
this.iconPath = new vscode.ThemeIcon(checked ? 'check' : 'circle-large-outline');
|
||||
this.command = {
|
||||
command: 'roadmap.toggleCheckbox',
|
||||
title: 'Toggle Task',
|
||||
arguments: [this]
|
||||
};
|
||||
} else {
|
||||
// this is a phase
|
||||
this.command = {
|
||||
command: 'roadmapChecklist.openTab',
|
||||
title: 'Open Tab View',
|
||||
arguments: [this]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
updatePhaseInfo() {
|
||||
if (this.children.length === 0) {return;}
|
||||
const total = this.children.length;
|
||||
const completed = this.children.filter(c => c.checked).length;
|
||||
const percent = Math.round((completed / total) * 100);
|
||||
const bar = '▓'.repeat(Math.floor(percent / 10)).padEnd(10, '░');
|
||||
this.description = `${bar} ${percent}%`;
|
||||
this.iconPath = new vscode.ThemeIcon(
|
||||
completed === total ? 'check' : 'tasklist'
|
||||
);
|
||||
}
|
||||
|
||||
static fromMarkdownLine(
|
||||
label: string,
|
||||
indent: number,
|
||||
checked: boolean,
|
||||
lines: string[],
|
||||
currentIndex: number
|
||||
): RoadmapItem {
|
||||
const collapsibleState = hasChildrenLater(lines, currentIndex, indent)
|
||||
? vscode.TreeItemCollapsibleState.Collapsed
|
||||
: vscode.TreeItemCollapsibleState.None;
|
||||
|
||||
return new RoadmapItem(label, collapsibleState, checked);
|
||||
}
|
||||
}
|
||||
|
||||
export class RoadmapTreeProvider implements vscode.TreeDataProvider<RoadmapItem> {
|
||||
private output = vscode.window.createOutputChannel('Roadmap Checklist');
|
||||
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<RoadmapItem | undefined | void> = new vscode.EventEmitter();
|
||||
readonly onDidChangeTreeData: vscode.Event<RoadmapItem | undefined | void> = this._onDidChangeTreeData.event;
|
||||
|
||||
private items: RoadmapItem[] = [];
|
||||
private panel: vscode.WebviewPanel | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly checklistPath: string,
|
||||
private readonly context: vscode.ExtensionContext
|
||||
) {
|
||||
this.refresh();
|
||||
vscode.workspace.onDidSaveTextDocument(doc => {
|
||||
if (doc.uri.fsPath === checklistPath) {
|
||||
this.refresh();
|
||||
}
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('roadmapChecklist.openTab', (item?: RoadmapItem) => {
|
||||
this.showWebview();
|
||||
});
|
||||
}
|
||||
|
||||
refresh(): void {
|
||||
let content = '';
|
||||
const doc = vscode.workspace.textDocuments.find(d => d.uri.fsPath === this.checklistPath);
|
||||
|
||||
if (doc) {
|
||||
content = doc.getText();
|
||||
} else {
|
||||
try {
|
||||
content = fs.readFileSync(this.checklistPath, 'utf8');
|
||||
} catch (err) {
|
||||
vscode.window.showErrorMessage(`Failed to read checklist file: ${err}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const expanded = this.context.workspaceState.get<string[]>('expandedPhases') || [];
|
||||
const { items, firstOpenPhase } = this.parseMarkdown(content, expanded);
|
||||
this.items = items;
|
||||
this._onDidChangeTreeData.fire();
|
||||
|
||||
if (firstOpenPhase) {
|
||||
setTimeout(() => {
|
||||
vscode.commands.executeCommand('roadmapChecklist.reveal', firstOpenPhase, { expand: true });
|
||||
}, 100);
|
||||
}
|
||||
|
||||
if (this.panel) {
|
||||
this.renderWebview();
|
||||
}
|
||||
}
|
||||
|
||||
getTreeItem(element: RoadmapItem): vscode.TreeItem {
|
||||
return element;
|
||||
}
|
||||
|
||||
getChildren(element?: RoadmapItem): vscode.ProviderResult<RoadmapItem[]> {
|
||||
if (!element) {return this.items;}
|
||||
|
||||
// Track expand/collapse state
|
||||
const expanded = this.context.workspaceState.get<string[]>('expandedPhases') || [];
|
||||
if (element.collapsibleState === vscode.TreeItemCollapsibleState.Expanded) {
|
||||
if (!expanded.includes(element.label)) {
|
||||
expanded.push(element.label);
|
||||
this.context.workspaceState.update('expandedPhases', expanded);
|
||||
}
|
||||
} else if (element.collapsibleState === vscode.TreeItemCollapsibleState.Collapsed) {
|
||||
if (expanded.includes(element.label)) {
|
||||
const updated = expanded.filter(label => label !== element.label);
|
||||
this.context.workspaceState.update('expandedPhases', updated);
|
||||
}
|
||||
}
|
||||
|
||||
return element.children;
|
||||
}
|
||||
|
||||
getParent(element: RoadmapItem): vscode.ProviderResult<RoadmapItem> {
|
||||
for (const phase of this.items) {
|
||||
for (const task of phase.children) {
|
||||
if (task === element) {return phase;}
|
||||
const match = findParentRecursive(task, element);
|
||||
if (match) {return match;}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private parseMarkdown(content: string, expandedLabels: string[]): { items: RoadmapItem[]; firstOpenPhase: RoadmapItem | null } {
|
||||
const lines = content.split('\n');
|
||||
const items: RoadmapItem[] = [];
|
||||
let currentPhase: RoadmapItem | null = null;
|
||||
const taskStack: { indent: number; item: RoadmapItem }[] = [];
|
||||
let firstOpenPhase: RoadmapItem | null = null;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const phaseMatch = line.match(/^##\s+(.+)/);
|
||||
const taskMatch = line.match(/^(\s*)[-*]\s+\[( |x)\]\s+(.+)/);
|
||||
|
||||
if (phaseMatch) {
|
||||
const label = phaseMatch[1].trim();
|
||||
const isExpanded = expandedLabels.includes(label);
|
||||
const state = isExpanded ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.Collapsed;
|
||||
currentPhase = new RoadmapItem(label, state);
|
||||
items.push(currentPhase);
|
||||
taskStack.length = 0;
|
||||
} else if (taskMatch && currentPhase) {
|
||||
const indent = taskMatch[1].length;
|
||||
const checked = taskMatch[2] === 'x';
|
||||
const label = taskMatch[3].trim();
|
||||
|
||||
const task = RoadmapItem.fromMarkdownLine(label, indent, checked, lines, i);
|
||||
|
||||
while (taskStack.length && taskStack[taskStack.length - 1].indent >= indent) {
|
||||
taskStack.pop();
|
||||
}
|
||||
|
||||
if (taskStack.length === 0) {
|
||||
currentPhase.children.push(task);
|
||||
} else {
|
||||
taskStack[taskStack.length - 1].item.children.push(task);
|
||||
}
|
||||
|
||||
taskStack.push({ indent, item: task });
|
||||
}
|
||||
}
|
||||
|
||||
for (const phase of items) {
|
||||
phase.updatePhaseInfo();
|
||||
updateParentCheckState(phase);
|
||||
|
||||
const total = phase.children.length;
|
||||
const completed = phase.children.filter(c => c.checked).length;
|
||||
if (!firstOpenPhase && total > 0 && completed < total) {
|
||||
firstOpenPhase = phase;
|
||||
}
|
||||
}
|
||||
|
||||
return { items, firstOpenPhase };
|
||||
}
|
||||
|
||||
private showWebview() {
|
||||
if (this.panel) {
|
||||
this.panel.reveal(vscode.ViewColumn.One);
|
||||
return;
|
||||
}
|
||||
|
||||
this.panel = vscode.window.createWebviewPanel(
|
||||
'roadmapTab',
|
||||
'Roadmap Overview',
|
||||
vscode.ViewColumn.One,
|
||||
{ enableScripts: true }
|
||||
);
|
||||
|
||||
this.renderWebview();
|
||||
|
||||
this.panel.onDidDispose(() => {
|
||||
this.panel = null;
|
||||
});
|
||||
}
|
||||
|
||||
private renderTasks(items: RoadmapItem[]): string {
|
||||
if (!items.length) {return '';}
|
||||
return `<ul>` + items.map(item => {
|
||||
const icon = item.checked ? '✅' : '⬜';
|
||||
return `<li>${icon} ${item.label}${this.renderTasks(item.children)}</li>`;
|
||||
}).join('') + `</ul>`;
|
||||
}
|
||||
|
||||
private buildWebviewHtml(items: RoadmapItem[], webview: vscode.Webview): string {
|
||||
const rows = items.map(phase => {
|
||||
const taskList = this.renderTasks(phase.children);
|
||||
|
||||
return `
|
||||
<h2>${phase.label}</h2>
|
||||
<p>${phase.description}</p>
|
||||
<ul>${taskList}</ul>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource} 'unsafe-inline'; script-src ${webview.cspSource};">
|
||||
<style>
|
||||
body { font-family: sans-serif; padding: 1em; }
|
||||
h2 { margin-top: 1em; }
|
||||
ul { padding-left: 1em; list-style-type: none; }
|
||||
li { list-style-type: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
${rows}
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderWebview() {
|
||||
if (this.panel) {
|
||||
this.panel.webview.html = this.buildWebviewHtml(this.items, this.panel.webview);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findParentRecursive(parent: RoadmapItem, child: RoadmapItem): RoadmapItem | null {
|
||||
for (const c of parent.children) {
|
||||
if (c === child) {return parent;}
|
||||
const found = findParentRecursive(c, child);
|
||||
if (found) {return found;}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function updateParentCheckState(item: RoadmapItem): boolean {
|
||||
if (item.children.length === 0) {return item.checked;}
|
||||
const allChildrenChecked = item.children.map(updateParentCheckState).every(Boolean);
|
||||
(item as any).checked = allChildrenChecked; // hacky override to set read-only property
|
||||
item.updatePhaseInfo();
|
||||
return allChildrenChecked;
|
||||
}
|
||||
|
||||
function hasChildrenLater(lines: string[], currentIndex: number, parentIndent: number): boolean {
|
||||
for (let i = currentIndex + 1; i < lines.length; i++) {
|
||||
const match = lines[i].match(/^(\s*)[-*]\s+\[( |x)\]/);
|
||||
if (!match) {continue;}
|
||||
const indent = match[1].length;
|
||||
if (indent <= parentIndent) {return false;}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user