feat: Add browser documentation
This commit is contained in:
75
docs-site/docs/desktop-and-local-api.md
Normal file
75
docs-site/docs/desktop-and-local-api.md
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Desktop and Local API
|
||||
|
||||
## Electron Hosting Model
|
||||
|
||||
The desktop app hosts local documentation through the existing Electron Local API server. This server is implemented with Node's `http` module in the Electron main process and uses async request handlers for routing, file reads, and streamed responses.
|
||||
|
||||
The endpoint is manually activated. Opening the Docusaurus docs from the desktop title bar enables the local server and docs endpoint if necessary, then opens the system browser to the generated static site.
|
||||
|
||||
This avoids:
|
||||
|
||||
- starting a Docusaurus development server inside Electron;
|
||||
- blocking the renderer thread;
|
||||
- serving docs from a remote host;
|
||||
- exposing the endpoint unless the user chooses to activate it.
|
||||
|
||||
## Local Server Settings
|
||||
|
||||
| Setting | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `enabled` | `false` | Starts or stops the local HTTP server. |
|
||||
| `port` | `17878` | Listening port. |
|
||||
| `exposeOnLan` | `false` | Uses `127.0.0.1` by default; when true, binds to `0.0.0.0`. |
|
||||
| `scalarEnabled` | `false` | Enables `/docs` for the Scalar OpenAPI reference. |
|
||||
| `docusaurusEnabled` | `false` | Enables `/docusaurus` for the built Docusaurus documentation. |
|
||||
| `allowedSignalingServers` | `[]` | Server URLs allowed for Local API login. |
|
||||
|
||||
## Routes
|
||||
|
||||
| Endpoint | Purpose | Auth |
|
||||
| --- | --- | --- |
|
||||
| `GET /api/health` | Liveness, app version, timestamp, and LAN exposure status. | No |
|
||||
| `GET /api/openapi.json` | OpenAPI 3.1 document for local automation clients. | No |
|
||||
| `GET /docs` | Scalar API reference when Scalar docs are enabled. | No |
|
||||
| `GET /docusaurus` | Docusaurus documentation entrypoint when Docusaurus docs are enabled. | No |
|
||||
| `GET /docusaurus/*` | Static Docusaurus assets and pages. | No |
|
||||
| `POST /api/auth/login` | Exchanges username, password, and allowed signaling server URL for a local bearer token. | No |
|
||||
| `POST /api/auth/logout` | Revokes the current local bearer token. | Bearer |
|
||||
| `GET /api/profile` | Reads the current local user profile. | Bearer |
|
||||
| `GET /api/rooms` | Lists rooms known to this device. | Bearer |
|
||||
| `GET /api/rooms/{roomId}/messages` | Reads local room messages with `limit` and `offset`. | Bearer |
|
||||
|
||||
## Authentication Flow
|
||||
|
||||
1. Add trusted signaling server URLs in desktop settings.
|
||||
2. Start the Local API server.
|
||||
3. Call `POST /api/auth/login` with `username`, `password`, and `serverUrl`.
|
||||
4. MetoYou validates credentials through the signaling server.
|
||||
5. The desktop app issues an opaque local bearer token.
|
||||
6. Use `Authorization: Bearer <token>` for protected routes.
|
||||
|
||||
Bearer tokens are local to the running desktop app and are cleared when the Local API server stops.
|
||||
|
||||
## Static Documentation Build
|
||||
|
||||
Docusaurus is a static site generator. The repo builds `docs-site/` into `docs-site/build/`, and Electron serves those files from the local API server.
|
||||
|
||||
Development commands:
|
||||
|
||||
```bash
|
||||
cd docs-site
|
||||
npm install
|
||||
npm run start
|
||||
```
|
||||
|
||||
Build command:
|
||||
|
||||
```bash
|
||||
npm run build:docs
|
||||
```
|
||||
|
||||
Packaged desktop builds include the generated static output as an Electron extra resource.
|
||||
40
docs-site/docs/intro.md
Normal file
40
docs-site/docs/intro.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
slug: /
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# MetoYou Documentation
|
||||
|
||||
MetoYou is a desktop-first chat stack with a peer-to-peer product client, an Electron desktop shell, a Node signaling server, local persistence, realtime voice/video, a local automation API, and a client plugin runtime.
|
||||
|
||||
The product experience is intentionally close to a modern team chat application:
|
||||
|
||||
- servers organize communities or workspaces;
|
||||
- text channels keep room conversations scoped and searchable;
|
||||
- direct messages stay separate from server context;
|
||||
- voice, video, and screen sharing use WebRTC peer connections;
|
||||
- invite links help members join the current server;
|
||||
- local desktop settings control data, updates, plugins, and automation access.
|
||||
|
||||
The Electron app also hosts this documentation. The docs endpoint is not a separate web server process: it is served from the same opt-in local HTTP server used for the Local API, and it only serves static files generated by Docusaurus.
|
||||
|
||||
## What Is Included
|
||||
|
||||
| Area | What it covers |
|
||||
| --- | --- |
|
||||
| Product client | Login, server discovery, channels, messages, voice, direct messages, themes, and plugin UI. |
|
||||
| Desktop shell | Window controls, notifications, tray behavior, app data import/export, updates, local plugins, and hosted documentation. |
|
||||
| Local HTTP API | A loopback-first API for local scripts and tools, with OpenAPI and Scalar reference docs. |
|
||||
| Plugin runtime | Browser-safe client plugins with explicit capabilities, lifecycle hooks, UI contributions, data storage, message bus, and server plugin requirements. |
|
||||
|
||||
## Runtime Boundaries
|
||||
|
||||
MetoYou keeps responsibilities split by package:
|
||||
|
||||
- `toju-app/` is the Angular product client and plugin runtime.
|
||||
- `electron/` is the main process, preload bridge, IPC, local persistence, and local HTTP host.
|
||||
- `server/` is the signaling and server-directory service.
|
||||
- `e2e/` contains Playwright coverage for browser and WebRTC workflows.
|
||||
- `docs-site/` is this Docusaurus site.
|
||||
|
||||
The desktop documentation endpoint serves the static `docs-site/build` output. It does not run the Docusaurus development server inside Electron.
|
||||
301
docs-site/docs/plugin-development/api-reference.md
Normal file
301
docs-site/docs/plugin-development/api-reference.md
Normal file
@@ -0,0 +1,301 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Plugin API Reference
|
||||
|
||||
`TojuClientPluginApi` is the object passed to a plugin activation context. The runtime freezes the API object before passing it to plugin code.
|
||||
|
||||
## Activation Types
|
||||
|
||||
```ts
|
||||
interface TojuPluginDisposable {
|
||||
dispose: () => void;
|
||||
}
|
||||
|
||||
interface TojuPluginActivationContext {
|
||||
api: TojuClientPluginApi;
|
||||
manifest: TojuPluginManifest;
|
||||
pluginId: string;
|
||||
subscriptions: TojuPluginDisposable[];
|
||||
}
|
||||
|
||||
interface TojuClientPluginModule {
|
||||
activate?: (context: TojuPluginActivationContext) => Promise<void> | void;
|
||||
deactivate?: (context: TojuPluginActivationContext) => Promise<void> | void;
|
||||
onPluginDataChanged?: (context: TojuPluginActivationContext, event: unknown) => Promise<void> | void;
|
||||
onServerRequirementsChanged?: (context: TojuPluginActivationContext, snapshot: PluginRequirementsSnapshot) => Promise<void> | void;
|
||||
ready?: (context: TojuPluginActivationContext) => Promise<void> | void;
|
||||
}
|
||||
```
|
||||
|
||||
## Profiles
|
||||
|
||||
```ts
|
||||
interface PluginApiProfileUpdate {
|
||||
description?: string;
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
interface PluginApiAvatarUpdate {
|
||||
avatarHash: string;
|
||||
avatarMime: string;
|
||||
avatarUrl: string;
|
||||
}
|
||||
```
|
||||
|
||||
| Method | Capability | Description |
|
||||
| --- | --- | --- |
|
||||
| `profile.getCurrent()` | `profile.read` | Returns the current `User` or `null`. |
|
||||
| `profile.update(profile)` | `profile.write` | Updates display name and optional description. |
|
||||
| `profile.updateAvatar(avatar)` | `profile.write` | Updates avatar URL, MIME type, and hash metadata. |
|
||||
|
||||
## Users and Roles
|
||||
|
||||
| Method | Capability | Description |
|
||||
| --- | --- | --- |
|
||||
| `users.getCurrent()` | `users.read` | Returns current `User` or `null`. |
|
||||
| `users.list()` | `users.read` | Returns known users. |
|
||||
| `users.readMembers()` | `users.read` | Returns active room members. |
|
||||
| `users.setRole(userId, role)` | `roles.manage` | Updates a user's role. |
|
||||
| `users.kick(userId)` | `users.manage` | Kicks a user. |
|
||||
| `users.ban(userId, reason?)` | `users.manage` | Bans a user with optional reason. |
|
||||
| `roles.list()` | `roles.read` | Returns room roles. |
|
||||
| `roles.setAssignments(assignments)` | `roles.manage` | Replaces role assignments. |
|
||||
|
||||
## Server
|
||||
|
||||
```ts
|
||||
interface PluginApiServerSettingsUpdate {
|
||||
description?: string;
|
||||
isPrivate?: boolean;
|
||||
maxUsers?: number;
|
||||
name?: string;
|
||||
password?: string;
|
||||
topic?: string;
|
||||
}
|
||||
|
||||
interface PluginApiPluginUserRequest {
|
||||
avatarUrl?: string;
|
||||
displayName: string;
|
||||
id?: string;
|
||||
}
|
||||
```
|
||||
|
||||
| Method | Capability | Description |
|
||||
| --- | --- | --- |
|
||||
| `server.getCurrent()` | `server.read` | Returns the current `Room` or `null`. |
|
||||
| `server.registerPluginUser(request)` | `users.manage` | Adds a plugin-owned user and returns its id. |
|
||||
| `server.updatePermissions(permissions)` | `server.manage` | Updates partial room permissions. |
|
||||
| `server.updateSettings(settings)` | `server.manage` | Updates room settings. |
|
||||
|
||||
## Channels
|
||||
|
||||
```ts
|
||||
interface PluginApiChannelRequest {
|
||||
id?: string;
|
||||
name: string;
|
||||
position?: number;
|
||||
}
|
||||
```
|
||||
|
||||
| Method | Capability | Description |
|
||||
| --- | --- | --- |
|
||||
| `channels.list()` | `channels.read` | Returns current room channels. |
|
||||
| `channels.select(channelId)` | `channels.read` | Selects a channel. |
|
||||
| `channels.addAudioChannel(request)` | `channels.manage` | Adds a voice channel. |
|
||||
| `channels.addVideoChannel(request)` | `channels.manage` | Registers a video channel section. |
|
||||
| `channels.rename(channelId, name)` | `channels.manage` | Renames a channel. |
|
||||
| `channels.remove(channelId)` | `channels.manage` | Removes a channel. |
|
||||
|
||||
## Messages
|
||||
|
||||
```ts
|
||||
interface PluginApiMessageAsPluginUserRequest {
|
||||
channelId?: string;
|
||||
content: string;
|
||||
pluginUserId: string;
|
||||
}
|
||||
```
|
||||
|
||||
| Method | Capability | Description |
|
||||
| --- | --- | --- |
|
||||
| `messages.readCurrent()` | `messages.read` | Returns current visible messages. |
|
||||
| `messages.send(content, channelId?)` | `messages.send` | Sends a message and returns the created `Message`. |
|
||||
| `messages.sendAsPluginUser(request)` | `messages.send` | Emits a message from a registered plugin user. |
|
||||
| `messages.edit(messageId, content)` | `messages.editOwn` | Edits a plugin message. |
|
||||
| `messages.delete(messageId)` | `messages.deleteOwn` | Deletes a plugin message. |
|
||||
| `messages.moderateDelete(messageId)` | `messages.moderate` | Performs a moderation delete. |
|
||||
| `messages.sync(messages)` | `messages.sync` | Syncs an array of messages into state. |
|
||||
|
||||
## Events
|
||||
|
||||
```ts
|
||||
interface PluginApiEventSubscription {
|
||||
eventName: string;
|
||||
handler: (event: PluginEventEnvelope) => void;
|
||||
}
|
||||
|
||||
interface PluginEventEnvelope<TPayload = unknown> {
|
||||
emittedAt?: number;
|
||||
eventId?: string;
|
||||
eventName: string;
|
||||
payload: TPayload;
|
||||
pluginId: string;
|
||||
serverId: string;
|
||||
sourcePluginUserId?: string;
|
||||
sourceUserId?: string;
|
||||
type: 'plugin_event';
|
||||
}
|
||||
```
|
||||
|
||||
| Method | Capability | Description |
|
||||
| --- | --- | --- |
|
||||
| `events.publishServer(eventName, payload)` | `events.server.publish` | Sends a declared plugin event through the signaling server. |
|
||||
| `events.subscribeServer(subscription)` | `events.server.subscribe` | Subscribes to a declared server plugin event. |
|
||||
| `events.publishP2p(eventName, payload)` | `events.p2p.publish` | Sends a declared plugin event over peer paths. |
|
||||
| `events.subscribeP2p(subscription)` | `events.p2p.subscribe` | Registers a P2P event subscription. |
|
||||
|
||||
## Message Bus
|
||||
|
||||
```ts
|
||||
interface PluginApiMessageBusEnvelope {
|
||||
channelId?: string;
|
||||
eventId: string;
|
||||
messages?: Message[];
|
||||
payload?: unknown;
|
||||
pluginId: string;
|
||||
roomId: string;
|
||||
sentAt: number;
|
||||
sourcePeerId?: string;
|
||||
sourceUserId?: string;
|
||||
topic: string;
|
||||
}
|
||||
|
||||
interface PluginApiMessageBusLatestRequest {
|
||||
channelId?: string;
|
||||
includeDeleted?: boolean;
|
||||
limit?: number;
|
||||
sinceTimestamp?: number;
|
||||
targetPeerId?: string;
|
||||
topic?: string;
|
||||
}
|
||||
|
||||
interface PluginApiMessageBusPublishRequest extends PluginApiMessageBusLatestRequest {
|
||||
includeLatestMessages?: boolean;
|
||||
includeSelf?: boolean;
|
||||
payload?: unknown;
|
||||
topic: string;
|
||||
}
|
||||
|
||||
interface PluginApiMessageBusSubscription {
|
||||
channelId?: string;
|
||||
handler: (event: PluginApiMessageBusEnvelope) => void;
|
||||
latestMessageLimit?: number;
|
||||
replayLatest?: boolean;
|
||||
topic?: string;
|
||||
}
|
||||
```
|
||||
|
||||
| Method | Capability | Description |
|
||||
| --- | --- | --- |
|
||||
| `messageBus.publish(request)` | `events.p2p.publish`, optionally `messages.read` | Publishes a plugin-bus event, optionally including latest messages. |
|
||||
| `messageBus.sendLatestMessages(request?)` | `events.p2p.publish` and `messages.read` | Sends a latest-message snapshot. |
|
||||
| `messageBus.subscribe(subscription)` | `events.p2p.subscribe`, optionally `messages.read` | Subscribes to plugin-bus events, optionally replaying latest messages. |
|
||||
|
||||
## P2P and Media
|
||||
|
||||
```ts
|
||||
interface PluginApiAudioClipRequest {
|
||||
volume?: number;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface PluginApiCustomStreamRequest {
|
||||
label?: string;
|
||||
stream: MediaStream;
|
||||
}
|
||||
```
|
||||
|
||||
| Method | Capability | Description |
|
||||
| --- | --- | --- |
|
||||
| `p2p.connectedPeers()` | `p2p.data` | Returns connected peer ids. |
|
||||
| `p2p.broadcastData(eventName, payload)` | `p2p.data` | Broadcasts plugin data. |
|
||||
| `p2p.sendData(peerId, eventName, payload)` | `p2p.data` | Sends plugin data targeted to a peer. |
|
||||
| `media.playAudioClip(request)` | `media.playAudio` | Plays an audio URL at optional volume. |
|
||||
| `media.addCustomAudioStream(request)` | `media.addAudioStream` | Contributes an audio `MediaStream`. |
|
||||
| `media.addCustomVideoStream(request)` | `media.addVideoStream` | Registers a video `MediaStream` contribution. |
|
||||
| `media.setInputVolume(volume)` | `audio.volume` | Sets local input volume. |
|
||||
| `media.setOutputVolume(volume)` | `audio.volume` | Sets local output volume. |
|
||||
|
||||
## Storage
|
||||
|
||||
| Method | Capability | Description |
|
||||
| --- | --- | --- |
|
||||
| `clientData.read(key)` | `storage.local` | Reads async plugin-local data. |
|
||||
| `clientData.write(key, value)` | `storage.local` | Writes async plugin-local data. |
|
||||
| `clientData.remove(key)` | `storage.local` | Removes async plugin-local data. |
|
||||
| `serverData.read(key)` | `storage.serverData.read` | Reads local per-user/per-server data. |
|
||||
| `serverData.write(key, value)` | `storage.serverData.write` | Writes local per-user/per-server data. |
|
||||
| `serverData.remove(key)` | `storage.serverData.write` | Removes local per-user/per-server data. |
|
||||
| `storage.get(key)` | `storage.local` | Legacy synchronous local read. |
|
||||
| `storage.set(key, value)` | `storage.local` | Legacy synchronous local write. |
|
||||
| `storage.remove(key)` | `storage.local` | Legacy synchronous local remove. |
|
||||
|
||||
## UI Contributions
|
||||
|
||||
```ts
|
||||
interface PluginApiActionContribution {
|
||||
icon?: string;
|
||||
label: string;
|
||||
run: () => Promise<void> | void;
|
||||
}
|
||||
|
||||
interface PluginApiPageContribution {
|
||||
label: string;
|
||||
path: string;
|
||||
render: () => HTMLElement | string;
|
||||
}
|
||||
|
||||
interface PluginApiPanelContribution {
|
||||
label: string;
|
||||
order?: number;
|
||||
render: () => HTMLElement | string;
|
||||
}
|
||||
|
||||
interface PluginApiSettingsPageContribution {
|
||||
label: string;
|
||||
order?: number;
|
||||
render: () => HTMLElement | string;
|
||||
settingsKey?: string;
|
||||
}
|
||||
|
||||
interface PluginApiChannelSectionContribution {
|
||||
label: string;
|
||||
order?: number;
|
||||
type?: 'audio' | 'custom' | 'video';
|
||||
}
|
||||
|
||||
interface PluginApiEmbedRendererContribution {
|
||||
embedType: string;
|
||||
render: (payload: unknown) => HTMLElement | string;
|
||||
}
|
||||
|
||||
interface PluginApiDomMountRequest {
|
||||
element: HTMLElement;
|
||||
position?: InsertPosition;
|
||||
target: Element | string;
|
||||
}
|
||||
```
|
||||
|
||||
| Method | Capability | Description |
|
||||
| --- | --- | --- |
|
||||
| `ui.registerAppPage(id, contribution)` | `ui.pages` | Adds a plugin app page. |
|
||||
| `ui.registerSettingsPage(id, contribution)` | `ui.settings` | Adds a plugin settings page. |
|
||||
| `ui.registerSidePanel(id, contribution)` | `ui.sidePanel` | Adds a side panel. |
|
||||
| `ui.registerChannelSection(id, contribution)` | `ui.channelsSection` | Adds a channel section. |
|
||||
| `ui.registerComposerAction(id, contribution)` | `ui.pages` | Adds a composer action. |
|
||||
| `ui.registerProfileAction(id, contribution)` | `ui.pages` | Adds a profile action. |
|
||||
| `ui.registerToolbarAction(id, contribution)` | `ui.pages` | Adds a toolbar action. |
|
||||
| `ui.registerEmbedRenderer(id, contribution)` | `ui.embeds` | Adds an embed renderer. |
|
||||
| `ui.mountElement(id, request)` | `ui.dom` | Mounts plugin-owned DOM into a target element or selector. |
|
||||
50
docs-site/docs/plugin-development/capabilities.md
Normal file
50
docs-site/docs/plugin-development/capabilities.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Capabilities
|
||||
|
||||
Capabilities protect privileged app surfaces. A plugin must declare a capability in its manifest and the user must grant it before the runtime allows the corresponding API call.
|
||||
|
||||
| Capability | API areas | Notes |
|
||||
| --- | --- | --- |
|
||||
| `profile.read` | `profile.getCurrent()` | Reads the current user. |
|
||||
| `profile.write` | `profile.update()`, `profile.updateAvatar()` | Updates local profile fields and avatar metadata. |
|
||||
| `users.read` | `users.getCurrent()`, `users.list()`, `users.readMembers()` | Reads users and server members. |
|
||||
| `users.manage` | `users.kick()`, `users.ban()`, `server.registerPluginUser()` | Can create plugin users and moderate members. |
|
||||
| `roles.read` | `roles.list()` | Reads server roles. |
|
||||
| `roles.manage` | `roles.setAssignments()`, `users.setRole()` | Changes role assignments or user roles. |
|
||||
| `messages.read` | `messages.readCurrent()`, message bus latest snapshots | Reads current channel messages. |
|
||||
| `messages.send` | `messages.send()`, `messages.sendAsPluginUser()` | Sends messages as the current user or registered plugin user. |
|
||||
| `messages.editOwn` | `messages.edit()` | Edits plugin-owned messages. |
|
||||
| `messages.deleteOwn` | `messages.delete()` | Deletes plugin-owned messages. |
|
||||
| `messages.moderate` | `messages.moderateDelete()` | Moderation delete path. |
|
||||
| `messages.sync` | `messages.sync()` | Syncs message arrays into client state. |
|
||||
| `channels.read` | `channels.list()`, `channels.select()` | Reads and selects channels. |
|
||||
| `channels.manage` | `channels.addAudioChannel()`, `channels.addVideoChannel()`, `channels.remove()`, `channels.rename()` | Mutates channel or channel-section state. |
|
||||
| `server.read` | `server.getCurrent()` | Reads active server. |
|
||||
| `server.manage` | `server.updatePermissions()`, `server.updateSettings()` | Updates server permissions or settings. |
|
||||
| `p2p.data` | `p2p.connectedPeers()`, `p2p.broadcastData()`, `p2p.sendData()` | Uses plugin peer data paths. |
|
||||
| `p2p.media` | Reserved peer media features. | Included for media-facing plugins. |
|
||||
| `media.playAudio` | `media.playAudioClip()` | Plays an audio URL locally. |
|
||||
| `media.addAudioStream` | `media.addCustomAudioStream()` | Adds a custom stream to voice handling. |
|
||||
| `media.addVideoStream` | `media.addCustomVideoStream()` | Registers custom video stream contribution. |
|
||||
| `audio.volume` | `media.setInputVolume()`, `media.setOutputVolume()` | Adjusts local voice volume. |
|
||||
| `audio.effects` | Reserved audio effect features. | Included for audio processing plugins. |
|
||||
| `ui.settings` | `ui.registerSettingsPage()` | Adds settings pages. |
|
||||
| `ui.pages` | `ui.registerAppPage()`, `ui.registerComposerAction()`, `ui.registerProfileAction()`, `ui.registerToolbarAction()` | Adds app pages and actions. |
|
||||
| `ui.sidePanel` | `ui.registerSidePanel()` | Adds side panels. |
|
||||
| `ui.channelsSection` | `ui.registerChannelSection()` | Adds channel sections. |
|
||||
| `ui.embeds` | `ui.registerEmbedRenderer()` | Renders custom embeds. |
|
||||
| `ui.dom` | `ui.mountElement()` | Mounts plugin-owned DOM into app targets. |
|
||||
| `storage.local` | `storage.*`, `clientData.*` | Reads and writes plugin-local data. |
|
||||
| `storage.serverData.read` | `serverData.read()` | Reads local per-user/per-server plugin data. |
|
||||
| `storage.serverData.write` | `serverData.write()`, `serverData.remove()` | Writes or removes local per-user/per-server plugin data. |
|
||||
| `events.server.publish` | `events.publishServer()` | Publishes declared server plugin events. |
|
||||
| `events.server.subscribe` | `events.subscribeServer()` | Subscribes to declared server plugin events. |
|
||||
| `events.p2p.publish` | `events.publishP2p()`, `messageBus.publish()`, `messageBus.sendLatestMessages()` | Publishes declared P2P/plugin bus events. |
|
||||
| `events.p2p.subscribe` | `events.subscribeP2p()`, `messageBus.subscribe()` | Subscribes to declared P2P/plugin bus events. |
|
||||
|
||||
## Recommended Practice
|
||||
|
||||
Request the fewest capabilities possible. Separate broad features into optional plugin modules when a single plugin would otherwise need many unrelated grants.
|
||||
106
docs-site/docs/plugin-development/create-a-plugin.md
Normal file
106
docs-site/docs/plugin-development/create-a-plugin.md
Normal file
@@ -0,0 +1,106 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Create a Plugin
|
||||
|
||||
MetoYou plugins are browser-safe ES modules loaded by the Angular renderer. A plugin receives a frozen `TojuClientPluginApi`, declares every privileged capability in its manifest, and registers cleanup work through disposables.
|
||||
|
||||
## Folder Layout
|
||||
|
||||
A local desktop plugin is discovered from an immediate child folder under the app data `plugins` directory.
|
||||
|
||||
```text
|
||||
my-plugin/
|
||||
toju-plugin.json
|
||||
main.js
|
||||
README.md
|
||||
icon.svg
|
||||
```
|
||||
|
||||
The manifest file can be named `toju-plugin.json` or `plugin.json`. Entrypoints and readmes must stay inside the plugin folder.
|
||||
|
||||
## Minimal Manifest
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "example.hello-world",
|
||||
"title": "Hello World",
|
||||
"description": "Adds a toolbar action that sends a message.",
|
||||
"version": "1.0.0",
|
||||
"kind": "client",
|
||||
"scope": "client",
|
||||
"apiVersion": "1.0.0",
|
||||
"compatibility": {
|
||||
"minimumTojuVersion": "1.0.0"
|
||||
},
|
||||
"entrypoint": "./main.js",
|
||||
"capabilities": ["messages.send", "ui.pages"]
|
||||
}
|
||||
```
|
||||
|
||||
## Entrypoint
|
||||
|
||||
```js
|
||||
export function activate(context) {
|
||||
const { api } = context;
|
||||
|
||||
api.logger.info('Hello World activated');
|
||||
|
||||
const disposable = api.ui.registerToolbarAction('hello', {
|
||||
label: 'Hello',
|
||||
run: () => api.messages.send('Hello from my plugin')
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
export function ready(context) {
|
||||
context.api.logger.info('All ready plugins have loaded');
|
||||
}
|
||||
|
||||
export function deactivate(context) {
|
||||
context.api.logger.info('Hello World deactivated');
|
||||
}
|
||||
```
|
||||
|
||||
## Lifecycle Hooks
|
||||
|
||||
| Hook | When it runs | Use it for |
|
||||
| --- | --- | --- |
|
||||
| `activate(context)` | During explicit plugin activation. | Register UI, subscribe to events, initialize state. |
|
||||
| `ready(context)` | After the load-order pass has activated ready plugins. | Cross-plugin coordination that needs other plugins loaded. |
|
||||
| `deactivate(context)` | During unload or reload. | Flush state and log shutdown. Disposables are also cleaned up by the host. |
|
||||
| `onPluginDataChanged(context, event)` | When plugin data changes are observed. | React to plugin-scoped persistence changes. |
|
||||
| `onServerRequirementsChanged(context, snapshot)` | When server plugin requirements change. | Adapt to required, optional, blocked, or incompatible server plugins. |
|
||||
|
||||
## Cleanup
|
||||
|
||||
Every API registration returns a disposable. Push it into `context.subscriptions`.
|
||||
|
||||
```js
|
||||
const subscription = api.messageBus.subscribe({
|
||||
topic: 'poll:votes',
|
||||
handler: (event) => api.logger.info('vote received', event.payload)
|
||||
});
|
||||
|
||||
context.subscriptions.push(subscription);
|
||||
```
|
||||
|
||||
The plugin host disposes subscriptions in reverse order when the plugin unloads.
|
||||
|
||||
## Capability Grants
|
||||
|
||||
A plugin can only call privileged APIs after the matching capability is declared in the manifest and granted by the user. Keep the manifest narrow. For example, a plugin that only adds a settings page does not need message or user management capabilities.
|
||||
|
||||
## Testing Locally
|
||||
|
||||
1. Create the plugin folder in the desktop plugins directory.
|
||||
2. Open the Plugin Manager.
|
||||
3. Register or refresh local plugins.
|
||||
4. Grant required capabilities.
|
||||
5. Activate the plugin.
|
||||
6. Inspect plugin logs in the manager.
|
||||
|
||||
For broad API examples, compare against the E2E fixture plugin under `toju-app/public/plugins/e2e-all-api/`.
|
||||
204
docs-site/docs/plugin-development/examples.md
Normal file
204
docs-site/docs/plugin-development/examples.md
Normal file
@@ -0,0 +1,204 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Examples
|
||||
|
||||
## Toolbar Message Plugin
|
||||
|
||||
`toju-plugin.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "example.toolbar-message",
|
||||
"title": "Toolbar Message",
|
||||
"description": "Adds a toolbar action that sends a reusable message.",
|
||||
"version": "1.0.0",
|
||||
"kind": "client",
|
||||
"scope": "client",
|
||||
"apiVersion": "1.0.0",
|
||||
"compatibility": {
|
||||
"minimumTojuVersion": "1.0.0",
|
||||
"verifiedTojuVersion": "1.0.0"
|
||||
},
|
||||
"entrypoint": "./main.js",
|
||||
"capabilities": ["messages.send", "ui.pages"]
|
||||
}
|
||||
```
|
||||
|
||||
`main.js`
|
||||
|
||||
```js
|
||||
export function activate(context) {
|
||||
const { api } = context;
|
||||
|
||||
context.subscriptions.push(api.ui.registerToolbarAction('standup-message', {
|
||||
label: 'Standup',
|
||||
run: () => api.messages.send('Standup: yesterday, today, blocked')
|
||||
}));
|
||||
}
|
||||
```
|
||||
|
||||
## Settings Page Plugin
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "example.settings-page",
|
||||
"title": "Settings Page Example",
|
||||
"description": "Adds a plugin settings page and stores a local preference.",
|
||||
"version": "1.0.0",
|
||||
"kind": "client",
|
||||
"apiVersion": "1.0.0",
|
||||
"compatibility": { "minimumTojuVersion": "1.0.0" },
|
||||
"entrypoint": "./main.js",
|
||||
"capabilities": ["ui.settings", "storage.local"],
|
||||
"settings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean", "default": true }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
export function activate(context) {
|
||||
const { api } = context;
|
||||
|
||||
context.subscriptions.push(api.ui.registerSettingsPage('preferences', {
|
||||
label: 'Example Preferences',
|
||||
render: () => {
|
||||
const root = document.createElement('section');
|
||||
const button = document.createElement('button');
|
||||
|
||||
button.type = 'button';
|
||||
button.textContent = 'Remember preference';
|
||||
button.onclick = () => api.storage.set('enabled', true);
|
||||
root.append(button);
|
||||
return root;
|
||||
}
|
||||
}));
|
||||
}
|
||||
```
|
||||
|
||||
## Server-Scoped Soundboard
|
||||
|
||||
A server-scoped plugin can be installed as a server requirement and auto-installed for server members when marked required.
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "example.soundboard",
|
||||
"title": "Server Soundboard",
|
||||
"description": "Adds a soundboard side panel and announces played sounds.",
|
||||
"version": "1.0.0",
|
||||
"kind": "client",
|
||||
"scope": "server",
|
||||
"apiVersion": "1.0.0",
|
||||
"compatibility": { "minimumTojuVersion": "1.0.0" },
|
||||
"entrypoint": "./main.js",
|
||||
"capabilities": [
|
||||
"server.read",
|
||||
"users.manage",
|
||||
"ui.sidePanel",
|
||||
"media.playAudio",
|
||||
"messages.send"
|
||||
],
|
||||
"pluginUser": {
|
||||
"displayName": "Soundboard",
|
||||
"label": "Audio helper"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
export function activate(context) {
|
||||
const { api } = context;
|
||||
const botId = api.server.registerPluginUser({
|
||||
id: 'soundboard-bot',
|
||||
displayName: 'Soundboard'
|
||||
});
|
||||
|
||||
context.subscriptions.push(api.ui.registerSidePanel('sounds', {
|
||||
label: 'Soundboard',
|
||||
render: () => {
|
||||
const panel = document.createElement('div');
|
||||
const button = document.createElement('button');
|
||||
|
||||
button.type = 'button';
|
||||
button.textContent = 'Play chime';
|
||||
button.onclick = async () => {
|
||||
await api.media.playAudioClip({ url: './chime.wav', volume: 0.7 });
|
||||
api.messages.sendAsPluginUser({ pluginUserId: botId, content: 'Played chime' });
|
||||
};
|
||||
|
||||
panel.append(button);
|
||||
return panel;
|
||||
}
|
||||
}));
|
||||
}
|
||||
```
|
||||
|
||||
## Message Bus Plugin
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "example.poll-bus",
|
||||
"title": "Poll Bus",
|
||||
"description": "Uses the plugin message bus for lightweight P2P poll votes.",
|
||||
"version": "1.0.0",
|
||||
"kind": "client",
|
||||
"apiVersion": "1.0.0",
|
||||
"compatibility": { "minimumTojuVersion": "1.0.0" },
|
||||
"entrypoint": "./main.js",
|
||||
"capabilities": ["events.p2p.publish", "events.p2p.subscribe", "messages.read"]
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
export function activate(context) {
|
||||
const { api } = context;
|
||||
|
||||
context.subscriptions.push(api.messageBus.subscribe({
|
||||
topic: 'poll:votes',
|
||||
replayLatest: true,
|
||||
latestMessageLimit: 20,
|
||||
handler: (event) => api.logger.info('Vote received', event.payload)
|
||||
}));
|
||||
|
||||
api.messageBus.publish({
|
||||
topic: 'poll:votes',
|
||||
payload: { option: 'A' },
|
||||
includeLatestMessages: true,
|
||||
includeSelf: true,
|
||||
latestMessageLimit: 20
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Custom DOM Mount
|
||||
|
||||
Use `ui.dom` sparingly and cleanly. The runtime tags mounted elements with plugin ownership metadata and removes remaining mounted elements when the plugin unloads.
|
||||
|
||||
```js
|
||||
export function activate(context) {
|
||||
const badge = document.createElement('div');
|
||||
|
||||
badge.textContent = 'Plugin active';
|
||||
badge.style.position = 'absolute';
|
||||
badge.style.right = '1rem';
|
||||
badge.style.bottom = '1rem';
|
||||
|
||||
context.subscriptions.push(context.api.ui.mountElement('active-badge', {
|
||||
target: 'body',
|
||||
element: badge
|
||||
}));
|
||||
}
|
||||
```
|
||||
|
||||
## All-API Fixture
|
||||
|
||||
The repo includes an E2E fixture at `toju-app/public/plugins/e2e-all-api/`. It intentionally calls every public plugin API surface so Playwright coverage can validate the runtime. Use it as a compatibility reference, not as the minimal style for production plugins.
|
||||
162
docs-site/docs/plugin-development/manifest.md
Normal file
162
docs-site/docs/plugin-development/manifest.md
Normal file
@@ -0,0 +1,162 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Manifest Model
|
||||
|
||||
The manifest is the source of truth for plugin identity, compatibility, runtime shape, capabilities, data, events, UI hints, and distribution metadata.
|
||||
|
||||
```ts
|
||||
type TojuPluginInstallScope = 'client' | 'server';
|
||||
type PluginEventDirection = 'clientToServer' | 'serverRelay' | 'p2pHint';
|
||||
type PluginEventScope = 'server' | 'channel' | 'user' | 'plugin';
|
||||
|
||||
type PluginCapabilityId =
|
||||
| 'profile.read'
|
||||
| 'profile.write'
|
||||
| 'users.read'
|
||||
| 'users.manage'
|
||||
| 'roles.read'
|
||||
| 'roles.manage'
|
||||
| 'messages.read'
|
||||
| 'messages.send'
|
||||
| 'messages.editOwn'
|
||||
| 'messages.deleteOwn'
|
||||
| 'messages.moderate'
|
||||
| 'messages.sync'
|
||||
| 'channels.read'
|
||||
| 'channels.manage'
|
||||
| 'server.read'
|
||||
| 'server.manage'
|
||||
| 'p2p.data'
|
||||
| 'p2p.media'
|
||||
| 'media.playAudio'
|
||||
| 'media.addAudioStream'
|
||||
| 'media.addVideoStream'
|
||||
| 'audio.volume'
|
||||
| 'audio.effects'
|
||||
| 'ui.settings'
|
||||
| 'ui.pages'
|
||||
| 'ui.sidePanel'
|
||||
| 'ui.channelsSection'
|
||||
| 'ui.embeds'
|
||||
| 'ui.dom'
|
||||
| 'storage.local'
|
||||
| 'storage.serverData.read'
|
||||
| 'storage.serverData.write'
|
||||
| 'events.server.publish'
|
||||
| 'events.server.subscribe'
|
||||
| 'events.p2p.publish'
|
||||
| 'events.p2p.subscribe';
|
||||
|
||||
interface TojuPluginManifest {
|
||||
schemaVersion: 1;
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
version: string;
|
||||
kind: 'client' | 'library';
|
||||
scope?: TojuPluginInstallScope;
|
||||
apiVersion: string;
|
||||
compatibility: {
|
||||
minimumTojuVersion: string;
|
||||
maximumTojuVersion?: string;
|
||||
verifiedTojuVersion?: string;
|
||||
};
|
||||
entrypoint?: string;
|
||||
bundle?: {
|
||||
url: string;
|
||||
entrypoint?: string;
|
||||
};
|
||||
readme?: string;
|
||||
homepage?: string;
|
||||
bugs?: string;
|
||||
changelog?: string;
|
||||
license?: string;
|
||||
authors?: {
|
||||
name: string;
|
||||
email?: string;
|
||||
url?: string;
|
||||
}[];
|
||||
capabilities?: PluginCapabilityId[];
|
||||
events?: {
|
||||
eventName: string;
|
||||
direction: PluginEventDirection;
|
||||
scope: PluginEventScope;
|
||||
maxPayloadBytes?: number;
|
||||
schema?: string;
|
||||
}[];
|
||||
data?: {
|
||||
key: string;
|
||||
schema?: string;
|
||||
scope: string;
|
||||
storage: 'local' | 'serverData';
|
||||
}[];
|
||||
relationships?: {
|
||||
after?: string[];
|
||||
before?: string[];
|
||||
conflicts?: string[];
|
||||
optional?: { id: string; versionRange?: string }[];
|
||||
requires?: { id: string; versionRange?: string }[];
|
||||
};
|
||||
load?: {
|
||||
priority?: 'bootstrap' | 'high' | 'default' | 'low';
|
||||
};
|
||||
pluginUser?: {
|
||||
avatar?: string;
|
||||
displayName: string;
|
||||
label?: string;
|
||||
};
|
||||
settings?: Record<string, unknown>;
|
||||
ui?: Record<string, unknown>;
|
||||
}
|
||||
```
|
||||
|
||||
## Required Fields
|
||||
|
||||
| Field | Meaning |
|
||||
| --- | --- |
|
||||
| `schemaVersion` | Manifest schema version. Currently `1`. |
|
||||
| `id` | Stable plugin id. Use a reverse-DNS or package-style id. |
|
||||
| `title` | Human-readable plugin name. |
|
||||
| `description` | Short explanation shown in plugin UI. |
|
||||
| `version` | Plugin version. |
|
||||
| `kind` | `client` for runtime plugins, `library` for shared dependency-style entries. |
|
||||
| `apiVersion` | Plugin API version expected by the plugin. |
|
||||
| `compatibility.minimumTojuVersion` | Oldest app version the plugin supports. |
|
||||
|
||||
## Scope
|
||||
|
||||
`scope: "client"` installs the plugin for the current client. Omit `scope` for the same behavior.
|
||||
|
||||
`scope: "server"` marks a plugin as server-scoped. Server-scoped store entries can be installed to a chat server as requirements. Required server plugins are auto-installed for members when that server opens; optional requirements stay listed but do not auto-install.
|
||||
|
||||
## Entrypoint and Bundle
|
||||
|
||||
Use `entrypoint` for a browser-resolvable module relative to the manifest. Use `bundle.url` when publishing a cached browser bundle through a plugin source manifest. Desktop installs cache bundle files into app data and load the cached manifest afterward.
|
||||
|
||||
## Events
|
||||
|
||||
Every server or P2P plugin event should be declared before it is published or subscribed to.
|
||||
|
||||
```json
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"eventName": "poll:vote",
|
||||
"direction": "p2pHint",
|
||||
"scope": "channel",
|
||||
"maxPayloadBytes": 2048
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Data Declarations
|
||||
|
||||
Use `data` to document plugin-owned data keys and intended storage.
|
||||
|
||||
- `local` maps to client-local plugin data.
|
||||
- `serverData` maps to local per-user/per-server plugin data.
|
||||
|
||||
Signal server HTTP persistence for arbitrary plugin data is disabled by design.
|
||||
56
docs-site/docs/using-metoyou.md
Normal file
56
docs-site/docs/using-metoyou.md
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Using MetoYou
|
||||
|
||||
## Sign In
|
||||
|
||||
MetoYou signs in through a signaling server. The signaling server validates the user account, coordinates server membership, relays selected realtime messages, and helps peers establish WebRTC connections.
|
||||
|
||||
For the desktop Local API, the same signaling server allow-list is used before local bearer tokens can be issued. This keeps local automation tied to servers you explicitly trust.
|
||||
|
||||
## Find or Join Servers
|
||||
|
||||
Use the server search flow to find known servers. A server can be public, private, or password-protected depending on its settings. Invite links can be created from the title bar menu while a server is active.
|
||||
|
||||
A server contains:
|
||||
|
||||
- basic profile information such as name, topic, description, privacy, and maximum users;
|
||||
- text channels;
|
||||
- voice or custom channel sections;
|
||||
- roles and permissions;
|
||||
- members and voice state;
|
||||
- optional server-scoped plugin requirements.
|
||||
|
||||
## Text Channels and Messages
|
||||
|
||||
Text channels are selected inside the active server. Messages are persisted locally by the client and synchronized through realtime events while connected. Plugins with the relevant capabilities can read, send, edit, delete, moderate, or sync messages.
|
||||
|
||||
Direct messages use the same shell but are not part of a room channel context.
|
||||
|
||||
## Voice, Video, and Screen Sharing
|
||||
|
||||
Voice and media are peer-to-peer. The signaling server coordinates connection setup, while media streams travel through WebRTC peer connections.
|
||||
|
||||
Desktop builds include platform integrations such as Linux display-server detection and optional monitor audio routing for screen sharing. Plugin media APIs can contribute custom audio or video streams when the user grants the necessary capabilities.
|
||||
|
||||
## Plugins
|
||||
|
||||
Open the Plugin Store from the title bar package button or menu. The plugin manager separates global client plugins from server-scoped plugins. Installed plugins can be activated, reloaded, unloaded, disabled, inspected for logs, and granted capabilities.
|
||||
|
||||
Plugins are explicit runtime modules. MetoYou loads browser-safe ES modules, passes a frozen API object, and cleans up registered disposables when a plugin unloads.
|
||||
|
||||
## Desktop Settings
|
||||
|
||||
Desktop settings cover:
|
||||
|
||||
- auto-start and close-to-tray behavior;
|
||||
- hardware acceleration and Linux VA-API video encode options;
|
||||
- update manifests and target update versions;
|
||||
- local HTTP API hosting;
|
||||
- Scalar API documentation;
|
||||
- Docusaurus app/plugin documentation;
|
||||
- allowed signaling servers for local API authentication;
|
||||
- local plugin discovery and store sources;
|
||||
- themes and user data import/export.
|
||||
65
docs-site/docusaurus.config.ts
Normal file
65
docs-site/docusaurus.config.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import type { Config } from '@docusaurus/types';
|
||||
import type * as Preset from '@docusaurus/preset-classic';
|
||||
|
||||
const config: Config = {
|
||||
title: 'MetoYou Docs',
|
||||
tagline: 'Desktop chat, local APIs, and plugin development',
|
||||
url: 'http://127.0.0.1',
|
||||
baseUrl: '/docusaurus/',
|
||||
organizationName: 'metoyou',
|
||||
projectName: 'metoyou',
|
||||
onBrokenLinks: 'throw',
|
||||
onBrokenMarkdownLinks: 'warn',
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en']
|
||||
},
|
||||
presets: [
|
||||
[
|
||||
'classic',
|
||||
{
|
||||
docs: {
|
||||
routeBasePath: '/',
|
||||
sidebarPath: './sidebars.ts'
|
||||
},
|
||||
blog: false,
|
||||
theme: {
|
||||
customCss: './src/css/custom.css'
|
||||
}
|
||||
} satisfies Preset.Options
|
||||
]
|
||||
],
|
||||
themeConfig: {
|
||||
navbar: {
|
||||
title: 'MetoYou Docs',
|
||||
items: [
|
||||
{ type: 'docSidebar', sidebarId: 'mainSidebar', position: 'left', label: 'Guides' },
|
||||
{ to: '/plugin-development/create-a-plugin', label: 'Plugin Guide', position: 'left' },
|
||||
{ to: '/plugin-development/api-reference', label: 'Plugin API', position: 'left' }
|
||||
]
|
||||
},
|
||||
footer: {
|
||||
style: 'dark',
|
||||
links: [
|
||||
{
|
||||
title: 'Docs',
|
||||
items: [
|
||||
{ label: 'Using MetoYou', to: '/using-metoyou' },
|
||||
{ label: 'Create a Plugin', to: '/plugin-development/create-a-plugin' },
|
||||
{ label: 'Plugin API Reference', to: '/plugin-development/api-reference' }
|
||||
]
|
||||
}
|
||||
],
|
||||
copyright: 'MetoYou local documentation. Built with Docusaurus.'
|
||||
},
|
||||
prism: {
|
||||
additionalLanguages: [
|
||||
'bash',
|
||||
'json',
|
||||
'typescript'
|
||||
]
|
||||
}
|
||||
} satisfies Preset.ThemeConfig
|
||||
};
|
||||
|
||||
export default config;
|
||||
18490
docs-site/package-lock.json
generated
Normal file
18490
docs-site/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
docs-site/package.json
Normal file
28
docs-site/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "metoyou-docs",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "docusaurus start --host 127.0.0.1",
|
||||
"build": "docusaurus build",
|
||||
"serve": "docusaurus serve --host 127.0.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.10.0",
|
||||
"@docusaurus/preset-classic": "3.10.0",
|
||||
"@mdx-js/react": "^3.1.1",
|
||||
"clsx": "^2.1.1",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "3.10.0",
|
||||
"@docusaurus/tsconfig": "3.10.0",
|
||||
"@docusaurus/types": "3.10.0",
|
||||
"typescript": "~5.9.2"
|
||||
},
|
||||
"overrides": {
|
||||
"webpack": "5.101.3"
|
||||
}
|
||||
}
|
||||
22
docs-site/sidebars.ts
Normal file
22
docs-site/sidebars.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { SidebarsConfig } from '@docusaurus/plugin-content-docs';
|
||||
|
||||
const sidebars: SidebarsConfig = {
|
||||
mainSidebar: [
|
||||
'intro',
|
||||
'using-metoyou',
|
||||
'desktop-and-local-api',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Plugin Development',
|
||||
items: [
|
||||
'plugin-development/create-a-plugin',
|
||||
'plugin-development/manifest',
|
||||
'plugin-development/capabilities',
|
||||
'plugin-development/api-reference',
|
||||
'plugin-development/examples'
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export default sidebars;
|
||||
40
docs-site/src/css/custom.css
Normal file
40
docs-site/src/css/custom.css
Normal file
@@ -0,0 +1,40 @@
|
||||
:root {
|
||||
--ifm-color-primary: #2f9ab2;
|
||||
--ifm-color-primary-dark: #2a8ba0;
|
||||
--ifm-color-primary-darker: #287f94;
|
||||
--ifm-color-primary-darkest: #216979;
|
||||
--ifm-color-primary-light: #36abc5;
|
||||
--ifm-color-primary-lighter: #43b4ce;
|
||||
--ifm-color-primary-lightest: #6cc5d8;
|
||||
--ifm-code-font-size: 92%;
|
||||
--ifm-border-radius: 6px;
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
--ifm-background-color: #101318;
|
||||
--ifm-background-surface-color: #171b22;
|
||||
--ifm-navbar-background-color: #12161d;
|
||||
--ifm-footer-background-color: #0b0e13;
|
||||
--ifm-color-primary: #58c4dc;
|
||||
--ifm-color-primary-dark: #36b7d3;
|
||||
--ifm-color-primary-darker: #27aeca;
|
||||
--ifm-color-primary-darkest: #208fa6;
|
||||
--ifm-color-primary-light: #79d1e3;
|
||||
--ifm-color-primary-lighter: #8bd7e7;
|
||||
--ifm-color-primary-lightest: #bde9f1;
|
||||
}
|
||||
|
||||
.hero--primary {
|
||||
--ifm-hero-background-color: #151b24;
|
||||
--ifm-hero-text-color: #f6f8fb;
|
||||
}
|
||||
|
||||
.theme-doc-markdown table code,
|
||||
.theme-doc-markdown li code,
|
||||
.theme-doc-markdown p code {
|
||||
border: 1px solid var(--ifm-color-emphasis-300);
|
||||
}
|
||||
|
||||
.plugin-api-table td:first-child {
|
||||
white-space: nowrap;
|
||||
}
|
||||
Reference in New Issue
Block a user