mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-26 09:00:57 -05:00
Improve category design for members list.
Implement new list for groups.
This commit is contained in:
parent
f2c59ae451
commit
5c45e29f92
3 changed files with 179 additions and 328 deletions
|
@ -1,23 +1,32 @@
|
||||||
import AutoSizer from "react-virtualized-auto-sizer";
|
import AutoSizer from "react-virtualized-auto-sizer";
|
||||||
import { FixedSizeList as List } from "react-window";
|
import { VariableSizeList as List } from "react-window";
|
||||||
|
import { Channel } from "revolt.js/dist/maps/Channels";
|
||||||
import { User } from "revolt.js/dist/maps/Users";
|
import { User } from "revolt.js/dist/maps/Users";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
import { Text } from "preact-i18n";
|
import { Text } from "preact-i18n";
|
||||||
import { forwardRef } from "preact/compat";
|
import { forwardRef } from "preact/compat";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Screen,
|
||||||
|
useIntermediate,
|
||||||
|
} from "../../../context/intermediate/Intermediate";
|
||||||
|
|
||||||
import { UserButton } from "../items/ButtonItem";
|
import { UserButton } from "../items/ButtonItem";
|
||||||
|
|
||||||
export type MemberListEntry = string | User;
|
export type MemberListEntry = string | User;
|
||||||
interface ItemData {
|
interface ItemData {
|
||||||
entries: MemberListEntry[];
|
entries: MemberListEntry[];
|
||||||
|
context: Channel;
|
||||||
|
openScreen: (screen: Screen) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PADDING_SIZE = 6;
|
const PADDING_SIZE = 6;
|
||||||
|
|
||||||
const ListCategory = styled.div`
|
const ListCategory = styled.div`
|
||||||
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 14px;
|
padding: 0 14px;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
@ -28,7 +37,7 @@ const ListCategory = styled.div`
|
||||||
|
|
||||||
const Row = ({
|
const Row = ({
|
||||||
data,
|
data,
|
||||||
style,
|
style: styleIn,
|
||||||
index,
|
index,
|
||||||
}: {
|
}: {
|
||||||
data: ItemData;
|
data: ItemData;
|
||||||
|
@ -36,7 +45,10 @@ const Row = ({
|
||||||
style: JSX.CSSProperties;
|
style: JSX.CSSProperties;
|
||||||
}) => {
|
}) => {
|
||||||
const item = data.entries[index];
|
const item = data.entries[index];
|
||||||
style.top = `${parseFloat(style.top as string) + PADDING_SIZE}px`;
|
const style = {
|
||||||
|
...styleIn,
|
||||||
|
top: `${parseFloat(styleIn.top as string) + PADDING_SIZE}px`,
|
||||||
|
};
|
||||||
|
|
||||||
if (typeof item === "string") {
|
if (typeof item === "string") {
|
||||||
const [cat, count] = item.split(":");
|
const [cat, count] = item.split(":");
|
||||||
|
@ -61,13 +73,13 @@ const Row = ({
|
||||||
key={item._id}
|
key={item._id}
|
||||||
user={item}
|
user={item}
|
||||||
margin
|
margin
|
||||||
/* context={channel}
|
context={data.context}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
openScreen({
|
data.openScreen({
|
||||||
id: "profile",
|
id: "profile",
|
||||||
user_id: user._id,
|
user_id: item._id,
|
||||||
})
|
})
|
||||||
} */
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -89,22 +101,29 @@ const innerElementType = forwardRef(({ style, ...rest }, ref) => (
|
||||||
|
|
||||||
export default function MemberList({
|
export default function MemberList({
|
||||||
entries,
|
entries,
|
||||||
|
context,
|
||||||
}: {
|
}: {
|
||||||
entries: MemberListEntry[];
|
entries: MemberListEntry[];
|
||||||
|
context: Channel;
|
||||||
}) {
|
}) {
|
||||||
|
const { openScreen } = useIntermediate();
|
||||||
return (
|
return (
|
||||||
<AutoSizer>
|
<AutoSizer>
|
||||||
{({ width, height }) => (
|
{({ width, height }) => (
|
||||||
<List
|
<List
|
||||||
className="virtualList"
|
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
itemData={{
|
itemData={{
|
||||||
entries,
|
entries,
|
||||||
|
context,
|
||||||
|
openScreen,
|
||||||
}}
|
}}
|
||||||
itemCount={entries.length}
|
itemCount={entries.length}
|
||||||
innerElementType={innerElementType}
|
innerElementType={innerElementType}
|
||||||
itemSize={42}>
|
itemSize={(index) =>
|
||||||
|
typeof entries[index] === "string" ? 24 : 42
|
||||||
|
}
|
||||||
|
estimatedItemSize={42}>
|
||||||
{
|
{
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
Row as any
|
Row as any
|
||||||
|
|
|
@ -1,34 +1,20 @@
|
||||||
/* eslint-disable react-hooks/rules-of-hooks */
|
/* eslint-disable react-hooks/rules-of-hooks */
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Link, useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { Presence } from "revolt-api/types/Users";
|
import { Presence } from "revolt-api/types/Users";
|
||||||
import { Channel } from "revolt.js/dist/maps/Channels";
|
import { Channel } from "revolt.js/dist/maps/Channels";
|
||||||
import { Message } from "revolt.js/dist/maps/Messages";
|
|
||||||
import { User } from "revolt.js/dist/maps/Users";
|
import { User } from "revolt.js/dist/maps/Users";
|
||||||
|
|
||||||
import { Text } from "preact-i18n";
|
import { useContext, useEffect, useMemo } from "preact/hooks";
|
||||||
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
|
|
||||||
|
|
||||||
import { getState } from "../../../redux";
|
|
||||||
|
|
||||||
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
|
||||||
import {
|
import {
|
||||||
ClientStatus,
|
ClientStatus,
|
||||||
StatusContext,
|
StatusContext,
|
||||||
useClient,
|
useClient,
|
||||||
} from "../../../context/revoltjs/RevoltClient";
|
} from "../../../context/revoltjs/RevoltClient";
|
||||||
|
|
||||||
import CollapsibleSection from "../../common/CollapsibleSection";
|
import { GenericSidebarBase } from "../SidebarBase";
|
||||||
import Button from "../../ui/Button";
|
import MemberList from "./MemberList";
|
||||||
import Category from "../../ui/Category";
|
|
||||||
import InputBox from "../../ui/InputBox";
|
|
||||||
import Preloader from "../../ui/Preloader";
|
|
||||||
import placeholderSVG from "../items/placeholder.svg";
|
|
||||||
|
|
||||||
import { GenericSidebarBase, GenericSidebarList } from "../SidebarBase";
|
|
||||||
import { UserButton } from "../items/ButtonItem";
|
|
||||||
import { ChannelDebugInfo } from "./ChannelDebugInfo";
|
|
||||||
import MemberList, { MemberListEntry } from "./MemberList";
|
|
||||||
|
|
||||||
export default function MemberSidebar({ channel: obj }: { channel?: Channel }) {
|
export default function MemberSidebar({ channel: obj }: { channel?: Channel }) {
|
||||||
const { channel: channel_id } = useParams<{ channel: string }>();
|
const { channel: channel_id } = useParams<{ channel: string }>();
|
||||||
|
@ -45,116 +31,77 @@ export default function MemberSidebar({ channel: obj }: { channel?: Channel }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GroupMemberSidebar = observer(
|
function useEntries(channel: Channel, keys: string[], isServer?: boolean) {
|
||||||
({ channel }: { channel: Channel }) => {
|
const client = channel.client;
|
||||||
const { openScreen } = useIntermediate();
|
return useMemo(() => {
|
||||||
|
const categories: { [key: string]: [User, string][] } = {
|
||||||
|
online: [],
|
||||||
|
offline: [],
|
||||||
|
};
|
||||||
|
|
||||||
const members = channel.recipients?.filter(
|
keys.forEach((key) => {
|
||||||
(x) => typeof x !== "undefined",
|
let u;
|
||||||
);
|
if (isServer) {
|
||||||
|
const { server, user } = JSON.parse(key);
|
||||||
/*const voice = useContext(VoiceContext);
|
if (server !== channel.server_id) return;
|
||||||
const voiceActive = voice.roomId === channel._id;
|
u = client.users.get(user);
|
||||||
|
} else {
|
||||||
let voiceParticipants: User[] = [];
|
u = client.users.get(key);
|
||||||
if (voiceActive) {
|
|
||||||
const idArray = Array.from(voice.participants.keys());
|
|
||||||
voiceParticipants = idArray
|
|
||||||
.map(x => users.find(y => y?._id === x))
|
|
||||||
.filter(x => typeof x !== "undefined") as User[];
|
|
||||||
|
|
||||||
members = members.filter(member => idArray.indexOf(member._id) === -1);
|
|
||||||
|
|
||||||
voiceParticipants.sort((a, b) => a.username.localeCompare(b.username));
|
|
||||||
}*/
|
|
||||||
|
|
||||||
members?.sort((a, b) => {
|
|
||||||
// ! FIXME: should probably rewrite all this code
|
|
||||||
const l =
|
|
||||||
+(
|
|
||||||
(a!.online && a!.status?.presence !== Presence.Invisible) ??
|
|
||||||
false
|
|
||||||
) | 0;
|
|
||||||
const r =
|
|
||||||
+(
|
|
||||||
(b!.online && b!.status?.presence !== Presence.Invisible) ??
|
|
||||||
false
|
|
||||||
) | 0;
|
|
||||||
|
|
||||||
const n = r - l;
|
|
||||||
if (n !== 0) {
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return a!.username.localeCompare(b!.username);
|
if (!u) return;
|
||||||
|
|
||||||
|
const member = client.members.get(key);
|
||||||
|
const sort = member?.nickname ?? u.username;
|
||||||
|
const entry = [u, sort] as [User, string];
|
||||||
|
|
||||||
|
if (isServer) {
|
||||||
|
// Sort users into hoisted roles here.
|
||||||
|
} else {
|
||||||
|
// Sort users into "participants" list here.
|
||||||
|
// For voice calls.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!u.online || u.status?.presence === Presence.Invisible) {
|
||||||
|
categories.offline.push(entry);
|
||||||
|
} else {
|
||||||
|
categories.online.push(entry);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Object.keys(categories).forEach((key) =>
|
||||||
|
categories[key].sort((a, b) => a[1].localeCompare(b[1])),
|
||||||
|
);
|
||||||
|
|
||||||
|
const entries = [];
|
||||||
|
|
||||||
|
if (categories.online.length > 0) {
|
||||||
|
entries.push(
|
||||||
|
`online:${categories.online.length}`,
|
||||||
|
...categories.online.map((x) => x[0]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (categories.offline.length > 0) {
|
||||||
|
entries.push(
|
||||||
|
`offline:${categories.offline.length}`,
|
||||||
|
...categories.offline.map((x) => x[0]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, [keys]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GroupMemberSidebar = observer(
|
||||||
|
({ channel }: { channel: Channel }) => {
|
||||||
|
const keys = [...channel.recipient_ids!];
|
||||||
|
const entries = useEntries(channel, keys);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GenericSidebarBase>
|
<GenericSidebarBase>
|
||||||
<GenericSidebarList>
|
<MemberList entries={entries} context={channel} />
|
||||||
<Search channel={channel} />
|
|
||||||
|
|
||||||
{/*voiceActive && voiceParticipants.length !== 0 && (
|
|
||||||
<Fragment>
|
|
||||||
<Category
|
|
||||||
type="members"
|
|
||||||
text={
|
|
||||||
<span>
|
|
||||||
<Text id="app.main.categories.participants" />{" "}
|
|
||||||
— {voiceParticipants.length}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{voiceParticipants.map(
|
|
||||||
user =>
|
|
||||||
user && (
|
|
||||||
<LinkProfile user_id={user._id}>
|
|
||||||
<UserButton
|
|
||||||
key={user._id}
|
|
||||||
user={user}
|
|
||||||
context={channel}
|
|
||||||
/>
|
|
||||||
</LinkProfile>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
)*/}
|
|
||||||
<CollapsibleSection
|
|
||||||
sticky
|
|
||||||
id="members"
|
|
||||||
defaultValue
|
|
||||||
summary={
|
|
||||||
<Category
|
|
||||||
variant="uniform"
|
|
||||||
text={
|
|
||||||
<span>
|
|
||||||
<Text id="app.main.categories.members" />{" "}
|
|
||||||
— {channel.recipients?.length ?? 0}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}>
|
|
||||||
{members?.length === 0 && (
|
|
||||||
<img src={placeholderSVG} loading="eager" />
|
|
||||||
)}
|
|
||||||
{members?.map(
|
|
||||||
(user) =>
|
|
||||||
user && (
|
|
||||||
<UserButton
|
|
||||||
key={user._id}
|
|
||||||
user={user}
|
|
||||||
context={channel!}
|
|
||||||
onClick={() =>
|
|
||||||
openScreen({
|
|
||||||
id: "profile",
|
|
||||||
user_id: user._id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</CollapsibleSection>
|
|
||||||
</GenericSidebarList>
|
|
||||||
</GenericSidebarBase>
|
</GenericSidebarBase>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -172,207 +119,12 @@ export const ServerMemberSidebar = observer(
|
||||||
}, [status, channel.server]);
|
}, [status, channel.server]);
|
||||||
|
|
||||||
const keys = [...client.members.keys()];
|
const keys = [...client.members.keys()];
|
||||||
const entries = useMemo(() => {
|
const entries = useEntries(channel, keys, true);
|
||||||
const categories: { [key: string]: [User, string][] } = {
|
|
||||||
online: [],
|
|
||||||
offline: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
keys.forEach((key) => {
|
|
||||||
const { server, user } = JSON.parse(key);
|
|
||||||
if (server !== channel.server_id) return;
|
|
||||||
|
|
||||||
const u = client.users.get(user);
|
|
||||||
if (!u) return;
|
|
||||||
|
|
||||||
const member = client.members.get(key);
|
|
||||||
const sort = member?.nickname ?? u.username;
|
|
||||||
const entry = [u, sort] as [User, string];
|
|
||||||
|
|
||||||
if (!u.online || u.status?.presence === Presence.Invisible) {
|
|
||||||
categories.offline.push(entry);
|
|
||||||
} else {
|
|
||||||
categories.online.push(entry);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(categories).forEach((key) =>
|
|
||||||
categories[key].sort((a, b) => a[1].localeCompare(b[1])),
|
|
||||||
);
|
|
||||||
|
|
||||||
const entries = [];
|
|
||||||
|
|
||||||
entries.push(
|
|
||||||
`online:${categories.online.length}`,
|
|
||||||
...categories.online.map((x) => x[0]),
|
|
||||||
`offline:${categories.offline.length}`,
|
|
||||||
...categories.offline.map((x) => x[0]),
|
|
||||||
);
|
|
||||||
|
|
||||||
return entries;
|
|
||||||
// eslint-disable-next-line
|
|
||||||
}, [keys]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GenericSidebarBase>
|
<GenericSidebarBase>
|
||||||
<MemberList entries={entries} />
|
<MemberList entries={entries} context={channel} />
|
||||||
</GenericSidebarBase>
|
</GenericSidebarBase>
|
||||||
);
|
);
|
||||||
|
|
||||||
/*
|
|
||||||
const client = useClient();
|
|
||||||
const { openScreen } = useIntermediate();
|
|
||||||
const status = useContext(StatusContext);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (status === ClientStatus.ONLINE) {
|
|
||||||
channel.server!.fetchMembers();
|
|
||||||
}
|
|
||||||
}, [status, channel.server]);
|
|
||||||
|
|
||||||
const users = [...client.members.keys()]
|
|
||||||
.map((x) => JSON.parse(x))
|
|
||||||
.filter((x) => x.server === channel.server_id)
|
|
||||||
.map((y) => client.users.get(y.user)!)
|
|
||||||
.filter((z) => typeof z !== "undefined");
|
|
||||||
|
|
||||||
// copy paste from above
|
|
||||||
users.sort((a, b) => {
|
|
||||||
// ! FIXME: should probably rewrite all this code
|
|
||||||
const l =
|
|
||||||
+(
|
|
||||||
(a.online && a.status?.presence !== Presence.Invisible) ??
|
|
||||||
false
|
|
||||||
) | 0;
|
|
||||||
const r =
|
|
||||||
+(
|
|
||||||
(b.online && b.status?.presence !== Presence.Invisible) ??
|
|
||||||
false
|
|
||||||
) | 0;
|
|
||||||
|
|
||||||
const n = r - l;
|
|
||||||
if (n !== 0) {
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.username.localeCompare(b.username);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<GenericSidebarBase>
|
|
||||||
<GenericSidebarList>
|
|
||||||
<Search channel={channel} />
|
|
||||||
<div>{users.length === 0 && <Preloader type="ring" />}</div>
|
|
||||||
{users.length > 0 && (
|
|
||||||
<CollapsibleSection
|
|
||||||
//sticky //will re-add later, need to fix css
|
|
||||||
id="members"
|
|
||||||
defaultValue
|
|
||||||
summary={
|
|
||||||
<span>
|
|
||||||
<Text id="app.main.categories.members" /> —{" "}
|
|
||||||
{users?.length ?? 0}
|
|
||||||
</span>
|
|
||||||
}>
|
|
||||||
{users.map(
|
|
||||||
(user) =>
|
|
||||||
user && (
|
|
||||||
<UserButton
|
|
||||||
key={user._id}
|
|
||||||
user={user}
|
|
||||||
context={channel}
|
|
||||||
onClick={() =>
|
|
||||||
openScreen({
|
|
||||||
id: "profile",
|
|
||||||
user_id: user._id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</CollapsibleSection>
|
|
||||||
)}
|
|
||||||
</GenericSidebarList>
|
|
||||||
</GenericSidebarBase>
|
|
||||||
);*/
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
function Search({ channel }: { channel: Channel }) {
|
|
||||||
if (!getState().experiments.enabled?.includes("search")) return null;
|
|
||||||
|
|
||||||
type Sort = "Relevance" | "Latest" | "Oldest";
|
|
||||||
const [sort, setSort] = useState<Sort>("Relevance");
|
|
||||||
|
|
||||||
const [query, setV] = useState("");
|
|
||||||
const [results, setResults] = useState<Message[]>([]);
|
|
||||||
|
|
||||||
async function search() {
|
|
||||||
const data = await channel.searchWithUsers({ query, sort });
|
|
||||||
setResults(data.messages);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CollapsibleSection
|
|
||||||
sticky
|
|
||||||
id="search"
|
|
||||||
defaultValue={false}
|
|
||||||
summary={
|
|
||||||
<>
|
|
||||||
<Text id="app.main.channel.search.title" /> (BETA)
|
|
||||||
</>
|
|
||||||
}>
|
|
||||||
<div style={{ display: "flex" }}>
|
|
||||||
{["Relevance", "Latest", "Oldest"].map((key) => (
|
|
||||||
<Button
|
|
||||||
key={key}
|
|
||||||
style={{ flex: 1, minWidth: 0 }}
|
|
||||||
compact
|
|
||||||
error={sort === key}
|
|
||||||
onClick={() => setSort(key as Sort)}>
|
|
||||||
<Text
|
|
||||||
id={`app.main.channel.search.sort.${key.toLowerCase()}`}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<InputBox
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
onKeyDown={(e) => e.key === "Enter" && search()}
|
|
||||||
value={query}
|
|
||||||
onChange={(e) => setV(e.currentTarget.value)}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: "4px",
|
|
||||||
marginTop: "8px",
|
|
||||||
}}>
|
|
||||||
{results.map((message) => {
|
|
||||||
let href = "";
|
|
||||||
if (channel?.channel_type === "TextChannel") {
|
|
||||||
href += `/server/${channel.server_id}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
href += `/channel/${message.channel_id}/${message._id}`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Link to={href} key={message._id}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
margin: "2px",
|
|
||||||
padding: "6px",
|
|
||||||
background: "var(--primary-background)",
|
|
||||||
}}>
|
|
||||||
<b>@{message.author?.username}</b>
|
|
||||||
<br />
|
|
||||||
{message.content}
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</CollapsibleSection>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
80
src/components/navigation/right/Search.note
Normal file
80
src/components/navigation/right/Search.note
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// this is the search code
|
||||||
|
|
||||||
|
function Search({ channel }: { channel: Channel }) {
|
||||||
|
if (!getState().experiments.enabled?.includes("search")) return null;
|
||||||
|
|
||||||
|
type Sort = "Relevance" | "Latest" | "Oldest";
|
||||||
|
const [sort, setSort] = useState<Sort>("Relevance");
|
||||||
|
|
||||||
|
const [query, setV] = useState("");
|
||||||
|
const [results, setResults] = useState<Message[]>([]);
|
||||||
|
|
||||||
|
async function search() {
|
||||||
|
const data = await channel.searchWithUsers({ query, sort });
|
||||||
|
setResults(data.messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CollapsibleSection
|
||||||
|
sticky
|
||||||
|
id="search"
|
||||||
|
defaultValue={false}
|
||||||
|
summary={
|
||||||
|
<>
|
||||||
|
<Text id="app.main.channel.search.title" /> (BETA)
|
||||||
|
</>
|
||||||
|
}>
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
{["Relevance", "Latest", "Oldest"].map((key) => (
|
||||||
|
<Button
|
||||||
|
key={key}
|
||||||
|
style={{ flex: 1, minWidth: 0 }}
|
||||||
|
compact
|
||||||
|
error={sort === key}
|
||||||
|
onClick={() => setSort(key as Sort)}>
|
||||||
|
<Text
|
||||||
|
id={`app.main.channel.search.sort.${key.toLowerCase()}`}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<InputBox
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
onKeyDown={(e) => e.key === "Enter" && search()}
|
||||||
|
value={query}
|
||||||
|
onChange={(e) => setV(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "4px",
|
||||||
|
marginTop: "8px",
|
||||||
|
}}>
|
||||||
|
{results.map((message) => {
|
||||||
|
let href = "";
|
||||||
|
if (channel?.channel_type === "TextChannel") {
|
||||||
|
href += `/server/${channel.server_id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
href += `/channel/${message.channel_id}/${message._id}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link to={href} key={message._id}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
margin: "2px",
|
||||||
|
padding: "6px",
|
||||||
|
background: "var(--primary-background)",
|
||||||
|
}}>
|
||||||
|
<b>@{message.author?.username}</b>
|
||||||
|
<br />
|
||||||
|
{message.content}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</CollapsibleSection>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in a new issue