feat: Add browser documentation

This commit is contained in:
2026-04-29 17:15:01 +02:00
parent d261bac0ed
commit 3d81c34159
29 changed files with 19981 additions and 40 deletions

View File

@@ -115,6 +115,7 @@ export interface LocalApiSettings {
port: number;
exposeOnLan: boolean;
scalarEnabled: boolean;
docusaurusEnabled: boolean;
allowedSignalingServers: string[];
}
@@ -128,6 +129,7 @@ export interface LocalApiSnapshot {
error: string | null;
exposeOnLan: boolean;
scalarEnabled: boolean;
docusaurusEnabled: boolean;
}
export interface DesktopNotificationPayload {
@@ -255,6 +257,7 @@ export interface ElectronApi {
setDesktopSettings: (patch: DesktopSettingsPatch) => Promise<DesktopSettingsSnapshot>;
getLocalApiStatus: () => Promise<LocalApiSnapshot>;
openLocalApiDocs: () => Promise<{ opened: boolean; reason?: string }>;
openDocusaurusDocs: () => Promise<{ opened: boolean; reason?: string }>;
relaunchApp: () => Promise<boolean>;
onDeepLinkReceived: (listener: (url: string) => void) => () => void;
readClipboardFiles: () => Promise<ClipboardFilePayload[]>;

View File

@@ -4,8 +4,8 @@
<div>
<h4 class="text-base font-semibold text-foreground">Local HTTP API</h4>
<p class="mt-1 text-sm text-muted-foreground">
Expose your client to local automation tools and scripts. Authentication is verified against your signaling server,
and access is off by default.
Expose your client to local automation tools and scripts. Authentication is verified against your signaling server, and access is off by
default.
</p>
</div>
@@ -23,9 +23,7 @@
<section class="space-y-4 rounded-lg border border-border bg-card/60 p-5">
<div>
<h5 class="text-sm font-semibold text-foreground">Server</h5>
<p class="mt-1 text-sm text-muted-foreground">
Enable to start a local HTTP server. By default it only listens on the loopback interface.
</p>
<p class="mt-1 text-sm text-muted-foreground">Enable to start a local HTTP server. By default it only listens on the loopback interface.</p>
</div>
<label class="flex items-start gap-3">
@@ -42,6 +40,20 @@
</span>
</label>
<label class="flex items-start gap-3">
<input
type="checkbox"
class="mt-1 h-4 w-4 rounded border-border text-primary focus:ring-primary"
[checked]="settings().docusaurusEnabled"
[disabled]="busy() || !settings().enabled"
(change)="toggleDocusaurus($event)"
/>
<span class="text-sm text-foreground">
<span class="font-medium">Serve Docusaurus documentation at <code>/docusaurus</code></span>
<span class="mt-1 block text-xs text-muted-foreground"> Hosts the built app and plugin documentation from local desktop resources. </span>
</span>
</label>
<label class="flex items-start gap-3">
<input
type="checkbox"
@@ -53,8 +65,7 @@
<span class="text-sm text-foreground">
<span class="font-medium">Allow connections from your network</span>
<span class="mt-1 block text-xs text-muted-foreground">
Bind to all interfaces (0.0.0.0). Other devices on your LAN will be able to reach the API. Only enable this on
networks you trust.
Bind to all interfaces (0.0.0.0). Other devices on your LAN will be able to reach the API. Only enable this on networks you trust.
</span>
</span>
</label>
@@ -73,9 +84,7 @@
/>
</label>
<p class="text-xs text-muted-foreground">
Change the listening port if 17878 is in use. Press save to apply.
</p>
<p class="text-xs text-muted-foreground">Change the listening port if 17878 is in use. Press save to apply.</p>
<button
type="button"
@@ -92,8 +101,8 @@
<div>
<h5 class="text-sm font-semibold text-foreground">Authentication</h5>
<p class="mt-1 text-sm text-muted-foreground">
Bearer tokens are issued only after a username/password is verified against one of the signaling servers below.
Add the full URL (including <code>https://</code>) of every signaling server you trust.
Bearer tokens are issued only after a username/password is verified against one of the signaling servers below. Add the full URL (including
<code>https://</code>) of every signaling server you trust.
</p>
</div>
@@ -153,6 +162,15 @@
Open API docs in browser
</button>
<button
type="button"
class="inline-flex items-center rounded-lg border border-primary/40 bg-primary/10 px-4 py-2 text-sm font-medium text-primary transition-colors hover:bg-primary/20 disabled:cursor-not-allowed disabled:opacity-60"
[disabled]="busy()"
(click)="openDocusaurusDocs()"
>
Open app docs in browser
</button>
<button
type="button"
class="inline-flex items-center rounded-lg border border-border bg-secondary px-4 py-2 text-sm font-medium text-foreground transition-colors hover:bg-secondary/80 disabled:cursor-not-allowed disabled:opacity-60"

View File

@@ -25,6 +25,7 @@ export class LocalApiSettingsComponent implements OnInit, OnDestroy {
port: 17878,
exposeOnLan: false,
scalarEnabled: false,
docusaurusEnabled: false,
allowedSignalingServers: []
});
@@ -35,7 +36,8 @@ export class LocalApiSettingsComponent implements OnInit, OnDestroy {
baseUrl: null,
error: null,
exposeOnLan: false,
scalarEnabled: false
scalarEnabled: false,
docusaurusEnabled: false
});
readonly allowedServersText = signal('');
@@ -94,6 +96,7 @@ export class LocalApiSettingsComponent implements OnInit, OnDestroy {
port: desktop.localApi.port,
exposeOnLan: desktop.localApi.exposeOnLan,
scalarEnabled: desktop.localApi.scalarEnabled,
docusaurusEnabled: desktop.localApi.docusaurusEnabled,
allowedSignalingServers: [...desktop.localApi.allowedSignalingServers]
});
@@ -135,6 +138,12 @@ export class LocalApiSettingsComponent implements OnInit, OnDestroy {
await this.savePatch({ scalarEnabled: checkbox.checked });
}
async toggleDocusaurus(event: Event): Promise<void> {
const checkbox = event.target as HTMLInputElement;
await this.savePatch({ docusaurusEnabled: checkbox.checked });
}
onPortInput(event: Event): void {
const input = event.target as HTMLInputElement;
@@ -187,6 +196,22 @@ export class LocalApiSettingsComponent implements OnInit, OnDestroy {
}
}
async openDocusaurusDocs(): Promise<void> {
const api = this.bridge.getApi();
if (!api)
return;
const result = await api.openDocusaurusDocs();
if (result && !result.opened) {
this.errorMessage.set(result.reason ?? 'Could not open documentation');
} else {
this.errorMessage.set(null);
await this.refresh();
}
}
async copyBaseUrl(): Promise<void> {
const baseUrl = this.status().baseUrl;
@@ -221,6 +246,7 @@ export class LocalApiSettingsComponent implements OnInit, OnDestroy {
port: updated.localApi.port,
exposeOnLan: updated.localApi.exposeOnLan,
scalarEnabled: updated.localApi.scalarEnabled,
docusaurusEnabled: updated.localApi.docusaurusEnabled,
allowedSignalingServers: [...updated.localApi.allowedSignalingServers]
});

View File

@@ -98,6 +98,20 @@
/>
</button>
@if (isElectron()) {
<button
type="button"
class="grid h-8 w-8 place-items-center rounded-md transition-colors hover:bg-secondary"
title="Open Documentation"
(click)="openDocumentation()"
>
<ng-icon
name="lucideBookOpen"
class="h-4 w-4 text-muted-foreground"
/>
</button>
}
<div class="relative">
<button
type="button"
@@ -151,6 +165,20 @@
>
Plugin Store
</button>
<button
type="button"
(click)="openSettings()"
class="w-full rounded-md px-3 py-2 text-left text-sm text-foreground transition-colors hover:bg-secondary"
>
Settings
</button>
<button
type="button"
(click)="openDocumentation()"
class="w-full rounded-md px-3 py-2 text-left text-sm text-foreground transition-colors hover:bg-secondary"
>
Documentation
</button>
<div class="mx-2 my-1 h-px bg-border"></div>
<button
type="button"

View File

@@ -14,6 +14,7 @@ import {
lucideSquare,
lucideX,
lucideChevronLeft,
lucideBookOpen,
lucideHash,
lucideMenu,
lucidePackage,
@@ -39,6 +40,7 @@ import { RealtimeSessionFacade } from '../../../core/realtime';
import { ServerDirectoryFacade } from '../../../domains/server-directory';
import { PlatformService } from '../../../core/platform';
import { clearStoredCurrentUserId } from '../../../core/storage/current-user-storage';
import { SettingsModalService } from '../../../core/services/settings-modal.service';
import { LeaveServerDialogComponent } from '../../../shared';
import { Room } from '../../../shared-kernel';
import { VoiceWorkspaceService } from '../../../domains/voice-session';
@@ -58,6 +60,7 @@ import { ThemeNodeDirective } from '../../../domains/theme';
lucideSquare,
lucideX,
lucideChevronLeft,
lucideBookOpen,
lucideHash,
lucideMenu,
lucidePackage,
@@ -76,6 +79,7 @@ export class TitleBarComponent {
private webrtc = inject(RealtimeSessionFacade);
private platform = inject(PlatformService);
private voiceWorkspace = inject(VoiceWorkspaceService);
private settingsModal = inject(SettingsModalService);
private getWindowControlsApi() {
return this.electronBridge.getApi();
@@ -188,6 +192,27 @@ export class TitleBarComponent {
void this.router.navigate(['/plugin-store'], { queryParams: { returnUrl } });
}
openSettings(): void {
this._showMenu.set(false);
this.settingsModal.open('general');
}
async openDocumentation(): Promise<void> {
const api = this.electronBridge.getApi();
this._showMenu.set(false);
if (!api) {
return;
}
const result = await api.openDocusaurusDocs();
if (result && !result.opened) {
this.inviteStatus.set(result.reason ?? 'Unable to open documentation.');
}
}
/** Open the unified leave-server confirmation dialog. */
private openLeaveConfirm() {
this._showMenu.set(false);