mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 18:10:18 +05:30
feat: plugin system with manifest-based discovery
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "writing-studio",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Goal tracking, manuscript panels, snapshots, and sprint engine for writers",
|
||||||
|
"author": "ConcreteInfo",
|
||||||
|
"builtin": true
|
||||||
|
}
|
||||||
@@ -4,3 +4,4 @@ pub mod git;
|
|||||||
pub mod app;
|
pub mod app;
|
||||||
pub mod pdf;
|
pub mod pdf;
|
||||||
pub mod dialog;
|
pub mod dialog;
|
||||||
|
pub mod plugins;
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
use crate::error::AppError;
|
||||||
|
use crate::plugin_manager::PluginMetadata;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn list_plugins() -> Vec<PluginMetadata> {
|
||||||
|
// This would be connected to the app state in a full implementation
|
||||||
|
// For now, return the built-in plugins
|
||||||
|
vec![
|
||||||
|
PluginMetadata {
|
||||||
|
name: "writing-studio".to_string(),
|
||||||
|
version: "1.0.0".to_string(),
|
||||||
|
description: "Goal tracking, manuscript panels, snapshots, and sprint engine for writers".to_string(),
|
||||||
|
author: "ConcreteInfo".to_string(),
|
||||||
|
builtin: true,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_plugin(name: String) -> Option<PluginMetadata> {
|
||||||
|
match name.as_str() {
|
||||||
|
"writing-studio" => Some(PluginMetadata {
|
||||||
|
name: "writing-studio".to_string(),
|
||||||
|
version: "1.0.0".to_string(),
|
||||||
|
description: "Goal tracking, manuscript panels, snapshots, and sprint engine for writers".to_string(),
|
||||||
|
author: "ConcreteInfo".to_string(),
|
||||||
|
builtin: true,
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
pub mod menu;
|
pub mod menu;
|
||||||
pub mod tray;
|
pub mod tray;
|
||||||
pub mod pdf_ops;
|
pub mod pdf_ops;
|
||||||
|
pub mod plugin_manager;
|
||||||
|
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
@@ -42,6 +43,8 @@ pub fn run() {
|
|||||||
crate::commands::dialog::open_file_dialog,
|
crate::commands::dialog::open_file_dialog,
|
||||||
crate::commands::dialog::save_file_dialog,
|
crate::commands::dialog::save_file_dialog,
|
||||||
crate::commands::dialog::select_folder_dialog,
|
crate::commands::dialog::select_folder_dialog,
|
||||||
|
crate::commands::plugins::list_plugins,
|
||||||
|
crate::commands::plugins::get_plugin,
|
||||||
])
|
])
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
log::info!("MarkdownConverter starting up");
|
log::info!("MarkdownConverter starting up");
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use crate::error::AppError;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PluginMetadata {
|
||||||
|
pub name: String,
|
||||||
|
pub version: String,
|
||||||
|
pub description: String,
|
||||||
|
pub author: String,
|
||||||
|
pub builtin: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PluginManager {
|
||||||
|
plugins: HashMap<String, PluginMetadata>,
|
||||||
|
enabled: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PluginManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
PluginManager {
|
||||||
|
plugins: HashMap::new(),
|
||||||
|
enabled: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_plugins(&mut self, plugin_dir: PathBuf) -> Result<(), AppError> {
|
||||||
|
if !plugin_dir.exists() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry in walkdir::WalkDir::new(&plugin_dir)
|
||||||
|
.max_depth(1)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
{
|
||||||
|
let path = entry.path();
|
||||||
|
if !path.is_dir() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let manifest_path = path.join("manifest.json");
|
||||||
|
if manifest_path.exists() {
|
||||||
|
let content = std::fs::read_to_string(&manifest_path)
|
||||||
|
.map_err(|e| AppError::Config(format!("Failed to read manifest: {}", e)))?;
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct RawPluginMetadata {
|
||||||
|
name: String,
|
||||||
|
version: String,
|
||||||
|
description: String,
|
||||||
|
author: String,
|
||||||
|
builtin: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw: RawPluginMetadata = serde_json::from_str(&content)
|
||||||
|
.map_err(|e| AppError::Config(format!("Failed to parse manifest: {}", e)))?;
|
||||||
|
|
||||||
|
let metadata = PluginMetadata {
|
||||||
|
name: raw.name,
|
||||||
|
version: raw.version,
|
||||||
|
description: raw.description,
|
||||||
|
author: raw.author,
|
||||||
|
builtin: raw.builtin.unwrap_or(false),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.plugins.insert(metadata.name.clone(), metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_plugin(&mut self, name: &str) {
|
||||||
|
if !self.enabled.contains(&name.to_string()) {
|
||||||
|
self.enabled.push(name.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable_plugin(&mut self, name: &str) {
|
||||||
|
self.enabled.retain(|n| n != name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_enabled(&self) -> Vec<String> {
|
||||||
|
self.enabled.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_plugins(&self) -> Vec<PluginMetadata> {
|
||||||
|
self.plugins.values().cloned().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_plugin(&self, name: &str) -> Option<PluginMetadata> {
|
||||||
|
self.plugins.get(name).cloned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PluginManager {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -86,3 +86,11 @@ export const events = {
|
|||||||
onConversionComplete: (callback) => listen('conversion-complete', callback),
|
onConversionComplete: (callback) => listen('conversion-complete', callback),
|
||||||
onConversionStatus: (callback) => listen('conversion-status', callback),
|
onConversionStatus: (callback) => listen('conversion-status', callback),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// PLUGIN OPERATIONS
|
||||||
|
// ============================================
|
||||||
|
export const plugins = {
|
||||||
|
list: () => invoke('list_plugins', {}),
|
||||||
|
get: (name) => invoke('get_plugin', { name }),
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user