7 Commits

Author SHA1 Message Date
d082f63635 Bump version 2023-09-23 17:37:28 +01:00
9a51c548d6 Update dependencies 2023-09-23 16:18:33 +01:00
4bc7ae8e23 Change migration 2023-09-23 15:16:45 +01:00
6f1ef206df Correctly highlight options on mobile 2023-09-17 18:33:01 +01:00
ec63c942d6 Move button row down 2023-09-17 18:11:22 +01:00
06165c1b36 Restyle to work on most screen sizes 2023-09-17 18:03:57 +01:00
5ee9094bac Handle deleted channels in sender 2023-09-17 14:09:50 +01:00
17 changed files with 1141 additions and 886 deletions

1407
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,18 @@
[package] [package]
name = "reminder-rs" name = "reminder-rs"
version = "1.6.38" version = "1.6.40"
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"
description = "Reminder Bot for Discord, now in Rust" description = "Reminder Bot for Discord, now in Rust"
[dependencies] [dependencies]
poise = "0.5.5" poise = "0.5"
dotenv = "0.15" dotenv = "0.15"
tokio = { version = "1", features = ["process", "full"] } tokio = { version = "1", features = ["process", "full"] }
reqwest = "0.11" reqwest = "0.11"
lazy-regex = "2.3.0" lazy-regex = "3.0"
regex = "1.6" regex = "1.9"
log = "0.4" log = "0.4"
env_logger = "0.10" env_logger = "0.10"
chrono = "0.4" chrono = "0.4"
@ -25,7 +25,7 @@ serde_repr = "0.1"
rmp-serde = "1.1" rmp-serde = "1.1"
rand = "0.8" rand = "0.8"
levenshtein = "1.0" levenshtein = "1.0"
sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "migrate"]} sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "migrate"]}
base64 = "0.21.0" base64 = "0.21.0"
[dependencies.postman] [dependencies.postman]

View File

@ -1,4 +1,4 @@
ALTER TABLE reminders ADD COLUMN `status_change_time` DATETIME; ALTER TABLE reminders ADD COLUMN `status_change_time` DATETIME;
-- This is a best-guess as to the status change time. -- This is a best-guess as to the status change time.
UPDATE reminders SET `status_change_time` = `utc_time`; UPDATE reminders SET `status_change_time` = `utc_time` WHERE `status` != 'pending';

View File

@ -5,12 +5,12 @@ edition = "2021"
[dependencies] [dependencies]
tokio = { version = "1", features = ["process", "full"] } tokio = { version = "1", features = ["process", "full"] }
regex = "1.4" regex = "1.9"
log = "0.4" log = "0.4"
chrono = "0.4" chrono = "0.4"
chrono-tz = { version = "0.5", features = ["serde"] } chrono-tz = { version = "0.8", features = ["serde"] }
lazy_static = "1.4" lazy_static = "1.4"
num-integer = "0.1" num-integer = "0.1"
serde = "1.0" serde = "1.0"
sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "json"]} sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "json"]}
serenity = { version = "0.11.1", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] } serenity = { version = "0.11", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] }

View File

