feat: Add Plugin Button Registration API documentation and Plugin System implementation summary
- Introduced PLUGIN_BUTTON_API.md detailing button registration, UI locations, and usage examples. - Added PLUGIN_SYSTEM_IMPLEMENTATION.md summarizing implemented features, usage examples, and API details for the plugin system.
This commit is contained in:
23
PLUGINS.md
23
PLUGINS.md
@@ -70,7 +70,7 @@ Plugins run with access to your Matrix client and can:
|
|||||||
|
|
||||||
## Developing Plugins
|
## Developing Plugins
|
||||||
|
|
||||||
Want to create your own plugin? Check out the [Plugin Development Guide](./cinny/src/app/features/settings/plugins/PLUGIN_DEVELOPMENT.md).
|
Want to create your own plugin? Check out the [Plugin API Reference](docs/PLUGIN_API.md).
|
||||||
|
|
||||||
### Quick Start
|
### Quick Start
|
||||||
|
|
||||||
@@ -101,19 +101,19 @@ Plugins have access to:
|
|||||||
- **Matrix Client**: Full matrix-js-sdk client instance
|
- **Matrix Client**: Full matrix-js-sdk client instance
|
||||||
- **React**: For building UI components
|
- **React**: For building UI components
|
||||||
- **Commands**: Register custom slash commands
|
- **Commands**: Register custom slash commands
|
||||||
- **UI Hooks**: Inject UI elements
|
- **UI Buttons**: Inject buttons into 11 locations across the app (nav lists, toolbars, headers, sidebar)
|
||||||
- **Settings**: Add custom settings sections
|
- **UI Renderers**: Custom message and content renderers
|
||||||
- **Utilities**: Notifications and helpers
|
- **Settings**: Add custom settings sections per plugin
|
||||||
|
- **Themes**: Register custom themes that appear in the theme selector
|
||||||
|
- **Matrix Events**: Hook into raw Matrix events
|
||||||
|
- **Timers**: Background intervals and timeouts, auto-cleaned on unload
|
||||||
|
- **Notifications**: System notifications
|
||||||
|
|
||||||
See the [Plugin Development Guide](./cinny/src/app/features/settings/plugins/PLUGIN_DEVELOPMENT.md) for complete API documentation.
|
See the [Plugin API Reference](docs/PLUGIN_API.md) and [Button Registration API](docs/PLUGIN_BUTTON_API.md) for complete documentation.
|
||||||
|
|
||||||
## Example Plugins
|
## Example Plugins
|
||||||
|
|
||||||
Check out the `example-plugin/` directory for a simple example demonstrating:
|
Check out the `example-plugin/` directory for a simple example, or the `plugins/example-showcase-plugin/` for a full demonstration of all 11 UI button locations.
|
||||||
- Matrix client access
|
|
||||||
- Event listeners
|
|
||||||
- Custom commands
|
|
||||||
- Notifications
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
@@ -147,7 +147,8 @@ Submit your plugins there to make them available in the marketplace!
|
|||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
- **Documentation**: [Plugin Development Guide](./cinny/src/app/features/settings/plugins/PLUGIN_DEVELOPMENT.md)
|
- **Plugin API Reference**: [docs/PLUGIN_API.md](docs/PLUGIN_API.md)
|
||||||
|
- **Button API Reference**: [docs/PLUGIN_BUTTON_API.md](docs/PLUGIN_BUTTON_API.md)
|
||||||
- **Issues**: https://github.com/Paarrot/cinny-desktop/issues
|
- **Issues**: https://github.com/Paarrot/cinny-desktop/issues
|
||||||
- **Community**: Join our Matrix room for plugin development help
|
- **Community**: Join our Matrix room for plugin development help
|
||||||
|
|
||||||
|
|||||||
47
README.md
47
README.md
@@ -29,3 +29,50 @@ To build the app locally, run:
|
|||||||
|
|
||||||
To start local dev server, run:
|
To start local dev server, run:
|
||||||
* `npm run dev`
|
* `npm run dev`
|
||||||
|
|
||||||
|
## Plugin System
|
||||||
|
|
||||||
|
Paarrot includes a plugin system for extending and customising the app with JavaScript modules.
|
||||||
|
|
||||||
|
### Installing Plugins
|
||||||
|
|
||||||
|
1. Drop a plugin folder into your plugins directory:
|
||||||
|
- **Windows**: `%APPDATA%\paarrot\plugins\<plugin-name>\`
|
||||||
|
- **Linux**: `~/.config/Paarrot/plugins/<plugin-name>/`
|
||||||
|
- **macOS**: `~/Library/Application Support/Paarrot/plugins/<plugin-name>/`
|
||||||
|
2. Enable it in **Settings → Plugins → Installed**
|
||||||
|
|
||||||
|
### What Plugins Can Do
|
||||||
|
|
||||||
|
- Register custom slash commands
|
||||||
|
- Intercept and modify messages
|
||||||
|
- Inject buttons into 11 UI locations (nav lists, toolbars, headers, sidebar, menus)
|
||||||
|
- Register custom themes
|
||||||
|
- Hook into raw Matrix events
|
||||||
|
- Run background tasks
|
||||||
|
- Show system notifications
|
||||||
|
|
||||||
|
### Plugin Locations (UI Buttons)
|
||||||
|
|
||||||
|
Plugins can inject buttons as **nav list rows** or **icon buttons** across the app:
|
||||||
|
|
||||||
|
| Location | Style | Where |
|
||||||
|
|---|---|---|
|
||||||
|
| `channel-list` | Nav row | Space channel list |
|
||||||
|
| `home-section` | Nav row | Home panel, above room list |
|
||||||
|
| `direct-messages` | Nav row | DMs panel, below "Create Chat" |
|
||||||
|
| `sidebar-actions` | Icon | Left sidebar — above Explore and above Search |
|
||||||
|
| `text-composer-toolbar` | Icon | Message composer toolbar |
|
||||||
|
| `composer-actions` | Icon | Beside the `+` attach button |
|
||||||
|
| `room-header` | Icon | Room header bar |
|
||||||
|
| `room-menu` | Icon | Room ⋮ dropdown |
|
||||||
|
| `message-actions` | Icon | Message hover bar |
|
||||||
|
| `user-menu` | Icon | Right-click on user avatar |
|
||||||
|
| `search-notification-section` | Icon | Notifications page header |
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- [Plugin System Overview](PLUGINS.md)
|
||||||
|
- [Full Plugin API Reference](docs/PLUGIN_API.md)
|
||||||
|
- [Button Registration API](docs/PLUGIN_BUTTON_API.md)
|
||||||
|
- [Example Plugins](plugins/)
|
||||||
|
|||||||
2
cinny
2
cinny
Submodule cinny updated: 9a463facce...64e252da94
@@ -9,7 +9,8 @@ Complete reference for developing plugins for Paarrot/Cinny Desktop.
|
|||||||
- [Plugin Context API](#plugin-context-api)
|
- [Plugin Context API](#plugin-context-api)
|
||||||
- [Commands](#commands)
|
- [Commands](#commands)
|
||||||
- [Message Interceptors](#message-interceptors)
|
- [Message Interceptors](#message-interceptors)
|
||||||
- [Custom Renderers](#custom-renderers)
|
- [UI: Button Registration](#ui-button-registration)
|
||||||
|
- [UI: Custom Renderers](#custom-renderers)
|
||||||
- [Settings](#settings)
|
- [Settings](#settings)
|
||||||
- [Themes](#themes)
|
- [Themes](#themes)
|
||||||
- [Matrix Events](#matrix-events)
|
- [Matrix Events](#matrix-events)
|
||||||
@@ -322,6 +323,8 @@ ctx.ui.registerRenderer("image", (data, defaultRenderer) => {
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
interface UIAPI {
|
interface UIAPI {
|
||||||
|
registerButton(button: UIButtonDefinition): void;
|
||||||
|
unregisterButton(id: string): void;
|
||||||
registerRenderer(
|
registerRenderer(
|
||||||
type: string,
|
type: string,
|
||||||
renderer: (data: any, defaultRenderer?: () => ReactNode) => ReactNode
|
renderer: (data: any, defaultRenderer?: () => ReactNode) => ReactNode
|
||||||
@@ -336,6 +339,97 @@ type CustomRenderer = (
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### UI: Button Registration
|
||||||
|
|
||||||
|
Inject buttons into various parts of the Paarrot UI. Buttons render in two visual styles depending on location — **nav list rows** (icon + label, full width) or **icon buttons** (compact, toolbar-style).
|
||||||
|
|
||||||
|
See [PLUGIN_BUTTON_API.md](PLUGIN_BUTTON_API.md) for the complete reference including positioning and grouping examples.
|
||||||
|
|
||||||
|
#### Register a Button
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
ctx.ui.registerButton({
|
||||||
|
id: 'my-button',
|
||||||
|
location: 'text-composer-toolbar',
|
||||||
|
label: 'My Tool',
|
||||||
|
icon: '🔧',
|
||||||
|
onClick: () => ctx.log('clicked!')
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### UI Locations — Nav List Rows
|
||||||
|
|
||||||
|
| Location | Where it appears |
|
||||||
|
|---|---|
|
||||||
|
| `channel-list` | Space channel list, below Lobby and Message Search |
|
||||||
|
| `home-section` | Home panel, inside the Rooms category above the room list |
|
||||||
|
| `direct-messages` | DMs panel, below "Create Chat" and above the CHATS dropdown |
|
||||||
|
|
||||||
|
#### UI Locations — Icon Buttons
|
||||||
|
|
||||||
|
| Location | Where it appears |
|
||||||
|
|---|---|
|
||||||
|
| `text-composer-toolbar` | Message composer toolbar (alongside emoji, sticker buttons) |
|
||||||
|
| `composer-actions` | Left side of the composer, beside the `+` attach button |
|
||||||
|
| `room-header` | Top room header bar, before the ⋮ menu |
|
||||||
|
| `room-menu` | Room ⋮ dropdown menu |
|
||||||
|
| `message-actions` | Message hover action bar |
|
||||||
|
| `user-menu` | Right-click popup on the user avatar |
|
||||||
|
| `search-notification-section` | Notifications page header (right side) |
|
||||||
|
| `sidebar-actions` | Left sidebar — above Explore Servers icon, and above Search icon in the sticky bottom section |
|
||||||
|
|
||||||
|
#### Positioning
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
ctx.ui.registerButton({
|
||||||
|
id: 'my-button',
|
||||||
|
location: 'text-composer-toolbar',
|
||||||
|
label: 'My Tool',
|
||||||
|
icon: '🔧',
|
||||||
|
position: {
|
||||||
|
after: 'emoji-picker-button',
|
||||||
|
group: 'my-tools',
|
||||||
|
order: 1
|
||||||
|
},
|
||||||
|
onClick: () => ctx.log('clicked!')
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Unregister
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
ctx.ui.unregisterButton('my-button');
|
||||||
|
// All buttons are automatically unregistered on plugin unload
|
||||||
|
```
|
||||||
|
|
||||||
|
#### API Reference
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type UILocation =
|
||||||
|
| 'channel-list' | 'direct-messages' | 'home-section'
|
||||||
|
| 'text-composer-toolbar' | 'composer-actions'
|
||||||
|
| 'room-header' | 'room-menu' | 'message-actions'
|
||||||
|
| 'user-menu' | 'search-notification-section' | 'sidebar-actions';
|
||||||
|
|
||||||
|
interface UIButtonDefinition {
|
||||||
|
id: string;
|
||||||
|
location: UILocation;
|
||||||
|
label: string;
|
||||||
|
icon?: string;
|
||||||
|
position?: UIButtonPosition;
|
||||||
|
onClick?: () => void | Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UIButtonPosition {
|
||||||
|
before?: string;
|
||||||
|
after?: string;
|
||||||
|
group?: string;
|
||||||
|
order?: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Settings
|
### Settings
|
||||||
|
|
||||||
Define settings that users can configure in the UI.
|
Define settings that users can configure in the UI.
|
||||||
432
docs/PLUGIN_BUTTON_API.md
Normal file
432
docs/PLUGIN_BUTTON_API.md
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
# Plugin Button Registration API
|
||||||
|
|
||||||
|
An easy way to register plugin buttons in various UI locations with positioning and grouping support.
|
||||||
|
|
||||||
|
## Example Plugins
|
||||||
|
|
||||||
|
- **[example-button-plugin/](example-button-plugin/)** - Simple examples showing basic usage patterns
|
||||||
|
- **[example-showcase-plugin/](example-showcase-plugin/)** - Complete demonstration of ALL 11 locations ⭐
|
||||||
|
|
||||||
|
## UI Locations
|
||||||
|
|
||||||
|
Buttons render in two visual styles depending on location — **nav list rows** (icon + label, full width) or **icon buttons** (compact, toolbar/header style).
|
||||||
|
|
||||||
|
### Nav List Rows
|
||||||
|
|
||||||
|
These render as full-width list entries matching the style of built-in items like "Create Room" and "Message Search":
|
||||||
|
|
||||||
|
| Location | Where it appears |
|
||||||
|
|---|---|
|
||||||
|
| `channel-list` | Space channel list, below Lobby and Message Search |
|
||||||
|
| `home-section` | Home panel, inside the Rooms category above the room list |
|
||||||
|
| `direct-messages` | Direct Messages panel, below "Create Chat" and above the CHATS dropdown |
|
||||||
|
|
||||||
|
### Icon Buttons
|
||||||
|
|
||||||
|
These render as compact icon buttons inline with other toolbar/header controls:
|
||||||
|
|
||||||
|
| Location | Where it appears |
|
||||||
|
|---|---|
|
||||||
|
| `text-composer-toolbar` | Message composer toolbar (alongside emoji, sticker buttons) |
|
||||||
|
| `composer-actions` | Left side of the composer, beside the `+` attach button |
|
||||||
|
| `room-header` | Top room header bar, before the ⋮ menu button |
|
||||||
|
| `room-menu` | Room ⋮ dropdown menu |
|
||||||
|
| `message-actions` | Message hover action bar |
|
||||||
|
| `user-menu` | Right-click popup on the user avatar |
|
||||||
|
| `search-notification-section` | Notifications page header (right side) |
|
||||||
|
| `sidebar-actions` | Left sidebar — appears in **two** places: above the Explore Servers icon, and above the Search icon in the sticky bottom section |
|
||||||
|
|
||||||
|
> 💡 **Tip:** Install the [example-showcase-plugin](example-showcase-plugin/) to see exactly where each location appears in the UI!
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
### Simple Button
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
module.exports = {
|
||||||
|
name: 'my-plugin',
|
||||||
|
version: '1.0.0',
|
||||||
|
|
||||||
|
onLoad: (context) => {
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'my-button',
|
||||||
|
location: 'text-composer-toolbar',
|
||||||
|
label: 'My Action',
|
||||||
|
icon: '🎨',
|
||||||
|
onClick: () => {
|
||||||
|
context.log('Button clicked!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onUnload: () => {
|
||||||
|
// Buttons are automatically cleaned up on plugin unload
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Positioning
|
||||||
|
|
||||||
|
### Before/After Positioning
|
||||||
|
|
||||||
|
Place your button before or after existing buttons:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'my-button',
|
||||||
|
location: 'text-composer-toolbar',
|
||||||
|
label: 'My Action',
|
||||||
|
icon: '✨',
|
||||||
|
position: {
|
||||||
|
before: 'emoji-picker-button', // Place before emoji picker
|
||||||
|
// OR
|
||||||
|
after: 'sticker-button' // Place after sticker button
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Grouping Buttons
|
||||||
|
|
||||||
|
Group related buttons together:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'action-1',
|
||||||
|
location: 'text-composer-toolbar',
|
||||||
|
label: 'Action 1',
|
||||||
|
icon: '1️⃣',
|
||||||
|
position: {
|
||||||
|
group: 'my-plugin-tools',
|
||||||
|
order: 1 // Lower numbers appear first
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'action-2',
|
||||||
|
location: 'text-composer-toolbar',
|
||||||
|
label: 'Action 2',
|
||||||
|
icon: '2️⃣',
|
||||||
|
position: {
|
||||||
|
group: 'my-plugin-tools',
|
||||||
|
order: 2
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Combined Positioning and Grouping
|
||||||
|
|
||||||
|
Place a group before/after other elements:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'tool-1',
|
||||||
|
location: 'text-composer-toolbar',
|
||||||
|
label: 'Tool 1',
|
||||||
|
icon: '🔧',
|
||||||
|
position: {
|
||||||
|
group: 'my-tools',
|
||||||
|
after: 'emoji-picker-button',
|
||||||
|
order: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'tool-2',
|
||||||
|
location: 'text-composer-toolbar',
|
||||||
|
label: 'Tool 2',
|
||||||
|
icon: '🔨',
|
||||||
|
position: {
|
||||||
|
group: 'my-tools',
|
||||||
|
order: 2
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete Example
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
module.exports = {
|
||||||
|
name: 'custom-tools',
|
||||||
|
version: '1.0.0',
|
||||||
|
|
||||||
|
onLoad: (context) => {
|
||||||
|
// Nav list row — appears below "Create Chat" in the DMs panel
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'dm-favorites',
|
||||||
|
location: 'direct-messages',
|
||||||
|
label: 'Favourites',
|
||||||
|
icon: '⭐',
|
||||||
|
onClick: () => context.log('Favourites clicked!')
|
||||||
|
});
|
||||||
|
|
||||||
|
// Nav list row — appears in the space channel list
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'quick-access',
|
||||||
|
location: 'channel-list',
|
||||||
|
label: 'Quick Access',
|
||||||
|
icon: '⚡',
|
||||||
|
onClick: () => context.log('Quick access clicked!')
|
||||||
|
});
|
||||||
|
|
||||||
|
// Icon button — appears in the message composer toolbar
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'format-bold',
|
||||||
|
location: 'text-composer-toolbar',
|
||||||
|
label: 'Bold',
|
||||||
|
icon: '𝐁',
|
||||||
|
position: {
|
||||||
|
group: 'custom-formatting',
|
||||||
|
order: 1
|
||||||
|
},
|
||||||
|
onClick: () => context.log('Bold clicked!')
|
||||||
|
});
|
||||||
|
|
||||||
|
// Icon button — appears in the room header
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'special-search',
|
||||||
|
location: 'room-header',
|
||||||
|
label: 'Special Search',
|
||||||
|
icon: '🔍',
|
||||||
|
position: { after: 'search-button' },
|
||||||
|
onClick: () => context.log('Special search clicked!')
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sidebar icon — appears above Explore and above Search
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'plugin-panel',
|
||||||
|
location: 'sidebar-actions',
|
||||||
|
label: 'Plugin Panel',
|
||||||
|
icon: '🧩',
|
||||||
|
onClick: () => context.log('Plugin panel clicked!')
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onUnload: () => {
|
||||||
|
// All registered buttons are automatically cleaned up
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Unregistering Buttons
|
||||||
|
|
||||||
|
Buttons are automatically unregistered when the plugin is unloaded. To manually unregister:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
context.ui.unregisterButton('my-button-id');
|
||||||
|
```
|
||||||
|
|
||||||
|
## TypeScript Types
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { UIButtonDefinition, UIButtonPosition, UILocation } from '@paarrot/plugin-manager';
|
||||||
|
|
||||||
|
const button: UIButtonDefinition = {
|
||||||
|
id: 'my-button',
|
||||||
|
location: 'text-composer-toolbar',
|
||||||
|
label: 'My Action',
|
||||||
|
icon: '🎨',
|
||||||
|
position: {
|
||||||
|
group: 'my-tools',
|
||||||
|
after: 'emoji-picker',
|
||||||
|
order: 1
|
||||||
|
},
|
||||||
|
onClick: () => console.log('Clicked!')
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
### Simple Button
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
module.exports = {
|
||||||
|
name: 'my-plugin',
|
||||||
|
version: '1.0.0',
|
||||||
|
|
||||||
|
onLoad: (context) => {
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'my-button',
|
||||||
|
location: 'text-composer-toolbar',
|
||||||
|
label: 'My Action',
|
||||||
|
icon: '🎨',
|
||||||
|
onClick: () => {
|
||||||
|
context.log('Button clicked!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onUnload: () => {
|
||||||
|
// Buttons are automatically cleaned up on plugin unload
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Positioning
|
||||||
|
|
||||||
|
### Before/After Positioning
|
||||||
|
|
||||||
|
Place your button before or after existing buttons:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'my-button',
|
||||||
|
location: 'text-composer-toolbar',
|
||||||
|
label: 'My Action',
|
||||||
|
icon: '✨',
|
||||||
|
position: {
|
||||||
|
before: 'emoji-picker-button', // Place before emoji picker
|
||||||
|
// OR
|
||||||
|
after: 'sticker-button' // Place after sticker button
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Grouping Buttons
|
||||||
|
|
||||||
|
Group related buttons together:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'action-1',
|
||||||
|
location: 'text-composer-toolbar',
|
||||||
|
label: 'Action 1',
|
||||||
|
icon: '1️⃣',
|
||||||
|
position: {
|
||||||
|
group: 'my-plugin-tools',
|
||||||
|
order: 1 // Lower numbers appear first
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'action-2',
|
||||||
|
location: 'text-composer-toolbar',
|
||||||
|
label: 'Action 2',
|
||||||
|
icon: '2️⃣',
|
||||||
|
position: {
|
||||||
|
group: 'my-plugin-tools',
|
||||||
|
order: 2
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Combined Positioning and Grouping
|
||||||
|
|
||||||
|
Place a group before/after other elements:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'tool-1',
|
||||||
|
location: 'text-composer-toolbar',
|
||||||
|
label: 'Tool 1',
|
||||||
|
icon: '🔧',
|
||||||
|
position: {
|
||||||
|
group: 'my-tools',
|
||||||
|
after: 'emoji-picker-button',
|
||||||
|
order: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'tool-2',
|
||||||
|
location: 'text-composer-toolbar',
|
||||||
|
label: 'Tool 2',
|
||||||
|
icon: '🔨',
|
||||||
|
position: {
|
||||||
|
group: 'my-tools',
|
||||||
|
order: 2
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete Example
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
module.exports = {
|
||||||
|
name: 'custom-tools',
|
||||||
|
version: '1.0.0',
|
||||||
|
|
||||||
|
onLoad: (context) => {
|
||||||
|
// Nav list row — appears below "Create Chat" in the DMs panel
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'dm-favorites',
|
||||||
|
location: 'direct-messages',
|
||||||
|
label: 'Favourites',
|
||||||
|
icon: '⭐',
|
||||||
|
onClick: () => context.log('Favourites clicked!')
|
||||||
|
});
|
||||||
|
|
||||||
|
// Nav list row — appears in the space channel list
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'quick-access',
|
||||||
|
location: 'channel-list',
|
||||||
|
label: 'Quick Access',
|
||||||
|
icon: '⚡',
|
||||||
|
onClick: () => context.log('Quick access clicked!')
|
||||||
|
});
|
||||||
|
|
||||||
|
// Icon button — appears in the message composer toolbar
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'format-bold',
|
||||||
|
location: 'text-composer-toolbar',
|
||||||
|
label: 'Bold',
|
||||||
|
icon: '𝐁',
|
||||||
|
position: {
|
||||||
|
group: 'custom-formatting',
|
||||||
|
order: 1
|
||||||
|
},
|
||||||
|
onClick: () => context.log('Bold clicked!')
|
||||||
|
});
|
||||||
|
|
||||||
|
// Icon button — appears in the room header
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'special-search',
|
||||||
|
location: 'room-header',
|
||||||
|
label: 'Special Search',
|
||||||
|
icon: '🔍',
|
||||||
|
position: { after: 'search-button' },
|
||||||
|
onClick: () => context.log('Special search clicked!')
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sidebar icon — appears above Explore and above Search
|
||||||
|
context.ui.registerButton({
|
||||||
|
id: 'plugin-panel',
|
||||||
|
location: 'sidebar-actions',
|
||||||
|
label: 'Plugin Panel',
|
||||||
|
icon: '🧩',
|
||||||
|
onClick: () => context.log('Plugin panel clicked!')
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onUnload: () => {
|
||||||
|
// All registered buttons are automatically cleaned up
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Unregistering Buttons
|
||||||
|
|
||||||
|
Buttons are automatically unregistered when the plugin is unloaded. To manually unregister:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
context.ui.unregisterButton('my-button-id');
|
||||||
|
```
|
||||||
|
|
||||||
|
## TypeScript Types
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { UIButtonDefinition, UIButtonPosition, UILocation } from '@paarrot/plugin-manager';
|
||||||
|
|
||||||
|
const button: UIButtonDefinition = {
|
||||||
|
id: 'my-button',
|
||||||
|
location: 'text-composer-toolbar',
|
||||||
|
label: 'My Action',
|
||||||
|
icon: '🎨',
|
||||||
|
position: {
|
||||||
|
group: 'my-tools',
|
||||||
|
after: 'emoji-picker',
|
||||||
|
order: 1
|
||||||
|
},
|
||||||
|
onClick: () => console.log('Clicked!')
|
||||||
|
};
|
||||||
|
```
|
||||||
@@ -16,11 +16,17 @@ All requested features have been implemented! 🎉
|
|||||||
- Full MessageContext with content, roomId, metadata
|
- Full MessageContext with content, roomId, metadata
|
||||||
- Async support for interceptors
|
- Async support for interceptors
|
||||||
|
|
||||||
### 3. ✅ Custom Renderers
|
### 3. ✅ Custom Renderers & Button Registration
|
||||||
- **Register**: `ctx.ui.registerRenderer("message", (msg, defaultRenderer) => { /* render */ })`
|
- **Register renderer**: `ctx.ui.registerRenderer("message", (msg, defaultRenderer) => { /* render */ })`
|
||||||
- **Unregister**: `ctx.ui.unregisterRenderer("message")`
|
- **Unregister renderer**: `ctx.ui.unregisterRenderer("message")`
|
||||||
- Fallback to default renderer support
|
- **Register button**: `ctx.ui.registerButton({ id, location, label, icon, onClick })`
|
||||||
- Discord-mod-tier customization enabled
|
- **Unregister button**: `ctx.ui.unregisterButton(id)`
|
||||||
|
- 11 UI locations across nav lists, toolbars, headers, sidebar, and menus
|
||||||
|
- Nav list rows (icon + label) for `channel-list`, `home-section`, `direct-messages`
|
||||||
|
- Icon buttons for all other locations
|
||||||
|
- Positioning and grouping support (`before`, `after`, `group`, `order`)
|
||||||
|
- Auto-cleanup on plugin unload
|
||||||
|
- See [PLUGIN_BUTTON_API.md](PLUGIN_BUTTON_API.md) for full reference
|
||||||
|
|
||||||
### 4. ✅ Settings UI Per Plugin
|
### 4. ✅ Settings UI Per Plugin
|
||||||
- **Define schema**: `ctx.settings.define({ theme: { type: "select", options: [...] } })`
|
- **Define schema**: `ctx.settings.define({ theme: { type: "select", options: [...] } })`
|
||||||
@@ -162,6 +168,8 @@ interface PluginContext {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
|
registerButton: (button: UIButtonDefinition) => void;
|
||||||
|
unregisterButton: (id: string) => void;
|
||||||
registerRenderer: (type: string, renderer: CustomRenderer) => void;
|
registerRenderer: (type: string, renderer: CustomRenderer) => void;
|
||||||
unregisterRenderer: (type: string) => void;
|
unregisterRenderer: (type: string) => void;
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user