Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c98265657 |
@@ -28,4 +28,3 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
.junie
|
|
||||||
|
|||||||
Generated
+1
-1
@@ -2623,7 +2623,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reminder-rs"
|
name = "reminder-rs"
|
||||||
version = "1.7.44"
|
version = "1.7.41"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "reminder-rs"
|
name = "reminder-rs"
|
||||||
version = "1.7.44"
|
version = "1.7.41"
|
||||||
authors = ["Jude Southworth <judesouthworth@pm.me>"]
|
authors = ["Jude Southworth <judesouthworth@pm.me>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0 only"
|
license = "AGPL-3.0 only"
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ COPY ./Cargo.lock ./
|
|||||||
COPY ./Cargo.toml ./
|
COPY ./Cargo.toml ./
|
||||||
COPY ./dp.py ./
|
COPY ./dp.py ./
|
||||||
|
|
||||||
|
# Build dashboard assets explicitly to ensure dist exists
|
||||||
|
RUN npm ci --prefix reminder-dashboard && npm run build --prefix reminder-dashboard
|
||||||
|
|
||||||
# Build and install the Rust binary
|
# Build and install the Rust binary
|
||||||
RUN cargo install --path .
|
RUN cargo install --path .
|
||||||
|
|
||||||
|
|||||||
@@ -8,26 +8,19 @@ fn main() {
|
|||||||
.arg("run")
|
.arg("run")
|
||||||
.arg("build")
|
.arg("build")
|
||||||
.current_dir(Path::new("reminder-dashboard"))
|
.current_dir(Path::new("reminder-dashboard"))
|
||||||
.env("VITE_VERSION", env!("CARGO_PKG_VERSION"))
|
|
||||||
.spawn()
|
.spawn()
|
||||||
.expect("Failed to build NPM")
|
.expect("Failed to build NPM");
|
||||||
.wait()
|
|
||||||
.expect("Failed to wait for NPM build");
|
|
||||||
|
|
||||||
Command::new("cp")
|
Command::new("cp")
|
||||||
.arg("reminder-dashboard/dist/index.html")
|
.arg("reminder-dashboard/dist/index.html")
|
||||||
.arg("static/index.html")
|
.arg("static/index.html")
|
||||||
.spawn()
|
.spawn()
|
||||||
.expect("Failed to copy index.html")
|
.expect("Failed to copy index.html");
|
||||||
.wait()
|
|
||||||
.expect("Failed to wait for index.html copy");
|
|
||||||
|
|
||||||
Command::new("cp")
|
Command::new("cp")
|
||||||
.arg("-r")
|
.arg("-r")
|
||||||
.arg("reminder-dashboard/dist/static/assets")
|
.arg("reminder-dashboard/dist/static/assets")
|
||||||
.arg("static/")
|
.arg("static/")
|
||||||
.spawn()
|
.spawn()
|
||||||
.expect("Failed to copy assets")
|
.expect("Failed to copy assets");
|
||||||
.wait()
|
|
||||||
.expect("Failed to wait for assets copy");
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
SET foreign_key_checks = 0;
|
|
||||||
|
|
||||||
-- Drop all old tables
|
|
||||||
DROP TABLE IF EXISTS users_old;
|
|
||||||
DROP TABLE IF EXISTS messages;
|
|
||||||
DROP TABLE IF EXISTS embeds;
|
|
||||||
DROP TABLE IF EXISTS embed_fields;
|
|
||||||
DROP TABLE IF EXISTS command_aliases;
|
|
||||||
DROP TABLE IF EXISTS macro;
|
|
||||||
DROP TABLE IF EXISTS roles;
|
|
||||||
DROP TABLE IF EXISTS command_restrictions;
|
|
||||||
|
|
||||||
SET foreign_key_checks = 1;
|
|
||||||
@@ -5,8 +5,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite build --watch --mode development",
|
"dev": "vite build --watch --mode development",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview"
|
||||||
"prettier": "prettier -w src/"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ enum ColorScheme {
|
|||||||
Light = "light",
|
Light = "light",
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserInfo = {
|
export type UserInfo = {
|
||||||
name: string;
|
name: string;
|
||||||
patreon: boolean;
|
patreon: boolean;
|
||||||
preferences: {
|
preferences: {
|
||||||
@@ -116,7 +116,7 @@ type Template = {
|
|||||||
embed_fields: EmbedField[] | null;
|
embed_fields: EmbedField[] | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const USER_INFO_STALE_TIME = 86_400_000;
|
const USER_INFO_STALE_TIME = 120_000;
|
||||||
const GUILD_INFO_STALE_TIME = 300_000;
|
const GUILD_INFO_STALE_TIME = 300_000;
|
||||||
const OTHER_STALE_TIME = 120_000;
|
const OTHER_STALE_TIME = 120_000;
|
||||||
|
|
||||||
@@ -236,7 +236,7 @@ export const fetchGuildTodos = (guild: string) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const patchGuildTodo = (guild: string) => ({
|
export const patchGuildTodo = (guild: string) => ({
|
||||||
mutationFn: ({id, todo}) => axios.patch(`/dashboard/api/guild/${guild}/todos/${id}`, todo),
|
mutationFn: ({ id, todo }) => axios.patch(`/dashboard/api/guild/${guild}/todos/${id}`, todo),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const postGuildTodo = (guild: string) => ({
|
export const postGuildTodo = (guild: string) => ({
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import { createContext } from "preact";
|
|
||||||
import { useContext } from "preact/compat";
|
|
||||||
import { useEffect, useState } from "preact/hooks";
|
|
||||||
import { fetchUserInfo } from "../../api";
|
|
||||||
import { useQuery } from "react-query";
|
|
||||||
|
|
||||||
type TColorScheme = "light" | "dark";
|
|
||||||
|
|
||||||
type TColorSchemeContext = {
|
|
||||||
colorScheme: TColorScheme;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ColorSchemeContext = createContext({ colorScheme: "light" } as TColorSchemeContext);
|
|
||||||
|
|
||||||
export const ColorSchemeProvider = ({ children }) => {
|
|
||||||
const { data } = useQuery({ ...fetchUserInfo() });
|
|
||||||
const [activeScheme, setActiveScheme] = useState<TColorScheme>("light");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const preference = data?.preferences?.dashboard_color_scheme || "system";
|
|
||||||
|
|
||||||
if (preference === "system") {
|
|
||||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
||||||
const handleChange = () => {
|
|
||||||
setActiveScheme(mediaQuery.matches ? "dark" : "light");
|
|
||||||
};
|
|
||||||
|
|
||||||
handleChange();
|
|
||||||
mediaQuery.addEventListener("change", handleChange);
|
|
||||||
return () => mediaQuery.removeEventListener("change", handleChange);
|
|
||||||
} else {
|
|
||||||
setActiveScheme(preference as TColorScheme);
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ColorSchemeContext.Provider value={{ colorScheme: activeScheme }}>
|
|
||||||
{children}
|
|
||||||
</ColorSchemeContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useColorScheme = () => useContext(ColorSchemeContext);
|
|
||||||
@@ -1,66 +1,59 @@
|
|||||||
import {Sidebar} from "../Sidebar";
|
import { Sidebar } from "../Sidebar";
|
||||||
import {QueryClient, QueryClientProvider} from "react-query";
|
import { QueryClient, QueryClientProvider } from "react-query";
|
||||||
import {Route, Router, Switch} from "wouter";
|
import { Route, Router, Switch } from "wouter";
|
||||||
import {Welcome} from "../Welcome";
|
import { Welcome } from "../Welcome";
|
||||||
import {Guild} from "../Guild";
|
import { Guild } from "../Guild";
|
||||||
import {FlashProvider} from "./FlashProvider";
|
import { FlashProvider } from "./FlashProvider";
|
||||||
import {TimezoneProvider} from "./TimezoneProvider";
|
import { TimezoneProvider } from "./TimezoneProvider";
|
||||||
import {User} from "../User";
|
import { User } from "../User";
|
||||||
import {GuildReminders} from "../Guild/GuildReminders";
|
import { GuildReminders } from "../Guild/GuildReminders";
|
||||||
import {GuildTodos} from "../Guild/GuildTodos";
|
import { GuildTodos } from "../Guild/GuildTodos";
|
||||||
import {ColorSchemeProvider, useColorScheme} from "./ColorSchemeProvider";
|
|
||||||
import {useEffect} from "preact/hooks";
|
|
||||||
|
|
||||||
const InnerApp = () => {
|
export function App() {
|
||||||
const {colorScheme} = useColorScheme();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
useEffect(() => {
|
let scheme = "light";
|
||||||
const body = document.querySelector("body");
|
// if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||||
body.className = body.className.replace(/scheme-\w+/g, "");
|
// scheme = "dark";
|
||||||
body.classList.add(`scheme-${colorScheme}`);
|
// }
|
||||||
}, [colorScheme]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<TimezoneProvider>
|
||||||
|
<FlashProvider>
|
||||||
<Router base={"/dashboard"}>
|
<Router base={"/dashboard"}>
|
||||||
<div class={`columns is-gapless dashboard-frame scheme-${colorScheme}`}>
|
<div class={`columns is-gapless dashboard-frame scheme-${scheme}`}>
|
||||||
<Sidebar/>
|
<Sidebar />
|
||||||
<div class="column is-main-content">
|
<div class="column is-main-content">
|
||||||
<div style={{margin: "0 12px 12px 12px"}}>
|
<div style={{ margin: "0 12px 12px 12px" }}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path={"/@me/reminders"} component={User}></Route>
|
<Route path={"/@me/reminders"} component={User}></Route>
|
||||||
<Route path={"/:guild/reminders"}>
|
<Route
|
||||||
|
path={"/:guild/reminders"}
|
||||||
|
component={() => (
|
||||||
<Guild>
|
<Guild>
|
||||||
<GuildReminders/>
|
<GuildReminders />
|
||||||
</Guild>
|
</Guild>
|
||||||
</Route>
|
)}
|
||||||
<Route path={"/:guild/todos"}>
|
></Route>
|
||||||
|
<Route
|
||||||
|
path={"/:guild/todos"}
|
||||||
|
component={() => (
|
||||||
<Guild>
|
<Guild>
|
||||||
<GuildTodos/>
|
<GuildTodos />
|
||||||
</Guild>
|
</Guild>
|
||||||
</Route>
|
)}
|
||||||
|
></Route>
|
||||||
<Route>
|
<Route>
|
||||||
<Welcome/>
|
<Welcome />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
|
||||||
|
|
||||||
export function App() {
|
|
||||||
return (
|
|
||||||
<QueryClientProvider client={queryClient}>
|
|
||||||
<ColorSchemeProvider>
|
|
||||||
<TimezoneProvider>
|
|
||||||
<FlashProvider>
|
|
||||||
<InnerApp/>
|
|
||||||
</FlashProvider>
|
</FlashProvider>
|
||||||
</TimezoneProvider>
|
</TimezoneProvider>
|
||||||
</ColorSchemeProvider>
|
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useQuery, useQueryClient } from "react-query";
|
import { useQuery, useQueryClient } from "react-query";
|
||||||
import { fetchGuildChannels, fetchGuildReminders } from "../../api";
|
import { fetchGuildChannels, fetchGuildInfo, fetchGuildReminders, fetchUserInfo } from "../../api";
|
||||||
import { EditReminder } from "../Reminder/EditReminder";
|
import { EditReminder } from "../Reminder/EditReminder";
|
||||||
import { CreateReminder } from "../Reminder/CreateReminder";
|
import { CreateReminder } from "../Reminder/CreateReminder";
|
||||||
import { useCallback, useState } from "preact/hooks";
|
import { useCallback, useMemo, useState } from "preact/hooks";
|
||||||
import { Loader } from "../Loader";
|
import { Loader } from "../Loader";
|
||||||
import { useGuild } from "../App/useGuild";
|
import { useGuild } from "../App/useGuild";
|
||||||
|
|
||||||
@@ -22,6 +22,11 @@ export const GuildReminders = () => {
|
|||||||
data: guildReminders,
|
data: guildReminders,
|
||||||
} = useQuery(fetchGuildReminders(guild));
|
} = useQuery(fetchGuildReminders(guild));
|
||||||
const { data: channels } = useQuery(fetchGuildChannels(guild));
|
const { data: channels } = useQuery(fetchGuildChannels(guild));
|
||||||
|
const channelNames = useMemo(() => {
|
||||||
|
return new Map(channels.map((ch) => [ch.id, ch.name]));
|
||||||
|
}, [channels]);
|
||||||
|
const { isSuccess: guildFetched, data: guildInfo } = useQuery({ ...fetchGuildInfo(guild) });
|
||||||
|
const { isSuccess: userFetched, data: userInfo } = useQuery({ ...fetchUserInfo() });
|
||||||
|
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
const [sort, _setSort] = useState(Sort.Time);
|
const [sort, _setSort] = useState(Sort.Time);
|
||||||
@@ -34,9 +39,49 @@ export const GuildReminders = () => {
|
|||||||
_setSort(sort);
|
_setSort(sort);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const sorted = useMemo(
|
||||||
|
() =>
|
||||||
|
[...guildReminders]
|
||||||
|
.sort((r1, r2) => {
|
||||||
|
if (sort === Sort.Time) {
|
||||||
|
return r1.utc_time > r2.utc_time ? 1 : -1;
|
||||||
|
} else if (sort === Sort.Name) {
|
||||||
|
return r1.name > r2.name ? 1 : -1;
|
||||||
|
} else {
|
||||||
|
return r1.channel > r2.channel ? 1 : -1;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map((reminder) => {
|
||||||
|
let breaker = <></>;
|
||||||
|
if (sort === Sort.Channel && channels) {
|
||||||
|
if (prevReminder === null || prevReminder.channel !== reminder.channel) {
|
||||||
|
breaker = (
|
||||||
|
<div class={"channel-tag"}>#{channelNames[reminder.channel]}</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prevReminder = reminder;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!isFetched && <Loader />}
|
{breaker}
|
||||||
|
<EditReminder
|
||||||
|
key={reminder.uid}
|
||||||
|
reminder={reminder}
|
||||||
|
guildInfo={guildInfo}
|
||||||
|
userInfo={userInfo}
|
||||||
|
globalCollapse={collapsed}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
[guildReminders, sort, channelNames, collapsed],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!(userFetched && guildFetched && isFetched) && <Loader />}
|
||||||
|
|
||||||
<strong>Create Reminder</strong>
|
<strong>Create Reminder</strong>
|
||||||
<div id={"reminderCreator"}>
|
<div id={"reminderCreator"}>
|
||||||
@@ -100,44 +145,7 @@ export const GuildReminders = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id={"guildReminders"} className={isFetching ? "loading" : ""}>
|
<div id={"guildReminders"} className={isFetching ? "loading" : ""}>
|
||||||
{isSuccess &&
|
{isSuccess && sorted}
|
||||||
guildReminders
|
|
||||||
.sort((r1, r2) => {
|
|
||||||
if (sort === Sort.Time) {
|
|
||||||
return r1.utc_time > r2.utc_time ? 1 : -1;
|
|
||||||
} else if (sort === Sort.Name) {
|
|
||||||
return r1.name > r2.name ? 1 : -1;
|
|
||||||
} else {
|
|
||||||
return r1.channel > r2.channel ? 1 : -1;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map((reminder) => {
|
|
||||||
let breaker = <></>;
|
|
||||||
if (sort === Sort.Channel && channels) {
|
|
||||||
if (
|
|
||||||
prevReminder === null ||
|
|
||||||
prevReminder.channel !== reminder.channel
|
|
||||||
) {
|
|
||||||
const channel = channels.find(
|
|
||||||
(ch) => ch.id === reminder.channel,
|
|
||||||
);
|
|
||||||
breaker = <div class={"channel-tag"}>#{channel.name}</div>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prevReminder = reminder;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{breaker}
|
|
||||||
<EditReminder
|
|
||||||
key={reminder.uid}
|
|
||||||
reminder={reminder}
|
|
||||||
globalCollapse={collapsed}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { JSX } from "preact";
|
import {JSX} from "preact";
|
||||||
import { createPortal } from "preact/compat";
|
import {createPortal} from "preact/compat";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
setModalOpen: (open: boolean) => never;
|
setModalOpen: (open: boolean) => never;
|
||||||
@@ -9,7 +9,7 @@ type Props = {
|
|||||||
children: string | JSX.Element | JSX.Element[] | (() => JSX.Element);
|
children: string | JSX.Element | JSX.Element[] | (() => JSX.Element);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Modal = ({ setModalOpen, title, onSubmit, onSubmitText, children }: Props) => {
|
export const Modal = ({setModalOpen, title, onSubmit, onSubmitText, children}: Props) => {
|
||||||
const body = document.querySelector("body");
|
const body = document.querySelector("body");
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useRef, useState } from "preact/hooks";
|
import { useRef, useState } from "preact/hooks";
|
||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation } from "react-query";
|
||||||
import { patchGuildReminder, patchUserReminder } from "../../../api";
|
import { patchGuildReminder, patchUserReminder } from "../../../api";
|
||||||
import { ICON_FLASH_TIME } from "../../../consts";
|
import { ICON_FLASH_TIME } from "../../../consts";
|
||||||
import { DeleteButton } from "./DeleteButton";
|
import { DeleteButton } from "./DeleteButton";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Reminder } from "../../api";
|
import { Reminder, UserInfo, GuildInfo } from "../../api";
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import { EditButtonRow } from "./ButtonRow/EditButtonRow";
|
import { EditButtonRow } from "./ButtonRow/EditButtonRow";
|
||||||
import { Message } from "./Message";
|
import { Message } from "./Message";
|
||||||
@@ -10,9 +10,16 @@ import "./styles.scss";
|
|||||||
type Props = {
|
type Props = {
|
||||||
reminder: Reminder;
|
reminder: Reminder;
|
||||||
globalCollapse: boolean;
|
globalCollapse: boolean;
|
||||||
|
userInfo: UserInfo;
|
||||||
|
guildInfo: GuildInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EditReminder = ({ reminder: initialReminder, globalCollapse }: Props) => {
|
export const EditReminder = ({
|
||||||
|
reminder: initialReminder,
|
||||||
|
globalCollapse,
|
||||||
|
userInfo,
|
||||||
|
guildInfo,
|
||||||
|
}: Props) => {
|
||||||
const [propReminder, setPropReminder] = useState(initialReminder);
|
const [propReminder, setPropReminder] = useState(initialReminder);
|
||||||
const [reminder, setReminder] = useState(initialReminder);
|
const [reminder, setReminder] = useState(initialReminder);
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
@@ -39,11 +46,15 @@ export const EditReminder = ({ reminder: initialReminder, globalCollapse }: Prop
|
|||||||
setCollapsed(!collapsed);
|
setCollapsed(!collapsed);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{!collapsed && (
|
||||||
|
<>
|
||||||
<div class="columns reminder-settings">
|
<div class="columns reminder-settings">
|
||||||
<Message />
|
<Message />
|
||||||
<Settings />
|
<Settings userInfo={userInfo} guildInfo={guildInfo} />
|
||||||
</div>
|
</div>
|
||||||
<EditButtonRow />
|
<EditButtonRow />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</ReminderContext.Provider>
|
</ReminderContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,24 +1,21 @@
|
|||||||
import { ChannelSelector } from "./ChannelSelector";
|
import { ChannelSelector } from "./ChannelSelector";
|
||||||
import { IntervalSelector } from "./IntervalSelector";
|
import { IntervalSelector } from "./IntervalSelector";
|
||||||
import { useQuery } from "react-query";
|
|
||||||
import { fetchGuildInfo, fetchUserInfo } from "../../api";
|
|
||||||
import { useReminder } from "./ReminderContext";
|
import { useReminder } from "./ReminderContext";
|
||||||
import { Attachment } from "./Attachment";
|
import { Attachment } from "./Attachment";
|
||||||
import { TTS } from "./TTS";
|
import { TTS } from "./TTS";
|
||||||
import { TimeInput } from "./TimeInput";
|
import { TimeInput } from "./TimeInput";
|
||||||
import { useGuild } from "../App/useGuild";
|
import { useGuild } from "../App/useGuild";
|
||||||
|
import { GuildInfo, UserInfo } from "../../api";
|
||||||
|
|
||||||
export const Settings = () => {
|
type Props = {
|
||||||
|
userInfo: UserInfo;
|
||||||
|
guildInfo: GuildInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Settings = ({ guildInfo, userInfo }: Props) => {
|
||||||
const guild = useGuild();
|
const guild = useGuild();
|
||||||
const { isSuccess: userFetched, data: userInfo } = useQuery({ ...fetchUserInfo() });
|
|
||||||
const { isSuccess: guildFetched, data: guildInfo } = useQuery({ ...fetchGuildInfo(guild) });
|
|
||||||
|
|
||||||
const [reminder, setReminder] = useReminder();
|
const [reminder, setReminder] = useReminder();
|
||||||
|
|
||||||
if (!userFetched || !guildFetched) {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="column settings">
|
<div class="column settings">
|
||||||
{guild && (
|
{guild && (
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.75);
|
box-shadow: 0 0 5px 0 rgba(0,0,0,0.75);
|
||||||
|
|
||||||
.highlight {
|
.highlight {
|
||||||
background-color: #35373c;
|
background-color: #35373c;
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ aside.menu {
|
|||||||
padding-left: 4px;
|
padding-left: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 1023px), print {
|
@media screen and (max-width: 1023px),print {
|
||||||
.columns:not(.is-desktop) {
|
.columns:not(.is-desktop) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,6 @@ import { useRef, useState } from "preact/hooks";
|
|||||||
import { ICON_FLASH_TIME } from "../../consts";
|
import { ICON_FLASH_TIME } from "../../consts";
|
||||||
import { useFlash } from "../App/FlashContext";
|
import { useFlash } from "../App/FlashContext";
|
||||||
|
|
||||||
enum ColorScheme {
|
|
||||||
System = "system",
|
|
||||||
Dark = "dark",
|
|
||||||
Light = "light",
|
|
||||||
}
|
|
||||||
|
|
||||||
export const UserPreferences = () => {
|
export const UserPreferences = () => {
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
|
||||||
@@ -102,40 +96,6 @@ const PreferencesModal = ({ setModalOpen }) => {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<br></br>
|
<br></br>
|
||||||
<div style={{ display: "flex", flexDirection: "row", alignContent: "center" }}>
|
|
||||||
<label>
|
|
||||||
<div class={"is-inline-block"} style={{ marginRight: "6px" }}>
|
|
||||||
Dashboard Color Scheme:
|
|
||||||
</div>
|
|
||||||
<div class={"control"}>
|
|
||||||
<div class={"is-inline-block select"}>
|
|
||||||
{isLoading && <i class={"fa fa-spinner"} />}
|
|
||||||
{isSuccess && (
|
|
||||||
<select
|
|
||||||
class={"channel-selector"}
|
|
||||||
value={
|
|
||||||
updatedSettings.dashboard_color_scheme === undefined
|
|
||||||
? data.preferences.dashboard_color_scheme
|
|
||||||
: updatedSettings.dashboard_color_scheme
|
|
||||||
}
|
|
||||||
onChange={(e) =>
|
|
||||||
setUpdatedSettings((s) => ({
|
|
||||||
...s,
|
|
||||||
dashboard_color_scheme: (e.target as HTMLSelectElement)
|
|
||||||
.value as ColorScheme,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<option value={ColorScheme.System}>System default</option>
|
|
||||||
<option value={ColorScheme.Light}>Light</option>
|
|
||||||
<option value={ColorScheme.Dark}>Dark</option>
|
|
||||||
</select>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<br></br>
|
|
||||||
<div class="has-text-centered">
|
<div class="has-text-centered">
|
||||||
<button
|
<button
|
||||||
class="button is-success is-outlined"
|
class="button is-success is-outlined"
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ export const Welcome = () => (
|
|||||||
<p>
|
<p>
|
||||||
<strong>Please report bugs!</strong> I can't fix issues if I am unaware of them.
|
<strong>Please report bugs!</strong> I can't fix issues if I am unaware of them.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
|
||||||
Client version: {import.meta.env.VITE_VERSION}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -57,225 +57,3 @@ div.reminderContent.is-collapsed .hide-box i {
|
|||||||
.button.is-outlined.is-success:not(.is-focused, :hover, :focus) {
|
.button.is-outlined.is-success:not(.is-focused, :hover, :focus) {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@import "vars";
|
|
||||||
|
|
||||||
.scheme-light {
|
|
||||||
background-color: $primary-background-light;
|
|
||||||
color: $primary-text-light;
|
|
||||||
|
|
||||||
.column.is-main-content {
|
|
||||||
background-color: $primary-background-light;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reminderContent {
|
|
||||||
background-color: $secondary-background-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.scheme-dark {
|
|
||||||
background-color: $secondary-background-dark;
|
|
||||||
color: $primary-text-dark;
|
|
||||||
|
|
||||||
.column.is-main-content {
|
|
||||||
background-color: $secondary-background-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reminderContent {
|
|
||||||
background-color: $primary-background-dark;
|
|
||||||
color: $primary-text-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title,
|
|
||||||
.subtitle,
|
|
||||||
.label,
|
|
||||||
.help,
|
|
||||||
strong {
|
|
||||||
color: $primary-text-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
textarea,
|
|
||||||
select,
|
|
||||||
.input,
|
|
||||||
.textarea {
|
|
||||||
background-color: $secondary-background-dark;
|
|
||||||
color: $primary-text-dark;
|
|
||||||
border-color: $contrast-background-dark;
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Buttons
|
|
||||||
.button.is-outlined.is-success:not(.is-focused, :hover, :focus) {
|
|
||||||
background-color: $primary-background-dark;
|
|
||||||
color: #48c78e;
|
|
||||||
border-color: #48c78e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.is-outlined.is-danger:not(.is-focused, :hover, :focus) {
|
|
||||||
background-color: $primary-background-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.is-outlined.is-warning:not(.is-focused, :hover, :focus) {
|
|
||||||
background-color: $primary-background-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:not(.is-success, .is-danger, .is-warning, .is-light) {
|
|
||||||
background-color: $secondary-background-dark;
|
|
||||||
color: $primary-text-dark;
|
|
||||||
border-color: $contrast-background-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.is-light {
|
|
||||||
background-color: $secondary-background-dark;
|
|
||||||
color: $primary-text-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modal
|
|
||||||
.modal-card-head,
|
|
||||||
.modal-card-foot {
|
|
||||||
background-color: $contrast-background-dark;
|
|
||||||
border-color: $contrast-background-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-card-head .modal-card-title {
|
|
||||||
color: $primary-text-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-card-body {
|
|
||||||
background-color: $primary-background-dark;
|
|
||||||
color: $primary-text-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Navbar
|
|
||||||
.navbar {
|
|
||||||
background-color: $contrast-background-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-item,
|
|
||||||
.navbar-burger {
|
|
||||||
color: $primary-text-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select dropdown arrow
|
|
||||||
.select:not(.is-multiple):not(.is-loading)::after {
|
|
||||||
border-color: $primary-text-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select select {
|
|
||||||
background-color: $secondary-background-dark;
|
|
||||||
color: $primary-text-dark;
|
|
||||||
border-color: $contrast-background-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interval selector inputs
|
|
||||||
.interval-group input {
|
|
||||||
background-color: $secondary-background-dark !important;
|
|
||||||
color: $primary-text-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notification
|
|
||||||
.notification {
|
|
||||||
background-color: $secondary-background-dark;
|
|
||||||
color: $primary-text-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Links
|
|
||||||
a:not(.menu a):not(.button) {
|
|
||||||
color: #7eaef1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Content area
|
|
||||||
.content {
|
|
||||||
color: $primary-text-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checkbox
|
|
||||||
.checkbox {
|
|
||||||
color: $primary-text-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message input
|
|
||||||
.message-input {
|
|
||||||
background-color: $secondary-background-dark;
|
|
||||||
color: $primary-text-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Box
|
|
||||||
.box {
|
|
||||||
background-color: $secondary-background-dark;
|
|
||||||
color: $primary-text-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panel
|
|
||||||
.panel {
|
|
||||||
background-color: $secondary-background-dark;
|
|
||||||
|
|
||||||
.panel-heading {
|
|
||||||
background-color: $contrast-background-dark;
|
|
||||||
color: $primary-text-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-block {
|
|
||||||
color: $primary-text-dark;
|
|
||||||
border-color: $contrast-background-dark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Horizontal rule
|
|
||||||
hr {
|
|
||||||
background-color: $contrast-background-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tag
|
|
||||||
.tag:not(.is-success, .is-danger, .is-warning, .is-info) {
|
|
||||||
background-color: $contrast-background-dark;
|
|
||||||
color: $primary-text-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Todo
|
|
||||||
.todo {
|
|
||||||
color: $primary-text-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Page links
|
|
||||||
.page-links .button {
|
|
||||||
background-color: $secondary-background-dark;
|
|
||||||
color: $primary-text-dark;
|
|
||||||
border-color: $contrast-background-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loading overlay
|
|
||||||
.load-screen {
|
|
||||||
background-color: rgba(36, 36, 36, 0.8);
|
|
||||||
color: $primary-text-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
// File/attachment input
|
|
||||||
.file-cta {
|
|
||||||
background-color: $secondary-background-dark;
|
|
||||||
color: $primary-text-dark;
|
|
||||||
border-color: $contrast-background-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-name {
|
|
||||||
background-color: $secondary-background-dark;
|
|
||||||
color: $primary-text-dark;
|
|
||||||
border-color: $contrast-background-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Date/time inputs color scheme
|
|
||||||
input[type="date"],
|
|
||||||
input[type="time"],
|
|
||||||
input[type="datetime-local"] {
|
|
||||||
color-scheme: dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modal background overlay
|
|
||||||
.modal-background {
|
|
||||||
background-color: rgba(10, 10, 10, 0.86);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ impl Recordable for Options {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
let channel_id = if let Some(channel) = ctx.cache().channel(ctx.channel_id()) {
|
let channel_id = if let Some(channel) = ctx.channel_id().to_channel_cached(&ctx.cache()) {
|
||||||
if Some(channel.guild_id) == ctx.guild_id() {
|
if Some(channel.guild_id) == ctx.guild_id() {
|
||||||
flags.channel_id.unwrap_or_else(|| ctx.channel_id())
|
flags.channel_id.unwrap_or_else(|| ctx.channel_id())
|
||||||
} else {
|
} else {
|
||||||
@@ -66,7 +66,8 @@ impl Recordable for Options {
|
|||||||
ctx.channel_id()
|
ctx.channel_id()
|
||||||
};
|
};
|
||||||
|
|
||||||
let channel_name = ctx.cache().channel(channel_id).map(|channel| channel.name.clone());
|
let channel_name =
|
||||||
|
channel_id.to_channel_cached(&ctx.cache()).map(|channel| channel.name.clone());
|
||||||
|
|
||||||
let reminders = Reminder::from_channel(&ctx.data().database, channel_id, &flags).await;
|
let reminders = Reminder::from_channel(&ctx.data().database, channel_id, &flags).await;
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ impl ComponentDataModel {
|
|||||||
let flags = pager.flags;
|
let flags = pager.flags;
|
||||||
|
|
||||||
let channel_id = {
|
let channel_id = {
|
||||||
let channel_opt = ctx.cache.channel(component.channel_id);
|
let channel_opt = component.channel_id.to_channel_cached(&ctx.cache);
|
||||||
|
|
||||||
if let Some(channel) = channel_opt {
|
if let Some(channel) = channel_opt {
|
||||||
if Some(channel.guild_id) == component.guild_id {
|
if Some(channel.guild_id) == component.guild_id {
|
||||||
@@ -85,7 +85,7 @@ impl ComponentDataModel {
|
|||||||
.div_ceil(EMBED_DESCRIPTION_MAX_LENGTH);
|
.div_ceil(EMBED_DESCRIPTION_MAX_LENGTH);
|
||||||
|
|
||||||
let channel_name =
|
let channel_name =
|
||||||
ctx.cache.channel(channel_id).map(|channel| channel.name.clone());
|
channel_id.to_channel_cached(&ctx.cache).map(|channel| channel.name.clone());
|
||||||
|
|
||||||
let next_page = pager.next_page(pages);
|
let next_page = pager.next_page(pages);
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ use crate::{consts::DEFAULT_AVATAR, Error};
|
|||||||
pub struct ChannelData {
|
pub struct ChannelData {
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
pub channel: u64,
|
pub channel: u64,
|
||||||
|
pub name: Option<String>,
|
||||||
pub nudge: i16,
|
pub nudge: i16,
|
||||||
|
pub blacklisted: bool,
|
||||||
pub webhook_id: Option<u64>,
|
pub webhook_id: Option<u64>,
|
||||||
pub webhook_token: Option<String>,
|
pub webhook_token: Option<String>,
|
||||||
pub paused: bool,
|
pub paused: bool,
|
||||||
@@ -25,7 +27,7 @@ impl ChannelData {
|
|||||||
if let Ok(c) = sqlx::query_as_unchecked!(
|
if let Ok(c) = sqlx::query_as_unchecked!(
|
||||||
Self,
|
Self,
|
||||||
"
|
"
|
||||||
SELECT id, channel, nudge, webhook_id, webhook_token, paused,
|
SELECT id, channel, name, nudge, blacklisted, webhook_id, webhook_token, paused,
|
||||||
paused_until
|
paused_until
|
||||||
FROM channels
|
FROM channels
|
||||||
WHERE channel = ?
|
WHERE channel = ?
|
||||||
@@ -43,8 +45,9 @@ impl ChannelData {
|
|||||||
if let Some((a, b)) = props { (Some(a), Some(b)) } else { (None, None) };
|
if let Some((a, b)) = props { (Some(a), Some(b)) } else { (None, None) };
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"INSERT IGNORE INTO channels (channel, guild_id) VALUES (?, (SELECT id FROM guilds WHERE guild = ?))",
|
"INSERT IGNORE INTO channels (channel, name, guild_id) VALUES (?, ?, (SELECT id FROM guilds WHERE guild = ?))",
|
||||||
channel_id,
|
channel_id,
|
||||||
|
channel_name,
|
||||||
guild_id
|
guild_id
|
||||||
)
|
)
|
||||||
.execute(&pool.clone())
|
.execute(&pool.clone())
|
||||||
@@ -53,7 +56,7 @@ impl ChannelData {
|
|||||||
Ok(sqlx::query_as_unchecked!(
|
Ok(sqlx::query_as_unchecked!(
|
||||||
Self,
|
Self,
|
||||||
"
|
"
|
||||||
SELECT id, channel, nudge, webhook_id, webhook_token, paused, paused_until
|
SELECT id, channel, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_until
|
||||||
FROM channels
|
FROM channels
|
||||||
WHERE channel = ?
|
WHERE channel = ?
|
||||||
",
|
",
|
||||||
@@ -69,14 +72,18 @@ impl ChannelData {
|
|||||||
"
|
"
|
||||||
UPDATE channels
|
UPDATE channels
|
||||||
SET
|
SET
|
||||||
|
name = ?,
|
||||||
nudge = ?,
|
nudge = ?,
|
||||||
|
blacklisted = ?,
|
||||||
webhook_id = ?,
|
webhook_id = ?,
|
||||||
webhook_token = ?,
|
webhook_token = ?,
|
||||||
paused = ?,
|
paused = ?,
|
||||||
paused_until = ?
|
paused_until = ?
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
",
|
",
|
||||||
|
self.name,
|
||||||
self.nudge,
|
self.nudge,
|
||||||
|
self.blacklisted,
|
||||||
self.webhook_id,
|
self.webhook_id,
|
||||||
self.webhook_token,
|
self.webhook_token,
|
||||||
self.paused,
|
self.paused,
|
||||||
|
|||||||
@@ -215,17 +215,17 @@ impl<'a> MultiReminderBuilder<'a> {
|
|||||||
for scope in self.scopes {
|
for scope in self.scopes {
|
||||||
let db_channel_id = match scope {
|
let db_channel_id = match scope {
|
||||||
ReminderScope::User(user_id) => {
|
ReminderScope::User(user_id) => {
|
||||||
let user_id = UserId::new(user_id);
|
if let Ok(user) = UserId::new(user_id).to_user(&self.ctx).await {
|
||||||
match UserData::from_user(
|
let user_data = UserData::from_user(
|
||||||
&user_id,
|
&user,
|
||||||
&self.ctx.serenity_context(),
|
&self.ctx.serenity_context(),
|
||||||
&self.ctx.data().database,
|
&self.ctx.data().database,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
.unwrap();
|
||||||
Ok(user_data) => {
|
|
||||||
if let Some(guild_id) = self.guild_id {
|
if let Some(guild_id) = self.guild_id {
|
||||||
if guild_id.member(&self.ctx, user_id).await.is_err() {
|
if guild_id.member(&self.ctx, user).await.is_err() {
|
||||||
Err(ReminderError::InvalidTag)
|
Err(ReminderError::InvalidTag)
|
||||||
} else if self.set_by.map_or(true, |i| i != user_data.id)
|
} else if self.set_by.map_or(true, |i| i != user_data.id)
|
||||||
&& !user_data.allowed_dm
|
&& !user_data.allowed_dm
|
||||||
@@ -237,8 +237,8 @@ impl<'a> MultiReminderBuilder<'a> {
|
|||||||
} else {
|
} else {
|
||||||
Ok((user_data.dm_channel, None))
|
Ok((user_data.dm_channel, None))
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
Err(_) => Err(ReminderError::InvalidTag),
|
Err(ReminderError::InvalidTag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ReminderScope::Channel(channel_with_thread) => {
|
ReminderScope::Channel(channel_with_thread) => {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ impl IpBlocking {
|
|||||||
fn contains<I: Into<u32>>(&self, ip: I) -> bool {
|
fn contains<I: Into<u32>>(&self, ip: I) -> bool {
|
||||||
let ip: u32 = ip.into();
|
let ip: u32 = ip.into();
|
||||||
|
|
||||||
let _prev_index = self.upper_ips.len() - 1;
|
let mut prev_index = self.upper_ips.len() - 1;
|
||||||
let mut index = self.upper_ips.len() / 2;
|
let mut index = self.upper_ips.len() / 2;
|
||||||
loop {
|
loop {
|
||||||
if self.upper_ips[index] <= ip && self.lower_ips[index] >= ip {
|
if self.upper_ips[index] <= ip && self.lower_ips[index] >= ip {
|
||||||
|
|||||||
+6
-2
@@ -63,6 +63,7 @@ use log::{error, info, warn};
|
|||||||
use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl};
|
use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl};
|
||||||
use poise::serenity_prelude::{
|
use poise::serenity_prelude::{
|
||||||
client::Context,
|
client::Context,
|
||||||
|
http::CacheHttp,
|
||||||
model::id::{GuildId, UserId},
|
model::id::{GuildId, UserId},
|
||||||
};
|
};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
@@ -75,10 +76,13 @@ use rocket::{
|
|||||||
};
|
};
|
||||||
use rocket_dyn_templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
use sqlx::{MySql, Pool};
|
use sqlx::{MySql, Pool};
|
||||||
|
use std::net::Ipv4Addr;
|
||||||
use std::{env, path::Path};
|
use std::{env, path::Path};
|
||||||
|
|
||||||
use crate::web::consts::{DISCORD_OAUTH_AUTHORIZE, DISCORD_OAUTH_TOKEN};
|
use crate::web::{
|
||||||
use crate::web::fairings::metrics::MetricProducer;
|
consts::{CNC_GUILD, DISCORD_OAUTH_AUTHORIZE, DISCORD_OAUTH_TOKEN, SUBSCRIPTION_ROLES},
|
||||||
|
fairings::metrics::MetricProducer,
|
||||||
|
};
|
||||||
|
|
||||||
type Database = MySql;
|
type Database = MySql;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ mod roles;
|
|||||||
mod templates;
|
mod templates;
|
||||||
pub mod todos;
|
pub mod todos;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
|
||||||
use crate::utils::check_subscription;
|
use crate::utils::check_subscription;
|
||||||
use crate::web::guards::transaction::Transaction;
|
use crate::web::guards::transaction::Transaction;
|
||||||
use crate::web::{check_authorization, routes::JsonResult};
|
use crate::web::{check_authorization, routes::JsonResult};
|
||||||
@@ -14,7 +16,10 @@ pub use reminders::*;
|
|||||||
use rocket::{get, http::CookieJar, serde::json::json, State};
|
use rocket::{get, http::CookieJar, serde::json::json, State};
|
||||||
pub use roles::get_guild_roles;
|
pub use roles::get_guild_roles;
|
||||||
use serenity::all::UserId;
|
use serenity::all::UserId;
|
||||||
use serenity::{client::Context, model::id::GuildId};
|
use serenity::{
|
||||||
|
client::Context,
|
||||||
|
model::id::{GuildId, RoleId},
|
||||||
|
};
|
||||||
pub use templates::*;
|
pub use templates::*;
|
||||||
|
|
||||||
#[get("/api/guild/<id>")]
|
#[get("/api/guild/<id>")]
|
||||||
|
|||||||
@@ -267,8 +267,9 @@ pub async fn edit_reminder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if reminder.channel > 0 {
|
if reminder.channel > 0 {
|
||||||
let channel_guild =
|
let channel_guild = ChannelId::new(reminder.channel)
|
||||||
ctx.cache.channel(ChannelId::new(reminder.channel)).map(|channel| channel.guild_id);
|
.to_channel_cached(&ctx.cache)
|
||||||
|
.map(|channel| channel.guild_id);
|
||||||
match channel_guild {
|
match channel_guild {
|
||||||
Some(channel_guild) => {
|
Some(channel_guild) => {
|
||||||
let channel_matches_guild = channel_guild.get() == id;
|
let channel_matches_guild = channel_guild.get() == id;
|
||||||
|
|||||||
@@ -3,10 +3,13 @@ use crate::web::{
|
|||||||
check_authorization,
|
check_authorization,
|
||||||
guards::transaction::Transaction,
|
guards::transaction::Transaction,
|
||||||
routes::{
|
routes::{
|
||||||
dashboard::{ImportBody, ReminderTemplateCsv},
|
dashboard::{
|
||||||
|
create_reminder, CreateReminder, ImportBody, ReminderCsv, ReminderTemplateCsv, TodoCsv,
|
||||||
|
},
|
||||||
JsonResult,
|
JsonResult,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use crate::Database;
|
||||||
use base64::{prelude::BASE64_STANDARD, Engine};
|
use base64::{prelude::BASE64_STANDARD, Engine};
|
||||||
use csv::{QuoteStyle, WriterBuilder};
|
use csv::{QuoteStyle, WriterBuilder};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
@@ -17,7 +20,10 @@ use rocket::{
|
|||||||
serde::json::{json, Json},
|
serde::json::{json, Json},
|
||||||
State,
|
State,
|
||||||
};
|
};
|
||||||
use serenity::{client::Context, model::id::GuildId};
|
use serenity::{
|
||||||
|
client::Context,
|
||||||
|
model::id::{ChannelId, GuildId, UserId},
|
||||||
|
};
|
||||||
use sqlx::{MySql, Pool};
|
use sqlx::{MySql, Pool};
|
||||||
|
|
||||||
#[get("/api/guild/<id>/export/reminder_templates")]
|
#[get("/api/guild/<id>/export/reminder_templates")]
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ use crate::web::{
|
|||||||
check_authorization,
|
check_authorization,
|
||||||
guards::transaction::Transaction,
|
guards::transaction::Transaction,
|
||||||
routes::{
|
routes::{
|
||||||
dashboard::{create_reminder, CreateReminder, ImportBody, ReminderCsv},
|
dashboard::{
|
||||||
|
create_reminder, CreateReminder, ImportBody, ReminderCsv, ReminderTemplateCsv, TodoCsv,
|
||||||
|
},
|
||||||
JsonResult,
|
JsonResult,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -19,7 +21,7 @@ use rocket::{
|
|||||||
};
|
};
|
||||||
use serenity::{
|
use serenity::{
|
||||||
client::Context,
|
client::Context,
|
||||||
model::id::{GuildId, UserId},
|
model::id::{ChannelId, GuildId, UserId},
|
||||||
};
|
};
|
||||||
use sqlx::{MySql, Pool};
|
use sqlx::{MySql, Pool};
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
use crate::web::{
|
use crate::web::{
|
||||||
check_authorization,
|
check_authorization,
|
||||||
|
guards::transaction::Transaction,
|
||||||
routes::{
|
routes::{
|
||||||
dashboard::{ImportBody, TodoCsv},
|
dashboard::{
|
||||||
|
create_reminder, CreateReminder, ImportBody, ReminderCsv, ReminderTemplateCsv, TodoCsv,
|
||||||
|
},
|
||||||
JsonResult,
|
JsonResult,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use crate::Database;
|
||||||
use base64::{prelude::BASE64_STANDARD, Engine};
|
use base64::{prelude::BASE64_STANDARD, Engine};
|
||||||
use csv::{QuoteStyle, WriterBuilder};
|
use csv::{QuoteStyle, WriterBuilder};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
@@ -17,7 +21,7 @@ use rocket::{
|
|||||||
};
|
};
|
||||||
use serenity::{
|
use serenity::{
|
||||||
client::Context,
|
client::Context,
|
||||||
model::id::{ChannelId, GuildId},
|
model::id::{ChannelId, GuildId, UserId},
|
||||||
};
|
};
|
||||||
use sqlx::{MySql, Pool};
|
use sqlx::{MySql, Pool};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="EN">
|
||||||
|
<head>
|
||||||
|
<meta name="description" content="The most powerful Discord Reminders Bot">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="yandex-verification" content="bb77b8681eb64a90"/>
|
||||||
|
<meta name="google-site-verification" content="7h7UVTeEe0AOzHiH3cFtsqMULYGN-zCZdMT_YCkW1Ho"/>
|
||||||
|
<!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src *; font-src fonts.gstatic.com 'self'"> -->
|
||||||
|
|
||||||
|
<!-- favicon -->
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180"
|
||||||
|
href="/static/favicon/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32"
|
||||||
|
href="/static/favicon/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16"
|
||||||
|
href="/static/favicon/favicon-16x16.png">
|
||||||
|
<link rel="manifest" href="/static/site.webmanifest">
|
||||||
|
<meta name="msapplication-TileColor" content="#da532c">
|
||||||
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
|
||||||
|
<title>Reminder Bot | Dashboard</title>
|
||||||
|
|
||||||
|
<!-- styles -->
|
||||||
|
<link rel="stylesheet" href="/static/css/fa.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/font.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/style.css">
|
||||||
|
<script type="module" crossorigin src="/static/assets/index-B8f0viPI.js"></script>
|
||||||
|
<link rel="stylesheet" crossorigin href="/static/assets/index-BZ8NJuKt.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user