@ -237,11 +237,11 @@ impl Into<CreateEmbed> for Embed {
pub struct Reminder { pub struct Reminder {
id: u32, id: u32,
channel_id: u64, channel_id: Option<u64>,
webhook_id: Option<u64>, webhook_id: Option<u64>,
webhook_token: Option<String>, webhook_token: Option<String>,
channel_paused: bool, channel_paused: Option<bool>,
channel_paused_until: Option<NaiveDateTime>, channel_paused_until: Option<NaiveDateTime>,
enabled: bool, enabled: bool,
@ -297,7 +297,7 @@ SELECT
reminders.`username` AS username reminders.`username` AS username
FROM FROM
reminders reminders
INNER JOIN LEFT JOIN
channels channels
ON ON
reminders.channel_id = channels.id reminders.channel_id = channels.id
@ -310,7 +310,6 @@ WHERE
reminders reminders
WHERE WHERE
reminders.`utc_time` <= NOW() AND reminders.`utc_time` <= NOW() AND
reminders.`channel_id` IS NOT NULL AND
`status` = 'pending' AND `status` = 'pending' AND
( (
reminders.`interval_seconds` IS NOT NULL reminders.`interval_seconds` IS NOT NULL
@ -344,7 +343,10 @@ WHERE
async fn reset_webhook(&self, pool: impl Executor<'_, Database = Database> + Copy) { async fn reset_webhook(&self, pool: impl Executor<'_, Database = Database> + Copy) {
let _ = sqlx::query!( let _ = sqlx::query!(
"UPDATE channels SET webhook_id = NULL, webhook_token = NULL WHERE channel = ?", "
UPDATE channels SET webhook_id = NULL, webhook_token = NULL
WHERE channel = ?
",
self.channel_id self.channel_id
) )
.execute(pool) .execute(pool)
@ -416,7 +418,9 @@ WHERE
self.set_sent(pool).await; self.set_sent(pool).await;
} else { } else {
sqlx::query!( sqlx::query!(
"UPDATE reminders SET `utc_time` = ? WHERE `id` = ?", "
UPDATE reminders SET `utc_time` = ? WHERE `id` = ?
",
updated_reminder_time.with_timezone(&Utc), updated_reminder_time.with_timezone(&Utc),
self.id self.id
) )
@ -449,7 +453,10 @@ WHERE
if *LOG_TO_DATABASE { if *LOG_TO_DATABASE {
sqlx::query!( sqlx::query!(
"INSERT INTO stat (type, reminder_id, message) VALUES ('reminder_failed', ?, ?)", "
INSERT INTO stat (type, reminder_id, message)
VALUES ('reminder_failed', ?, ?)
",
self.id, self.id,
message, message,
) )
@ -462,7 +469,10 @@ WHERE
async fn log_success(&self, pool: impl Executor<'_, Database = Database> + Copy) { async fn log_success(&self, pool: impl Executor<'_, Database = Database> + Copy) {
if *LOG_TO_DATABASE { if *LOG_TO_DATABASE {
sqlx::query!( sqlx::query!(
"INSERT INTO stat (type, reminder_id) VALUES ('reminder_sent', ?)", "
INSERT INTO stat (type, reminder_id)
VALUES ('reminder_sent', ?)
",
self.id, self.id,
) )
.execute(pool) .execute(pool)
@ -491,7 +501,11 @@ WHERE
message: &'static str, message: &'static str,
) { ) {
sqlx::query!( sqlx::query!(
"UPDATE reminders SET `status` = 'failed', `status_message` = ? WHERE `id` = ?", "
UPDATE reminders
SET `status` = 'failed', `status_message` = ?, `status_change_time` = NOW()
WHERE `id` = ?
",
message, message,
self.id self.id
) )
@ -501,7 +515,9 @@ WHERE
} }
async fn pin_message<M: Into<u64>>(&self, message_id: M, http: impl AsRef<Http>) { async fn pin_message<M: Into<u64>>(&self, message_id: M, http: impl AsRef<Http>) {
let _ = http.as_ref().pin_message(self.channel_id, message_id.into(), None).await; if let Some(channel_id) = self.channel_id {
let _ = http.as_ref().pin_message(channel_id, message_id.into(), None).await;
}
} }
pub async fn send( pub async fn send(
@ -511,10 +527,11 @@ WHERE
) { ) {
async fn send_to_channel( async fn send_to_channel(
cache_http: impl CacheHttp, cache_http: impl CacheHttp,
channel_id: u64,
reminder: &Reminder, reminder: &Reminder,
embed: Option<CreateEmbed>, embed: Option<CreateEmbed>,
) -> Result<()> { ) -> Result<()> {
let channel = ChannelId(reminder.channel_id).to_channel(&cache_http).await; let channel = ChannelId(channel_id).to_channel(&cache_http).await;
match channel { match channel {
Ok(Channel::Guild(channel)) => { Ok(Channel::Guild(channel)) => {
@ -546,6 +563,7 @@ WHERE
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
Ok(Channel::Private(channel)) => { Ok(Channel::Private(channel)) => {
match channel match channel
.send_message(&cache_http.http(), |m| { .send_message(&cache_http.http(), |m| {
@ -575,7 +593,9 @@ WHERE
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
Err(e) => Err(e), Err(e) => Err(e),
_ => Err(Error::Other("Channel not of valid type")), _ => Err(Error::Other("Channel not of valid type")),
} }
} }
@ -630,14 +650,20 @@ WHERE
} }
} }
match self.channel_id {
Some(channel_id) => {
if self.enabled if self.enabled
&& !(self.channel_paused && !(self.channel_paused.unwrap_or(false)
&& self && self
.channel_paused_until .channel_paused_until
.map_or(true, |inner| inner >= Utc::now().naive_local())) .map_or(true, |inner| inner >= Utc::now().naive_local()))
{ {
let _ = sqlx::query!( let _ = sqlx::query!(
"UPDATE `channels` SET paused = 0, paused_until = NULL WHERE `channel` = ?", "
UPDATE `channels`
SET paused = 0, paused_until = NULL
WHERE `channel` = ?
",
self.channel_id self.channel_id
) )
.execute(pool) .execute(pool)
@ -648,8 +674,10 @@ WHERE
let result = if let (Some(webhook_id), Some(webhook_token)) = let result = if let (Some(webhook_id), Some(webhook_token)) =
(self.webhook_id, &self.webhook_token) (self.webhook_id, &self.webhook_token)
{ {
let webhook_res = let webhook_res = cache_http
cache_http.http().get_webhook_with_token(webhook_id, webhook_token).await; .http()
.get_webhook_with_token(webhook_id, webhook_token)
.await;
if let Ok(webhook) = webhook_res { if let Ok(webhook) = webhook_res {
send_to_webhook(cache_http, &self, webhook, embed).await send_to_webhook(cache_http, &self, webhook, embed).await
@ -657,10 +685,10 @@ WHERE
warn!("Webhook vanished for reminder {}: {:?}", self.id, webhook_res); warn!("Webhook vanished for reminder {}: {:?}", self.id, webhook_res);
self.reset_webhook(pool).await; self.reset_webhook(pool).await;
send_to_channel(cache_http, &self, embed).await send_to_channel(cache_http, channel_id, &self, embed).await
} }
} else { } else {
send_to_channel(cache_http, &self, embed).await send_to_channel(cache_http, channel_id, &self, embed).await
}; };
if let Err(e) = result { if let Err(e) = result {
@ -687,7 +715,10 @@ WHERE
None::<&'static str>, None::<&'static str>,
) )
.await; .await;
self.set_failed(pool, "Could not be sent as guild does not exist") self.set_failed(
pool,
"Could not be sent as guild does not exist",
)
.await; .await;
} }
50001 => { 50001 => {
@ -697,7 +728,11 @@ WHERE
None::<&'static str>, None::<&'static str>,
) )
.await; .await;
self.set_failed(pool, "Could not be sent as missing access").await; self.set_failed(
pool,
"Could not be sent as missing access",
)
.await;
} }
50007 => { 50007 => {
self.log_error( self.log_error(
@ -706,7 +741,10 @@ WHERE
None::<&'static str>, None::<&'static str>,
) )
.await; .await;
self.set_failed(pool, "Could not be sent as user has DMs disabled") self.set_failed(
pool,
"Could not be sent as user has DMs disabled",
)
.await; .await;
} }
50013 => { 50013 => {
@ -750,4 +788,13 @@ WHERE
self.refresh(pool).await; self.refresh(pool).await;
} }
} }
None => {
info!("Reminder {} is orphaned", self.id);
self.log_error(pool, "Orphaned", Option::<u8>::None).await;
self.set_failed(pool, "Could not be sent as channel was deleted").await;
}
}
}
} }

View File

@ -27,7 +27,7 @@ pub async fn migrate_macro(ctx: Context<'_>) -> Result<(), Error> {
"SELECT name, command FROM command_aliases WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)", "SELECT name, command FROM command_aliases WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)",
guild_id.0 guild_id.0
) )
.fetch_all(&mut transaction) .fetch_all(&mut *transaction)
.await?; .await?;
let mut added_aliases = 0; let mut added_aliases = 0;
@ -42,7 +42,7 @@ pub async fn migrate_macro(ctx: Context<'_>) -> Result<(), Error> {
cmd_macro.description, cmd_macro.description,
cmd_macro.commands cmd_macro.commands
) )
.execute(&mut transaction) .execute(&mut *transaction)
.await?; .await?;
added_aliases += 1; added_aliases += 1;

View File

@ -75,7 +75,8 @@ impl ChannelData {
UPDATE channels UPDATE channels
SET name = ?, nudge = ?, blacklisted = ?, webhook_id = ?, webhook_token = ?, SET name = ?, nudge = ?, blacklisted = ?, webhook_id = ?, webhook_token = ?,
paused = ?, paused_until = ? paused = ?, paused_until = ?
WHERE id = ?", WHERE id = ?
",
self.name, self.name,
self.nudge, self.nudge,
self.blacklisted, self.blacklisted,

View File

@ -7,14 +7,14 @@ edition = "2018"
[dependencies] [dependencies]
rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "master", features = ["tls", "secrets", "json"] } rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "master", features = ["tls", "secrets", "json"] }
rocket_dyn_templates = { git = "https://github.com/SergioBenitez/Rocket", branch = "master", features = ["tera"] } rocket_dyn_templates = { git = "https://github.com/SergioBenitez/Rocket", branch = "master", features = ["tera"] }
serenity = { version = "0.11.1", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] } serenity = { version = "0.11", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] }
oauth2 = "4" oauth2 = "4"
log = "0.4" log = "0.4"
reqwest = "0.11" reqwest = "0.11"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "macros", "mysql", "chrono", "json"] } sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "macros", "mysql", "chrono", "json"] }
chrono = "0.4" chrono = "0.4"
chrono-tz = "0.5" chrono-tz = "0.8"
lazy_static = "1.4.0" lazy_static = "1.4.0"
rand = "0.7" rand = "0.7"
base64 = "0.13" base64 = "0.13"

