Add mentioning for channels

This commit is contained in:
jude 2024-03-04 20:36:37 +00:00
parent 85a114e55c
commit dbe8e8e358
13 changed files with 251 additions and 56 deletions

View File

@ -0,0 +1,42 @@
import { useEffect, useMemo } from "preact/hooks";
import { useQuery } from "react-query";
import { fetchGuildChannels, fetchGuildRoles } from "../../api";
import Tribute from "tributejs";
import { useGuild } from "./useGuild";
export const Mentions = ({ input }) => {
const guild = useGuild();
const { data: roles } = useQuery(fetchGuildRoles(guild));
const { data: channels } = useQuery(fetchGuildChannels(guild));
const tribute = useMemo(() => {
return new Tribute({
collection: [
{
trigger: "@",
values: (roles || []).map(({ id, name }) => ({ key: name, value: id })),
allowSpaces: true,
selectTemplate: (item) => `<@&${item.original.value}>`,
menuItemTemplate: (item) => `@${item.original.key}`,
},
{
trigger: "#",
values: (channels || []).map(({ id, name }) => ({ key: name, value: id })),
allowSpaces: true,
selectTemplate: (item) => `<#${item.original.value}>`,
menuItemTemplate: (item) => `#${item.original.key}`,
},
],
});
}, [roles, channels]);
useEffect(() => {
tribute.detach(input.current);
if (input.current !== null) {
tribute.attach(input.current);
}
}, [tribute]);
return <></>;
};

View File

@ -1,6 +1,6 @@
import { useParams } from "wouter"; import { useParams } from "wouter";
export const useGuild = () => { export const useGuild = () => {
const { guild } = useParams() as { guild: string }; const { guild } = useParams() as { guild?: string };
return guild; return guild || null;
}; };

View File

@ -1,28 +0,0 @@
import { useEffect, useMemo } from "preact/hooks";
import { useQuery } from "react-query";
import { fetchGuildRoles } from "../../api";
import Tribute from "tributejs";
import { useGuild } from "./useGuild";
export const useMentions = (input) => {
const guild = useGuild();
const { data: roles } = useQuery(fetchGuildRoles(guild));
const tribute = useMemo(() => {
return new Tribute({
values: (roles || []).map(({ id, name }) => ({ key: name, value: id })),
allowSpaces: true,
selectTemplate: (item) => {
return `<@&${item.original.value}>`;
},
});
}, [roles]);
useEffect(() => {
tribute.detach(input.current);
if (input.current !== null) {
tribute.attach(input.current);
}
}, [tribute]);
};

View File

