mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-24 07:20:16 +05:30
feat(plugins): add export-format registration hook to plugin API
Add context.formats.registerExportFormat(id, opts) to PluginContext,
backed by a new FormatRegistry (mirrors PluginRegistry's Map-based
shape). Plugins register namespaced (${pluginId}:${id}) export
formats with a label/extension/handler; the writing-studio built-in
plugin registers a trivial "sprint-summary" .txt export as a
worked example.
The plugin system lives entirely in the renderer process while the
Export menu is built in main.js, so wiring formats into the menu
required a small IPC round-trip: renderer sends format metadata to
main after plugin load (main rebuilds the menu via the already-
idempotent createMenu()), and a menu click sends the resolved save
path back to the renderer, which is the only process holding the
plugin's handler function.
Amit Haridas
This commit is contained in:
@@ -29,6 +29,7 @@ class WritingStudioPlugin extends PluginAPI {
|
||||
|
||||
this._registerCommands(context);
|
||||
this._registerStatusBar(context);
|
||||
this._registerExportFormats(context);
|
||||
}
|
||||
|
||||
_registerCommands(context) {
|
||||
@@ -120,6 +121,42 @@ class WritingStudioPlugin extends PluginAPI {
|
||||
});
|
||||
}
|
||||
|
||||
// Example usage of context.formats.registerExportFormat (Task 17): adds
|
||||
// a "Writing Studio Summary" entry to the Export menu that writes a
|
||||
// plain-text snapshot of today's sprint/goal progress instead of going
|
||||
// through Pandoc. Doubles as documentation for how a plugin can offer
|
||||
// its own export target.
|
||||
_registerExportFormats(context) {
|
||||
const { sprintEngine, goalTracker } = this;
|
||||
|
||||
context.formats.registerExportFormat('sprint-summary', {
|
||||
label: 'Writing Studio Summary (.txt)',
|
||||
extension: 'txt',
|
||||
handler: async (markdownContent, outputPath) => {
|
||||
const fs = require('fs');
|
||||
const goal = context.settings.get('dailyGoal') || 1000;
|
||||
const progress = goalTracker.getDailyProgress(goal);
|
||||
const streak = goalTracker.getStreak(goal);
|
||||
const wordCount = (markdownContent || '').split(/\s+/).filter(Boolean).length;
|
||||
|
||||
const lines = [
|
||||
'Writing Studio Summary',
|
||||
'=======================',
|
||||
`Generated: ${new Date().toISOString()}`,
|
||||
'',
|
||||
`Document word count: ${wordCount}`,
|
||||
`Daily goal: ${goal}`,
|
||||
`Words written today: ${progress.written} (${progress.pct}%)`,
|
||||
`Current streak: ${streak} day(s)`,
|
||||
`Sprint active: ${sprintEngine.isActive() ? 'yes' : 'no'}`,
|
||||
'',
|
||||
];
|
||||
|
||||
fs.writeFileSync(outputPath, lines.join('\n'), 'utf-8');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
deactivate() {
|
||||
if (this._sprintInterval) clearInterval(this._sprintInterval);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,14 @@
|
||||
"shortcut": "Ctrl+Alt+G"
|
||||
}
|
||||
],
|
||||
"statusBar": { "indicators": ["sprint-timer", "word-goal"] }
|
||||
"statusBar": { "indicators": ["sprint-timer", "word-goal"] },
|
||||
"exportFormats": [
|
||||
{
|
||||
"id": "sprint-summary",
|
||||
"label": "Writing Studio Summary (.txt)",
|
||||
"extension": "txt"
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": [
|
||||
{ "key": "dailyGoal", "type": "number", "default": 1000, "label": "Daily word goal" },
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* FormatRegistry — tracks export formats registered by plugins via
|
||||
* `context.formats.registerExportFormat(id, opts)`.
|
||||
*
|
||||
* Mirrors the simple Map-backed storage pattern used by PluginRegistry
|
||||
* (see plugin-registry.js), scoped to a single concern: export format
|
||||
* metadata + handler functions rather than whole plugin instances.
|
||||
*/
|
||||
class FormatRegistry {
|
||||
constructor() {
|
||||
this.formats = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register (or overwrite) an export format entry.
|
||||
* @param {string} id - Fully-namespaced format id, e.g. "writing-studio:sprint-summary"
|
||||
* @param {object} opts - { label, extension, handler: async (markdownContent, outputPath, options) => void }
|
||||
*/
|
||||
register(id, opts) {
|
||||
this.formats.set(id, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a single registered format entry by its namespaced id.
|
||||
* @param {string} id
|
||||
* @returns {object|undefined}
|
||||
*/
|
||||
get(id) {
|
||||
return this.formats.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all registered formats as an array of { id, ...opts }.
|
||||
*/
|
||||
getAll() {
|
||||
return Array.from(this.formats.entries()).map(([id, opts]) => ({ id, ...opts }));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { FormatRegistry };
|
||||
@@ -10,10 +10,21 @@ class PluginContext {
|
||||
* @param {object} deps.editor - { getContent, getSelection, insertAtCursor, onContentChanged }
|
||||
* @param {object} deps.ipc - { invoke, on }
|
||||
* @param {object} deps.exportHooks - { preHooks: [], postHooks: [] }
|
||||
* @param {object} deps.formatRegistry - FormatRegistry instance ({ register, get, getAll })
|
||||
*/
|
||||
constructor(deps) {
|
||||
const { pluginId, sidebar, commands, statusBar, eventBus, settings, editor, ipc, exportHooks } =
|
||||
deps;
|
||||
const {
|
||||
pluginId,
|
||||
sidebar,
|
||||
commands,
|
||||
statusBar,
|
||||
eventBus,
|
||||
settings,
|
||||
editor,
|
||||
ipc,
|
||||
exportHooks,
|
||||
formatRegistry,
|
||||
} = deps;
|
||||
|
||||
this.sidebar = {
|
||||
registerPanel: (id, opts) => sidebar.registerPanel(`${pluginId}:${id}`, opts),
|
||||
@@ -69,6 +80,19 @@ class PluginContext {
|
||||
if (exportHooks) exportHooks.postHooks.push(handler);
|
||||
},
|
||||
};
|
||||
|
||||
this.formats = {
|
||||
/**
|
||||
* Register a plugin-provided export format. It is namespaced as
|
||||
* `${pluginId}:${id}` so plugins can't collide with each other or
|
||||
* with the built-in Pandoc-backed formats.
|
||||
* @param {string} id - Format id, unique within this plugin.
|
||||
* @param {object} opts - { label, extension, handler: async (markdownContent, outputPath, options) => void }
|
||||
*/
|
||||
registerExportFormat: (id, opts) => {
|
||||
if (formatRegistry) formatRegistry.register(`${pluginId}:${id}`, opts);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ class PluginRegistry {
|
||||
editor: this.deps.editor,
|
||||
ipc: this.deps.ipc,
|
||||
exportHooks: this.exportHooks,
|
||||
formatRegistry: this.deps.formatRegistry,
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user