Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
Showing
with 461 additions and 244 deletions
import { useEffect, useState } from "preact/hooks";
import { Plus } from "@styled-icons/boxicons-regular";
import isEqual from "lodash.isequal";
import { observer } from "mobx-react-lite";
import { ChannelPermission, ServerPermission } from "revolt.js";
import { Server } from "revolt.js/dist/maps/Servers";
import styles from "./Panes.module.scss";
import { Text } from "preact-i18n";
import { useCallback, useEffect, useMemo, useState } from "preact/hooks";
import { useIntermediate } from "../../../context/intermediate/Intermediate";
import Button from "../../../components/ui/Button";
import { Servers } from "revolt.js/dist/api/objects";
import Checkbox from "../../../components/ui/Checkbox";
import { ChannelPermission, ServerPermission } from "revolt.js/dist/api/permissions";
import ColourSwatches from "../../../components/ui/ColourSwatches";
import InputBox from "../../../components/ui/InputBox";
import Overline from "../../../components/ui/Overline";
import ButtonItem from "../../../components/navigation/items/ButtonItem";
interface Props {
server: Servers.Server;
server: Server;
}
export function Roles({ server }: Props) {
const [ selected, setSelected ] = useState('default');
const roles = server.roles ?? {};
const keys = [ 'default', ...Object.keys(roles) ];
const I32ToU32 = (arr: number[]) => arr.map((x) => x >>> 0);
const defaultRole = { name: 'Default', permissions: server.default_permissions };
const selectedRole: Servers.Role = selected === 'default' ? defaultRole : roles[selected];
// ! FIXME: bad code :)
export const Roles = observer(({ server }: Props) => {
const [role, setRole] = useState("default");
const { openScreen } = useIntermediate();
const roles = useMemo(() => server.roles ?? {}, [server]);
if (!selectedRole) {
useEffect(() => setSelected('default'), [ ]);
if (role !== "default" && typeof roles[role] === "undefined") {
useEffect(() => setRole("default"), [role]);
return null;
}
return (
<div>
<h2>select role</h2>
{ keys
.map(id => {
let role: Servers.Role = id === 'default' ? defaultRole : roles[id];
return (
<Checkbox checked={selected === id} onChange={selected => selected && setSelected(id)}>
{ role.name }
</Checkbox>
)
})
}
<Button disabled={selected === 'default'} error onClick={() => {}}>delete role</Button>
<h2>server permmissions</h2>
{ Object.keys(ServerPermission)
.map(perm => {
let value = ServerPermission[perm as keyof typeof ServerPermission];
const {
name: roleName,
colour: roleColour,
permissions,
} = roles[role] ?? {};
return (
<Checkbox checked={((selectedRole.permissions[0] >>> 0) & value) > 0} onChange={() => {}}>
{ perm }
</Checkbox>
)
})
}
<h2>channel permmissions</h2>
{ Object.keys(ChannelPermission)
.map(perm => {
let value = ChannelPermission[perm as keyof typeof ChannelPermission];
const getPermissions = useCallback(
(id: string) => {
return I32ToU32(
id === "default"
? server.default_permissions
: roles[id].permissions,
);
},
[roles, server],
);
const [perm, setPerm] = useState(getPermissions(role));
const [name, setName] = useState(roleName);
const [colour, setColour] = useState(roleColour);
useEffect(
() => setPerm(getPermissions(role)),
[getPermissions, role, permissions],
);
useEffect(() => setName(roleName), [role, roleName]);
useEffect(() => setColour(roleColour), [role, roleColour]);
const modified =
!isEqual(perm, getPermissions(role)) ||
!isEqual(name, roleName) ||
!isEqual(colour, roleColour);
const save = () => {
if (!isEqual(perm, getPermissions(role))) {
server.setPermissions(role, {
server: perm[0],
channel: perm[1],
});
}
if (!isEqual(name, roleName) || !isEqual(colour, roleColour)) {
server.editRole(role, { name, colour });
}
};
const deleteRole = () => {
setRole("default");
server.deleteRole(role);
};
return (
<div className={styles.roles}>
<div className={styles.list}>
<div className={styles.title}>
<h1>
<Text id="app.settings.server_pages.roles.title" />
</h1>
<Plus
size={22}
onClick={() =>
openScreen({
id: "special_input",
type: "create_role",
server,
callback: (id) => setRole(id),
})
}
/>
</div>
{["default", ...Object.keys(roles)].map((id) => {
if (id === "default") {
return (
<ButtonItem
active={role === "default"}
onClick={() => setRole("default")}>
<Text id="app.settings.permissions.default_role" />
</ButtonItem>
);
}
return (
<Checkbox checked={((selectedRole.permissions[1] >>> 0) & value) > 0} onChange={() => {}}>
{ perm }
</Checkbox>
)
})
}
<ButtonItem
key={id}
active={role === id}
onClick={() => setRole(id)}
style={{ color: roles[id].colour }}>
{roles[id].name}
</ButtonItem>
);
})}
</div>
<div className={styles.permissions}>
<div className={styles.title}>
<h2>
{role === "default" ? (
<Text id="app.settings.permissions.default_role" />
) : (
roles[role].name
)}
</h2>
<Button contrast disabled={!modified} onClick={save}>
Save
</Button>
</div>
{role !== "default" && (
<>
<section>
<Overline type="subtle">Role Name</Overline>
<p>
<InputBox
value={name}
onChange={(e) =>
setName(e.currentTarget.value)
}
contrast
/>
</p>
</section>
<section>
<Overline type="subtle">Role Colour</Overline>
<p>
<ColourSwatches
value={colour ?? "gray"}
onChange={(value) => setColour(value)}
/>
</p>
</section>
</>
)}
<section>
<Overline type="subtle">
<Text id="app.settings.permissions.server" />
</Overline>
{Object.keys(ServerPermission).map((key) => {
if (key === "View") return;
const value =
ServerPermission[
key as keyof typeof ServerPermission
];
return (
<Checkbox
key={key}
checked={(perm[0] & value) > 0}
onChange={() =>
setPerm([perm[0] ^ value, perm[1]])
}
description={
<Text id={`permissions.server.${key}.d`} />
}>
<Text id={`permissions.server.${key}.t`} />
</Checkbox>
);
})}
</section>
<section>
<Overline type="subtle">
<Text id="app.settings.permissions.channel" />
</Overline>
{Object.keys(ChannelPermission).map((key) => {
if (key === "ManageChannel") return;
const value =
ChannelPermission[
key as keyof typeof ChannelPermission
];
return (
<Checkbox
key={key}
checked={((perm[1] >>> 0) & value) > 0}
onChange={() =>
setPerm([perm[0], perm[1] ^ value])
}
disabled={key === "View"}
description={
<Text id={`permissions.channel.${key}.d`} />
}>
<Text id={`permissions.channel.${key}.t`} />
</Checkbox>
);
})}
</section>
<div className={styles.actions}>
<Button contrast disabled={!modified} onClick={save}>
Save
</Button>
{role !== "default" && (
<Button contrast error onClick={deleteRole}>
Delete
</Button>
)}
</div>
</div>
</div>
);
}
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
/* eslint-disable */
import JSX = preact.JSX;
import { store } from ".";
import localForage from "localforage";
import { Provider } from "react-redux";
import { Children } from "../types/Preact";
import { useEffect, useState } from "preact/hooks";
import { dispatch, State, store } from ".";
import { Children } from "../types/Preact";
interface Props {
children: Children;
}
export default function State(props: Props) {
export default function StateLoader(props: Props) {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
localForage.getItem("state")
.then(state => {
if (state !== null) {
store.dispatch({ type: "__INIT", state });
}
setLoaded(true);
});
localForage.getItem("state").then((state) => {
if (state !== null) {
dispatch({ type: "__INIT", state: state as State });
}
setLoaded(true);
});
}, []);
if (!loaded) return null;
......
/* eslint-disable @typescript-eslint/no-explicit-any */
import { connect, ConnectedComponent } from "react-redux";
import { State } from ".";
import { h } from "preact";
import { memo } from "preact/compat";
import { connect, ConnectedComponent } from "react-redux";
import { State } from ".";
export function connectState<T>(
component: (props: any) => h.JSX.Element | null,
mapKeys: (state: State, props: T) => any,
useDispatcher?: boolean,
memoize?: boolean
memoize?: boolean,
): ConnectedComponent<(props: any) => h.JSX.Element | null, T> {
let c = (
useDispatcher
? connect(mapKeys, (dispatcher) => {
return { dispatcher };
})
: connect(mapKeys)
)(component);
const c = connect(mapKeys)(component);
return memoize ? memo(c) : c;
}
import { createStore } from "redux";
import rootReducer from "./reducers";
import localForage from "localforage";
import { createStore } from "redux";
import { RevoltConfiguration } from "revolt-api/types/Core";
import { Core } from "revolt.js/dist/api/objects";
import { Typing } from "./reducers/typing";
import { Drafts } from "./reducers/drafts";
import { AuthState } from "./reducers/auth";
import { Language } from "../context/Locale";
import { Unreads } from "./reducers/unreads";
import { SyncOptions } from "./reducers/sync";
import { Settings } from "./reducers/settings";
import { QueuedMessage } from "./reducers/queue";
import rootReducer, { Action } from "./reducers";
import { AuthState } from "./reducers/auth";
import { Drafts } from "./reducers/drafts";
import { ExperimentOptions } from "./reducers/experiments";
import { LastOpened } from "./reducers/last_opened";
import { Notifications } from "./reducers/notifications";
import { QueuedMessage } from "./reducers/queue";
import { SectionToggle } from "./reducers/section_toggle";
import { Settings } from "./reducers/settings";
import { SyncOptions } from "./reducers/sync";
import { Unreads } from "./reducers/unreads";
export type State = {
config: Core.RevoltNodeConfiguration,
config: RevoltConfiguration;
locale: Language;
auth: AuthState;
settings: Settings;
unreads: Unreads;
queue: QueuedMessage[];
typing: Typing;
drafts: Drafts;
sync: SyncOptions;
experiments: ExperimentOptions;
lastOpened: LastOpened;
notifications: Notifications;
sectionToggle: SectionToggle;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
......@@ -56,7 +57,8 @@ store.subscribe(() => {
sync,
experiments,
lastOpened,
notifications
notifications,
sectionToggle,
} = store.getState() as State;
localForage.setItem("state", {
......@@ -70,6 +72,15 @@ store.subscribe(() => {
sync,
experiments,
lastOpened,
notifications
notifications,
sectionToggle,
});
});
export function dispatch(action: Action) {
store.dispatch(action);
}
export function getState(): State {
return store.getState();
}
import type { Auth } from "revolt.js/dist/api/objects";
import { Session } from "revolt-api/types/Auth";
export interface AuthState {
accounts: {
[key: string]: {
session: Auth.Session;
session: Session;
};
};
active?: string;
......@@ -13,7 +13,7 @@ export type AuthAction =
| { type: undefined }
| {
type: "LOGIN";
session: Auth.Session;
session: Session;
}
| {
type: "LOGOUT";
......@@ -22,7 +22,7 @@ export type AuthAction =
export function auth(
state = { accounts: {} } as AuthState,
action: AuthAction
action: AuthAction,
): AuthState {
switch (action.type) {
case "LOGIN":
......
export type Experiments = never;
export const AVAILABLE_EXPERIMENTS: Experiments[] = [];
export type Experiments = "search";
export const AVAILABLE_EXPERIMENTS: Experiments[] = ["search"];
export const EXPERIMENTS: {
[key in Experiments]: { title: string; description: string };
} = {
search: {
title: "Search",
description: "Allows you to search for messages in channels.",
},
};
export interface ExperimentOptions {
enabled?: Experiments[];
......@@ -18,7 +26,7 @@ export type ExperimentsAction =
export function experiments(
state = {} as ExperimentOptions,
action: ExperimentsAction
action: ExperimentsAction,
): ExperimentOptions {
switch (action.type) {
case "EXPERIMENTS_ENABLE":
......
import { State } from "..";
import { combineReducers } from "redux";
import { config, ConfigAction } from "./server_config";
import { settings, SettingsAction } from "./settings";
import { locale, LocaleAction } from "./locale";
import { State } from "..";
import { auth, AuthAction } from "./auth";
import { unreads, UnreadsAction } from "./unreads";
import { queue, QueueAction } from "./queue";
import { typing, TypingAction } from "./typing";
import { drafts, DraftAction } from "./drafts";
import { sync, SyncAction } from "./sync";
import { experiments, ExperimentsAction } from "./experiments";
import { lastOpened, LastOpenedAction } from "./last_opened";
import { locale, LocaleAction } from "./locale";
import { notifications, NotificationsAction } from "./notifications";
import { queue, QueueAction } from "./queue";
import { sectionToggle, SectionToggleAction } from "./section_toggle";
import { config, ConfigAction } from "./server_config";
import { settings, SettingsAction } from "./settings";
import { sync, SyncAction } from "./sync";
import { unreads, UnreadsAction } from "./unreads";
export default combineReducers({
config,
......@@ -21,12 +21,12 @@ export default combineReducers({
settings,
unreads,
queue,
typing,
drafts,
sync,
experiments,
lastOpened,
notifications
notifications,
sectionToggle,
});
export type Action =
......@@ -36,24 +36,10 @@ export type Action =
| SettingsAction
| UnreadsAction
| QueueAction
| TypingAction
| DraftAction
| SyncAction
| ExperimentsAction
| LastOpenedAction
| NotificationsAction
| SectionToggleAction
| { type: "__INIT"; state: State };
export type WithDispatcher = { dispatcher: (action: Action) => void };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function filter(obj: any, keys: string[]) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const newObj: any = {};
for (const key of keys) {
const v = obj[key];
if (v) newObj[key] = v;
}
return newObj;
}
export interface LastOpened {
[key: string]: string
[key: string]: string;
}
export type LastOpenedAction =
| { type: undefined }
| {
type: "LAST_OPENED_SET";
parent: string;
child: string;
type: "LAST_OPENED_SET";
parent: string;
child: string;
}
| {
type: "RESET";
};
export function lastOpened(state = {} as LastOpened, action: LastOpenedAction): LastOpened {
export function lastOpened(
state = {} as LastOpened,
action: LastOpenedAction,
): LastOpened {
switch (action.type) {
case "LAST_OPENED_SET": {
return {
...state,
[action.parent]: action.child
}
[action.parent]: action.child,
};
}
case "RESET":
return {};
......
import { Language } from "../../context/Locale";
import type { SyncUpdateAction } from "./sync";
export type LocaleAction =
......
import type { Channel, Message } from "revolt.js";
import { Channel } from "revolt.js/dist/maps/Channels";
import { Message } from "revolt.js/dist/maps/Messages";
import type { SyncUpdateAction } from "./sync";
export type NotificationState = 'all' | 'mention' | 'none' | 'muted';
export type NotificationState = "all" | "mention" | "none" | "muted";
export type Notifications = {
[key: string]: NotificationState
}
[key: string]: NotificationState;
};
export const DEFAULT_STATES: { [key in Channel['channel_type']]: NotificationState } = {
'SavedMessages': 'all',
'DirectMessage': 'all',
'Group': 'all',
'TextChannel': 'mention',
'VoiceChannel': 'mention'
export const DEFAULT_STATES: {
[key in Channel["channel_type"]]: NotificationState;
} = {
SavedMessages: "all",
DirectMessage: "all",
Group: "all",
TextChannel: "mention",
VoiceChannel: "mention",
};
export function getNotificationState(notifications: Notifications, channel: Channel) {
export function getNotificationState(
notifications: Notifications,
channel: Channel,
) {
return notifications[channel._id] ?? DEFAULT_STATES[channel.channel_type];
}
export function shouldNotify(state: NotificationState, message: Message, user_id: string) {
export function shouldNotify(
state: NotificationState,
message: Message,
user_id: string,
) {
switch (state) {
case 'muted':
case 'none': return false;
case 'mention': {
if (!message.mentions?.includes(user_id)) return false;
case "muted":
case "none":
return false;
case "mention": {
if (!message.mention_ids?.includes(user_id)) return false;
}
}
......@@ -34,34 +46,33 @@ export function shouldNotify(state: NotificationState, message: Message, user_id
export type NotificationsAction =
| { type: undefined }
| {
type: "NOTIFICATIONS_SET";
key: string;
state: NotificationState;
type: "NOTIFICATIONS_SET";
key: string;
state: NotificationState;
}
| {
type: "NOTIFICATIONS_REMOVE";
key: string;
type: "NOTIFICATIONS_REMOVE";
key: string;
}
| SyncUpdateAction
| {
type: "RESET";
type: "RESET";
};
export function notifications(
state = {} as Notifications,
action: NotificationsAction
action: NotificationsAction,
): Notifications {
switch (action.type) {
case "NOTIFICATIONS_SET":
return {
...state,
[action.key]: action.state
[action.key]: action.state,
};
case "NOTIFICATIONS_REMOVE":
{
const { [action.key]: _, ...newState } = state;
return newState;
}
case "NOTIFICATIONS_REMOVE": {
const { [action.key]: _, ...newState } = state;
return newState;
}
case "SYNC_UPDATE":
return action.update.notifications?.[1] ?? state;
case "RESET":
......
import type { MessageObject } from "../../context/revoltjs/util";
export enum QueueStatus {
SENDING = "sending",
ERRORED = "errored",
}
export interface Reply {
id: string,
mention: boolean
id: string;
mention: boolean;
}
export type QueuedMessageData = Omit<MessageObject, 'content' | 'replies'> & {
export type QueuedMessageData = {
_id: string;
author: string;
channel: string;
content: string;
replies: Reply[];
}
};
export interface QueuedMessage {
id: string;
......@@ -56,7 +58,7 @@ export type QueueAction =
export function queue(
state: QueuedMessage[] = [],
action: QueueAction
action: QueueAction,
): QueuedMessage[] {
switch (action.type) {
case "QUEUE_ADD": {
......@@ -72,7 +74,7 @@ export function queue(
}
case "QUEUE_FAIL": {
const entry = state.find(
(x) => x.id === action.nonce
(x) => x.id === action.nonce,
) as QueuedMessage;
return [
...state.filter((x) => x.id !== action.nonce),
......@@ -85,7 +87,7 @@ export function queue(
}
case "QUEUE_START": {
const entry = state.find(
(x) => x.id === action.nonce
(x) => x.id === action.nonce,
) as QueuedMessage;
return [
...state.filter((x) => x.id !== action.nonce),
......
export interface SectionToggle {
[key: string]: boolean;
}
export type SectionToggleAction =
| { type: undefined }
| {
type: "SECTION_TOGGLE_SET";
id: string;
state: boolean;
}
| {
type: "SECTION_TOGGLE_UNSET";
id: string;
}
| {
type: "RESET";
};
export function sectionToggle(
state = {} as SectionToggle,
action: SectionToggleAction,
): SectionToggle {
switch (action.type) {
case "SECTION_TOGGLE_SET": {
return {
...state,
[action.id]: action.state,
};
}
case "SECTION_TOGGLE_UNSET": {
const { [action.id]: _, ...newState } = state;
return newState;
}
case "RESET":
return {};
default:
return state;
}
}
import type { Core } from "revolt.js/dist/api/objects";
import type { RevoltConfiguration } from "revolt-api/types/Core";
export type ConfigAction =
| { type: undefined }
| {
type: "SET_CONFIG";
config: Core.RevoltNodeConfiguration;
type: "SET_CONFIG";
config: RevoltConfiguration;
};
export function config(
state = { } as Core.RevoltNodeConfiguration,
action: ConfigAction
): Core.RevoltNodeConfiguration {
state = {} as RevoltConfiguration,
action: ConfigAction,
): RevoltConfiguration {
switch (action.type) {
case "SET_CONFIG":
return action.config;
......
import { filter } from ".";
import type { SyncUpdateAction } from "./sync";
import type { Sounds } from "../../assets/sounds/Audio";
import type { Theme, ThemeOptions } from "../../context/Theme";
import { setEmojiPack } from "../../components/common/Emoji";
import type { Sounds } from "../../assets/sounds/Audio";
import type { SyncUpdateAction } from "./sync";
export type SoundOptions = {
[key in Sounds]?: boolean
}
[key in Sounds]?: boolean;
};
export const DEFAULT_SOUNDS: SoundOptions = {
message: true,
outbound: false,
call_join: true,
call_leave: true
call_leave: true,
};
export interface NotificationOptions {
desktopEnabled?: boolean;
sounds?: SoundOptions
sounds?: SoundOptions;
}
export type EmojiPacks = "mutant" | "twemoji" | "noto" | "openmoji";
......@@ -56,16 +57,16 @@ export type SettingsAction =
export function settings(
state = {} as Settings,
action: SettingsAction
action: SettingsAction,
): Settings {
setEmojiPack(state.appearance?.emojiPack ?? 'mutant');
setEmojiPack(state.appearance?.emojiPack ?? "mutant");
switch (action.type) {
case "SETTINGS_SET_THEME":
return {
...state,
theme: {
...filter(state.theme, ["custom", "preset"]),
...state.theme,
...action.theme,
},
};
......@@ -92,7 +93,7 @@ export function settings(
return {
...state,
appearance: {
...filter(state.appearance, ["emojiPack"]),
...state.appearance,
...action.options,
},
};
......
import type { AppearanceOptions } from "./settings";
import type { Language } from "../../context/Locale";
import type { ThemeOptions } from "../../context/Theme";
import type { Notifications } from "./notifications";
import type { AppearanceOptions } from "./settings";
export type SyncKeys = "theme" | "appearance" | "locale" | "notifications";
......@@ -16,7 +17,7 @@ export const DEFAULT_ENABLED_SYNC: SyncKeys[] = [
"theme",
"appearance",
"locale",
"notifications"
"notifications",
];
export interface SyncOptions {
disabled?: SyncKeys[];
......@@ -49,7 +50,7 @@ export type SyncAction =
export function sync(
state = {} as SyncOptions,
action: SyncAction
action: SyncAction,
): SyncOptions {
switch (action.type) {
case "SYNC_DISABLE_KEY":
......
export type TypingUser = { id: string; started: number };
export type Typing = { [key: string]: TypingUser[] };
export type TypingAction =
| { type: undefined }
| {
type: "TYPING_START";
channel: string;
user: string;
}
| {
type: "TYPING_STOP";
channel: string;
user: string;
}
| {
type: "RESET";
};
export function typing(state: Typing = {}, action: TypingAction): Typing {
switch (action.type) {
case "TYPING_START":
return {
...state,
[action.channel]: [
...(state[action.channel] ?? []).filter(
(x) => x.id !== action.user
),
{
id: action.user,
started: +new Date(),
},
],
};
case "TYPING_STOP":
return {
...state,
[action.channel]:
state[action.channel]?.filter(
(x) => x.id !== action.user
) ?? [],
};
case "RESET":
return {};
default:
return state;
}
}
import type { Sync } from "revolt.js/dist/api/objects";
import type { ChannelUnread } from "revolt-api/types/Sync";
export interface Unreads {
[key: string]: Partial<Omit<Sync.ChannelUnread, "_id">>;
[key: string]: Partial<Omit<ChannelUnread, "_id">>;
}
export type UnreadsAction =
......@@ -13,7 +13,7 @@ export type UnreadsAction =
}
| {
type: "UNREADS_SET";
unreads: Sync.ChannelUnread[];
unreads: ChannelUnread[];
}
| {
type: "UNREADS_MENTION";
......
export const REPO_URL = 'https://gitlab.insrt.uk/revolt/revite/-/commit';
export const GIT_REVISION = '__GIT_REVISION__';
export const GIT_BRANCH: string = '__GIT_BRANCH__';
/* eslint-disable */
// Strings needs to be explictly stated here as they can cause type issues elsewhere.
export const REPO_URL: string =
"https://gitlab.insrt.uk/revolt/revite/-/commit";
export const GIT_REVISION: string = "__GIT_REVISION__";
export const GIT_BRANCH: string = "__GIT_BRANCH__";
.preact-context-menu .context-menu {
z-index: 100;
min-width: 180px;
z-index: 5000;
min-width: 190px;
padding: 6px 8px;
user-select: none;
border-radius: 4px;
font-size: .875rem;
color: var(--secondary-foreground);
border-radius: var(--border-radius);
background: var(--primary-background) !important;
box-shadow: 0px 0px 8px 8px rgba(0, 0, 0, 0.05);
> span {
> span, .main > span {
gap: 6px;
margin: 2px 0;
display: flex;
padding: 6px 8px;
border-radius: 3px;
font-size: .875rem;
align-items: center;
white-space: nowrap;
......@@ -33,6 +33,41 @@
color: var(--tertiary-foreground);
}
}
}
.context-menu.Status {
.header {
gap: 8px;
display: flex;
padding: 6px 8px;
font-weight: 600;
align-items: center;
color: var(--foreground);
.main {
flex-grow: 1;
display: flex;
flex-direction: column;
.username {
> div {
cursor: pointer;
width: fit-content;
}
}
}
.status {
cursor: pointer;
max-width: 132px;
font-size: .625rem;
color: var(--secondary-foreground);
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
.indicator {
width: 8px;
......