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",
|
"description": "What it does",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"rendererPreload": "renderer-preload.js",
|
"rendererPreload": "renderer-preload.js",
|
||||||
|
"categories": ["Search", "Productivity"],
|
||||||
|
"authors": ["Jane Doe", { "name": "Acme Labs", "email": "oss@acme.example" }],
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -30,6 +32,8 @@ Fields:
|
|||||||
- id: Unique id. Defaults to folder name if omitted.
|
- id: Unique id. Defaults to folder name if omitted.
|
||||||
- main: Optional entry for main process integration.
|
- main: Optional entry for main process integration.
|
||||||
- rendererPreload: Optional file injected into the preload. Use it to expose limited APIs.
|
- 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.
|
- enabled: Defaults to true.
|
||||||
|
|
||||||
## Main process API (activate)
|
## 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 dir = path.join(root, ent.name);
|
||||||
const manifestPath = path.join(dir, 'plugin.json');
|
const manifestPath = path.join(dir, 'plugin.json');
|
||||||
let manifest;
|
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 enabled = manifest.enabled !== false; // default true
|
||||||
const id = manifest.id || ent.name;
|
const id = manifest.id || ent.name;
|
||||||
const record = { id, dir, manifest, enabled, mod: null, mainPath: null };
|
const record = { id, dir, manifest, enabled, mod: null, mainPath: null };
|
||||||
@@ -171,6 +183,10 @@ class PluginManager {
|
|||||||
name: p.manifest.name || p.id,
|
name: p.manifest.name || p.id,
|
||||||
version: p.manifest.version || '0.0.0',
|
version: p.manifest.version || '0.0.0',
|
||||||
description: p.manifest.description || '',
|
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,
|
enabled: !!p.enabled,
|
||||||
hasMain: !!p.manifest.main,
|
hasMain: !!p.manifest.main,
|
||||||
hasRendererPreload: !!p.manifest.rendererPreload,
|
hasRendererPreload: !!p.manifest.rendererPreload,
|
||||||
@@ -190,11 +206,21 @@ class PluginManager {
|
|||||||
const manifestPath = path.join(dir, 'plugin.json');
|
const manifestPath = path.join(dir, 'plugin.json');
|
||||||
try {
|
try {
|
||||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
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({
|
out.push({
|
||||||
id: manifest.id || ent.name,
|
id: manifest.id || ent.name,
|
||||||
name: manifest.name || ent.name,
|
name: manifest.name || ent.name,
|
||||||
version: manifest.version || '0.0.0',
|
version: manifest.version || '0.0.0',
|
||||||
description: manifest.description || '',
|
description: manifest.description || '',
|
||||||
|
categories,
|
||||||
|
authors,
|
||||||
enabled: manifest.enabled !== false,
|
enabled: manifest.enabled !== false,
|
||||||
hasMain: !!manifest.main,
|
hasMain: !!manifest.main,
|
||||||
hasRendererPreload: !!manifest.rendererPreload,
|
hasRendererPreload: !!manifest.rendererPreload,
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
{
|
{
|
||||||
"id": "ollama-chat",
|
"id": "nebot-chat",
|
||||||
"name": "Nebot",
|
"name": "Nebot",
|
||||||
"version": "0.1.0",
|
"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.",
|
"description": "Nebot: a floating chat panel that talks to a local/remote Ollama server and saves chats in the plugin folder.",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"rendererPreload": "renderer-preload.js",
|
"rendererPreload": "renderer-preload.js",
|
||||||
|
"categories": ["AI", "Chat", "Utilities"],
|
||||||
|
"authors": [
|
||||||
|
{ "name": "Nebula Team", "email": "andrewzambazos@gmail.com" },
|
||||||
|
"Bobbybear007"
|
||||||
|
],
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,10 @@
|
|||||||
.plugin-desc { opacity:.8; font-size:.9em; }
|
.plugin-desc { opacity:.8; font-size:.9em; }
|
||||||
.plugin-actions { display:flex; gap:8px; align-items:center; }
|
.plugin-actions { display:flex; gap:8px; align-items:center; }
|
||||||
.plugin-actions .spacer { width:8px; }
|
.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 {
|
:root {
|
||||||
--bg: #121418;
|
--bg: #121418;
|
||||||
--dark-blue: #0B1C2B;
|
--dark-blue: #0B1C2B;
|
||||||
|
|||||||
@@ -355,6 +355,10 @@ async function loadPluginsUI() {
|
|||||||
listEl.appendChild(empty);
|
listEl.appendChild(empty);
|
||||||
} else {
|
} else {
|
||||||
for (const p of items) {
|
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');
|
const row = document.createElement('div');
|
||||||
row.className = 'plugin-item';
|
row.className = 'plugin-item';
|
||||||
row.setAttribute('role', 'listitem');
|
row.setAttribute('role', 'listitem');
|
||||||
@@ -362,6 +366,8 @@ async function loadPluginsUI() {
|
|||||||
<div class="plugin-meta">
|
<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-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>
|
<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 class="plugin-desc" style="opacity:.6; font-size:.85em;">${escapeHtml(p.dir)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="plugin-actions">
|
<div class="plugin-actions">
|
||||||
|
|||||||
Reference in New Issue
Block a user