View File

@ -12,8 +12,7 @@ use sqlx::{MySql, Pool};
use crate::routes::{ use crate::routes::{
dashboard::{ dashboard::{
create_reminder, generate_uid, ImportBody, Reminder, ReminderCsv, ReminderTemplateCsv, create_reminder, ImportBody, ReminderCreate, ReminderCsv, ReminderTemplateCsv, TodoCsv,
TodoCsv,
}, },
JsonResult, JsonResult,
}; };
@ -141,11 +140,11 @@ pub async fn import_reminders(
match channel_id.parse::<u64>() { match channel_id.parse::<u64>() {
Ok(channel_id) => { Ok(channel_id) => {
let reminder = Reminder { let reminder = ReminderCreate {
attachment: record.attachment, attachment: record.attachment,
attachment_name: record.attachment_name, attachment_name: record.attachment_name,
avatar: record.avatar, avatar: record.avatar,
channel: Some(channel_id), channel: channel_id,
content: record.content, content: record.content,
embed_author: record.embed_author, embed_author: record.embed_author,
embed_author_url: record.embed_author_url, embed_author_url: record.embed_author_url,
@ -168,11 +167,8 @@ pub async fn import_reminders(
name: record.name, name: record.name,
restartable: record.restartable, restartable: record.restartable,
tts: record.tts, tts: record.tts,
uid: generate_uid(),
username: record.username, username: record.username,
utc_time: record.utc_time, utc_time: record.utc_time,
status: "pending".to_string(),
status_change_time: None,
}; };
create_reminder( create_reminder(

View File

@ -26,7 +26,7 @@ use crate::{
routes::{ routes::{
dashboard::{ dashboard::{
create_database_channel, create_reminder, template_name_default, DeleteReminder, create_database_channel, create_reminder, template_name_default, DeleteReminder,
DeleteReminderTemplate, PatchReminder, Reminder, ReminderTemplate, DeleteReminderTemplate, PatchReminder, Reminder, ReminderCreate, ReminderTemplate,
}, },
JsonResult, JsonResult,
}, },
@ -298,7 +298,7 @@ pub async fn delete_reminder_template(
#[post("/api/guild/<id>/reminders", data = "<reminder>")] #[post("/api/guild/<id>/reminders", data = "<reminder>")]
pub async fn create_guild_reminder( pub async fn create_guild_reminder(
id: u64, id: u64,
reminder: Json<Reminder>, reminder: Json<ReminderCreate>,
cookies: &CookieJar<'_>, cookies: &CookieJar<'_>,
serenity_context: &State<Context>, serenity_context: &State<Context>,
pool: &State<Pool<MySql>>, pool: &State<Pool<MySql>>,
@ -361,7 +361,8 @@ pub async fn get_reminders(
reminders.username, reminders.username,
reminders.utc_time, reminders.utc_time,
reminders.status, reminders.status,
reminders.status_change_time reminders.status_change_time,
reminders.status_message
FROM reminders FROM reminders
LEFT JOIN channels ON channels.id = reminders.channel_id LEFT JOIN channels ON channels.id = reminders.channel_id
WHERE FIND_IN_SET(`status`, ?) AND reminders.guild_id = (SELECT id FROM guilds WHERE guild = ?)", WHERE FIND_IN_SET(`status`, ?) AND reminders.guild_id = (SELECT id FROM guilds WHERE guild = ?)",
@ -549,7 +550,8 @@ pub async fn edit_reminder(
match sqlx::query_as_unchecked!( match sqlx::query_as_unchecked!(
Reminder, Reminder,
"SELECT reminders.attachment, "
SELECT reminders.attachment,
reminders.attachment_name, reminders.attachment_name,
reminders.avatar, reminders.avatar,
channels.channel, channels.channel,
@ -576,7 +578,8 @@ pub async fn edit_reminder(
reminders.username, reminders.username,
reminders.utc_time, reminders.utc_time,
reminders.status, reminders.status,
reminders.status_change_time reminders.status_change_time,
reminders.status_message
FROM reminders FROM reminders
LEFT JOIN channels ON channels.id = reminders.channel_id LEFT JOIN channels ON channels.id = reminders.channel_id
WHERE uid = ?", WHERE uid = ?",

View File

@ -118,6 +118,38 @@ pub struct EmbedField {
inline: bool, inline: bool,
} }
#[derive(Deserialize)]
pub struct ReminderCreate {
#[serde(with = "base64s")]
attachment: Option<Vec<u8>>,
attachment_name: Option<String>,
avatar: Option<String>,
#[serde(with = "string")]
channel: u64,
content: String,
embed_author: String,
embed_author_url: Option<String>,
embed_color: u32,
embed_description: String,
embed_footer: String,
embed_footer_url: Option<String>,
embed_image_url: Option<String>,
embed_thumbnail_url: Option<String>,
embed_title: String,
embed_fields: Option<Json<Vec<EmbedField>>>,
enabled: bool,
expires: Option<NaiveDateTime>,
interval_seconds: Option<u32>,
interval_days: Option<u32>,
interval_months: Option<u32>,
#[serde(default = "name_default")]
name: String,
restartable: bool,
tts: bool,
username: Option<String>,
utc_time: NaiveDateTime,
}
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Reminder { pub struct Reminder {
#[serde(with = "base64s")] #[serde(with = "base64s")]
@ -151,6 +183,7 @@ pub struct Reminder {
username: Option<String>, username: Option<String>,
utc_time: NaiveDateTime, utc_time: NaiveDateTime,
status: String, status: String,
status_message: Option<String>,
status_change_time: Option<NaiveDateTime>, status_change_time: Option<NaiveDateTime>,
} }
@ -290,15 +323,7 @@ pub fn generate_uid() -> String {
mod string { mod string {
use std::{fmt::Display, str::FromStr}; use std::{fmt::Display, str::FromStr};
use serde::{de, Deserialize, Deserializer, Serializer}; use serde::{de, Deserialize, Deserializer};
pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: Display,
S: Serializer,
{
serializer.collect_str(value)
}
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error> pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where where
@ -382,7 +407,7 @@ pub async fn create_reminder(
pool: impl sqlx::Executor<'_, Database = Database> + Copy, pool: impl sqlx::Executor<'_, Database = Database> + Copy,
guild_id: GuildId, guild_id: GuildId,
user_id: UserId, user_id: UserId,
reminder: Reminder, reminder: ReminderCreate,
) -> JsonResult { ) -> JsonResult {
// check guild in db // check guild in db
match sqlx::query!("SELECT 1 as A FROM guilds WHERE guild = ?", guild_id.0) match sqlx::query!("SELECT 1 as A FROM guilds WHERE guild = ?", guild_id.0)
@ -402,7 +427,7 @@ pub async fn create_reminder(
} }
// validate channel // validate channel
let channel = reminder.channel.map(|c| ChannelId(c).to_channel_cached(&ctx)).flatten(); let channel = ChannelId(reminder.channel).to_channel_cached(&ctx);
let channel_exists = channel.is_some(); let channel_exists = channel.is_some();
let channel_matches_guild = let channel_matches_guild =
@ -417,7 +442,7 @@ pub async fn create_reminder(
return Err(json!({"error": "Channel not found"})); return Err(json!({"error": "Channel not found"}));
} }
let channel = create_database_channel(&ctx, ChannelId(reminder.channel.unwrap()), pool).await; let channel = create_database_channel(&ctx, ChannelId(reminder.channel), pool).await;
if let Err(e) = channel { if let Err(e) = channel {
warn!("`create_database_channel` returned an error code: {:?}", e); warn!("`create_database_channel` returned an error code: {:?}", e);
@ -504,7 +529,8 @@ pub async fn create_reminder(
// write to db // write to db
match sqlx::query!( match sqlx::query!(
"INSERT INTO reminders ( "
INSERT INTO reminders (
uid, uid,
attachment, attachment,
attachment_name, attachment_name,
@ -532,7 +558,9 @@ pub async fn create_reminder(
tts, tts,
username, username,
`utc_time` `utc_time`
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", ) VALUES (?, ?, ?, ?,
(SELECT id FROM guilds WHERE guild = ?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?)",
new_uid, new_uid,
attachment_data, attachment_data,
reminder.attachment_name, reminder.attachment_name,
@ -594,7 +622,8 @@ pub async fn create_reminder(
reminders.username, reminders.username,
reminders.utc_time, reminders.utc_time,
reminders.status, reminders.status,
reminders.status_change_time reminders.status_change_time,
reminders.status_message
FROM reminders FROM reminders
LEFT JOIN channels ON channels.id = reminders.channel_id LEFT JOIN channels ON channels.id = reminders.channel_id
WHERE uid = ?", WHERE uid = ?",

View File

@ -15,6 +15,18 @@ div.reminderContent.is-collapsed .column.settings {
display: none; display: none;
} }
div.reminderContent.is-collapsed .button-row {
display: none;
}
div.reminderContent.is-collapsed .button-row-edit {
display: none;
}
div.reminderContent.is-collapsed .reminder-topbar {
padding-bottom: 0;
}
div.reminderContent.is-collapsed .invert-collapses { div.reminderContent.is-collapsed .invert-collapses {
display: inline-flex; display: inline-flex;
} }
@ -129,6 +141,12 @@ div.split-controls {
margin-top: 0 !important; margin-top: 0 !important;
} }
.reminder-settings > .column {
flex-grow: 0;
flex-shrink: 0;
flex-basis: 50%;
}
div.reminderContent { div.reminderContent {
margin-top: 10px; margin-top: 10px;
margin-bottom: 10px; margin-bottom: 10px;
@ -294,7 +312,7 @@ div.dashboard-sidebar:not(.mobile-sidebar) {
ul.guildList { ul.guildList {
flex-grow: 1; flex-grow: 1;
flex-shrink: 1; flex-shrink: 1;
overflow: scroll; overflow: auto;
} }
div.dashboard-sidebar:not(.mobile-sidebar) .aside-footer { div.dashboard-sidebar:not(.mobile-sidebar) .aside-footer {
@ -453,8 +471,7 @@ input.default-width {
.customizable.is-400x300 img { .customizable.is-400x300 img {
margin-top: 10px; margin-top: 10px;
width: 100%; width: 100%;
min-height: 100px; height: 100px;
max-height: 400px;
} }
.customizable.is-32x32 img { .customizable.is-32x32 img {
@ -598,6 +615,14 @@ input.default-width {
border-bottom: 1px solid #fff; border-bottom: 1px solid #fff;
} }
.channel-selector {
width: 100%;
}
.select {
width: 100%;
}
li.highlight { li.highlight {
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
@ -621,7 +646,22 @@ li.highlight {
padding: 2px; padding: 2px;
} }
@media only screen and (max-width: 1408px) { @media only screen and (max-width: 1023px) {
p.title.pageTitle {
display: none;
}
.dashboard-frame {
margin-top: 4rem !important;
}
.customizable.thumbnail img {
width: 60px;
height: 60px;
}
}
@media only screen and (max-width: 768px) {
.button-row { .button-row {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -639,37 +679,13 @@ li.highlight {
.button-row button { .button-row button {
width: 100%; width: 100%;
} }
}
@media only screen and (max-width: 768px) { .reminder-settings {
.button-row-edit { margin-bottom: 0 !important;
display: flex;
flex-direction: column;
} }
.button-row-edit > button { .tts-row {
width: 100%; padding-bottom: 0;
margin: 4px;
}
p.title.pageTitle {
display: none;
}
.dashboard-frame {
margin-top: 4rem !important;
}
}
@media only screen and (max-width: 768px) {
.customizable.thumbnail img {
width: 60px;
height: 60px;
}
.customizable.is-24x24 img {
width: 16px;
height: 16px;
} }
} }
@ -758,6 +774,16 @@ div.reminderError .errorHead .reminderTime {
border-style: solid; border-style: solid;
} }
div.reminderError .reminderMessage {
font-size: 1rem;
display: flex;
flex-direction: column;
justify-content: center;
color: rgb(54, 54, 54);
flex-grow: 1;
font-style: italic;
}
/* other stuff */ /* other stuff */
.half-rem { .half-rem {

View File

@ -454,17 +454,25 @@ document.addEventListener("guildSwitched", async (e) => {
.querySelectorAll(".patreon-only") .querySelectorAll(".patreon-only")
.forEach((el) => el.classList.add("is-locked")); .forEach((el) => el.classList.add("is-locked"));
let $li = document.querySelector(`li[data-guild="${e.detail.guild_id}"]`); let $li = document.querySelectorAll(`li[data-guild="${e.detail.guild_id}"]`);
if ($li === null) { if ($li.length === 0) {
switch_pane("user-error"); switch_pane("user-error");
return; return;
} }
switch_pane(e.detail.pane); switch_pane(e.detail.pane);
reset_guild_pane(); reset_guild_pane();
$li.querySelector("li > a").classList.add("is-active"); document
$li.querySelectorAll(`*[data-pane="${e.detail.pane}"]`).forEach((el) => { .querySelectorAll(`li[data-guild="${e.detail.guild_id}"] > a`)
.forEach((el) => {
el.classList.add("is-active");
});
document
.querySelectorAll(
`li[data-guild="${e.detail.guild_id}"] *[data-pane="${e.detail.pane}"]`
)
.forEach((el) => {
el.classList.add("is-active"); el.classList.add("is-active");
}); });

View File

@ -30,6 +30,8 @@ document.addEventListener("paneLoad", (ev) => {
{ zone: "UTC" } { zone: "UTC" }
); );
newRow.querySelector(".reminderName").textContent = reminder.name; newRow.querySelector(".reminderName").textContent = reminder.name;
newRow.querySelector(".reminderMessage").textContent =
reminder.status_message;
newRow.querySelector(".reminderTime").textContent = statusTime newRow.querySelector(".reminderTime").textContent = statusTime
.toLocal() .toLocal()
.toLocaleString(luxon.DateTime.DATETIME_MED); .toLocaleString(luxon.DateTime.DATETIME_MED);

View File

@ -40,7 +40,7 @@
<div class="navbar-brand"> <div class="navbar-brand">
<a class="navbar-item" href="/"> <a class="navbar-item" href="/">
<figure class="image"> <figure class="image">
<img src="/static/img/logo_nobg.webp" alt="Reminder Bot Logo"> <img width="28px" height="28px" src="/static/img/logo_nobg.webp" alt="Reminder Bot Logo">
</figure> </figure>
</a> </a>
@ -234,6 +234,7 @@
<a href="/"> <a href="/">
<div class="brand"> <div class="brand">
<img src="/static/img/logo_nobg.webp" alt="Reminder bot logo" <img src="/static/img/logo_nobg.webp" alt="Reminder bot logo"
width="52px" height="52px"
class="dashboard-brand"> class="dashboard-brand">
</div> </div>
</a> </a>

View File

@ -133,8 +133,6 @@
</article> </article>
</div> </div>
<div class="column settings"> <div class="column settings">
<div class="columns">
<div class="column">
<div class="field channel-field"> <div class="field channel-field">
<div class="collapses"> <div class="collapses">
<label class="label" for="channelOption">Channel*</label> <label class="label" for="channelOption">Channel*</label>
@ -149,8 +147,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="column">
<div class="field"> <div class="field">
<div class="control"> <div class="control">
<label class="label collapses"> <label class="label collapses">
@ -159,8 +156,6 @@
</label> </label>
</div> </div>
</div> </div>
</div>
</div>
<div class="collapses split-controls"> <div class="collapses split-controls">
<div> <div>
@ -236,7 +231,9 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
</div>
{% if creating %} {% if creating %}
<div class="button-row"> <div class="button-row">
<div class="button-row-reminder"> <div class="button-row-reminder">
@ -269,7 +266,4 @@
</button> </button>
</div> </div>
{% endif %} {% endif %}
</div>
</div>
</div>
</div> </div>

View File

@ -9,6 +9,9 @@
</div> </div>
<div class="reminderName"> <div class="reminderName">
Reminder Reminder
</div>
<div class="reminderMessage">
</div> </div>
<div class="reminderTime"> <div class="reminderTime">