removed remainder of old personal dashboard code. fixed big lighthouse issues.

This commit is contained in:
jude 2022-04-07 21:41:24 +01:00
parent 85d27c5bba
commit 0f05018cab
12 changed files with 593 additions and 389 deletions

View File

@ -24,10 +24,11 @@ CREATE TABLE reminder_template (
`embed_author` VARCHAR(256) NOT NULL DEFAULT '', `embed_author` VARCHAR(256) NOT NULL DEFAULT '',
`embed_author_url` VARCHAR(512), `embed_author_url` VARCHAR(512),
`embed_color` INT UNSIGNED NOT NULL DEFAULT 0x0, `embed_color` INT UNSIGNED NOT NULL DEFAULT 0x0,
`embed_fields` JSON,
PRIMARY KEY (id), PRIMARY KEY (id),
FOREIGN KEY (`guild_id`) REFERENCES channels (`id`) ON DELETE CASCADE FOREIGN KEY (`guild_id`) REFERENCES guilds (`id`) ON DELETE CASCADE
); );
ALTER TABLE reminders ADD COLUMN embed_fields JSON; ALTER TABLE reminders ADD COLUMN embed_fields JSON;

View File

@ -11,7 +11,8 @@ use std::{collections::HashMap, env};
use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl}; use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl};
use rocket::{ use rocket::{
fs::FileServer, fs::FileServer,
serde::json::{json, Json, Value as JsonValue}, serde::json::{json, Value as JsonValue},
shield::Shield,
tokio::sync::broadcast::Sender, tokio::sync::broadcast::Sender,
}; };
use rocket_dyn_templates::Template; use rocket_dyn_templates::Template;
@ -119,12 +120,15 @@ pub async fn initialize(
.mount( .mount(
"/dashboard", "/dashboard",
routes![ routes![
routes::dashboard::dashboard,
routes::dashboard::dashboard_home, routes::dashboard::dashboard_home,
routes::dashboard::user::get_user_info, routes::dashboard::user::get_user_info,
routes::dashboard::user::update_user_info, routes::dashboard::user::update_user_info,
routes::dashboard::user::get_user_guilds, routes::dashboard::user::get_user_guilds,
routes::dashboard::guild::get_guild_channels, routes::dashboard::guild::get_guild_channels,
routes::dashboard::guild::get_guild_roles, routes::dashboard::guild::get_guild_roles,
routes::dashboard::guild::get_reminder_templates,
routes::dashboard::guild::create_reminder_template,
routes::dashboard::guild::create_reminder, routes::dashboard::guild::create_reminder,
routes::dashboard::guild::get_reminders, routes::dashboard::guild::get_reminders,
routes::dashboard::guild::edit_reminder, routes::dashboard::guild::edit_reminder,

View File

@ -24,8 +24,8 @@ use crate::{
MAX_URL_LENGTH, MAX_USERNAME_LENGTH, MIN_INTERVAL, MAX_URL_LENGTH, MAX_USERNAME_LENGTH, MIN_INTERVAL,
}, },
routes::dashboard::{ routes::dashboard::{
create_database_channel, generate_uid, name_default, DeleteReminder, PatchReminder, create_database_channel, generate_uid, name_default, template_name_default, DeleteReminder,
Reminder, PatchReminder, Reminder, ReminderTemplate,
}, },
}; };
@ -131,6 +131,135 @@ pub async fn get_guild_roles(id: u64, cookies: &CookieJar<'_>, ctx: &State<Conte
} }
} }
#[get("/api/guild/<id>/templates")]
pub async fn get_reminder_templates(
id: u64,
cookies: &CookieJar<'_>,
ctx: &State<Context>,
pool: &State<Pool<MySql>>,
) -> JsonValue {
check_authorization!(cookies, ctx.inner(), id);
match sqlx::query_as_unchecked!(
ReminderTemplate,
"SELECT * FROM reminder_template WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)",
id
)
.fetch_all(pool.inner())
.await
{
Ok(templates) => {
json!(templates)
}
Err(e) => {
warn!("Could not fetch templates from {}: {:?}", id, e);
json!({"error": "Could not get templates"})
}
}
}
#[post("/api/guild/<id>/templates", data = "<reminder_template>")]
pub async fn create_reminder_template(
id: u64,
reminder_template: Json<ReminderTemplate>,
cookies: &CookieJar<'_>,
ctx: &State<Context>,
pool: &State<Pool<MySql>>,
) -> JsonValue {
check_authorization!(cookies, ctx.inner(), id);
// validate lengths
check_length!(MAX_CONTENT_LENGTH, reminder_template.content);
check_length!(MAX_EMBED_DESCRIPTION_LENGTH, reminder_template.embed_description);
check_length!(MAX_EMBED_TITLE_LENGTH, reminder_template.embed_title);
check_length!(MAX_EMBED_AUTHOR_LENGTH, reminder_template.embed_author);
check_length!(MAX_EMBED_FOOTER_LENGTH, reminder_template.embed_footer);
check_length_opt!(MAX_EMBED_FIELDS, reminder_template.embed_fields);
if let Some(fields) = &reminder_template.embed_fields {
for field in &fields.0 {
check_length!(MAX_EMBED_FIELD_VALUE_LENGTH, field.value);
check_length!(MAX_EMBED_FIELD_TITLE_LENGTH, field.title);
}
}
check_length_opt!(MAX_USERNAME_LENGTH, reminder_template.username);
check_length_opt!(
MAX_URL_LENGTH,
reminder_template.embed_footer_url,
reminder_template.embed_thumbnail_url,
reminder_template.embed_author_url,
reminder_template.embed_image_url,
reminder_template.avatar
);
// validate urls
check_url_opt!(
reminder_template.embed_footer_url,
reminder_template.embed_thumbnail_url,
reminder_template.embed_author_url,
reminder_template.embed_image_url,
reminder_template.avatar
);
let name = if reminder_template.name.is_empty() {
template_name_default()
} else {
reminder_template.name.clone()
};
match sqlx::query!(
"INSERT INTO reminder_template
(guild_id,
name,
attachment,
attachment_name,
avatar,
content,
embed_author,
embed_author_url,
embed_color,
embed_description,
embed_footer,
embed_footer_url,
embed_image_url,
embed_thumbnail_url,
embed_title,
embed_fields,
tts,
username
) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
id, name,
reminder_template.attachment,
reminder_template.attachment_name,
reminder_template.avatar,
reminder_template.content,
reminder_template.embed_author,
reminder_template.embed_author_url,
reminder_template.embed_color,
reminder_template.embed_description,
reminder_template.embed_footer,
reminder_template.embed_footer_url,
reminder_template.embed_image_url,
reminder_template.embed_thumbnail_url,
reminder_template.embed_title,
reminder_template.embed_fields,
reminder_template.tts,
reminder_template.username,
)
.fetch_all(pool.inner())
.await
{
Ok(_) => {
json!({})
}
Err(e) => {
warn!("Could not fetch templates from {}: {:?}", id, e);
json!({"error": "Could not get templates"})
}
}
}
#[post("/api/guild/<id>/reminders", data = "<reminder>")] #[post("/api/guild/<id>/reminders", data = "<reminder>")]
pub async fn create_reminder( pub async fn create_reminder(
id: u64, id: u64,
@ -550,9 +679,8 @@ pub async fn edit_reminder(
} }
} }
#[delete("/api/guild/<id>/reminders", data = "<reminder>")] #[delete("/api/guild/<_>/reminders", data = "<reminder>")]
pub async fn delete_reminder( pub async fn delete_reminder(
id: u64,
reminder: Json<DeleteReminder>, reminder: Json<DeleteReminder>,
pool: &State<Pool<MySql>>, pool: &State<Pool<MySql>>,
) -> JsonValue { ) -> JsonValue {

View File

@ -22,10 +22,44 @@ fn name_default() -> String {
"Reminder".to_string() "Reminder".to_string()
} }
fn template_name_default() -> String {
"Template".to_string()
}
fn channel_default() -> u64 { fn channel_default() -> u64 {
0 0
} }
fn id_default() -> u32 {
0
}
#[derive(Serialize, Deserialize)]
pub struct ReminderTemplate {
#[serde(default = "id_default")]
id: u32,
#[serde(default = "id_default")]
guild_id: u32,
#[serde(default = "template_name_default")]
name: String,
attachment: Option<Vec<u8>>,
attachment_name: Option<String>,
avatar: Option<String>,
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>>>,
tts: bool,
username: Option<String>,
}
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct EmbedField { pub struct EmbedField {
title: String, title: String,
@ -241,3 +275,13 @@ pub async fn dashboard_home(cookies: &CookieJar<'_>) -> Result<Template, Redirec
Err(Redirect::to("/login/discord")) Err(Redirect::to("/login/discord"))
} }
} }
#[get("/<_>")]
pub async fn dashboard(cookies: &CookieJar<'_>) -> Result<Template, Redirect> {
if cookies.get_private("userid").is_some() {
let map: HashMap<&str, String> = HashMap::new();
Ok(Template::render("dashboard", &map))
} else {
Err(Redirect::to("/login/discord"))
}
}