@ -1,15 +1,16 @@
import { useReminder } from "./ReminderContext"; import { useReminder } from "./ReminderContext";
import { useRef } from "preact/hooks"; import { useRef } from "preact/hooks";
import { useMentions } from "../App/useMentions"; import { Mentions } from "../App/Mentions";
import { useGuild } from "../App/useGuild";
export const Content = () => { export const Content = () => {
const guild = useGuild();
const [reminder, setReminder] = useReminder(); const [reminder, setReminder] = useReminder();
const input = useRef(null); const input = useRef(null);
useMentions(input);
return ( return (
<> <>
{guild && <Mentions input={input} />}
<label class="is-sr-only">Content</label> <label class="is-sr-only">Content</label>
<textarea <textarea
class="message-input autoresize discord-content" class="message-input autoresize discord-content"

View File

@ -1,7 +1,8 @@
import { ImagePicker } from "../ImagePicker"; import { ImagePicker } from "../ImagePicker";
import { Reminder } from "../../../api"; import { Reminder } from "../../../api";
import { useMentions } from "../../App/useMentions"; import { Mentions } from "../../App/Mentions";
import { useRef } from "preact/hooks"; import { useRef } from "preact/hooks";
import { useGuild } from "../../App/useGuild";
type Props = { type Props = {
name: string; name: string;
@ -10,8 +11,8 @@ type Props = {
}; };
export const Author = ({ name, icon, setReminder }: Props) => { export const Author = ({ name, icon, setReminder }: Props) => {
const guild = useGuild();
const input = useRef(null); const input = useRef(null);
useMentions(input);
return ( return (
<div class="embed-author-box"> <div class="embed-author-box">
@ -32,6 +33,7 @@ export const Author = ({ name, icon, setReminder }: Props) => {
</div> </div>
<div class="b"> <div class="b">
{guild && <Mentions input={input} />}
<label class="is-sr-only" for="embedAuthor"> <label class="is-sr-only" for="embedAuthor">
Embed Author Embed Author
</label> </label>

View File

@ -1,12 +1,14 @@
import { useMentions } from "../../App/useMentions"; import { Mentions } from "../../App/Mentions";
import { useRef } from "preact/hooks"; import { useRef } from "preact/hooks";
import { useGuild } from "../../App/useGuild";
export const Description = ({ description, onInput }) => { export const Description = ({ description, onInput }) => {
const guild = useGuild();
const input = useRef(null); const input = useRef(null);
useMentions(input);
return ( return (
<> <>
{guild && <Mentions input={input} />}
<label class="is-sr-only" for="embedDescription"> <label class="is-sr-only" for="embedDescription">
Embed Description Embed Description
</label> </label>

View File

@ -1,9 +1,10 @@
import { useRef } from "preact/hooks"; import { useRef } from "preact/hooks";
import { useMentions } from "../../../App/useMentions"; import { Mentions } from "../../../App/Mentions";
import { useGuild } from "../../../App/useGuild";
export const Field = ({ title, value, inline, index, onUpdate }) => { export const Field = ({ title, value, inline, index, onUpdate }) => {
const guild = useGuild();
const input = useRef(null); const input = useRef(null);
useMentions(input);
return ( return (
<div data-inlined={inline ? "1" : "0"} class="embed-field-box" key={index}> <div data-inlined={inline ? "1" : "0"} class="embed-field-box" key={index}>
@ -41,6 +42,7 @@ export const Field = ({ title, value, inline, index, onUpdate }) => {
)} )}
</div> </div>
{guild && <Mentions input={input} />}
<label class="is-sr-only" for="embedFieldValue"> <label class="is-sr-only" for="embedFieldValue">
Field Value Field Value
</label> </label>

View File

@ -1,7 +1,8 @@
import { Reminder } from "../../../api"; import { Reminder } from "../../../api";
import { ImagePicker } from "../ImagePicker"; import { ImagePicker } from "../ImagePicker";
import { useMentions } from "../../App/useMentions"; import { Mentions } from "../../App/Mentions";
import { useRef } from "preact/hooks"; import { useRef } from "preact/hooks";
import { useGuild } from "../../App/useGuild";
type Props = { type Props = {
footer: string; footer: string;
@ -10,8 +11,8 @@ type Props = {
}; };
export const Footer = ({ footer, icon, setReminder }: Props) => { export const Footer = ({ footer, icon, setReminder }: Props) => {
const guild = useGuild();
const input = useRef(null); const input = useRef(null);
useMentions(input);
return ( return (
<div class="embed-footer-box"> <div class="embed-footer-box">
@ -31,6 +32,7 @@ export const Footer = ({ footer, icon, setReminder }: Props) => {
<label class="is-sr-only" for="embedFooter"> <label class="is-sr-only" for="embedFooter">
Embed Footer text Embed Footer text
</label> </label>
{guild && <Mentions input={input} />}
<textarea <textarea
class="discord-embed-footer message-input autoresize " class="discord-embed-footer message-input autoresize "
placeholder="Embed Footer..." placeholder="Embed Footer..."

View File

@ -1,13 +1,14 @@
import { useParams } from "wouter";
import { useRef } from "preact/hooks"; import { useRef } from "preact/hooks";
import { useMentions } from "../../App/useMentions"; import { useGuild } from "../../App/useGuild";
import { Mentions } from "../../App/Mentions";
export const Title = ({ title, onInput }) => { export const Title = ({ title, onInput }) => {
const guild = useGuild();
const input = useRef(null); const input = useRef(null);
useMentions(input);
return ( return (
<> <>
{guild && <Mentions input={input} />}
<label class="is-sr-only" for="embedTitle"> <label class="is-sr-only" for="embedTitle">
Embed Title Embed Title
</label> </label>

View File

@ -1,23 +1,26 @@
import { useReminder } from "./ReminderContext"; import { useGuild } from "../../App/useGuild";
import { Name } from "./Name"; import { useReminder } from "../ReminderContext";
import { fetchGuildChannels, Reminder } from "../../api";
import { useQuery } from "react-query"; import { useQuery } from "react-query";
import { useParams } from "wouter"; import { fetchGuildChannels, Reminder } from "../../../api";
import { useCallback } from "preact/hooks";
import { DateTime } from "luxon"; import { DateTime } from "luxon";
import { Name } from "../Name";
export const TopBar = ({ toggleCollapsed }) => { export const Guild = ({ toggleCollapsed }) => {
const { guild } = useParams(); const guild = useGuild();
const [reminder] = useReminder(); const [reminder] = useReminder();
const { isSuccess, data: guildChannels } = useQuery(fetchGuildChannels(guild)); const { isSuccess, data: guildChannels } = useQuery(fetchGuildChannels(guild));
const channelName = (reminder: Reminder) => { const channelName = useCallback(
(reminder: Reminder) => {
const channel = guildChannels.find((c) => c.id === reminder.channel); const channel = guildChannels.find((c) => c.id === reminder.channel);
return channel === undefined ? "" : channel.name; return channel === undefined ? "" : channel.name;
}; },
[guildChannels],
);
let days, hours, minutes, seconds; let days, hours, minutes, seconds;
seconds = Math.floor( seconds = Math.floor(
DateTime.fromISO(reminder.utc_time, { zone: "UTC" }).diffNow("seconds").seconds, DateTime.fromISO(reminder.utc_time, { zone: "UTC" }).diffNow("seconds").seconds,
); );

View File

@ -0,0 +1,51 @@
import { Name } from "../Name";
import { DateTime } from "luxon";
import { useReminder } from "../ReminderContext";
export const User = ({ toggleCollapsed }) => {
const [reminder] = useReminder();
let days, hours, minutes, seconds;
seconds = Math.floor(
DateTime.fromISO(reminder.utc_time, { zone: "UTC" }).diffNow("seconds").seconds,
);
[days, seconds] = [Math.floor(seconds / 86400), seconds % 86400];
[hours, seconds] = [Math.floor(seconds / 3600), seconds % 3600];
[minutes, seconds] = [Math.floor(seconds / 60), seconds % 60];
let string;
if (days !== 0) {
if (hours !== 0) {
string = `${days} days, ${hours} hours`;
} else {
string = `${days} days`;
}
} else if (hours !== 0) {
if (minutes !== 0) {
string = `${hours} hours, ${minutes} minutes`;
} else {
string = `${hours} hours`;
}
} else if (minutes !== 0) {
if (seconds !== 0) {
string = `${minutes} minutes, ${seconds} seconds`;
} else {
string = `${minutes} minutes`;
}
} else {
string = `${seconds} seconds`;
}
return (
<div class="columns is-mobile column reminder-topbar">
<Name />
<div class="invert-collapses time-bar">in {string}</div>
<div class="hide-button-bar">
<button class="button hide-box" onClick={toggleCollapsed}>
<span class="is-sr-only">Hide reminder</span>
<i class="fas fa-chevron-down"></i>
</button>
</div>
</div>
);
};

View File

@ -0,0 +1,13 @@
import { useGuild } from "../../App/useGuild";
import { Guild } from "./Guild";
import { User } from "./User";
export const TopBar = ({ toggleCollapsed }) => {
const guild = useGuild();
if (guild) {
return <Guild toggleCollapsed={toggleCollapsed} />;
} else {
return <User toggleCollapsed={toggleCollapsed} />;
}
};

View File

@ -1,3 +1,107 @@
import { useQuery } from "react-query";
import { fetchUserReminders } from "../../api";
import { EditReminder } from "../Reminder/EditReminder";
import { CreateReminder } from "../Reminder/CreateReminder";
import { useState } from "preact/hooks";
import { Loader } from "../Loader";
enum Sort {
Time = "time",
Name = "name",
}
export const UserReminders = () => { export const UserReminders = () => {
return <></>; const {
isSuccess,
isFetching,
isFetched,
data: guildReminders,
} = useQuery(fetchUserReminders());
const [collapsed, setCollapsed] = useState(false);
const [sort, setSort] = useState(Sort.Time);
return (
<>
{!isFetched && <Loader />}
<div style={{ margin: "0 12px 12px 12px" }}>
<strong>Create Reminder</strong>
<div id={"reminderCreator"}>
<CreateReminder />
</div>
<br></br>
<div class={"field"}>
<div class={"columns is-mobile"}>
<div class={"column"}>
<strong>Reminders</strong>
</div>
<div class={"column is-narrow"}>
<div class="control has-icons-left">
<div class="select is-small">
<select
id="orderBy"
onInput={(ev) => {
setSort(ev.currentTarget.value as Sort);
}}
>
<option value={Sort.Time} selected={sort == Sort.Time}>
Time
</option>
<option value={Sort.Name} selected={sort == Sort.Name}>
Name
</option>
</select>
</div>
<div class="icon is-small is-left">
<i class="fas fa-sort-amount-down"></i>
</div>
</div>
</div>
<div class={"column is-narrow"}>
<div class="control has-icons-left">
<div class="select is-small">
<select
id="expandAll"
onInput={(ev) => {
if (ev.currentTarget.value === "expand") {
setCollapsed(false);
} else if (ev.currentTarget.value === "collapse") {
setCollapsed(true);
}
}}
>
<option value="" selected></option>
<option value="expand">Expand All</option>
<option value="collapse">Collapse All</option>
</select>
</div>
<div class="icon is-small is-left">
<i class="fas fa-expand-arrows"></i>
</div>
</div>
</div>
</div>
</div>
<div id={"guildReminders"} className={isFetching ? "loading" : ""}>
{isSuccess &&
guildReminders
.sort((r1, r2) => {
if (sort === Sort.Time) {
return r1.utc_time > r2.utc_time ? 1 : -1;
} else {
return r1.name > r2.name ? 1 : -1;
}
})
.map((reminder) => (
<EditReminder
key={reminder.uid}
reminder={reminder}
globalCollapse={collapsed}
/>
))}
</div>
</div>
</>
);
}; };