diff --git a/lite/CHANGELOG.md b/lite/CHANGELOG.md index dec17149..5edc0790 100644 --- a/lite/CHANGELOG.md +++ b/lite/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to the Front Matter Lite extension will be documented in thi ## [Unreleased] ### Added +- **Metadata Panel** - Edit front matter fields directly in the sidebar panel + - View and edit all front matter fields for the current markdown file + - Support for text, textarea, date, and array fields (tags/categories) + - Auto-save changes to the file + - Refresh button to reload metadata - Initial release of Front Matter Lite for virtual workspaces - Dashboard webview with folder and file listing - Register content folders via context menu @@ -19,6 +24,7 @@ All notable changes to the Front Matter Lite extension will be documented in thi ### Features - ✅ Register content folders - ✅ Create new markdown files with front matter +- ✅ **Edit front matter metadata in panel** - ✅ View registered folders - ✅ List content files - ✅ Open files from dashboard diff --git a/lite/README.md b/lite/README.md index 9b377401..15a35883 100644 --- a/lite/README.md +++ b/lite/README.md @@ -16,6 +16,7 @@ The lite version provides core content management functionality: ### ✅ Supported Features +- **Metadata Panel** - View and edit front matter for the currently open markdown file - **Register Content Folders** - Right-click on folders in the Explorer to register them as content folders - **Create Content** - Create new markdown files with front matter - **View Configuration** - Manage your content folder settings @@ -24,7 +25,7 @@ The lite version provides core content management functionality: The following features from the full extension are not available in the lite version due to virtual workspace limitations: -- **Dashboard** - Full dashboard UI (under development) +- **Dashboard** - Full dashboard UI (basic version available) - **Media Management** - File upload and media library - **Local Server Preview** - Starting/stopping local dev servers - **Git Integration** - Advanced git operations @@ -40,6 +41,18 @@ The following features from the full extension are not available in the lite ver ## Usage +### Edit Front Matter Metadata + +1. Open a markdown file in the editor +2. The **Metadata** panel in the Front Matter Lite sidebar shows all front matter fields +3. Edit fields directly in the panel: + - **Title** - Edit the page title + - **Description** - Edit the description (multiline) + - **Date** - Use the date picker to set publish date + - **Tags/Categories** - Add or remove tags by typing and pressing Enter + - **Other fields** - Edit any custom front matter fields +4. Changes are saved automatically to the file + ### Register a Content Folder 1. In the Explorer, right-click on any folder diff --git a/lite/package.json b/lite/package.json index b8b32aab..167c9ae8 100644 --- a/lite/package.json +++ b/lite/package.json @@ -51,6 +51,11 @@ }, "views": { "frontmatter-lite": [ + { + "type": "webview", + "id": "frontMatterLite.panel", + "name": "Metadata" + }, { "type": "webview", "id": "frontMatterLite.dashboard", diff --git a/lite/src/PanelProvider.ts b/lite/src/PanelProvider.ts new file mode 100644 index 00000000..e5205854 --- /dev/null +++ b/lite/src/PanelProvider.ts @@ -0,0 +1,514 @@ +import * as vscode from 'vscode'; + +/** + * Panel provider for editing front matter metadata of the current file + */ +export class PanelProvider implements vscode.WebviewViewProvider { + public static readonly viewType = 'frontMatterLite.panel'; + private _view?: vscode.WebviewView; + private _outputChannel: vscode.OutputChannel; + private _currentFileUri?: vscode.Uri; + + constructor( + private readonly _extensionUri: vscode.Uri, + outputChannel: vscode.OutputChannel + ) { + this._outputChannel = outputChannel; + } + + public resolveWebviewView( + webviewView: vscode.WebviewView, + context: vscode.WebviewViewResolveContext, + _token: vscode.CancellationToken + ) { + this._view = webviewView; + + webviewView.webview.options = { + enableScripts: true, + localResourceRoots: [this._extensionUri] + }; + + webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); + + // Handle messages from the webview + webviewView.webview.onDidReceiveMessage(async (data) => { + switch (data.type) { + case 'updateField': { + await this._updateFrontMatterField(data.field, data.value); + break; + } + case 'refresh': { + await this._loadCurrentFile(); + break; + } + } + }); + + // Listen for active editor changes + vscode.window.onDidChangeActiveTextEditor(() => { + this._loadCurrentFile(); + }); + + // Initial load + this._loadCurrentFile(); + } + + private async _loadCurrentFile() { + if (!this._view) { + return; + } + + const editor = vscode.window.activeTextEditor; + if (!editor) { + this._view.webview.postMessage({ + type: 'noFile' + }); + return; + } + + const doc = editor.document; + const fileName = doc.uri.path.split('/').pop() || ''; + + // Only process markdown files + if (!fileName.match(/\.(md|mdx|markdown)$/i)) { + this._view.webview.postMessage({ + type: 'notMarkdown' + }); + return; + } + + this._currentFileUri = doc.uri; + + try { + const content = doc.getText(); + const frontMatter = this._parseFrontMatter(content); + + this._view.webview.postMessage({ + type: 'fileLoaded', + fileName, + frontMatter + }); + } catch (error) { + this._outputChannel.appendLine(`Error loading file: ${error}`); + this._view.webview.postMessage({ + type: 'error', + message: error instanceof Error ? error.message : 'Unknown error' + }); + } + } + + private _parseFrontMatter(content: string): Record { + const frontMatterRegex = /^---\s*\n([\s\S]*?)\n---/; + const match = content.match(frontMatterRegex); + + if (!match) { + return {}; + } + + const frontMatterText = match[1]; + const frontMatter: Record = {}; + + // Simple YAML parser (for basic key: value pairs) + const lines = frontMatterText.split('\n'); + for (const line of lines) { + const colonIndex = line.indexOf(':'); + if (colonIndex === -1) continue; + + const key = line.substring(0, colonIndex).trim(); + let valueStr = line.substring(colonIndex + 1).trim(); + + // Handle arrays + if (valueStr.startsWith('[') && valueStr.endsWith(']')) { + frontMatter[key] = valueStr.substring(1, valueStr.length - 1) + .split(',') + .map(v => v.trim().replace(/^['"]|['"]$/g, '')); + } else { + // Remove quotes + frontMatter[key] = valueStr.replace(/^['"]|['"]$/g, ''); + } + } + + return frontMatter; + } + + private async _updateFrontMatterField(field: string, value: any) { + if (!this._currentFileUri) { + return; + } + + try { + const doc = await vscode.workspace.openTextDocument(this._currentFileUri); + const content = doc.getText(); + const frontMatterRegex = /^---\s*\n([\s\S]*?)\n---/; + const match = content.match(frontMatterRegex); + + if (!match) { + vscode.window.showErrorMessage('No front matter found in file'); + return; + } + + const frontMatterText = match[1]; + const lines = frontMatterText.split('\n'); + let updated = false; + + // Update the field + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const colonIndex = line.indexOf(':'); + if (colonIndex === -1) continue; + + const key = line.substring(0, colonIndex).trim(); + if (key === field) { + // Format the value + let formattedValue: string; + if (Array.isArray(value)) { + formattedValue = `[${value.map(v => `"${v}"`).join(', ')}]`; + } else if (typeof value === 'string') { + formattedValue = value; + } else { + formattedValue = String(value); + } + + lines[i] = `${key}: ${formattedValue}`; + updated = true; + break; + } + } + + if (!updated) { + // Field doesn't exist, add it + let formattedValue: string; + if (Array.isArray(value)) { + formattedValue = `[${value.map(v => `"${v}"`).join(', ')}]`; + } else if (typeof value === 'string') { + formattedValue = value; + } else { + formattedValue = String(value); + } + lines.push(`${field}: ${formattedValue}`); + } + + const newFrontMatter = lines.join('\n'); + const newContent = content.replace(frontMatterRegex, `---\n${newFrontMatter}\n---`); + + // Write the updated content + const edit = new vscode.WorkspaceEdit(); + edit.replace( + this._currentFileUri, + new vscode.Range(0, 0, doc.lineCount, 0), + newContent + ); + + await vscode.workspace.applyEdit(edit); + + // Reload to show updated values + await this._loadCurrentFile(); + + this._outputChannel.appendLine(`Updated field "${field}" with value: ${value}`); + } catch (error) { + const errorMsg = `Error updating front matter: ${error instanceof Error ? error.message : 'Unknown error'}`; + vscode.window.showErrorMessage(errorMsg); + this._outputChannel.appendLine(errorMsg); + } + } + + private _getHtmlForWebview(webview: vscode.Webview) { + return ` + + + + + Front Matter Panel + + + +
+
+
📄
+

Open a markdown file to edit its front matter

+
+
+ + + +`; + } +} diff --git a/lite/src/extension.ts b/lite/src/extension.ts index 94a6af0a..e888dfb8 100644 --- a/lite/src/extension.ts +++ b/lite/src/extension.ts @@ -1,5 +1,6 @@ import * as vscode from 'vscode'; import { DashboardProvider } from './DashboardProvider'; +import { PanelProvider } from './PanelProvider'; import { isVirtualWorkspace } from './utils'; /** @@ -14,6 +15,15 @@ export function activate(context: vscode.ExtensionContext) { outputChannel = vscode.window.createOutputChannel('Front Matter Lite'); outputChannel.appendLine('Front Matter Lite activated for virtual workspace'); + // Register Panel Webview Provider + const panelProvider = new PanelProvider(context.extensionUri, outputChannel); + context.subscriptions.push( + vscode.window.registerWebviewViewProvider( + PanelProvider.viewType, + panelProvider + ) + ); + // Register Dashboard Webview Provider const dashboardProvider = new DashboardProvider(context.extensionUri, outputChannel); context.subscriptions.push(