feat(plugins): implement plugin loading and management system

- Added PluginLoader component to dynamically load and initialize plugins.
- Created Plugins component for managing installed and marketplace plugins.
- Introduced PluginAPI for plugin interaction and settings management.
- Defined types for plugin metadata, installed plugins, and plugin index.
- Implemented settings rendering for plugins based on their schema.
- Integrated marketplace plugin fetching and installation logic.
- Added support for enabling/disabling and uninstalling plugins.
This commit is contained in:
2026-04-18 02:55:44 +10:00
parent 9eb5e4fa32
commit d1d3033c15
13 changed files with 2674 additions and 49 deletions

View File

@@ -102,6 +102,7 @@ import {
import { getMemberDisplayName, getMentionContent, trimReplyFromBody } from '../../utils/room';
import { CommandAutocomplete } from './CommandAutocomplete';
import { Command, SHRUG, TABLEFLIP, UNFLIP, useCommands } from '../../hooks/useCommands';
import { pluginRegistry } from '../settings/plugins/PluginAPI';
import { mobileOrTablet } from '../../utils/user-agent';
import { useElementSizeObserver } from '../../hooks/useElementSizeObserver';
import { ReplyLayout, ThreadIndicator } from '../../components/message';
@@ -308,7 +309,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
contents.forEach((content) => mx.sendMessage(roomId, content as any));
};
const submit = useCallback(() => {
const submit = useCallback(async () => {
uploadBoardHandlers.current?.handleSend();
const commandName = getBeginCommand(editor);
@@ -340,9 +341,30 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
plainText = `${UNFLIP} ${plainText}`;
customHtml = `${UNFLIP} ${customHtml}`;
} else if (commandName) {
// Check built-in commands first
const commandContent = commands[commandName as Command];
if (commandContent) {
commandContent.exe(plainText);
resetEditor(editor);
resetEditorHistory(editor);
sendTypingStatus(false);
return;
}
// Check plugin commands
try {
const result = await pluginRegistry.executeCommand(commandName, plainText);
if (result) {
// If command returns text, send it as a message
const content: IContent = {
msgtype: MsgType.Text,
body: result,
};
mx.sendMessage(roomId, content as any);
}
} catch (err) {
console.error(`[RoomInput] Plugin command /${commandName} failed:`, err);
// Show error to user (could integrate with notification system)
}
resetEditor(editor);
resetEditorHistory(editor);
@@ -398,6 +420,27 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
'm.in_reply_to': { event_id: threadRootId },
};
}
// Process message through plugin interceptors
try {
const messageContext = {
content: content.body,
roomId,
eventType: 'm.room.message',
formatted: content.formatted_body,
metadata: {},
};
const processedContext = await pluginRegistry.processBeforeSend(messageContext);
// Update content with intercepted values
content.body = processedContext.content;
if (processedContext.formatted) {
content.formatted_body = processedContext.formatted;
}
} catch (err) {
console.error('[RoomInput] Plugin interceptor error:', err);
}
mx.sendMessage(roomId, content as any);
resetEditor(editor);
resetEditorHistory(editor);