Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
faa3e3f
fix: Fix for #541, add call button to header bar instead
Pecacheu Feb 14, 2026
4420084
fix: More mobile UI fixes
Pecacheu Feb 14, 2026
abf0f07
fix: Mobile UI marches onwards
Pecacheu Feb 15, 2026
54ea8eb
fix: Add back button to Channel & Server settings
Pecacheu Feb 15, 2026
7c009fc
fix: Simplified CSS
Pecacheu Feb 15, 2026
eef9460
fix: Move the CSS again & misc
Pecacheu Feb 16, 2026
8457242
revert: Undo propagation bugfix due to issues on non-Chromium browsers
Pecacheu Feb 17, 2026
e8df227
feat: Mobile UI
Pecacheu Feb 17, 2026
0c5f6f9
feat: Slide that SlideDrawer real smooth-like
Pecacheu Feb 18, 2026
ec8a954
fix: Enable mobile autocorrect and autocapitalize on text input
Pecacheu Feb 18, 2026
33756e6
fix: Un-round them corners for portrait phone view
Pecacheu Feb 19, 2026
e9361e0
fix: Hide message content when message is image link (not only for GIFs)
Pecacheu Feb 21, 2026
4f02b28
fix: Sensible margains for modals on mobile to prevent weird scroll b…
Pecacheu Feb 21, 2026
3d35317
feat: TryPWA install dialog
Pecacheu Feb 21, 2026
020aa17
fix: Small chance that two modals have the same ID using Math.random …
Pecacheu Feb 22, 2026
077f2a1
fix: Clicking channels/DMs/settings buttons opens slide drawer on mobile
Pecacheu Feb 22, 2026
8c03d29
fix: Cease this madness
Pecacheu Feb 22, 2026
b78b8bb
revert: Undo committed temp changes
Pecacheu Feb 23, 2026
65fa48d
fix: Disable context menu when editing message, so you can accept OS …
Pecacheu Feb 23, 2026
eb94aef
fix: Second ':' should auto-relace the typed emoji
Pecacheu Feb 27, 2026
0e38155
fix: Off-center loading progress wheel is now nice n centered
Pecacheu Feb 27, 2026
c082004
feat: Adding a react button to the context menu was harder than antic…
Pecacheu Feb 28, 2026
e950b43
fix: dismiss other open user cards when opening a new one
ajlittle Mar 2, 2026
5a8f45e
chore: generate i18n catalog
stoat-ci Mar 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/client/assets
134 changes: 84 additions & 50 deletions packages/client/components/app/interface/channels/text/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useState } from "@revolt/state";
import {
Attachment,
Avatar,
CompositionMediaPicker,
Embed,
MessageContainer,
MessageReply,
Expand All @@ -30,6 +31,8 @@ import {
floatingUserMenusFromMessage,
} from "../../../menus/UserContextMenu";

import { startsWithPackPUA } from "@revolt/markdown/emoji/UnicodeEmoji";
import { MediaPickerProps } from "@revolt/ui/components/features/messaging/composition/picker/CompositionMediaPicker";
import { EditMessage } from "./EditMessage";

/**
Expand Down Expand Up @@ -75,18 +78,22 @@ export function Message(props: Props) {
const client = useClient();

const [isHovering, setIsHovering] = createSignal(false);
const [reactPicker, setReactPicker] = createSignal<MediaPickerProps>();
let msgRef;

/**
* Determine whether this message only contains a GIF
* Determine if this message only contains an image
*/
const isOnlyGIF = () =>
const isOnlyImg = () =>
props.message.embeds &&
props.message.embeds.length === 1 &&
props.message.embeds[0].type === "Website" &&
((props.message.embeds[0] as WebsiteEmbed).specialContent?.type === "GIF" ||
(props.message.embeds[0] as WebsiteEmbed).originalUrl?.startsWith(
"https://tenor.com",
)) &&
(props.message.embeds[0].type === "Image" ||
(props.message.embeds[0].type === "Website" &&
((props.message.embeds[0] as WebsiteEmbed).specialContent?.type ===
"GIF" ||
(props.message.embeds[0] as WebsiteEmbed).originalUrl?.startsWith(
"https://tenor.com",
)))) &&
props.message.content &&
!props.message.content.replace(RE_URL, "").length;

Expand All @@ -104,6 +111,8 @@ export function Message(props: Props) {

return (
<MessageContainer
ref={msgRef}
reactPicker={reactPicker}
message={props.message}
onHover={setIsHovering}
username={
Expand Down Expand Up @@ -135,7 +144,16 @@ export function Message(props: Props) {
/>
</div>
}
contextMenu={() => <MessageContextMenu message={props.message} />}
contextMenu={
props.editing
? undefined
: () => (
<MessageContextMenu
message={props.message}
reactPicker={reactPicker}
/>
)
}
timestamp={props.message.createdAt}
edited={props.message.editedAt}
mentioned={props.message.mentioned}
Expand All @@ -144,31 +162,29 @@ export function Message(props: Props) {
isLink={props.isLink}
tail={props.tail || state.settings.getValue("appearance:compact_mode")}
header={
<Show when={props.message.replyIds}>
<For each={props.message.replyIds}>
{(reply_id) => {
/**
* Signal the actual message
*/
const message = () => client().messages.get(reply_id);
<For each={props.message.replyIds}>
{(reply_id) => {
/**
* Signal the actual message
*/
const message = () => client().messages.get(reply_id);

onMount(() => {
if (!message()) {
props.message.channel!.fetchMessage(reply_id);
}
});
onMount(() => {
if (!message()) {
props.message.channel!.fetchMessage(reply_id);
}
});

return (
<MessageReply
mention={props.message.mentionIds?.includes(
message()!.authorId!,
)}
message={message()}
/>
);
}}
</For>
</Show>
return (
<MessageReply
mention={props.message.mentionIds?.includes(
message()!.authorId!,
)}
message={message()}
/>
);
}}
</For>
}
info={
<Switch fallback={<div />}>
Expand Down Expand Up @@ -257,6 +273,29 @@ export function Message(props: Props) {
</Match>
}
>
<CompositionMediaPicker
onMessage={(content) =>
props.message?.channel?.sendMessage({
content,
replies: [{ id: props.message.id, mention: true }],
})
}
onTextReplacement={(emoji) =>
react(
emoji.startsWith(":")
? emoji.slice(1, emoji.length - 1)
: startsWithPackPUA(emoji)
? emoji.slice(1)
: emoji,
)
}
>
{(trigProps) => {
trigProps.ref(msgRef);
setReactPicker(trigProps);
return <></>;
}}
</CompositionMediaPicker>
<Show when={props.message.systemMessage}>
<SystemMessage
systemMessage={props.message.systemMessage!}
Expand All @@ -276,36 +315,31 @@ export function Message(props: Props) {
<Match when={props.editing}>
<EditMessage message={props.message} />
</Match>
<Match when={props.message.content && !isOnlyGIF()}>
<Match when={props.message.content && !isOnlyImg()}>
<BreakText>
<Markdown content={props.message.content!} />
</BreakText>
</Match>
</Switch>
<Show when={props.message.attachments}>
<For each={props.message.attachments}>
{(attachment) => (
<Attachment message={props.message} file={attachment} />
)}
</For>
</Show>
<Show when={props.message.embeds}>
<For each={props.message.embeds}>
{(embed) => <Embed embed={embed} />}
</For>
</Show>
<For each={props.message.attachments}>
{(attachment) => (
<Attachment
message={props.message}
file={attachment}
reactPicker={reactPicker}
/>
)}
</For>
<For each={props.message.embeds}>
{(embed) => <Embed embed={embed} />}
</For>
<Reactions
reactions={props.message.reactions as never as Map<string, Set<string>>}
interactions={props.message.interactions}
userId={client().user!.id}
addReaction={react}
removeReaction={unreact}
sendGIF={(content) =>
props.message?.channel?.sendMessage({
content,
replies: [{ id: props.message.id, mention: true }],
})
}
reactPicker={reactPicker}
/>
</MessageContainer>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { ChannelPermissionsEditor } from "./channel/permissions/ChannelPermissio
import { ChannelPermissionsOverview } from "./channel/permissions/ChannelPermissionsOverview";
import { ViewWebhook } from "./channel/webhooks/ViewWebhook";
import { WebhooksList } from "./channel/webhooks/WebhooksList";
import { BackCard } from "./user/_AccountCard";

const Config: SettingsConfiguration<Channel> = {
/**
Expand Down Expand Up @@ -98,11 +99,12 @@ const Config: SettingsConfiguration<Channel> = {
* Generate list of categories / entries for channel settings
* @returns List
*/
list(channel) {
list(channel, onClose) {
const { openModal } = useModals();

return {
context: channel,
prepend: <BackCard onClose={onClose} />,
entries: [
{
title: <TextWithEmoji content={channel.name} />,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { EmojiList } from "./server/emojis/EmojiList";
import { ListServerInvites } from "./server/invites/ListServerInvites";
import { ServerRoleEditor } from "./server/roles/ServerRoleEditor";
import { ServerRoleOverview } from "./server/roles/ServerRoleOverview";
import { BackCard } from "./user/_AccountCard";

const Config: SettingsConfiguration<Server> = {
/**
Expand Down Expand Up @@ -89,12 +90,13 @@ const Config: SettingsConfiguration<Server> = {
* Generate list of categories / entries for server settings
* @returns List
*/
list(server) {
list(server, onClose) {
const user = useUser();
const { openModal } = useModals();

return {
context: server,
prepend: <BackCard onClose={onClose} />,
entries: [
{
title: <TextWithEmoji content={server.name} />,
Expand Down
16 changes: 12 additions & 4 deletions packages/client/components/app/interface/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
createContext,
createMemo,
createSignal,
Setter,
untrack,
useContext,
} from "solid-js";
Expand All @@ -25,6 +26,8 @@ export interface SettingsProps {
* Settings context
*/
context: never;

contentRef: Setter<HTMLDivElement | undefined>;
}

/**
Expand Down Expand Up @@ -89,11 +92,16 @@ export function Settings(props: SettingsProps & SettingsConfiguration<never>) {
navigate,
}}
>
<MemoisedList context={props.context} list={props.list}>
<MemoisedList
context={props.context}
list={props.list}
onClose={props.onClose}
>
{(list) => (
<>
<SettingsSidebar list={list} page={page} setPage={setPage} />
<SettingsContent
ref={props.contentRef}
page={page}
list={list}
title={props.title}
Expand Down Expand Up @@ -156,14 +164,14 @@ export function Settings(props: SettingsProps & SettingsConfiguration<never>) {
*/
function MemoisedList(props: {
context: never;
list: (context: never) => SettingsList<unknown>;
onClose?: () => void;
list: (context: never, onClose?: () => void) => SettingsList<unknown>;
children: (list: Accessor<SettingsList<unknown>>) => JSX.Element;
}) {
/**
* Generate list of categories / links
*/
const list = createMemo(() => props.list(props.context));

const list = createMemo(() => props.list(props.context, props.onClose));
return <>{props.children(list)}</>;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { Feedback } from "./user/Feedback";
import { LanguageSettings } from "./user/Language";
import Native from "./user/Native";
import { Sessions } from "./user/Sessions";
import { AccountCard } from "./user/_AccountCard";
import { AccountCard, BackCard } from "./user/_AccountCard";
import { AppearanceMenu } from "./user/appearance";
import { MyBots, ViewBot } from "./user/bots";
import { EditProfile } from "./user/profile";
Expand Down Expand Up @@ -112,14 +112,15 @@ const Config: SettingsConfiguration<{ server: Server }> = {
* Generate list of categories / entries for client settings
* @returns List
*/
list() {
list(_, onClose) {
const { pop } = useModals();
const { logout } = useClientLifecycle();

return {
context: null!,
prepend: (
<Column gap="s">
<BackCard onClose={onClose} />
<AccountCard />
<div />
</Column>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Accessor, JSX, Show } from "solid-js";
import { Accessor, JSX, Setter, Show } from "solid-js";

import { css, cva } from "styled-system/css";
import { styled } from "styled-system/jsx";
Expand All @@ -19,34 +19,33 @@ export function SettingsContent(props: {
list: Accessor<SettingsList<unknown>>;
title: (ctx: SettingsList<never>, key: string) => string;
page: Accessor<string | undefined>;
ref: Setter<HTMLDivElement | undefined>;
}) {
const { navigate } = useSettingsNavigation();

return (
<div
use:scrollable={{
class: base(),
}}
>
<div ref={props.ref} class="setBase" use:scrollable={{ class: base() }}>
<Show when={props.page()}>
<InnerContent>
<InnerContent class="setCont">
<InnerColumn>
<Text class="title" size="large">
<Breadcrumbs
elements={props.page()!.split("/")}
renderElement={(key) =>
props.title(props.list() as SettingsList<never>, key)
}
navigate={(keys) => navigate(keys.join("/"))}
/>
</Text>
<Show when={props.page() !== "account"}>
<Text class="title" size="large">
<Breadcrumbs
elements={props.page()!.split("/")}
renderElement={(key) =>
props.title(props.list() as SettingsList<never>, key)
}
navigate={(keys) => navigate(keys.join("/"))}
/>
</Text>
</Show>
{props.children}
<div class={css({ minHeight: "80px" })} />
</InnerColumn>
</InnerContent>
</Show>
<Show when={props.onClose}>
<CloseAction>
<CloseAction class="setClose">
<IconButton variant="tonal" onPress={props.onClose}>
<MdClose />
</IconButton>
Expand Down Expand Up @@ -121,7 +120,7 @@ const CloseAction = styled("div", {
marginTop: "4px",
display: "flex",
justifyContent: "center",
width: "36px",
width: "40px",
fontWeight: 600,
color: "var(--md-sys-color-on-surface)",
fontSize: "0.75rem",
Expand Down
Loading