View File

@ -3,52 +3,61 @@
font-style: italic; font-style: italic;
font-weight: 300; font-weight: 300;
src: local('Source Sans Pro Light Italic'), local('SourceSansPro-LightItalic'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKwdSBYKcSV-LCoeQqfX1RYOo3qPZZMkids18E.ttf) format('truetype'); src: local('Source Sans Pro Light Italic'), local('SourceSansPro-LightItalic'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKwdSBYKcSV-LCoeQqfX1RYOo3qPZZMkids18E.ttf) format('truetype');
font-display: swap;
} }
@font-face { @font-face {
font-family: 'Source Sans Pro'; font-family: 'Source Sans Pro';
font-style: italic; font-style: italic;
font-weight: 400; font-weight: 400;
src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7nsDc.ttf) format('truetype'); src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7nsDc.ttf) format('truetype');
font-display: swap;
} }
@font-face { @font-face {
font-family: 'Source Sans Pro'; font-family: 'Source Sans Pro';
font-style: italic; font-style: italic;
font-weight: 600; font-weight: 600;
src: local('Source Sans Pro SemiBold Italic'), local('SourceSansPro-SemiBoldItalic'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKwdSBYKcSV-LCoeQqfX1RYOo3qPZY4lCds18E.ttf) format('truetype'); src: local('Source Sans Pro SemiBold Italic'), local('SourceSansPro-SemiBoldItalic'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKwdSBYKcSV-LCoeQqfX1RYOo3qPZY4lCds18E.ttf) format('truetype');
font-display: swap;
} }
@font-face { @font-face {
font-family: 'Source Sans Pro'; font-family: 'Source Sans Pro';
font-style: normal; font-style: normal;
font-weight: 300; font-weight: 300;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlxdr.ttf) format('truetype'); src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlxdr.ttf) format('truetype');
font-display: swap;
} }
@font-face { @font-face {
font-family: 'Source Sans Pro'; font-family: 'Source Sans Pro';
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7g.ttf) format('truetype'); src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7g.ttf) format('truetype');
font-display: swap;
} }
@font-face { @font-face {
font-family: 'Source Sans Pro'; font-family: 'Source Sans Pro';
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwlxdr.ttf) format('truetype'); src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwlxdr.ttf) format('truetype');
font-display: swap;
} }
@font-face { @font-face {
font-family: 'Source Sans Pro'; font-family: 'Source Sans Pro';
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdr.ttf) format('truetype'); src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdr.ttf) format('truetype');
font-display: swap;
} }
@font-face { @font-face {
font-family: 'Ubuntu'; font-family: 'Ubuntu';
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: url(https://fonts.gstatic.com/s/ubuntu/v15/4iCs6KVjbNBYlgo6eA.ttf) format('truetype'); src: url(https://fonts.gstatic.com/s/ubuntu/v15/4iCs6KVjbNBYlgo6eA.ttf) format('truetype');
font-display: swap;
} }
@font-face { @font-face {
font-family: 'Ubuntu'; font-family: 'Ubuntu';
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
src: url(https://fonts.gstatic.com/s/ubuntu/v15/4iCv6KVjbNBYlgoCxCvTtw.ttf) format('truetype'); src: url(https://fonts.gstatic.com/s/ubuntu/v15/4iCv6KVjbNBYlgoCxCvTtw.ttf) format('truetype');
font-display: swap;
} }

View File

@ -2,6 +2,10 @@
font-family: "Ubuntu Bold", "Ubuntu", sans-serif; font-family: "Ubuntu Bold", "Ubuntu", sans-serif;
} }
button {
font-weight: 700;
}
/* override styles for when the div is collapsed */ /* override styles for when the div is collapsed */
div.reminderContent.is-collapsed .column.discord-frame { div.reminderContent.is-collapsed .column.discord-frame {
display: none; display: none;
@ -55,6 +59,11 @@ div.reminderContent.is-collapsed button.hide-box i {
/* END */ /* END */
/* dashboard styles */ /* dashboard styles */
button.inline-btn {
height: 100%;
padding: 5px;
}
button.change-color { button.change-color {
position: absolute; position: absolute;
left: calc(-1rem - 40px); left: calc(-1rem - 40px);
@ -88,7 +97,7 @@ div.interval-group > button {
} }
/* Interval inputs */ /* Interval inputs */
div.interval-group > .interval-group-left > input { div.interval-group > .interval-group-left input {
-webkit-appearance: none; -webkit-appearance: none;
border-style: none; border-style: none;
background-color: #eee; background-color: #eee;
@ -96,11 +105,11 @@ div.interval-group > .interval-group-left > input {
font-family: monospace; font-family: monospace;
} }
div.interval-group > .interval-group-left > input.w2 { div.interval-group > .interval-group-left input.w2 {
width: 3ch; width: 3ch;
} }
div.interval-group > .interval-group-left > input.w3 { div.interval-group > .interval-group-left input.w3 {
width: 6ch; width: 6ch;
} }
@ -153,6 +162,15 @@ span.patreon-color {
color: #f96854; color: #f96854;
} }
p.pageTitle {
margin-left: 12px;
}
#welcome > div {
height: 100%;
padding-top: 30vh;
}
div#pageNavbar { div#pageNavbar {
background-color: #363636; background-color: #363636;
} }
@ -188,6 +206,17 @@ div.dashboard-sidebar {
padding-right: 0; padding-right: 0;
} }
div.dashboard-sidebar:not(.mobile-sidebar) {
display: flex;
flex-direction: column;
}
div.dashboard-sidebar:not(.mobile-sidebar) .aside-footer {
position: fixed;
bottom: 0;
width: 226px;
}
div.mobile-sidebar { div.mobile-sidebar {
z-index: 100; z-index: 100;
min-height: 100vh; min-height: 100vh;
@ -197,10 +226,24 @@ div.mobile-sidebar {
flex-direction: column; flex-direction: column;
} }
#expandAll {
width: 60px;
}
div.mobile-sidebar .aside-footer {
margin-top: auto;
}
div.mobile-sidebar.is-active { div.mobile-sidebar.is-active {
display: flex; display: flex;
} }
aside.menu {
display: flex;
flex-direction: column;
flex-grow: 1;
}
div.dashboard-frame { div.dashboard-frame {
min-height: 100vh; min-height: 100vh;
margin-bottom: 0 !important; margin-bottom: 0 !important;
@ -475,3 +518,40 @@ textarea, input {
height: 16px; height: 16px;
} }
} }
/* loader */
#loader {
position: fixed;
background-color: rgba(255, 255, 255, 0.8);
width: 100vw;
z-index: 999;
}
#loader .title {
font-size: 6rem;
}
/* END */
/* other stuff */
.half-rem {
width: 0.5rem;
}
.pad-left {
width: 12px;
}
#dead {
display: none;
}
.colorpicker-container {
display: flex;
justify-content: center;
}
.create-reminder {
margin: 0 12px 12px 12px;
}

