feat: add plugin button and navigation slots for enhanced plugin integration across various components
This commit is contained in:
@@ -42,6 +42,7 @@ import { useClosedNavCategoriesAtom } from '../../state/hooks/closedNavCategorie
|
|||||||
import { useRoomsUnread } from '../../state/hooks/unread';
|
import { useRoomsUnread } from '../../state/hooks/unread';
|
||||||
import { useMarkRoomsAsRead } from '../../hooks/useMarkAsRead';
|
import { useMarkRoomsAsRead } from '../../hooks/useMarkAsRead';
|
||||||
import { stopPropagation } from '../../utils/keyboard';
|
import { stopPropagation } from '../../utils/keyboard';
|
||||||
|
import { PluginButtonSlot } from '../../features/settings/plugins/PluginButtonSlot';
|
||||||
import { useSetting } from '../../state/hooks/settings';
|
import { useSetting } from '../../state/hooks/settings';
|
||||||
import { settingsAtom } from '../../state/settings';
|
import { settingsAtom } from '../../state/settings';
|
||||||
import {
|
import {
|
||||||
@@ -121,7 +122,8 @@ export function DirectListHeader({ title = 'Direct Messages' }: DirectListHeader
|
|||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box gap="100" alignItems="Center">
|
||||||
|
<PluginButtonSlot location="direct-messages" />
|
||||||
<IconButton aria-pressed={!!menuAnchor} variant="Background" onClick={handleOpenMenu}>
|
<IconButton aria-pressed={!!menuAnchor} variant="Background" onClick={handleOpenMenu}>
|
||||||
<Icon src={Icons.VerticalDots} size="200" />
|
<Icon src={Icons.VerticalDots} size="200" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ import { getMemberDisplayName, getMentionContent, trimReplyFromBody } from '../.
|
|||||||
import { CommandAutocomplete } from './CommandAutocomplete';
|
import { CommandAutocomplete } from './CommandAutocomplete';
|
||||||
import { Command, SHRUG, TABLEFLIP, UNFLIP, useCommands } from '../../hooks/useCommands';
|
import { Command, SHRUG, TABLEFLIP, UNFLIP, useCommands } from '../../hooks/useCommands';
|
||||||
import { pluginRegistry } from '../settings/plugins/PluginAPI';
|
import { pluginRegistry } from '../settings/plugins/PluginAPI';
|
||||||
|
import { PluginButtonSlot } from '../settings/plugins/PluginButtonSlot';
|
||||||
import { mobileOrTablet } from '../../utils/user-agent';
|
import { mobileOrTablet } from '../../utils/user-agent';
|
||||||
import { useElementSizeObserver } from '../../hooks/useElementSizeObserver';
|
import { useElementSizeObserver } from '../../hooks/useElementSizeObserver';
|
||||||
import { ReplyLayout, ThreadIndicator } from '../../components/message';
|
import { ReplyLayout, ThreadIndicator } from '../../components/message';
|
||||||
@@ -746,14 +747,17 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
before={
|
before={
|
||||||
<IconButton
|
<>
|
||||||
onClick={() => pickFile('*')}
|
<IconButton
|
||||||
variant="SurfaceVariant"
|
onClick={() => pickFile('*')}
|
||||||
size="300"
|
variant="SurfaceVariant"
|
||||||
radii="300"
|
size="300"
|
||||||
>
|
radii="300"
|
||||||
<Icon src={Icons.PlusCircle} />
|
>
|
||||||
</IconButton>
|
<Icon src={Icons.PlusCircle} />
|
||||||
|
</IconButton>
|
||||||
|
<PluginButtonSlot location="composer-actions" />
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
after={
|
after={
|
||||||
<>
|
<>
|
||||||
@@ -832,6 +836,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
</PopOut>
|
</PopOut>
|
||||||
)}
|
)}
|
||||||
</UseStateProvider>
|
</UseStateProvider>
|
||||||
|
<PluginButtonSlot location="text-composer-toolbar" />
|
||||||
<IconButton onClick={submit} variant="SurfaceVariant" size="300" radii="300">
|
<IconButton onClick={submit} variant="SurfaceVariant" size="300" radii="300">
|
||||||
<Icon src={Icons.Send} />
|
<Icon src={Icons.Send} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
|||||||
import { stopPropagation } from '../../utils/keyboard';
|
import { stopPropagation } from '../../utils/keyboard';
|
||||||
import { getMatrixToRoom } from '../../plugins/matrix-to';
|
import { getMatrixToRoom } from '../../plugins/matrix-to';
|
||||||
import { getViaServers } from '../../plugins/via-servers';
|
import { getViaServers } from '../../plugins/via-servers';
|
||||||
|
import { PluginButtonSlot } from '../settings/plugins/PluginButtonSlot';
|
||||||
import { BackRouteHandler } from '../../components/BackRouteHandler';
|
import { BackRouteHandler } from '../../components/BackRouteHandler';
|
||||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
import { useRoomPinnedEvents } from '../../hooks/useRoomPinnedEvents';
|
import { useRoomPinnedEvents } from '../../hooks/useRoomPinnedEvents';
|
||||||
@@ -229,6 +230,10 @@ const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(({ room, requestClose
|
|||||||
</UseStateProvider>
|
</UseStateProvider>
|
||||||
</Box>
|
</Box>
|
||||||
<Line variant="Surface" size="300" />
|
<Line variant="Surface" size="300" />
|
||||||
|
<Box direction="Row" gap="100" style={{ padding: config.space.S100 }}>
|
||||||
|
<PluginButtonSlot location="room-menu" />
|
||||||
|
</Box>
|
||||||
|
<Line variant="Surface" size="300" />
|
||||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||||
<UseStateProvider initial={false}>
|
<UseStateProvider initial={false}>
|
||||||
{(promptLeave, setPromptLeave) => (
|
{(promptLeave, setPromptLeave) => (
|
||||||
@@ -713,6 +718,7 @@ export function RoomViewHeader() {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
<PluginButtonSlot location="room-header" />
|
||||||
<TooltipProvider
|
<TooltipProvider
|
||||||
position="Bottom"
|
position="Bottom"
|
||||||
align="End"
|
align="End"
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ import { stopPropagation } from '../../../utils/keyboard';
|
|||||||
import { getMatrixToRoomEvent } from '../../../plugins/matrix-to';
|
import { getMatrixToRoomEvent } from '../../../plugins/matrix-to';
|
||||||
import { getViaServers } from '../../../plugins/via-servers';
|
import { getViaServers } from '../../../plugins/via-servers';
|
||||||
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
import { PluginButtonSlot } from '../../../features/settings/plugins/PluginButtonSlot';
|
||||||
import { useRoomPinnedEvents } from '../../../hooks/useRoomPinnedEvents';
|
import { useRoomPinnedEvents } from '../../../hooks/useRoomPinnedEvents';
|
||||||
import { MemberPowerTag, StateEvent } from '../../../../types/matrix/room';
|
import { MemberPowerTag, StateEvent } from '../../../../types/matrix/room';
|
||||||
import { PowerIcon } from '../../../components/power';
|
import { PowerIcon } from '../../../components/power';
|
||||||
@@ -1070,6 +1071,7 @@ export const Message = as<'div', MessageProps>(
|
|||||||
<Icon src={Icons.Delete} size="100" />
|
<Icon src={Icons.Delete} size="100" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
|
<PluginButtonSlot location="message-actions" />
|
||||||
<PopOut
|
<PopOut
|
||||||
anchor={menuAnchor}
|
anchor={menuAnchor}
|
||||||
position="Bottom"
|
position="Bottom"
|
||||||
@@ -1333,6 +1335,7 @@ export const Event = as<'div', EventProps>(
|
|||||||
<div className={css.MessageOptionsBase}>
|
<div className={css.MessageOptionsBase}>
|
||||||
<Menu className={css.MessageOptionsBar} variant="SurfaceVariant">
|
<Menu className={css.MessageOptionsBar} variant="SurfaceVariant">
|
||||||
<Box gap="100">
|
<Box gap="100">
|
||||||
|
<PluginButtonSlot location="message-actions" />
|
||||||
<PopOut
|
<PopOut
|
||||||
anchor={menuAnchor}
|
anchor={menuAnchor}
|
||||||
position="Bottom"
|
position="Bottom"
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ export type {
|
|||||||
PluginContext,
|
PluginContext,
|
||||||
PluginSettingsSection,
|
PluginSettingsSection,
|
||||||
UILocation,
|
UILocation,
|
||||||
|
UIButtonDefinition,
|
||||||
|
UIButtonPosition,
|
||||||
Plugin,
|
Plugin,
|
||||||
PluginLogEntry,
|
PluginLogEntry,
|
||||||
} from '@paarrot/plugin-manager';
|
} from '@paarrot/plugin-manager';
|
||||||
@@ -54,7 +56,7 @@ import {
|
|||||||
} from '@paarrot/plugin-manager';
|
} from '@paarrot/plugin-manager';
|
||||||
|
|
||||||
const PLUGIN_INDEX_URL =
|
const PLUGIN_INDEX_URL =
|
||||||
'https://raw.githubusercontent.com/Paarrot/Plugin-Directory/refs/heads/main/plugins/index.json';
|
`https://raw.githubusercontent.com/Paarrot/Plugin-Directory/refs/heads/main/plugins/index.json?cache_burst=${ (Math.random()*256).toString()}`;
|
||||||
const PLUGIN_BASE_URL =
|
const PLUGIN_BASE_URL =
|
||||||
'https://raw.githubusercontent.com/Paarrot/Plugin-Directory/refs/heads/main/plugins/';
|
'https://raw.githubusercontent.com/Paarrot/Plugin-Directory/refs/heads/main/plugins/';
|
||||||
|
|
||||||
@@ -107,6 +109,13 @@ export const pluginMarketplaceManager = new PluginMarketplaceManager({
|
|||||||
return result.data.map((plugin) => ({
|
return result.data.map((plugin) => ({
|
||||||
id: plugin.id,
|
id: plugin.id,
|
||||||
name: plugin.name,
|
name: plugin.name,
|
||||||
|
version: plugin.version,
|
||||||
|
description: plugin.description,
|
||||||
|
author: plugin.author,
|
||||||
|
repository: plugin.repository,
|
||||||
|
thumbnail: plugin.thumbnail,
|
||||||
|
homepage: plugin.homepage,
|
||||||
|
tags: plugin.tags,
|
||||||
installedDate: plugin.installedDate,
|
installedDate: plugin.installedDate,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|||||||
71
src/app/features/settings/plugins/PluginButtonSlot.tsx
Normal file
71
src/app/features/settings/plugins/PluginButtonSlot.tsx
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import React, { useSyncExternalStore } from 'react';
|
||||||
|
import { IconButton } from 'folds';
|
||||||
|
import { UILocation } from '@paarrot/plugin-manager';
|
||||||
|
import { pluginRegistry } from './PluginAPI';
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Module-level subscription store — works correctly with React 18 concurrent
|
||||||
|
// rendering, lazy-mounted components (nav panels, route changes), and popup
|
||||||
|
// menus that mount fresh after plugins have already loaded.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type Listener = () => void;
|
||||||
|
const listeners = new Set<Listener>();
|
||||||
|
let storeVersion = 0;
|
||||||
|
|
||||||
|
export function subscribeToButtons(callback: Listener): () => void {
|
||||||
|
listeners.add(callback);
|
||||||
|
return () => listeners.delete(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStoreVersion(): number {
|
||||||
|
return storeVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify all subscribed slots that the button registry has changed.
|
||||||
|
* Call this after plugins load or unload.
|
||||||
|
*/
|
||||||
|
export function dispatchPluginButtonsChanged(): void {
|
||||||
|
storeVersion += 1;
|
||||||
|
for (const listener of listeners) {
|
||||||
|
listener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PluginButtonSlotProps {
|
||||||
|
/** The UI location to render buttons for. */
|
||||||
|
location: UILocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders all plugin-registered buttons for the given UI location.
|
||||||
|
* Correctly handles components that mount before OR after plugins load,
|
||||||
|
* including popup menus, navigation panels, and concurrent React renders.
|
||||||
|
*/
|
||||||
|
export function PluginButtonSlot({ location }: PluginButtonSlotProps): React.ReactElement | null {
|
||||||
|
useSyncExternalStore(subscribeToButtons, getStoreVersion);
|
||||||
|
|
||||||
|
const buttons = pluginRegistry.getButtonsForLocation(location);
|
||||||
|
if (buttons.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{buttons.map((button) => (
|
||||||
|
<IconButton
|
||||||
|
key={button.id}
|
||||||
|
variant="SurfaceVariant"
|
||||||
|
size="300"
|
||||||
|
radii="300"
|
||||||
|
aria-label={button.label}
|
||||||
|
title={button.label}
|
||||||
|
onClick={() => button.onClick?.()}
|
||||||
|
>
|
||||||
|
<span aria-hidden style={{ fontSize: '1.1em', lineHeight: 1 }}>
|
||||||
|
{button.icon ?? '🔌'}
|
||||||
|
</span>
|
||||||
|
</IconButton>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { MatrixClient } from 'matrix-js-sdk';
|
|||||||
import { Plugin, createPluginContext } from '@paarrot/plugin-manager';
|
import { Plugin, createPluginContext } from '@paarrot/plugin-manager';
|
||||||
import { pluginMarketplaceManager, pluginRegistry } from './PluginAPI';
|
import { pluginMarketplaceManager, pluginRegistry } from './PluginAPI';
|
||||||
import { sendNotification } from '../../../utils/tauri';
|
import { sendNotification } from '../../../utils/tauri';
|
||||||
|
import { dispatchPluginButtonsChanged } from './PluginButtonSlot';
|
||||||
|
|
||||||
interface PluginLoaderProps {
|
interface PluginLoaderProps {
|
||||||
matrixClient: MatrixClient;
|
matrixClient: MatrixClient;
|
||||||
@@ -87,6 +88,7 @@ export function PluginLoader({ matrixClient, children }: PluginLoaderProps) {
|
|||||||
pluginRegistry.registerPlugin(installedPlugin.id, plugin, context);
|
pluginRegistry.registerPlugin(installedPlugin.id, plugin, context);
|
||||||
await plugin.onLoad(compatContext as any);
|
await plugin.onLoad(compatContext as any);
|
||||||
console.log(`[PluginLoader] Successfully loaded plugin: ${installedPlugin.id}`);
|
console.log(`[PluginLoader] Successfully loaded plugin: ${installedPlugin.id}`);
|
||||||
|
dispatchPluginButtonsChanged();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[PluginLoader] Failed to load plugin ${installedPlugin.id}:`, error);
|
console.error(`[PluginLoader] Failed to load plugin ${installedPlugin.id}:`, error);
|
||||||
pluginRegistry.addLog(installedPlugin.id, 'error', ['Failed to load:', error]);
|
pluginRegistry.addLog(installedPlugin.id, 'error', ['Failed to load:', error]);
|
||||||
@@ -102,6 +104,7 @@ export function PluginLoader({ matrixClient, children }: PluginLoaderProps) {
|
|||||||
return () => {
|
return () => {
|
||||||
console.log('[PluginLoader] Cleaning up plugins');
|
console.log('[PluginLoader] Cleaning up plugins');
|
||||||
pluginRegistry.clear();
|
pluginRegistry.clear();
|
||||||
|
dispatchPluginButtonsChanged();
|
||||||
};
|
};
|
||||||
}, [matrixClient]);
|
}, [matrixClient]);
|
||||||
|
|
||||||
|
|||||||
48
src/app/features/settings/plugins/PluginNavSlot.tsx
Normal file
48
src/app/features/settings/plugins/PluginNavSlot.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import React, { useSyncExternalStore } from 'react';
|
||||||
|
import { Avatar, Box, Text } from 'folds';
|
||||||
|
import { UILocation } from '@paarrot/plugin-manager';
|
||||||
|
import { NavItem, NavButton, NavItemContent } from '../../../components/nav';
|
||||||
|
import { pluginRegistry } from './PluginAPI';
|
||||||
|
import { subscribeToButtons, getStoreVersion } from './PluginButtonSlot';
|
||||||
|
|
||||||
|
interface PluginNavSlotProps {
|
||||||
|
/** The UI location to render nav list entries for. */
|
||||||
|
location: UILocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders plugin buttons as `NavItem` list entries, matching the style of
|
||||||
|
* built-in sidebar items like "Create Room" and "Message Search".
|
||||||
|
* Intended for locations that live inside the channel / room list, not toolbars.
|
||||||
|
*/
|
||||||
|
export function PluginNavSlot({ location }: PluginNavSlotProps): React.ReactElement | null {
|
||||||
|
useSyncExternalStore(subscribeToButtons, getStoreVersion);
|
||||||
|
|
||||||
|
const buttons = pluginRegistry.getButtonsForLocation(location);
|
||||||
|
if (buttons.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{buttons.map((button) => (
|
||||||
|
<NavItem key={button.id} variant="Background" radii="400">
|
||||||
|
<NavButton onClick={() => button.onClick?.()}>
|
||||||
|
<NavItemContent>
|
||||||
|
<Box as="span" grow="Yes" alignItems="Center" gap="200">
|
||||||
|
<Avatar size="200" radii="400">
|
||||||
|
<span aria-hidden style={{ fontSize: '1em', lineHeight: 1 }}>
|
||||||
|
{button.icon ?? '🔌'}
|
||||||
|
</span>
|
||||||
|
</Avatar>
|
||||||
|
<Box as="span" grow="Yes">
|
||||||
|
<Text as="span" size="Inherit" truncate>
|
||||||
|
{button.label}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</NavItemContent>
|
||||||
|
</NavButton>
|
||||||
|
</NavItem>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
49
src/app/features/settings/plugins/PluginSidebarSlot.tsx
Normal file
49
src/app/features/settings/plugins/PluginSidebarSlot.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import React, { useSyncExternalStore } from 'react';
|
||||||
|
import { UILocation } from '@paarrot/plugin-manager';
|
||||||
|
import {
|
||||||
|
SidebarItem,
|
||||||
|
SidebarItemTooltip,
|
||||||
|
SidebarAvatar,
|
||||||
|
} from '../../../components/sidebar';
|
||||||
|
import { pluginRegistry } from './PluginAPI';
|
||||||
|
import { subscribeToButtons, getStoreVersion } from './PluginButtonSlot';
|
||||||
|
|
||||||
|
interface PluginSidebarSlotProps {
|
||||||
|
/** The UI location to render sidebar icon entries for. */
|
||||||
|
location: UILocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders plugin buttons as `SidebarItem` entries, matching the style of
|
||||||
|
* built-in sidebar tabs like SearchTab, InboxTab, and SettingsTab.
|
||||||
|
* Intended for the `sidebar-actions` location in the main left sidebar.
|
||||||
|
*/
|
||||||
|
export function PluginSidebarSlot({ location }: PluginSidebarSlotProps): React.ReactElement | null {
|
||||||
|
useSyncExternalStore(subscribeToButtons, getStoreVersion);
|
||||||
|
|
||||||
|
const buttons = pluginRegistry.getButtonsForLocation(location);
|
||||||
|
if (buttons.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{buttons.map((button) => (
|
||||||
|
<SidebarItem key={button.id}>
|
||||||
|
<SidebarItemTooltip tooltip={button.label}>
|
||||||
|
{(triggerRef) => (
|
||||||
|
<SidebarAvatar
|
||||||
|
as="button"
|
||||||
|
ref={triggerRef}
|
||||||
|
onClick={() => button.onClick?.()}
|
||||||
|
aria-label={button.label}
|
||||||
|
>
|
||||||
|
<span aria-hidden style={{ fontSize: '1.2em', lineHeight: 1 }}>
|
||||||
|
{button.icon ?? '🔌'}
|
||||||
|
</span>
|
||||||
|
</SidebarAvatar>
|
||||||
|
)}
|
||||||
|
</SidebarItemTooltip>
|
||||||
|
</SidebarItem>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
import { CreateTab } from './sidebar/CreateTab';
|
import { CreateTab } from './sidebar/CreateTab';
|
||||||
import { HiddenSpacesTabs } from './sidebar/SpaceTabs';
|
import { HiddenSpacesTabs } from './sidebar/SpaceTabs';
|
||||||
import { useHomeHidden } from '../../hooks/useSidebarItems';
|
import { useHomeHidden } from '../../hooks/useSidebarItems';
|
||||||
|
import { PluginSidebarSlot } from '../../features/settings/plugins/PluginSidebarSlot';
|
||||||
|
|
||||||
export function SidebarNav() {
|
export function SidebarNav() {
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -37,6 +38,7 @@ export function SidebarNav() {
|
|||||||
<SpaceTabs scrollRef={scrollRef} />
|
<SpaceTabs scrollRef={scrollRef} />
|
||||||
<SidebarStackSeparator />
|
<SidebarStackSeparator />
|
||||||
<SidebarStack>
|
<SidebarStack>
|
||||||
|
<PluginSidebarSlot location="sidebar-actions" />
|
||||||
<ExploreTab />
|
<ExploreTab />
|
||||||
<CreateTab />
|
<CreateTab />
|
||||||
</SidebarStack>
|
</SidebarStack>
|
||||||
@@ -47,6 +49,7 @@ export function SidebarNav() {
|
|||||||
<>
|
<>
|
||||||
<SidebarStackSeparator />
|
<SidebarStackSeparator />
|
||||||
<SidebarStack>
|
<SidebarStack>
|
||||||
|
<PluginSidebarSlot location="sidebar-actions" />
|
||||||
<SearchTab />
|
<SearchTab />
|
||||||
<UnverifiedTab />
|
<UnverifiedTab />
|
||||||
<InboxTab />
|
<InboxTab />
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCatego
|
|||||||
import { useRoomsUnread } from '../../../state/hooks/unread';
|
import { useRoomsUnread } from '../../../state/hooks/unread';
|
||||||
import { useMarkRoomsAsRead } from '../../../hooks/useMarkAsRead';
|
import { useMarkRoomsAsRead } from '../../../hooks/useMarkAsRead';
|
||||||
import { stopPropagation } from '../../../utils/keyboard';
|
import { stopPropagation } from '../../../utils/keyboard';
|
||||||
|
import { PluginButtonSlot } from '../../../features/settings/plugins/PluginButtonSlot';
|
||||||
|
import { PluginNavSlot } from '../../../features/settings/plugins/PluginNavSlot';
|
||||||
import { useSetting } from '../../../state/hooks/settings';
|
import { useSetting } from '../../../state/hooks/settings';
|
||||||
import { settingsAtom } from '../../../state/settings';
|
import { settingsAtom } from '../../../state/settings';
|
||||||
import {
|
import {
|
||||||
@@ -228,6 +230,7 @@ export function Direct() {
|
|||||||
</NavItemContent>
|
</NavItemContent>
|
||||||
</NavButton>
|
</NavButton>
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
<PluginNavSlot location="direct-messages" />
|
||||||
</NavCategory>
|
</NavCategory>
|
||||||
<NavCategory>
|
<NavCategory>
|
||||||
<NavCategoryHeader>
|
<NavCategoryHeader>
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ import { useRoomsUnread } from '../../../state/hooks/unread';
|
|||||||
import { useMarkRoomsAsRead } from '../../../hooks/useMarkAsRead';
|
import { useMarkRoomsAsRead } from '../../../hooks/useMarkAsRead';
|
||||||
import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCategories';
|
import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCategories';
|
||||||
import { stopPropagation } from '../../../utils/keyboard';
|
import { stopPropagation } from '../../../utils/keyboard';
|
||||||
|
import { PluginNavSlot } from '../../../features/settings/plugins/PluginNavSlot';
|
||||||
import { useSetting } from '../../../state/hooks/settings';
|
import { useSetting } from '../../../state/hooks/settings';
|
||||||
import { settingsAtom } from '../../../state/settings';
|
import { settingsAtom } from '../../../state/settings';
|
||||||
import {
|
import {
|
||||||
@@ -152,7 +153,7 @@ function HomeHeader() {
|
|||||||
Home
|
Home
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box alignItems="Center" gap="100">
|
||||||
<IconButton aria-pressed={!!menuAnchor} variant="Background" onClick={handleOpenMenu}>
|
<IconButton aria-pressed={!!menuAnchor} variant="Background" onClick={handleOpenMenu}>
|
||||||
<Icon src={Icons.VerticalDots} size="200" />
|
<Icon src={Icons.VerticalDots} size="200" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -409,6 +410,7 @@ export function Home() {
|
|||||||
</NavItemContent>
|
</NavItemContent>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
<PluginNavSlot location="channel-list" />
|
||||||
</NavCategory>
|
</NavCategory>
|
||||||
<NavCategory>
|
<NavCategory>
|
||||||
<NavCategoryHeader>
|
<NavCategoryHeader>
|
||||||
@@ -420,6 +422,7 @@ export function Home() {
|
|||||||
Rooms
|
Rooms
|
||||||
</RoomNavCategoryButton>
|
</RoomNavCategoryButton>
|
||||||
</NavCategoryHeader>
|
</NavCategoryHeader>
|
||||||
|
<PluginNavSlot location="home-section" />
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ import { useMentionClickHandler } from '../../../hooks/useMentionClickHandler';
|
|||||||
import { useSpoilerClickHandler } from '../../../hooks/useSpoilerClickHandler';
|
import { useSpoilerClickHandler } from '../../../hooks/useSpoilerClickHandler';
|
||||||
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
|
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
|
||||||
import { BackRouteHandler } from '../../../components/BackRouteHandler';
|
import { BackRouteHandler } from '../../../components/BackRouteHandler';
|
||||||
|
import { PluginButtonSlot } from '../../../features/settings/plugins/PluginButtonSlot';
|
||||||
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
import { allRoomsAtom } from '../../../state/room-list/roomList';
|
import { allRoomsAtom } from '../../../state/room-list/roomList';
|
||||||
import { usePowerLevels } from '../../../hooks/usePowerLevels';
|
import { usePowerLevels } from '../../../hooks/usePowerLevels';
|
||||||
@@ -701,7 +702,9 @@ export function Notifications() {
|
|||||||
Notification Messages
|
Notification Messages
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box grow="Yes" basis="No" />
|
<Box grow="Yes" basis="No" justifyContent="End" alignItems="Center">
|
||||||
|
<PluginButtonSlot location="search-notification-section" />
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { useUserProfile } from '../../../hooks/useUserProfile';
|
|||||||
import { Modal500 } from '../../../components/Modal500';
|
import { Modal500 } from '../../../components/Modal500';
|
||||||
import { AccountSwitcher } from '../../../components/account-switcher';
|
import { AccountSwitcher } from '../../../components/account-switcher';
|
||||||
import { stopPropagation } from '../../../utils/keyboard';
|
import { stopPropagation } from '../../../utils/keyboard';
|
||||||
|
import { PluginButtonSlot } from '../../../features/settings/plugins/PluginButtonSlot';
|
||||||
import { getLoginPath } from '../../pathUtils';
|
import { getLoginPath } from '../../pathUtils';
|
||||||
import { logoutClient } from '../../../../client/initMatrix';
|
import { logoutClient } from '../../../../client/initMatrix';
|
||||||
import { CustomStatusDialog } from '../../../components/custom-status';
|
import { CustomStatusDialog } from '../../../components/custom-status';
|
||||||
@@ -110,6 +111,10 @@ export function SettingsTab() {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Box>
|
</Box>
|
||||||
<Line variant="Surface" size="300" />
|
<Line variant="Surface" size="300" />
|
||||||
|
<Box direction="Row" gap="100" style={{ padding: config.space.S100 }}>
|
||||||
|
<PluginButtonSlot location="user-menu" />
|
||||||
|
</Box>
|
||||||
|
<Line variant="Surface" size="300" />
|
||||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCatego
|
|||||||
import { useStateEvent } from '../../../hooks/useStateEvent';
|
import { useStateEvent } from '../../../hooks/useStateEvent';
|
||||||
import { Membership, StateEvent } from '../../../../types/matrix/room';
|
import { Membership, StateEvent } from '../../../../types/matrix/room';
|
||||||
import { stopPropagation } from '../../../utils/keyboard';
|
import { stopPropagation } from '../../../utils/keyboard';
|
||||||
|
import { PluginNavSlot } from '../../../features/settings/plugins/PluginNavSlot';
|
||||||
import { getMatrixToRoom } from '../../../plugins/matrix-to';
|
import { getMatrixToRoom } from '../../../plugins/matrix-to';
|
||||||
import { getViaServers } from '../../../plugins/via-servers';
|
import { getViaServers } from '../../../plugins/via-servers';
|
||||||
import { useSetting } from '../../../state/hooks/settings';
|
import { useSetting } from '../../../state/hooks/settings';
|
||||||
@@ -549,6 +550,7 @@ export function Space() {
|
|||||||
</NavItemContent>
|
</NavItemContent>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
<PluginNavSlot location="channel-list" />
|
||||||
</NavCategory>
|
</NavCategory>
|
||||||
<NavCategory
|
<NavCategory
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
Reference in New Issue
Block a user