@@ -304,6 +308,9 @@
Edit email content
{{/each}}
+
+
+
Rendered HTML and text:
@@ -361,7 +368,7 @@ config.json
options
-
+
diff --git a/client/js/admin.ts b/client/js/admin.ts
index 1ab64560..ee80b8f7 100644
--- a/client/js/admin.ts
+++ b/client/js/admin.ts
@@ -570,6 +570,7 @@ sendAcceptancesButton.addEventListener("click", async e => {
declare let SimpleMDE: any;
const emailTypeSelect = document.getElementById("email-type") as HTMLSelectElement;
+const emailSubject = document.getElementById("email-subject") as HTMLInputElement;
let emailRenderedArea: HTMLElement | ShadowRoot = document.getElementById("email-rendered") as HTMLElement;
if (document.head.attachShadow) {
// Browser supports Shadow DOM
@@ -616,8 +617,9 @@ async function emailTypeChange(): Promise
{
// Load editor content via AJAX
try {
- let content = (await fetch(`/api/settings/email_content/${emailTypeSelect.value}`, { credentials: "same-origin" }).then(checkStatus).then(parseJSON)).content as string;
- markdownEditor.value(content);
+ let emailSettings: { subject: string; content: string } = await fetch(`/api/settings/email_content/${emailTypeSelect.value}`, { credentials: "same-origin" }).then(checkStatus).then(parseJSON);
+ emailSubject.value = emailSettings.subject;
+ markdownEditor.value(emailSettings.content);
}
catch {
markdownEditor.value("Couldn't retrieve email content");
@@ -645,14 +647,23 @@ function parseDateTime(dateTime: string) {
let digits = dateTime.split(/\D+/).map(num => parseInt(num, 10));
return new Date(digits[0], digits[1] - 1, digits[2], digits[3], digits[4], digits[5] || 0, digits[6] || 0);
}
-let settingsUpdateButton = document.querySelector("#settings input[type=submit]") as HTMLInputElement;
+let settingsUpdateButtons = document.querySelectorAll("#settings input[type=submit]") as NodeListOf;
let settingsForm = document.querySelector("#settings form") as HTMLFormElement;
-settingsUpdateButton.addEventListener("click", e => {
+for (let i = 0; i < settingsUpdateButtons.length; i++) {
+ settingsUpdateButtons[i].addEventListener("click", settingsUpdate);
+}
+function settingsUpdateButtonDisabled(disabled: boolean) {
+ for (let i = 0; i < settingsUpdateButtons.length; i++) {
+ settingsUpdateButtons[i].disabled = disabled;
+ }
+}
+
+function settingsUpdate(e: MouseEvent) {
if (!settingsForm.checkValidity() || !settingsForm.dataset.action) {
return;
}
e.preventDefault();
- settingsUpdateButton.disabled = true;
+ settingsUpdateButtonDisabled(true);
let teamsEnabledData = new FormData();
teamsEnabledData.append("enabled", (document.getElementById("teams-enabled") as HTMLInputElement).checked ? "true" : "false");
@@ -700,6 +711,7 @@ settingsUpdateButton.addEventListener("click", e => {
}
let emailContentData = new FormData();
+ emailContentData.append("subject", emailSubject.value);
emailContentData.append("content", markdownEditor.value());
const defaultOptions: RequestInit = {
@@ -739,9 +751,9 @@ settingsUpdateButton.addEventListener("click", e => {
window.location.reload();
}).catch(async (err: Error) => {
await sweetAlert("Oh no!", err.message, "error");
- settingsUpdateButton.disabled = false;
+ settingsUpdateButtonDisabled(false);
});
-});
+}
//
// Graphs
diff --git a/package-lock.json b/package-lock.json
index 59ec5049..62a56fa6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "registration",
- "version": "1.12.4",
+ "version": "1.12.5",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -208,7 +208,7 @@
"integrity": "sha512-HG4pUK/fTrGY3FerMlINxK74MxdAxkCRYrp5AM+oJ2jLcK0jWUi64ZV15JKwDR4TYLIxrT3y9SVnEWcLPbC/YA==",
"dev": true,
"requires": {
- "moment": "2.18.1"
+ "moment": "2.21.0"
}
},
"@types/mongodb": {
@@ -2198,16 +2198,16 @@
}
},
"moment": {
- "version": "2.18.1",
- "resolved": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz",
- "integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8="
+ "version": "2.21.0",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.21.0.tgz",
+ "integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ=="
},
"moment-timezone": {
"version": "0.5.13",
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.13.tgz",
"integrity": "sha1-mc5cfYJyYusPH3AgRBd/YHRde5A=",
"requires": {
- "moment": "2.18.1"
+ "moment": "2.21.0"
}
},
"mongodb": {
diff --git a/package.json b/package.json
index f8b03340..fcb3704d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "registration",
- "version": "1.12.4",
+ "version": "1.12.5",
"description": "TBD",
"main": "server/app.js",
"scripts": {
@@ -51,7 +51,7 @@
"json-schema-to-typescript": "^4.4.0",
"json2csv": "^3.7.3",
"marked": "^0.3.9",
- "moment": "^2.18.1",
+ "moment": "^2.21.0",
"moment-timezone": "^0.5.13",
"mongoose": "^4.10.3",
"morgan": "^1.8.2",
diff --git a/server/common.ts b/server/common.ts
index 5ff57a5d..c1070d45 100644
--- a/server/common.ts
+++ b/server/common.ts
@@ -376,6 +376,11 @@ import * as marked from "marked";
const striptags = require("striptags");
import { IUser, Team, IFormItem } from "./schema";
+export const defaultEmailSubjects = {
+ apply: `[${config.eventName}] - Thank you for applying!`,
+ accept: `[${config.eventName}] - You've been accepted!`,
+ attend: `[${config.eventName}] - Thank you for RSVPing!`
+};
interface IMailObject {
to: string;
from: string;
diff --git a/server/routes/api/settings.ts b/server/routes/api/settings.ts
index f0d2a111..66c239f4 100644
--- a/server/routes/api/settings.ts
+++ b/server/routes/api/settings.ts
@@ -1,7 +1,7 @@
import * as express from "express";
import {
- getSetting, updateSetting, setDefaultSettings, renderEmailHTML, renderEmailText
+ getSetting, updateSetting, setDefaultSettings, renderEmailHTML, renderEmailText, defaultEmailSubjects
} from "../../common";
import {
isAdmin, uploadHandler
@@ -180,19 +180,41 @@ settingsRoutes.route("/branch_roles")
settingsRoutes.route("/email_content/:type")
.get(isAdmin, async (request, response) => {
let content: string;
+ let subject: string;
try {
content = await getSetting(`${request.params.type}-email`, false);
}
- catch (err) {
+ catch {
// Content not set yet
content = "";
}
+ try {
+ subject = await getSetting(`${request.params.type}-email-subject`, false);
+ }
+ catch {
+ // Subject not set yet
+ let type: string = request.params.type;
+ if (type.match(/-apply$/)) {
+ subject = defaultEmailSubjects.apply;
+ }
+ else if (type.match(/-accept$/)) {
+ subject = defaultEmailSubjects.accept;
+ }
+ else if (type.match(/-attend$/)) {
+ subject = defaultEmailSubjects.attend;
+ }
+ else {
+ subject = "";
+ }
+ }
- response.json({ content });
+ response.json({ subject, content });
})
.put(isAdmin, uploadHandler.any(), async (request, response) => {
- let content = request.body.content as string;
+ let subject: string = request.body.subject;
+ let content: string = request.body.content;
try {
+ await updateSetting(`${request.params.type}-email-subject`, subject);
await updateSetting(`${request.params.type}-email`, content);
response.json({
"success": true
diff --git a/server/routes/api/user.ts b/server/routes/api/user.ts
index 85aeed95..9811c849 100644
--- a/server/routes/api/user.ts
+++ b/server/routes/api/user.ts
@@ -7,7 +7,7 @@ import * as moment from "moment-timezone";
import {
STORAGE_ENGINE,
formatSize,
- config, getSetting, renderEmailHTML, renderEmailText, sendMailAsync
+ config, getSetting, renderEmailHTML, renderEmailText, sendMailAsync, defaultEmailSubjects
} from "../../common";
import {
MAX_FILE_SIZE, postParser, uploadHandler,
@@ -177,12 +177,19 @@ async function postApplicationBranchHandler(request: express.Request, response:
return item;
});
// Email the applicant to confirm
+ let type = requestType === ApplicationType.Application ? "apply" : "attend";
+ let emailSubject: string | null;
+ try {
+ emailSubject = await getSetting(`${questionBranch.name}-${type}-email-subject`, false);
+ }
+ catch {
+ emailSubject = null;
+ }
let emailMarkdown: string;
try {
- let type = requestType === ApplicationType.Application ? "apply" : "attend";
emailMarkdown = await getSetting(`${questionBranch.name}-${type}-email`, false);
}
- catch (err) {
+ catch {
// Content not set yet
emailMarkdown = "";
}
@@ -195,7 +202,7 @@ async function postApplicationBranchHandler(request: express.Request, response:
await sendMailAsync({
from: config.email.from,
to: user.email,
- subject: `[${config.eventName}] - Thank you for applying!`,
+ subject: emailSubject || defaultEmailSubjects.apply,
html: emailHTML,
text: emailText
});
@@ -222,7 +229,7 @@ async function postApplicationBranchHandler(request: express.Request, response:
await sendMailAsync({
from: config.email.from,
to: user.email,
- subject: `[${config.eventName}] - Thank you for RSVPing!`,
+ subject: emailSubject || defaultEmailSubjects.attend,
html: emailHTML,
text: emailText
});
@@ -337,11 +344,18 @@ userRoutes.route("/send_acceptances").post(isAdmin, async (request, response): P
let users = await User.find({ "accepted": true, "acceptedEmailSent": { $ne: true } });
for (let user of users) {
// Email the applicant about their acceptance
+ let emailSubject: string | null;
+ try {
+ emailSubject = await getSetting(`${user.applicationBranch}-accept-email-subject`, false);
+ }
+ catch {
+ emailSubject = null;
+ }
let emailMarkdown: string;
try {
emailMarkdown = await getSetting(`${user.applicationBranch}-accept-email`, false);
}
- catch (err) {
+ catch {
// Content not set yet
emailMarkdown = "";
}
@@ -352,7 +366,7 @@ userRoutes.route("/send_acceptances").post(isAdmin, async (request, response): P
await sendMailAsync({
from: config.email.from,
to: user.email,
- subject: `[${config.eventName}] - You've been accepted!`,
+ subject: emailSubject || defaultEmailSubjects.accept,
html: emailHTML,
text: emailText
});