mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 09:00:10 +00:00
Add email list accessible by edit button in settings
This commit is contained in:
parent
058cac2e7b
commit
d1b1b90de3
16
api.go
16
api.go
@ -1276,13 +1276,13 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
|
||||
// @tags Configuration
|
||||
func (app *appContext) GetEmails(gc *gin.Context) {
|
||||
gc.JSON(200, emailListDTO{
|
||||
"UserCreated": app.storage.lang.Email["en-us"].UserCreated["name"],
|
||||
"InviteExpiry": app.storage.lang.Email["en-us"].InviteExpiry["name"],
|
||||
"PasswordReset": app.storage.lang.Email["en-us"].PasswordReset["name"],
|
||||
"UserDeleted": app.storage.lang.Email["en-us"].UserDeleted["name"],
|
||||
"InviteEmail": app.storage.lang.Email["en-us"].InviteEmail["name"],
|
||||
"WelcomeEmail": app.storage.lang.Email["en-us"].WelcomeEmail["name"],
|
||||
"EmailConfirmation": app.storage.lang.Email["en-us"].EmailConfirmation["name"],
|
||||
"UserCreated": {Name: app.storage.lang.Email["en-us"].UserCreated["name"], Enabled: app.storage.customEmails.UserCreated.Enabled},
|
||||
"InviteExpiry": {Name: app.storage.lang.Email["en-us"].InviteExpiry["name"], Enabled: app.storage.customEmails.InviteExpiry.Enabled},
|
||||
"PasswordReset": {Name: app.storage.lang.Email["en-us"].PasswordReset["name"], Enabled: app.storage.customEmails.PasswordReset.Enabled},
|
||||
"UserDeleted": {Name: app.storage.lang.Email["en-us"].UserDeleted["name"], Enabled: app.storage.customEmails.UserDeleted.Enabled},
|
||||
"InviteEmail": {Name: app.storage.lang.Email["en-us"].InviteEmail["name"], Enabled: app.storage.customEmails.InviteEmail.Enabled},
|
||||
"WelcomeEmail": {Name: app.storage.lang.Email["en-us"].WelcomeEmail["name"], Enabled: app.storage.customEmails.WelcomeEmail.Enabled},
|
||||
"EmailConfirmation": {Name: app.storage.lang.Email["en-us"].EmailConfirmation["name"], Enabled: app.storage.customEmails.EmailConfirmation.Enabled},
|
||||
})
|
||||
}
|
||||
|
||||
@ -1339,7 +1339,7 @@ func (app *appContext) SetEmail(gc *gin.Context) {
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Failure 500 {object} boolResponse
|
||||
// @Router /config/emails/{id}/{enable/disable} [post]
|
||||
// @Router /config/emails/{id}/state/{enable/disable} [post]
|
||||
// @tags Configuration
|
||||
func (app *appContext) SetEmailState(gc *gin.Context) {
|
||||
id := gc.Param("id")
|
||||
|
@ -153,6 +153,11 @@ div.card:contains(section.banner.footer) {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 400px) {
|
||||
.row {
|
||||
flex-direction: column;
|
||||
@ -273,6 +278,10 @@ sup.\~critical, .text-critical {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.flex-auto {
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
@ -109,15 +109,33 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div id="modal-customize" class="modal">
|
||||
<div class="modal-content card">
|
||||
<span class="heading">{{ .strings.customizeEmails }} <span class="modal-close">×</span></span>
|
||||
<p class="content">{{ .strings.customizeEmailsDescription }}</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ .strings.name }}</th>
|
||||
<th>{{ .strings.reset }}</th>
|
||||
<th>{{ .strings.edit }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="customize-list"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="modal-editor" class="modal">
|
||||
<form class="modal-content wide card" id="form-editor" href="">
|
||||
<span class="heading"><span id="header-editor"></span> <span class="modal-close">×</span></span>
|
||||
<div class="row">
|
||||
<div class="col content mt-half">
|
||||
<div class="col flex-col content mt-half">
|
||||
<span class="label supra" for="editor-variables">{{ .strings.variables }}</span>
|
||||
<div id="editor-variables"></div>
|
||||
<label class="label supra" for="textarea-editor">{{ .strings.message }}</label>
|
||||
<textarea id="textarea-editor" class="textarea full-width ~neutral !normal mt-half monospace"></textarea>
|
||||
<textarea id="textarea-editor" class="textarea full-width flex-auto ~neutral !normal mt-half monospace"></textarea>
|
||||
<p class="support mt-half mb-1">{{ .strings.markdownSupported }}</p>
|
||||
<div class="flex-row">
|
||||
<label class="full-width ml-half">
|
||||
|
@ -35,6 +35,10 @@
|
||||
"message": "Message",
|
||||
"variables": "Variables",
|
||||
"preview": "Preview",
|
||||
"reset": "Reset",
|
||||
"edit": "Edit",
|
||||
"customizeEmails": "Customize Emails",
|
||||
"customizeEmailsDescription": "If you don't want to use jfa-go's email templates, you can create your own using Markdown.",
|
||||
"markdownSupported": "Markdown is supported.",
|
||||
"modifySettings": "Modify Settings",
|
||||
"modifySettingsDescription": "Apply settings from an existing profile, or source them directly from a user.",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{{ .aUserWasCreated }}
|
||||
|
||||
{{ .nameString }}: {{ .name }}
|
||||
|
||||
{{ .addressString }}: {{ .address }}
|
||||
|
||||
{{ .timeString }}: {{ .time }}
|
||||
|
||||
{{ .notificationNotice }}
|
||||
|
@ -1,4 +1,5 @@
|
||||
{{ .yourAccountWasDeleted }}
|
||||
|
||||
{{ .reasonString }}: {{ .reason }}
|
||||
|
||||
{{ .message }}
|
||||
|
@ -1,8 +1,11 @@
|
||||
{{ .helloUser }}
|
||||
|
||||
{{ .someoneHasRequestedReset }}
|
||||
|
||||
{{ .ifItWasYou }}
|
||||
|
||||
{{ .codeExpiry }}
|
||||
|
||||
{{ .ifItWasNotYou }}
|
||||
|
||||
{{ .pinString }}: {{ .pin }}
|
||||
|
@ -175,7 +175,12 @@ type settings struct {
|
||||
|
||||
type langDTO map[string]string
|
||||
|
||||
type emailListDTO map[string]string
|
||||
type emailListDTO map[string]emailListEl
|
||||
|
||||
type emailListEl struct {
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type emailSetDTO struct {
|
||||
Content string `json:"content"`
|
||||
|
@ -55,6 +55,8 @@ window.availableProfiles = window.availableProfiles || [];
|
||||
window.modals.announce = new Modal(document.getElementById("modal-announce"));
|
||||
|
||||
window.modals.editor = new Modal(document.getElementById("modal-editor"));
|
||||
|
||||
window.modals.customizeEmails = new Modal(document.getElementById("modal-customize"));
|
||||
})();
|
||||
|
||||
var inviteCreator = new createInvite();
|
||||
|
@ -479,14 +479,16 @@ export class settingsList {
|
||||
private _sections: { [name: string]: sectionPanel }
|
||||
private _buttons: { [name: string]: HTMLSpanElement }
|
||||
private _needsRestart: boolean = false;
|
||||
private _emailEditor = new EmailEditor();
|
||||
|
||||
addSection = (name: string, s: Section) => {
|
||||
addSection = (name: string, s: Section, subButton?: HTMLElement) => {
|
||||
const section = new sectionPanel(s, name);
|
||||
this._sections[name] = section;
|
||||
this._panel.appendChild(this._sections[name].asElement());
|
||||
const button = document.createElement("span") as HTMLSpanElement;
|
||||
button.classList.add("button", "~neutral", "!low", "settings-section-button", "mb-half");
|
||||
button.textContent = s.meta.name;
|
||||
if (subButton) { button.appendChild(subButton); }
|
||||
button.onclick = () => { this._showPanel(name); };
|
||||
if (s.meta.depends_true || s.meta.depends_false) {
|
||||
let dependant = splitDependant(name, s.meta.depends_true || s.meta.depends_false);
|
||||
@ -580,10 +582,25 @@ export class settingsList {
|
||||
for (let name of settings.order) {
|
||||
if (name in this._sections) {
|
||||
this._sections[name].update(settings.sections[name]);
|
||||
} else {
|
||||
if (name == "email") {
|
||||
const editButton = document.createElement("div");
|
||||
editButton.classList.add("tooltip", "left");
|
||||
editButton.innerHTML = `
|
||||
<span class="button ~neutral !normal">
|
||||
<i class="icon ri-edit-line"></i>
|
||||
</span>
|
||||
<span class="content sm">
|
||||
${window.lang.get("strings", "customizeEmails")}
|
||||
</span>
|
||||
`;
|
||||
(editButton.querySelector("span.button") as HTMLSpanElement).onclick = this._emailEditor.showList;
|
||||
this.addSection(name, settings.sections[name], editButton);
|
||||
} else {
|
||||
this.addSection(name, settings.sections[name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
this._showPanel(settings.order[0]);
|
||||
document.dispatchEvent(new CustomEvent("settings-loaded"));
|
||||
this._saveButton.classList.add("unfocused");
|
||||
@ -665,15 +682,22 @@ interface templateEmail {
|
||||
variables: string[];
|
||||
}
|
||||
|
||||
interface emailListEl {
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
class EmailEditor {
|
||||
private _currentID: string;
|
||||
private _names: { [id: string]: string };
|
||||
private _names: { [id: string]: emailListEl };
|
||||
private _content: string;
|
||||
private _form = document.getElementById("form-editor") as HTMLFormElement;
|
||||
private _header = document.getElementById("header-editor") as HTMLSpanElement;
|
||||
private _variables = document.getElementById("editor-variables") as HTMLDivElement;
|
||||
private _textArea = document.getElementById("textarea-editor") as HTMLTextAreaElement;
|
||||
private _preview = document.getElementById("editor-preview") as HTMLDivElement;
|
||||
private _timeout: number;
|
||||
private _finishInterval = 1000;
|
||||
|
||||
insert = (textarea: HTMLTextAreaElement, text: string) => { // https://kubyshkin.name/posts/insert-text-into-textarea-at-cursor-position <3
|
||||
const isSuccess = document.execCommand("insertText", false, text);
|
||||
@ -693,9 +717,8 @@ class EmailEditor {
|
||||
}
|
||||
}
|
||||
|
||||
load = (id: string) => {
|
||||
loadEditor = (id: string) => {
|
||||
this._currentID = id;
|
||||
this.loadPreview();
|
||||
_get("/config/emails/" + id, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 200) {
|
||||
@ -703,10 +726,11 @@ class EmailEditor {
|
||||
return;
|
||||
}
|
||||
if (this._names[id] !== undefined) {
|
||||
this._header.textContent = this._names[id];
|
||||
this._header.textContent = this._names[id].name;
|
||||
}
|
||||
const templ = req.response as templateEmail;
|
||||
this._textArea.value = templ.content;
|
||||
this.loadPreview();
|
||||
this._content = templ.content;
|
||||
const colors = ["info", "urge", "positive", "neutral"];
|
||||
let innerHTML = '';
|
||||
@ -718,7 +742,10 @@ class EmailEditor {
|
||||
const buttons = this._variables.querySelectorAll("span.button") as NodeListOf<HTMLSpanElement>;
|
||||
for (let i = 0; i < templ.variables.length; i++) {
|
||||
buttons[i].innerHTML = `<span class="monospace">` + templ.variables[i] + `</span>`;
|
||||
buttons[i].onclick = () => this.insert(this._textArea, templ.variables[i]);
|
||||
buttons[i].onclick = () => {
|
||||
this.insert(this._textArea, templ.variables[i]);
|
||||
this._timeout = setTimeout(this.loadPreview, this._finishInterval);
|
||||
}
|
||||
}
|
||||
window.modals.editor.show();
|
||||
}
|
||||
@ -731,11 +758,12 @@ class EmailEditor {
|
||||
window.notifications.customError("loadTemplateError", window.lang.notif("errorFailureCheckLogs"));
|
||||
return;
|
||||
}
|
||||
this._preview.innerHTML = req.response.html;
|
||||
this._preview.innerHTML = (req.response as Email).html;
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
constructor() {
|
||||
|
||||
showList = () => {
|
||||
_get("/config/emails", null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 200) {
|
||||
@ -743,17 +771,49 @@ class EmailEditor {
|
||||
return;
|
||||
}
|
||||
this._names = req.response;
|
||||
const list = document.getElementById("customize-list") as HTMLDivElement;
|
||||
list.textContent = '';
|
||||
for (let id in this._names) {
|
||||
const tr = document.createElement("tr") as HTMLTableRowElement;
|
||||
let resetButton = ``;
|
||||
if (this._names[id].enabled) {
|
||||
resetButton = `<i class="icon ri-restart-line" title="${window.lang.get("strings", "reset")}"></i>`;
|
||||
}
|
||||
tr.innerHTML = `
|
||||
<td>${this._names[id].name}</td>
|
||||
<td>${resetButton}</td>
|
||||
<td><span class="button ~info !normal" title="${window.lang.get("strings", "edit")}"><i class="icon ri-edit-line"></i></span></td>
|
||||
`;
|
||||
(tr.querySelector("span.button") as HTMLSpanElement).onclick = () => {
|
||||
window.modals.customizeEmails.close()
|
||||
this.loadEditor(id);
|
||||
};
|
||||
if (this._names[id].enabled) {
|
||||
const rb = tr.querySelector("i.ri-restart-line") as HTMLElement;
|
||||
rb.onclick = () => _post("/config/emails/" + id + "/state/disable", null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 200 && req.status != 204) {
|
||||
window.notifications.customError("setEmailStateError", window.lang.notif("errorFailureCheckLogs"));
|
||||
return;
|
||||
}
|
||||
rb.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
list.appendChild(tr);
|
||||
}
|
||||
window.modals.customizeEmails.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let timeout: number;
|
||||
const finishInterval = 1000;
|
||||
constructor() {
|
||||
this._textArea.onkeyup = () => {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(this.loadPreview, finishInterval);
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout = setTimeout(this.loadPreview, this._finishInterval);
|
||||
};
|
||||
this._textArea.onkeydown = () => {
|
||||
clearTimeout(timeout);
|
||||
clearTimeout(this._timeout);
|
||||
};
|
||||
|
||||
this._form.onsubmit = (event: Event) => {
|
||||
@ -775,5 +835,3 @@ class EmailEditor {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).ee = () => { (window as any).ee = new EmailEditor(); };
|
||||
|
@ -76,6 +76,7 @@ declare interface Modals {
|
||||
addProfile: Modal;
|
||||
announce: Modal;
|
||||
editor: Modal;
|
||||
customizeEmails: Modal;
|
||||
}
|
||||
|
||||
interface Invite {
|
||||
|
Loading…
Reference in New Issue
Block a user