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:
2026-08-23 19:31:33 +05:30
parent 44624cd4bf
commit d6baa2daf7
10 changed files with 304 additions and 3 deletions
+26 -2
View File
@@ -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);
},
};
}
}