View File

@ -6,9 +6,19 @@ const $colorPickerInput = $colorPickerModal.querySelector("input");
const $deleteReminderBtn = document.querySelector("#delete-reminder-confirm"); const $deleteReminderBtn = document.querySelector("#delete-reminder-confirm");
const $reminderTemplate = document.querySelector("template#guildReminder"); const $reminderTemplate = document.querySelector("template#guildReminder");
const $embedFieldTemplate = document.querySelector("template#embedFieldTemplate"); const $embedFieldTemplate = document.querySelector("template#embedFieldTemplate");
const $createReminder = document.querySelector("#reminderCreator");
const $createReminderBtn = $createReminder.querySelector("button#createReminder");
const $createTemplateBtn = $createReminder.querySelector("button#createTemplate");
const $loadTemplateBtn = document.querySelector("button#load-template");
const $templateSelect = document.querySelector("select#templateSelect");
let channels; let channels = [];
let roles; let roles = [];
let templates = {};
function guildId() {
return document.querySelector(".guildList a.is-active").dataset["guild"];
}
function colorToInt(r, g, b) { function colorToInt(r, g, b) {
return (r << 16) + (g << 8) + b; return (r << 16) + (g << 8) + b;
@ -77,6 +87,30 @@ function fetch_roles(guild_id) {
}); });
} }
function fetch_templates(guild_id) {
fetch(`/dashboard/api/guild/${guild_id}/templates`)
.then((response) => response.json())
.then((data) => {
if (data.error) {
show_error(data.error);
} else {
templates = {};
const select = document.querySelector("#templateSelect");
select.innerHTML = "";
for (let template of data) {
templates[template["id"]] = template;
let option = document.createElement("option");
option.value = template["id"];
option.textContent = template["name"];
select.appendChild(option);
}
}
});
}
async function fetch_channels(guild_id) { async function fetch_channels(guild_id) {
const event = new Event("channelsLoading"); const event = new Event("channelsLoading");
document.dispatchEvent(event); document.dispatchEvent(event);
@ -121,7 +155,7 @@ async function fetch_reminders(guild_id) {
newFrame.querySelector(".reminderContent").dataset["uid"] = newFrame.querySelector(".reminderContent").dataset["uid"] =
reminder["uid"]; reminder["uid"];
deserialize_reminder(reminder, newFrame); deserialize_reminder(reminder, newFrame, "load");
$reminderBox.appendChild(newFrame); $reminderBox.appendChild(newFrame);
@ -137,8 +171,21 @@ async function fetch_reminders(guild_id) {
}); });
} }
async function serialize_reminder(node) { async function serialize_reminder(node, mode) {
let interval = get_interval(node); let interval, utc_time;
if (mode !== "template") {
interval = get_interval(node);
utc_time = luxon.DateTime.fromISO(
node.querySelector('input[name="time"]').value
).setZone("UTC");
if (utc_time.invalid) {
return { error: "Time provided invalid." };
} else {
utc_time = utc_time.toFormat("yyyy-LL-dd'T'HH:mm:ss");
}
}
let rgb_color = window.getComputedStyle( let rgb_color = window.getComputedStyle(
node.querySelector("div.discord-embed") node.querySelector("div.discord-embed")
@ -146,21 +193,13 @@ async function serialize_reminder(node) {
let rgb = rgb_color.match(/\d+/g); let rgb = rgb_color.match(/\d+/g);
let color = colorToInt(parseInt(rgb[0]), parseInt(rgb[1]), parseInt(rgb[2])); let color = colorToInt(parseInt(rgb[0]), parseInt(rgb[1]), parseInt(rgb[2]));
let utc_time = luxon.DateTime.fromISO(
node.querySelector('input[name="time"]').value
).setZone("UTC");
if (utc_time.invalid) {
return { error: "Time provided invalid." };
}
let fields = [ let fields = [
...node.querySelectorAll("div.embed-multifield-box div.embed-field-box"), ...node.querySelectorAll("div.embed-multifield-box div.embed-field-box"),
] ]
.map((el) => { .map((el) => {
return { return {
title: el.querySelector("textarea#embedFieldTitle").value, title: el.querySelector("textarea.discord-field-title").value,
value: el.querySelector("textarea#embedFieldValue").value, value: el.querySelector("textarea.discord-field-value").value,
inline: el.dataset["inlined"] === "1", inline: el.dataset["inlined"] === "1",
}; };
}) })
@ -181,13 +220,21 @@ async function serialize_reminder(node) {
attachment_name = file.name; attachment_name = file.name;
} }
const reminderContent = node.closest(".reminderContent"); let uid = "";
if (mode === "edit") {
uid = node.closest(".reminderContent").dataset["uid"];
}
let enabled = null;
if (mode === "create") {
enabled = true;
}
return { return {
// if we're creating a reminder, ignore this field // if we're creating a reminder, ignore this field
uid: reminderContent !== null ? reminderContent.dataset["uid"] : "", uid: uid,
// if we're editing a reminder, ignore this field // if we're editing a reminder, ignore this field
enabled: reminderContent !== null ? null : true, enabled: enabled,
restartable: false, restartable: false,
attachment: attachment, attachment: attachment,
attachment_name: attachment_name, attachment_name: attachment_name,
@ -207,17 +254,17 @@ async function serialize_reminder(node) {
embed_title: node.querySelector('textarea[name="embed_title"]').value, embed_title: node.querySelector('textarea[name="embed_title"]').value,
embed_fields: fields, embed_fields: fields,
expires: null, expires: null,
interval_seconds: interval.seconds, interval_seconds: mode !== "template" ? interval.seconds : null,
interval_months: interval.months, interval_months: mode !== "template" ? interval.months : null,
name: node.querySelector('input[name="name"]').value, name: node.querySelector('input[name="name"]').value,
pin: node.querySelector('input[name="pin"]').checked, pin: node.querySelector('input[name="pin"]').checked,
tts: node.querySelector('input[name="tts"]').checked, tts: node.querySelector('input[name="tts"]').checked,
username: node.querySelector('input[name="username"]').value, username: node.querySelector('input[name="username"]').value,
utc_time: utc_time.toFormat("yyyy-LL-dd'T'HH:mm:ss"), utc_time: utc_time,
}; };
} }
function deserialize_reminder(reminder, frame) { function deserialize_reminder(reminder, frame, mode) {
// populate channels // populate channels
set_channels(frame.querySelector("select.channel-selector")); set_channels(frame.querySelector("select.channel-selector"));
@ -256,16 +303,18 @@ function deserialize_reminder(reminder, frame) {
.insertBefore(embed_field, lastChild); .insertBefore(embed_field, lastChild);
} }
if (reminder["interval_seconds"] !== null) update_interval(frame); if (mode !== "template") {
if (reminder["interval_seconds"]) update_interval(frame);
let $enableBtn = frame.querySelector(".disable-enable"); let $enableBtn = frame.querySelector(".disable-enable");
$enableBtn.dataset["action"] = reminder["enabled"] ? "disable" : "enable"; $enableBtn.dataset["action"] = reminder["enabled"] ? "disable" : "enable";
let timeInput = frame.querySelector('input[name="time"]'); let timeInput = frame.querySelector('input[name="time"]');
let localTime = luxon.DateTime.fromISO(reminder["utc_time"], { zone: "UTC" }).setZone( let localTime = luxon.DateTime.fromISO(reminder["utc_time"], {
timezone zone: "UTC",
); }).setZone(timezone);
timeInput.value = localTime.toFormat("yyyy-LL-dd'T'HH:mm:ss"); timeInput.value = localTime.toFormat("yyyy-LL-dd'T'HH:mm:ss");
}
} }
document.addEventListener("guildSwitched", async (e) => { document.addEventListener("guildSwitched", async (e) => {
@ -276,11 +325,11 @@ document.addEventListener("guildSwitched", async (e) => {
); );
switch_pane($anchor.dataset["pane"]); switch_pane($anchor.dataset["pane"]);
reset_guild_pane();
$anchor.classList.add("is-active"); $anchor.classList.add("is-active");
reset_guild_pane();
fetch_roles(e.detail.guild_id); fetch_roles(e.detail.guild_id);
fetch_templates(e.detail.guild_id);
await fetch_channels(e.detail.guild_id); await fetch_channels(e.detail.guild_id);
fetch_reminders(e.detail.guild_id); fetch_reminders(e.detail.guild_id);
@ -303,7 +352,7 @@ document.addEventListener("channelsLoaded", () => {
}); });
document.addEventListener("remindersLoaded", (event) => { document.addEventListener("remindersLoaded", (event) => {
const guild = document.querySelector(".guildList a.is-active").dataset["guild"]; const guild = guildId();
for (let reminder of event.detail) { for (let reminder of event.detail) {
let node = reminder.node; let node = reminder.node;
@ -351,13 +400,13 @@ document.addEventListener("remindersLoaded", (event) => {
"fas fa-spinner fa-spin", "fas fa-spinner fa-spin",
]; ];
let reminder = await serialize_reminder(node); let reminder = await serialize_reminder(node, "edit");
if (reminder.error) { if (reminder.error) {
show_error(reminder.error); show_error(reminder.error);
return; return;
} }
let guild = document.querySelector(".guildList a.is-active").dataset["guild"]; let guild = guildId();
fetch(`/dashboard/api/guild/${guild}/reminders`, { fetch(`/dashboard/api/guild/${guild}/reminders`, {
method: "PATCH", method: "PATCH",
@ -381,7 +430,7 @@ document.addEventListener("remindersLoaded", (event) => {
}); });
$deleteReminderBtn.addEventListener("click", () => { $deleteReminderBtn.addEventListener("click", () => {
let guild = document.querySelector(".guildList a.is-active").dataset["guild"]; let guild = guildId();
fetch(`/dashboard/api/guild/${guild}/reminders`, { fetch(`/dashboard/api/guild/${guild}/reminders`, {
method: "DELETE", method: "DELETE",
@ -422,7 +471,7 @@ $colorPickerModal.querySelector("button.is-success").addEventListener("click", (
$colorPickerModal.classList.remove("is-active"); $colorPickerModal.classList.remove("is-active");
}); });
document.querySelectorAll("a.show-modal").forEach((element) => { document.querySelectorAll(".show-modal").forEach((element) => {
element.addEventListener("click", (e) => { element.addEventListener("click", (e) => {
e.preventDefault(); e.preventDefault();
document.getElementById(element.dataset["modal"]).classList.toggle("is-active"); document.getElementById(element.dataset["modal"]).classList.toggle("is-active");
@ -480,10 +529,15 @@ document.addEventListener("DOMContentLoaded", () => {
); );
$anchor.dataset["guild"] = guild.id; $anchor.dataset["guild"] = guild.id;
$anchor.dataset["name"] = guild.name; $anchor.dataset["name"] = guild.name;
$anchor.href = `/dashboard/${guild.id}?name=${guild.name}`;
$anchor.addEventListener("click", async (e) => { $anchor.addEventListener("click", async (e) => {
e.preventDefault(); e.preventDefault();
window.history.pushState(
{},
"",
`/dashboard/${guild.id}?name=${guild.name}`
);
const event = new CustomEvent("guildSwitched", { const event = new CustomEvent("guildSwitched", {
detail: { detail: {
guild_name: guild.name, guild_name: guild.name,
@ -497,6 +551,21 @@ document.addEventListener("DOMContentLoaded", () => {
element.append($clone); element.append($clone);
}); });
} }
const matches = window.location.href.match(/dashboard\/(\d+)/);
if (matches) {
let id = matches[1];
let name =
new URLSearchParams(window.location.search).get("name") || id;
const event = new CustomEvent("guildSwitched", {
detail: {
guild_name: name,
guild_id: id,
},
});
document.dispatchEvent(event);
}
} }
}); });
@ -530,34 +599,18 @@ function has_source(string) {
} }
} }
let $createReminder = document.querySelector("#reminderCreator"); $createReminderBtn.addEventListener("click", async () => {
let $createBtn = $createReminder.querySelector("button#createReminder"); $createReminderBtn.querySelector("span.icon > i").classList = [
"fas fa-spinner fa-spin",
];
$createBtn.addEventListener("click", async () => { let reminder = await serialize_reminder($createReminder, "create");
$createBtn.querySelector("span.icon > i").classList = ["fas fa-spinner fa-spin"];
let attachment = null;
let attachment_name = null;
if ($createReminder.querySelector('input[name="attachment"]').files.length > 0) {
let file = $createReminder.querySelector('input[name="attachment"]').files[0];
attachment = await new Promise((resolve) => {
let fileReader = new FileReader();
fileReader.onload = (e) => resolve(fileReader.result);
fileReader.readAsDataURL(file);
});
attachment = attachment.split(",")[1];
attachment_name = file.name;
}
let reminder = await serialize_reminder($createReminder);
if (reminder.error) { if (reminder.error) {
show_error(reminder.error); show_error(reminder.error);
return; return;
} }
let guild = document.querySelector(".guildList a.is-active").dataset["guild"]; let guild = guildId();
fetch(`/dashboard/api/guild/${guild}/reminders`, { fetch(`/dashboard/api/guild/${guild}/reminders`, {
method: "POST", method: "POST",
@ -570,13 +623,17 @@ $createBtn.addEventListener("click", async () => {
.then((data) => { .then((data) => {
if (data.error) { if (data.error) {
show_error(data.error); show_error(data.error);
$createReminderBtn.querySelector("span.icon > i").classList = [
"fas fa-sparkles",
];
} else { } else {
const $reminderBox = document.querySelector("div#guildReminders"); const $reminderBox = document.querySelector("div#guildReminders");
let newFrame = $reminderTemplate.content.cloneNode(true); let newFrame = $reminderTemplate.content.cloneNode(true);
newFrame.querySelector(".reminderContent").dataset["uid"] = data["uid"]; newFrame.querySelector(".reminderContent").dataset["uid"] = data["uid"];
deserialize_reminder(data, newFrame); deserialize_reminder(data, newFrame, "load");
$reminderBox.appendChild(newFrame); $reminderBox.appendChild(newFrame);
@ -587,16 +644,62 @@ $createBtn.addEventListener("click", async () => {
detail: [data], detail: [data],
}) })
); );
}
$createBtn.querySelector("span.icon > i").classList = ["fas fa-check"]; $createReminderBtn.querySelector("span.icon > i").classList = [
"fas fa-check",
];
window.setTimeout(() => { window.setTimeout(() => {
$createBtn.querySelector("span.icon > i").classList = ["fas fa-sparkles"]; $createReminderBtn.querySelector("span.icon > i").classList = [
"fas fa-sparkles",
];
}, 1500); }, 1500);
}
}); });
}); });
$createTemplateBtn.addEventListener("click", async () => {
let reminder = await serialize_reminder($createReminder, "template");
let guild = guildId();
fetch(`/dashboard/api/guild/${guild}/templates`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(reminder),
})
.then((response) => response.json())
.then((data) => {
if (data.error) {
show_error(data.error);
$createTemplateBtn.querySelector("span.icon > i").classList = [
"fas fa-file-spreadsheet",
];
} else {
fetch_templates(guildId());
$createTemplateBtn.querySelector("span.icon > i").classList = [
"fas fa-check",
];
window.setTimeout(() => {
$createTemplateBtn.querySelector("span.icon > i").classList = [
"fas fa-file-spreadsheet",
];
}, 1500);
}
});
});
$loadTemplateBtn.addEventListener("click", (ev) => {
deserialize_reminder(
templates[parseInt($templateSelect.value)],
$createReminder,
"template"
);
});
document.querySelectorAll("textarea.autoresize").forEach((element) => { document.querySelectorAll("textarea.autoresize").forEach((element) => {
element.addEventListener("input", () => { element.addEventListener("input", () => {
element.style.height = ""; element.style.height = "";

View File

@ -6,6 +6,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="yandex-verification" content="bb77b8681eb64a90"/> <meta name="yandex-verification" content="bb77b8681eb64a90"/>
<meta name="google-site-verification" content="7h7UVTeEe0AOzHiH3cFtsqMULYGN-zCZdMT_YCkW1Ho"/> <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 --> <!-- favicon -->
<link rel="apple-touch-icon" sizes="180x180" <link rel="apple-touch-icon" sizes="180x180"
@ -27,10 +28,7 @@
<link rel="stylesheet" href="/static/css/style.css"> <link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/dtsel.css"> <link rel="stylesheet" href="/static/css/dtsel.css">
<script src="/static/js/iro.js"></script>
<script src="/static/js/dtsel.js"></script>
<script src="/static/js/luxon.min.js"></script> <script src="/static/js/luxon.min.js"></script>
</head> </head>
<body> <body>
<nav class="navbar is-spaced is-size-4 is-hidden-desktop dashboard-navbar" role="navigation" <nav class="navbar is-spaced is-size-4 is-hidden-desktop dashboard-navbar" role="navigation"
@ -54,10 +52,10 @@
</div> </div>
</nav> </nav>
<div id="loader" class="is-hidden hero is-fullheight" style="position: fixed; background-color: rgba(255, 255, 255, 0.8); width: 100vw; z-index: 999;"> <div id="loader" class="is-hidden hero is-fullheight">
<div class="hero-body"> <div class="hero-body">
<div class="container has-text-centered"> <div class="container has-text-centered">
<p class="title" style="font-size: 6rem; color: #8fb677"> <p class="title">
<i class="fas fa-cog fa-spin"></i> <i class="fas fa-cog fa-spin"></i>
</p> </p>
<p class="subtitle"> <p class="subtitle">
@ -68,7 +66,7 @@
</div> </div>
<!-- dead image used to check which other images are dead --> <!-- dead image used to check which other images are dead -->
<img style="display: none;" src="" id="dead"> <img src="" id="dead">
<div class="notification is-danger flash-message" id="errors"> <div class="notification is-danger flash-message" id="errors">
<span class="icon"><i class="far fa-exclamation-circle"></i></span> <span class="error-message"></span> <span class="icon"><i class="far fa-exclamation-circle"></i></span> <span class="error-message"></span>
@ -100,7 +98,7 @@
<button class="delete close-modal" aria-label="close"></button> <button class="delete close-modal" aria-label="close"></button>
</header> </header>
<section class="modal-card-body"> <section class="modal-card-body">
<div style="display: flex; justify-content: center"> <div class="colorpicker-container">
<div id="colorpicker"></div> <div id="colorpicker"></div>
</div> </div>
<input class="input" id="colorInput"> <input class="input" id="colorInput">
@ -140,6 +138,33 @@
<button class="modal-close is-large close-modal" aria-label="close"></button> <button class="modal-close is-large close-modal" aria-label="close"></button>
</div> </div>
<div class="modal" id="chooseTemplateModal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<label class="modal-card-title" for="urlInput">Load Template</label>
<button class="delete close-modal" aria-label="close"></button>
</header>
<section class="modal-card-body">
<div class="control has-icons-left">
<div class="select is-fullwidth">
<select id="templateSelect">
</select>
</div>
<div class="icon is-small is-left">
<i class="fas fa-file-spreadsheet"></i>
</div>
</div>
<br>
<div class="has-text-centered">
<button class="button is-success close-modal" id="load-template">Load Template</button>
<button class="button is-danger" id="delete-template">Delete</button>
</div>
</section>
</div>
<button class="modal-close is-large close-modal" aria-label="close"></button>
</div>
<div class="modal" id="dataManagerModal"> <div class="modal" id="dataManagerModal">
<div class="modal-background"></div> <div class="modal-background"></div>
<div class="modal-card"> <div class="modal-card">
@ -179,7 +204,7 @@
</div> </div>
<div class="columns is-gapless dashboard-frame"> <div class="columns is-gapless dashboard-frame">
<div class="column is-2 is-sidebar-menu dashboard-sidebar is-hidden-touch" style="display: flex; flex-direction: column;"> <div class="column is-2 is-sidebar-menu dashboard-sidebar is-hidden-touch">
<a href="/"> <a href="/">
<div class="brand"> <div class="brand">
<img src="/static/img/logo_flat.webp" alt="Reminder bot logo" <img src="/static/img/logo_flat.webp" alt="Reminder bot logo"
@ -192,14 +217,14 @@
d="M0,192L60,170.7C120,149,240,107,360,96C480,85,600,107,720,138.7C840,171,960,213,1080,197.3C1200,181,1320,107,1380,69.3L1440,32L1440,0L1380,0C1320,0,1200,0,1080,0C960,0,840,0,720,0C600,0,480,0,360,0C240,0,120,0,60,0L0,0Z"></path> d="M0,192L60,170.7C120,149,240,107,360,96C480,85,600,107,720,138.7C840,171,960,213,1080,197.3C1200,181,1320,107,1380,69.3L1440,32L1440,0L1380,0C1320,0,1200,0,1080,0C960,0,840,0,720,0C600,0,480,0,360,0C240,0,120,0,60,0L0,0Z"></path>
</g> </g>
</svg> </svg>
<aside class="menu" style="display: flex; flex-direction: column; flex-grow: 1;"> <aside class="menu">
<p class="menu-label"> <p class="menu-label">
Servers Servers
</p> </p>
<ul class="menu-list guildList"> <ul class="menu-list guildList">
</ul> </ul>
<div class="aside-footer" style="position: fixed; bottom: 0; width: 226px;"> <div class="aside-footer">
<p class="menu-label"> <p class="menu-label">
Settings Settings
</p> </p>
@ -230,14 +255,14 @@
d="M0,192L60,170.7C120,149,240,107,360,96C480,85,600,107,720,138.7C840,171,960,213,1080,197.3C1200,181,1320,107,1380,69.3L1440,32L1440,0L1380,0C1320,0,1200,0,1080,0C960,0,840,0,720,0C600,0,480,0,360,0C240,0,120,0,60,0L0,0Z"></path> d="M0,192L60,170.7C120,149,240,107,360,96C480,85,600,107,720,138.7C840,171,960,213,1080,197.3C1200,181,1320,107,1380,69.3L1440,32L1440,0L1380,0C1320,0,1200,0,1080,0C960,0,840,0,720,0C600,0,480,0,360,0C240,0,120,0,60,0L0,0Z"></path>
</g> </g>
</svg> </svg>
<aside class="menu" style="display: flex; flex-direction: column; flex-grow: 1;"> <aside class="menu">
<p class="menu-label"> <p class="menu-label">
Servers Servers
</p> </p>
<ul class="menu-list guildList"> <ul class="menu-list guildList">
</ul> </ul>
<div class="aside-footer" style="margin-top: auto;"> <div class="aside-footer">
<p class="menu-label"> <p class="menu-label">
Settings Settings
</p> </p>
@ -257,17 +282,14 @@
<!-- main content --> <!-- main content -->
<div class="column is-main-content"> <div class="column is-main-content">
<p class="title pageTitle" style="margin-left: 12px;"></p> <p class="title pageTitle"></p>
<section id="welcome"> <section id="welcome">
<div class="has-text-centered" style="height: 100%; padding-top: 30vh;"> <div class="has-text-centered">
<p class="title">Welcome!</p> <p class="title">Welcome!</p>
<p class="subtitle is-hidden-touch">Select an option from the side to get started</p> <p class="subtitle is-hidden-touch">Select an option from the side to get started</p>
<p class="subtitle is-hidden-desktop">Press the <span class="icon"><i class="fal fa-bars"></i></span> to get started</p> <p class="subtitle is-hidden-desktop">Press the <span class="icon"><i class="fal fa-bars"></i></span> to get started</p>
</div> </div>
</section> </section>
<section id="personal" class="is-hidden">
{% include "reminder_dashboard/reminder_dashboard_personal" %}
</section>
<section id="guild" class="is-hidden"> <section id="guild" class="is-hidden">
{% include "reminder_dashboard/reminder_dashboard" %} {% include "reminder_dashboard/reminder_dashboard" %}
</section> </section>
@ -294,20 +316,26 @@
<template id="embedFieldTemplate"> <template id="embedFieldTemplate">
<div data-inlined="1" class="embed-field-box"> <div data-inlined="1" class="embed-field-box">
<label class="is-sr-only" for="embedFieldTitle">Field Title</label> <div class="is-flex">
<div style="display: flex;"> <label>
<span class="is-sr-only">Field Title</span>
<textarea class="discord-field-title field-input message-input autoresize" <textarea class="discord-field-title field-input message-input autoresize"
placeholder="Field Title..." rows="1" placeholder="Field Title..." rows="1"
maxlength="256" id="embedFieldTitle" name="embed_field_title[]"></textarea> maxlength="256" name="embed_field_title[]"></textarea>
<button class="button is-small inline-btn" style="height: 100%; padding: 5px;"><i class="fas fa-arrows-h"></i></button> </label>
<button class="button is-small inline-btn">
<span class="is-sr-only">Toggle field inline</span><i class="fas fa-arrows-h"></i>
</button>
</div> </div>
<label class="is-sr-only" for="embedFieldValue">Field Value</label> <label>
<span class="is-sr-only">Field Value</span>
<textarea <textarea
class="discord-field-value field-input message-input autoresize" class="discord-field-value field-input message-input autoresize"
placeholder="Field Value..." placeholder="Field Value..."
maxlength="1024" id="embedFieldValue" name="embed_field_value[]" maxlength="1024" name="embed_field_value[]"
rows="1"></textarea> rows="1"></textarea>
</label>
</div> </div>
</template> </template>
@ -323,13 +351,12 @@
{% include "reminder_dashboard/guild_reminder" %} {% include "reminder_dashboard/guild_reminder" %}
</template> </template>
<template id="personalReminder"> <script src="/static/js/iro.js"></script>
{% include "reminder_dashboard/personal_reminder" %} <script src="/static/js/dtsel.js"></script>
</template>
<script src="/static/js/interval.js"></script> <script src="/static/js/interval.js"></script>
<script src="/static/js/timezone.js"></script> <script src="/static/js/timezone.js" defer></script>
<script src="/static/js/main.js"></script> <script src="/static/js/main.js" defer></script>
</body> </body>
</html> </html>

View File

@ -4,33 +4,33 @@
<figure class="media-left"> <figure class="media-left">
<p class="image is-32x32 customizable"> <p class="image is-32x32 customizable">
<a> <a>
<img class="is-rounded discord-avatar" src="/static/img/bg.webp"> <img class="is-rounded discord-avatar" src="/static/img/bg.webp" alt="Image for discord avatar">
</a> </a>
</p> </p>
</figure> </figure>
<div class="media-content"> <div class="media-content">
<div class="content"> <div class="content">
<div class="discord-message-header"> <div class="discord-message-header">
<label class="is-sr-only" for="reminderUsername">Username Override</label> <label class="is-sr-only">Username Override</label>
<input class="discord-username message-input" placeholder="Username Override" <input class="discord-username message-input" placeholder="Username Override"
maxlength="32" id="reminderUsername" name="username"> maxlength="32" name="username">
</div> </div>
<label class="is-sr-only" for="messageContent">Message</label> <label class="is-sr-only">Message</label>
<textarea class="message-input autoresize discord-content" <textarea class="message-input autoresize discord-content"
placeholder="Message Content..." placeholder="Message Content..."
maxlength="2000" id="messageContent" name="content" rows="1"></textarea> maxlength="2000" name="content" rows="1"></textarea>
<div class="discord-embed"> <div class="discord-embed">
<div class="embed-body"> <div class="embed-body">
<button class="change-color button is-rounded is-small"> <button class="change-color button is-rounded is-small">
<i class="fas fa-eye-dropper"></i> <span class="is-sr-only">Choose embed color</span><i class="fas fa-eye-dropper"></i>
</button> </button>
<div class="a"> <div class="a">
<div class="embed-author-box"> <div class="embed-author-box">
<div class="a"> <div class="a">
<p class="image is-24x24 customizable"> <p class="image is-24x24 customizable">
<a> <a>
<img class="is-rounded embed_author_url" src="/static/img/bg.webp"> <img class="is-rounded embed_author_url" src="/static/img/bg.webp" alt="Image for embed author">
</a> </a>
</p> </p>
</div> </div>
@ -40,38 +40,40 @@
<textarea <textarea
class="discord-embed-author message-input autoresize" class="discord-embed-author message-input autoresize"
placeholder="Embed Author..." rows="1" maxlength="256" placeholder="Embed Author..." rows="1" maxlength="256"
id="embedAuthor" name="embed_author"></textarea> name="embed_author"></textarea>
</div> </div>
</div> </div>
<label class="is-sr-only" for="embedTitle">Embed Title</label> <label class="is-sr-only" for="embedTitle">Embed Title</label>
<textarea class="discord-title message-input autoresize" <textarea class="discord-title message-input autoresize"
placeholder="Embed Title..." placeholder="Embed Title..."
maxlength="256" id="embedTitle" rows="1" maxlength="256" rows="1"
name="embed_title"></textarea> name="embed_title"></textarea>
<br> <br>
<label class="is-sr-only" for="embedDescription">Embed Description</label> <label class="is-sr-only" for="embedDescription">Embed Description</label>
<textarea class="discord-description message-input autoresize " <textarea class="discord-description message-input autoresize "
placeholder="Embed Description..." placeholder="Embed Description..."
maxlength="4096" id="embedDescription" name="embed_description" maxlength="4096" name="embed_description"
rows="1"></textarea> rows="1"></textarea>
<br> <br>
<div class="embed-multifield-box"> <div class="embed-multifield-box">
<div data-inlined="1" class="embed-field-box"> <div data-inlined="1" class="embed-field-box">
<label class="is-sr-only" for="embedFieldTitle">Field Title</label> <label class="is-sr-only" for="embedFieldTitle">Field Title</label>
<div style="display: flex;"> <div class="is-flex">
<textarea class="discord-field-title field-input message-input autoresize" <textarea class="discord-field-title field-input message-input autoresize"
placeholder="Field Title..." rows="1" placeholder="Field Title..." rows="1"
maxlength="256" id="embedFieldTitle" name="embed_field_title[]"></textarea> maxlength="256" name="embed_field_title[]"></textarea>
<button class="button is-small inline-btn" style="height: 100%; padding: 5px;"><i class="fas fa-arrows-h"></i></button> <button class="button is-small inline-btn">
<span class="is-sr-only">Toggle field inline</span><i class="fas fa-arrows-h"></i>
</button>
</div> </div>
<label class="is-sr-only" for="embedFieldValue">Field Value</label> <label class="is-sr-only" for="embedFieldValue">Field Value</label>
<textarea <textarea
class="discord-field-value field-input message-input autoresize " class="discord-field-value field-input message-input autoresize "
placeholder="Field Value..." placeholder="Field Value..."
maxlength="1024" id="embedFieldValue" name="embed_field_value[]" maxlength="1024" name="embed_field_value[]"
rows="1"></textarea> rows="1"></textarea>
</div> </div>
</div> </div>
@ -102,7 +104,7 @@
<label class="is-sr-only" for="embedFooter">Embed Footer text</label> <label class="is-sr-only" for="embedFooter">Embed Footer text</label>
<textarea class="discord-embed-footer message-input autoresize " <textarea class="discord-embed-footer message-input autoresize "
placeholder="Embed Footer..." placeholder="Embed Footer..."
maxlength="2048" id="embedFooter" name="embed_footer" rows="1"></textarea> maxlength="2048" name="embed_footer" rows="1"></textarea>
</div> </div>
</div> </div>
</div> </div>
@ -121,7 +123,7 @@
</div> </div>
<div class="column is-narrow"> <div class="column is-narrow">
<button class="button is-rounded hide-box"> <button class="button is-rounded hide-box">
<i class="fas fa-chevron-down"></i> <span class="is-sr-only">Hide reminder</span><i class="fas fa-chevron-down"></i>
</button> </button>
</div> </div>
</div> </div>
@ -132,20 +134,21 @@
</div> </div>
<div class="control has-icons-left"> <div class="control has-icons-left">
<div class="select"> <div class="select">
<select id="channelOption" name="channel" class="channel-selector"> <select name="channel" class="channel-selector">
</select> </select>
</div> </div>
<div class="icon is-small is-left"> <div class="icon is-small is-left">
<i class="fas fa-hashtag"></i> <i class="fas fa-hashtag"></i>
</div> </div>
</div> </div>
</div>
<div class="collapses"> <div class="collapses">
<div class="field"> <div class="field">
<label class="label">Time</label>
<div class="control"> <div class="control">
<label class="label">
Time
<input class="input" type="datetime-local" step="1" name="time"> <input class="input" type="datetime-local" step="1" name="time">
</label>
</div> </div>
</div> </div>
@ -154,13 +157,28 @@
<div class="control intervalSelector"> <div class="control intervalSelector">
<div class="input interval-group"> <div class="input interval-group">
<div class="interval-group-left"> <div class="interval-group-left">
<input class="w2" type="text" pattern="\d*" name="interval_months" maxlength="2" placeholder=""> <span style="width: 0.5rem"></span> months, <span style="width: 0.5rem"></span> <label>
<input class="w3" type="text" pattern="\d*" name="interval_days" maxlength="4" placeholder=""> <span style="width: 0.5rem"></span> days, <span style="width: 0.5rem"></span> <span class="is-sr-only">Interval months</span>
<input class="w2" type="text" pattern="\d*" name="interval_months" maxlength="2" placeholder=""> <span class="half-rem"></span> months, <span class="half-rem"></span>
</label>
<label>
<span class="is-sr-only">Interval days</span>
<input class="w3" type="text" pattern="\d*" name="interval_days" maxlength="4" placeholder=""> <span class="half-rem"></span> days, <span class="half-rem"></span>
</label>
<label>
<span class="is-sr-only">Interval hours</span>
<input class="w2" type="text" pattern="\d*" name="interval_hours" maxlength="2" placeholder="HH">: <input class="w2" type="text" pattern="\d*" name="interval_hours" maxlength="2" placeholder="HH">:
</label>
<label>
<span class="is-sr-only">Interval minutes</span>
<input class="w2" type="text" pattern="\d*" name="interval_minutes" maxlength="2" placeholder="MM">: <input class="w2" type="text" pattern="\d*" name="interval_minutes" maxlength="2" placeholder="MM">:
</label>
<label>
<span class="is-sr-only">Interval seconds</span>
<input class="w2" type="text" pattern="\d*" name="interval_seconds" maxlength="2" placeholder="SS"> <input class="w2" type="text" pattern="\d*" name="interval_seconds" maxlength="2" placeholder="SS">
</label>
</div> </div>
<button class="clear"><span class="icon"><i class="fas fa-trash"></i></span></button> <button class="clear"><span class="is-sr-only">Clear interval</span><span class="icon"><i class="fas fa-trash"></i></span></button>
</div> </div>
</div> </div>
</div> </div>
@ -193,13 +211,13 @@
</div> </div>
</div> </div>
<span style="width: 12px;"></span> <span class="pad-left"></span>
{% if creating %} {% if creating %}
<button class="button is-outlined"> <button class="button is-outlined show-modal" data-modal="chooseTemplateModal">
Load Template Load Template
</button> </button>
<button class="button is-success is-outlined" id="createTemplate"> <button class="button is-success is-outlined" id="createTemplate">
Create Template <span>Create Template</span> <span class="icon"><i class="fas fa-file-spreadsheet"></i></span>
</button> </button>
<button class="button is-success" id="createReminder"> <button class="button is-success" id="createReminder">
<span>Create Reminder</span> <span class="icon"><i class="fas fa-sparkles"></i></span> <span>Create Reminder</span> <span class="icon"><i class="fas fa-sparkles"></i></span>
@ -209,6 +227,7 @@
<span>Save</span> <span class="icon"><i class="fas fa-save"></i></span> <span>Save</span> <span class="icon"><i class="fas fa-save"></i></span>
</button> </button>
<button class="button is-warning disable-enable"> <button class="button is-warning disable-enable">
<span class="is-sr-only">Text content filled by CSS</span>
</button> </button>
<button class="button is-danger delete-reminder"> <button class="button is-danger delete-reminder">
Delete Delete

View File

@ -1,186 +0,0 @@
<div class="discord-frame">
<article class="media">
<figure class="media-left">
<p class="image is-32x32">
<img class="is-rounded" src="/static/img/icon.png" alt="reminder bot icon">
</p>
</figure>
<div class="media-content">
<div class="content">
<div class="is-hidden-touch">
<div class="discord-message-header">
Reminder Bot -
<label class="is-sr-only" for="reminderDate">Reminder Date</label>
<input class="time-input date" placeholder="YYYY/MM/DD"
id="reminderDate" name="date">
<label class="is-sr-only" for="reminderTime">Reminder Time</label>
<input class="time-input time" placeholder="HH:MM:SS"
id="reminderTime" name="time">
</div>
</div>
<div class="is-hidden-desktop">
<label class="is-sr-only" for="reminderDate">Reminder Date</label>
<input class="time-input date" placeholder="YYYY/MM/DD"
id="reminderDate" name="date">
<label class="is-sr-only" for="reminderTime">Reminder Time</label>
<input class="time-input time" placeholder="HH:MM:SS"
id="reminderTime" name="time">
<div class="discord-message-header">
Reminder Bot
</div>
</div>
<label class="is-sr-only" for="messageContent">Message</label>
<textarea class="message-input autoresize discord-content preview-mode"
placeholder="Message Content..."
maxlength="2000" id="messageContent" name="content" rows="1"></textarea>
<div class="discord-embed">
<div class="embed-body">
<div class="a">
<div class="embed-author-box">
<div class="a">
<p class="image is-24x24 customizable">
<a>
<img class="is-rounded preview-mode" src="">
</a>
</p>
</div>
<div class="b">
<label class="is-sr-only" for="embedAuthor">Embed Author</label>
<textarea
class="discord-embed-author message-input preview-mode autoresize"
placeholder="Embed Author..." rows="1" maxlength="256"
id="embedAuthor" name="embed_author"></textarea>
</div>
</div>
<label class="is-sr-only" for="embedTitle">Embed Title</label>
<textarea class="discord-title message-input preview-mode autoresize"
placeholder="Embed Title..."
maxlength="256" id="embedTitle" rows="1"
name="embed_title"></textarea>
<br>
<label class="is-sr-only" for="embedDescription">Embed Description</label>
<textarea class="discord-description message-input autoresize preview-mode"
placeholder="Embed Description..."
maxlength="2048" id="embedDescription" name="embed_description"
rows="1"></textarea>
<br>
<div class="embed-multifield-box">
<div class="embed-field-box">
<label class="is-sr-only" for="embedFieldTitle">Field Title</label>
<textarea
class="discord-field-title field-input message-input autoresize preview-mode"
placeholder="Field Title..." rows="1"
maxlength="256" id="embedFieldTitle"
name="embed_field_title[]"></textarea>
<label class="is-sr-only" for="embedFieldValue">Field Value</label>
<textarea
class="discord-field-value field-input message-input autoresize preview-mode"
placeholder="Field Value..."
maxlength="1024" id="embedFieldValue" name="embed_field_value[]"
rows="1"></textarea>
</div>
</div>
</div>
<div class="b">
<p class="image thumbnail customizable">
<a>
<img class="preview-mode" src="">
</a>
</p>
</div>
</div>
<p class="image is-400x300 customizable">
<a>
<img class="preview-mode" src="">
</a>
</p>
<div class="embed-footer-box">
<p class="image is-20x20 customizable">
<a>
<img class="is-rounded preview-mode" src="">
</a>
</p>
<label class="is-sr-only" for="embedAuthor">Embed Author</label>
<textarea class="discord-embed-footer message-input autoresize preview-mode"
placeholder="Embed Footer..."
maxlength="2048" id="embedAuthor" name="embed_author" rows="1"></textarea>
</div>
</div>
</div>
<nav class="level is-mobile">
<div class="level-left">
<a class="level-item icon-toggle tts-toggle" title="Enable TTS">
<p>
TTS <span class="icon is-small"><i class="far fa-comment-lines"></i></span>
</p>
</a>
<a class="level-item icon-toggle autopin-toggle" title="Enable Autopin">
<p>
Pin <span class="icon is-small"><i class="far fa-thumbtack"></i></span>
</p>
</a>
<span style="width: 12px;"></span>
<a class="level-item set-color">
<p>
Set Embed Color <span class="icon is-small"><i
class="far fa-eye-dropper"></i></span>
</p>
</a>
<a class="level-item file-upload">
<div class="file">
<input class="file-input" type="file" name="attachment">
<p>
Attach File
<span class="icon is-small">
<i class="far fa-file-upload"></i>
</span>
</p>
</div>
</a>
<a class="level-item set-interval">
<p>
Set Interval <span class="icon is-small"><i class="far fa-repeat"></i></span>
</p>
</a>
<span style="width: 12px;"></span>
<a class="level-item preview-toggle" title="Preview Message">
<p>
Preview Mode <span class="icon is-small"><i class="far fa-eye"></i></span>
</p>
</a>
</div>
</nav>
<nav class="level is-mobile">
<div class="level-left">
<a class="level-item create-reminder" title="Create Reminder">
<p>
Create <span class="icon is-small"><i class="far fa-calendar-plus"></i></span>
</p>
</a>
<a class="level-item icon-toggle disable-reminder" title="Disable/enable Reminder">
<p>
Disable <span class="icon is-small"><i class="far fa-comment-slash"></i></span>
</p>
</a>
<a class="level-item delete-reminder" title="Delete Reminder">
<p>
Delete <span class="icon is-small"><i class="far fa-trash"></i></span>
</p>
</a>
</div>
</nav>
</div>
</article>
</div>

View File

@ -1,5 +1,4 @@
<div style="margin: 0 12px 12px 12px;"> <div class="create-reminder">
<div class="create-reminder">
<strong>Create Reminder</strong> <strong>Create Reminder</strong>
<div id="reminderCreator"> <div id="reminderCreator">
{% set creating = true %} {% set creating = true %}
@ -30,7 +29,7 @@
<div class="column is-narrow"> <div class="column is-narrow">
<div class="control has-icons-left"> <div class="control has-icons-left">
<div class="select is-small"> <div class="select is-small">
<select id="expandAll" style="width: 60px"> <select id="expandAll">
<option value="" selected></option> <option value="" selected></option>
<option value="expand">Expand All</option> <option value="expand">Expand All</option>
<option value="collapse">Collapse All</option> <option value="collapse">Collapse All</option>
@ -47,7 +46,6 @@
<div id="guildReminders"> <div id="guildReminders">
</div> </div>
</div>
</div> </div>
<script src="/static/js/sort.js"></script> <script src="/static/js/sort.js"></script>

View File

@ -1,23 +0,0 @@
<div style="margin: 0 12px 12px 12px;">
<div class="create-reminder">
<p>
<strong>Message Designer</strong>
</p>
{% include "reminder_dashboard/personal_reminder" %}
<p style="font-size: 0.8rem;">
Most fields are optional. Use 'Preview Mode' to see how the reminder will appear in Discord.
Scaling is not exact.
</p>
<div class="field">
<p class="control">
<a class="button is-success">
Create
</a>
</p>
</div>
</div>
<div id="personalReminders">
</div>
</div>