Add categories and authors metadata to plugins
Introduces optional 'categories' and 'authors' fields to plugin manifests, updates plugin-manager.js to normalize and expose these fields, and enhances the settings UI to display plugin tags and authors. Also updates documentation and an example plugin manifest to demonstrate the new fields.
This commit is contained in:
@@ -22,6 +22,8 @@ Example:
|
||||
"description": "What it does",
|
||||
"main": "main.js",
|
||||
"rendererPreload": "renderer-preload.js",
|
||||
"categories": ["Search", "Productivity"],
|
||||
"authors": ["Jane Doe", { "name": "Acme Labs", "email": "oss@acme.example" }],
|
||||
"enabled": true
|
||||
}
|
||||
```
|
||||
@@ -30,6 +32,8 @@ Fields:
|
||||
- id: Unique id. Defaults to folder name if omitted.
|
||||
- main: Optional entry for main process integration.
|
||||
- rendererPreload: Optional file injected into the preload. Use it to expose limited APIs.
|
||||
- categories: Optional string or array of strings used for organizing/filtering plugins in UI and APIs. Example: ["AI", "Utilities"].
|
||||
- authors: Optional string or array of strings/objects describing authors. Objects support { name, email, url }. In APIs/UI, names are displayed.
|
||||
- enabled: Defaults to true.
|
||||
|
||||
## Main process API (activate)
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
[
|
||||
{
|
||||
"title": "Discord",
|
||||
"url": "discord.gg",
|
||||
"icon": "data:image/svg+xml;utf8,%3Csvg%20role%3D%22img%22%20viewBox%3D%220%200%2024%2024%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ctitle%3EDiscord%3C%2Ftitle%3E%3Cpath%20d%3D%22M20.317%204.3698a19.7913%2019.7913%200%2000-4.8851-1.5152.0741.0741%200%2000-.0785.0371c-.211.3753-.4447.8648-.6083%201.2495-1.8447-.2762-3.68-.2762-5.4868%200-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077%200%2000-.0785-.037%2019.7363%2019.7363%200%2000-4.8852%201.515.0699.0699%200%2000-.0321.0277C.5334%209.0458-.319%2013.5799.0992%2018.0578a.0824.0824%200%2000.0312.0561c2.0528%201.5076%204.0413%202.4228%205.9929%203.0294a.0777.0777%200%2000.0842-.0276c.4616-.6304.8731-1.2952%201.226-1.9942a.076.076%200%2000-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077%200%2001-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743%200%2001.0776-.0105c3.9278%201.7933%208.18%201.7933%2012.0614%200a.0739.0739%200%2001.0785.0095c.1202.099.246.1981.3728.2924a.077.077%200%2001-.0066.1276%2012.2986%2012.2986%200%2001-1.873.8914.0766.0766%200%2000-.0407.1067c.3604.698.7719%201.3628%201.225%201.9932a.076.076%200%2000.0842.0286c1.961-.6067%203.9495-1.5219%206.0023-3.0294a.077.077%200%2000.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061%200%2000-.0312-.0286zM8.02%2015.3312c-1.1825%200-2.1569-1.0857-2.1569-2.419%200-1.3332.9555-2.4189%202.157-2.4189%201.2108%200%202.1757%201.0952%202.1568%202.419%200%201.3332-.9555%202.4189-2.1569%202.4189zm7.9748%200c-1.1825%200-2.1569-1.0857-2.1569-2.419%200-1.3332.9554-2.4189%202.1569-2.4189%201.2108%200%202.1757%201.0952%202.1568%202.419%200%201.3332-.946%202.4189-2.1568%202.4189Z%22%2F%3E%3C%2Fsvg%3E",
|
||||
"iconSet": "simple"
|
||||
}
|
||||
|
||||
]
|
||||
+27
-1
@@ -42,7 +42,19 @@ class PluginManager {
|
||||
const dir = path.join(root, ent.name);
|
||||
const manifestPath = path.join(dir, 'plugin.json');
|
||||
let manifest;
|
||||
try { manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); } catch { continue; }
|
||||
try {
|
||||
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
||||
// Normalize optional fields
|
||||
const cats = manifest.categories;
|
||||
if (typeof cats === 'string') manifest.categories = [cats];
|
||||
else if (Array.isArray(cats)) manifest.categories = cats.filter(x => typeof x === 'string');
|
||||
else if (cats == null) manifest.categories = [];
|
||||
|
||||
const au = manifest.authors;
|
||||
if (typeof au === 'string') manifest.authors = [au];
|
||||
else if (Array.isArray(au)) manifest.authors = au.filter(x => (typeof x === 'string') || (x && typeof x === 'object' && typeof x.name === 'string'));
|
||||
else if (au == null) manifest.authors = [];
|
||||
} catch { continue; }
|
||||
const enabled = manifest.enabled !== false; // default true
|
||||
const id = manifest.id || ent.name;
|
||||
const record = { id, dir, manifest, enabled, mod: null, mainPath: null };
|
||||
@@ -171,6 +183,10 @@ class PluginManager {
|
||||
name: p.manifest.name || p.id,
|
||||
version: p.manifest.version || '0.0.0',
|
||||
description: p.manifest.description || '',
|
||||
categories: Array.isArray(p.manifest.categories) ? p.manifest.categories : [],
|
||||
authors: Array.isArray(p.manifest.authors)
|
||||
? p.manifest.authors.map(x => (typeof x === 'string' ? x : (x && x.name) || '')).filter(Boolean)
|
||||
: [],
|
||||
enabled: !!p.enabled,
|
||||
hasMain: !!p.manifest.main,
|
||||
hasRendererPreload: !!p.manifest.rendererPreload,
|
||||
@@ -190,11 +206,21 @@ class PluginManager {
|
||||
const manifestPath = path.join(dir, 'plugin.json');
|
||||
try {
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
||||
const cats = manifest.categories;
|
||||
const categories = typeof cats === 'string' ? [cats] : Array.isArray(cats) ? cats.filter(x => typeof x === 'string') : [];
|
||||
const au = manifest.authors;
|
||||
const authors = typeof au === 'string'
|
||||
? [au]
|
||||
: Array.isArray(au)
|
||||
? au.map(x => (typeof x === 'string' ? x : (x && x.name) || null)).filter(Boolean)
|
||||
: [];
|
||||
out.push({
|
||||
id: manifest.id || ent.name,
|
||||
name: manifest.name || ent.name,
|
||||
version: manifest.version || '0.0.0',
|
||||
description: manifest.description || '',
|
||||
categories,
|
||||
authors,
|
||||
enabled: manifest.enabled !== false,
|
||||
hasMain: !!manifest.main,
|
||||
hasRendererPreload: !!manifest.rendererPreload,
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
{
|
||||
"id": "ollama-chat",
|
||||
"id": "nebot-chat",
|
||||
"name": "Nebot",
|
||||
"version": "0.1.0",
|
||||
"description": "Nebot: a floating chat panel that talks to a local/remote Ollama server and saves chats in the plugin folder.",
|
||||
"main": "main.js",
|
||||
"rendererPreload": "renderer-preload.js",
|
||||
"categories": ["AI", "Chat", "Utilities"],
|
||||
"authors": [
|
||||
{ "name": "Nebula Team", "email": "andrewzambazos@gmail.com" },
|
||||
"Bobbybear007"
|
||||
],
|
||||
"enabled": true
|
||||
}
|
||||
@@ -8,6 +8,10 @@
|
||||
.plugin-desc { opacity:.8; font-size:.9em; }
|
||||
.plugin-actions { display:flex; gap:8px; align-items:center; }
|
||||
.plugin-actions .spacer { width:8px; }
|
||||
.plugin-tags { display:flex; flex-wrap: wrap; gap:6px; margin-top: 4px; }
|
||||
.plugin-tag { display:inline-flex; align-items:center; padding:2px 8px; border-radius:999px; font-size:.75em; opacity:.9; border:1px solid rgba(255,255,255,0.16); background: rgba(255,255,255,0.06); }
|
||||
.plugin-authors { margin-top: 4px; font-size:.85em; opacity:.85; }
|
||||
.plugin-authors .muted { opacity:.7; margin-right: 6px; }
|
||||
:root {
|
||||
--bg: #121418;
|
||||
--dark-blue: #0B1C2B;
|
||||
|
||||
@@ -355,6 +355,10 @@ async function loadPluginsUI() {
|
||||
listEl.appendChild(empty);
|
||||
} else {
|
||||
for (const p of items) {
|
||||
const categories = Array.isArray(p.categories) ? p.categories.filter(x => x && typeof x === 'string') : [];
|
||||
const authors = Array.isArray(p.authors) ? p.authors.filter(x => x && typeof x === 'string') : [];
|
||||
const tagsHtml = categories.length ? `<div class="plugin-tags">${categories.map(c => `<span class=\"plugin-tag\">${escapeHtml(c)}</span>`).join('')}</div>` : '';
|
||||
const authorsHtml = authors.length ? `<div class=\"plugin-authors\"><span class=\"muted\">Authors:</span> ${authors.map(a => `<span class=\"plugin-author\">${escapeHtml(a)}</span>`).join(', ')}</div>` : '';
|
||||
const row = document.createElement('div');
|
||||
row.className = 'plugin-item';
|
||||
row.setAttribute('role', 'listitem');
|
||||
@@ -362,6 +366,8 @@ async function loadPluginsUI() {
|
||||
<div class="plugin-meta">
|
||||
<div class="plugin-title">${escapeHtml(p.name)} <span style="opacity:.7;font-weight:400">v${escapeHtml(p.version)}</span></div>
|
||||
<div class="plugin-desc">${escapeHtml(p.description || '')}</div>
|
||||
${tagsHtml}
|
||||
${authorsHtml}
|
||||
<div class="plugin-desc" style="opacity:.6; font-size:.85em;">${escapeHtml(p.dir)}</div>
|
||||
</div>
|
||||
<div class="plugin-actions">
|
||||
|
||||
Reference in New Issue
Block a user