Add mentioning for channels
This commit is contained in:
parent
85a114e55c
commit
dbe8e8e358
42
reminder-dashboard/src/components/App/Mentions.tsx
Normal file
42
reminder-dashboard/src/components/App/Mentions.tsx
Normal 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 <></>;
|
||||||
|
};
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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]);
|
|
||||||
};
|
|
@ -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"
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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..."
|
||||||
|
@ -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>
|
||||||
|
@ -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,
|
||||||
);
|
);
|
51
reminder-dashboard/src/components/Reminder/TopBar/User.tsx
Normal file
51
reminder-dashboard/src/components/Reminder/TopBar/User.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
13
reminder-dashboard/src/components/Reminder/TopBar/index.tsx
Normal file
13
reminder-dashboard/src/components/Reminder/TopBar/index.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
|
};
|
@ -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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user