1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-10-18 17:10:11 +00:00

Compare commits

..

3 Commits

Author SHA1 Message Date
d772e43e44
merge language changes 2021-04-15 15:34:52 +01:00
8fdab39b18
use templateEmail and show conditionals in editor 2021-04-15 15:34:17 +01:00
f7d2771263
add email templater with basic if statements
at this point I really should've just used text/template, but I guess
this way compatibility is kept with existing custom emails. If statement
works as so:

{if variable}variable was true{endif}
{if !variable}variable was false{endif}

no else yet, just do as above (two if statements).
2021-04-14 23:58:54 +01:00
8 changed files with 234 additions and 88 deletions

12
api.go
View File

@ -1570,6 +1570,7 @@ func (app *appContext) GetEmail(gc *gin.Context) {
var err error var err error
var msg *Email var msg *Email
var variables []string var variables []string
var conditionals []string
var values map[string]interface{} var values map[string]interface{}
var writeVars func(variables []string) var writeVars func(variables []string)
newEmail := false newEmail := false
@ -1661,16 +1662,21 @@ func (app *appContext) GetEmail(gc *gin.Context) {
// app.storage.customEmails.InviteEmail = content // app.storage.customEmails.InviteEmail = content
} else if id == "WelcomeEmail" { } else if id == "WelcomeEmail" {
content = app.storage.customEmails.WelcomeEmail.Content content = app.storage.customEmails.WelcomeEmail.Content
conditionals = []string{"{yourAccountWillExpire}"}
app.storage.customEmails.WelcomeEmail.Conditionals = conditionals
if content == "" { if content == "" {
newEmail = true newEmail = true
msg, err = app.email.constructWelcome("", time.Time{}, app, true) msg, err = app.email.constructWelcome("", time.Time{}, app, true)
content = msg.Text content = msg.Text
conditionals = []string{"{yourAccountWillExpire}"}
} else { } else {
variables = app.storage.customEmails.WelcomeEmail.Variables variables = app.storage.customEmails.WelcomeEmail.Variables
} }
writeVars = func(variables []string) { app.storage.customEmails.WelcomeEmail.Variables = variables } writeVars = func(variables []string) {
app.storage.customEmails.WelcomeEmail.Variables = variables
}
// app.storage.customEmails.WelcomeEmail = content // app.storage.customEmails.WelcomeEmail = content
values = app.email.welcomeValues(username, time.Time{}, app, false, true) values = app.email.welcomeValues(username, time.Now(), app, false, true)
} else if id == "EmailConfirmation" { } else if id == "EmailConfirmation" {
content = app.storage.customEmails.EmailConfirmation.Content content = app.storage.customEmails.EmailConfirmation.Content
if content == "" { if content == "" {
@ -1734,7 +1740,7 @@ func (app *appContext) GetEmail(gc *gin.Context) {
respondBool(500, false, gc) respondBool(500, false, gc)
return return
} }
gc.JSON(200, customEmailDTO{Content: content, Variables: variables, Values: values, HTML: email.HTML, Plaintext: email.Text}) gc.JSON(200, customEmailDTO{Content: content, Variables: variables, Conditionals: conditionals, Values: values, HTML: email.HTML, Plaintext: email.Text})
} }
// @Summary Returns whether there's a new update, and extra info if there is. // @Summary Returns whether there's a new update, and extra info if there is.

132
email.go
View File

@ -274,13 +274,12 @@ func (emailer *Emailer) constructConfirmation(code, username, key string, app *a
var err error var err error
template := emailer.confirmationValues(code, username, key, app, noSub) template := emailer.confirmationValues(code, username, key, app, noSub)
if app.storage.customEmails.EmailConfirmation.Enabled { if app.storage.customEmails.EmailConfirmation.Enabled {
content := app.storage.customEmails.EmailConfirmation.Content content := templateEmail(
for _, v := range app.storage.customEmails.EmailConfirmation.Variables { app.storage.customEmails.EmailConfirmation.Content,
replaceWith, ok := template[v[1:len(v)-1]] app.storage.customEmails.EmailConfirmation.Variables,
if ok { nil,
content = strings.ReplaceAll(content, v, replaceWith.(string)) template,
} )
}
email, err = emailer.constructTemplate(email.Subject, content, app) email, err = emailer.constructTemplate(email.Subject, content, app)
} else { } else {
email.HTML, email.Text, err = emailer.construct(app, "email_confirmation", "email_", template) email.HTML, email.Text, err = emailer.construct(app, "email_confirmation", "email_", template)
@ -346,13 +345,12 @@ func (emailer *Emailer) constructInvite(code string, invite Invite, app *appCont
template := emailer.inviteValues(code, invite, app, noSub) template := emailer.inviteValues(code, invite, app, noSub)
var err error var err error
if app.storage.customEmails.InviteEmail.Enabled { if app.storage.customEmails.InviteEmail.Enabled {
content := app.storage.customEmails.InviteEmail.Content content := templateEmail(
for _, v := range app.storage.customEmails.InviteEmail.Variables { app.storage.customEmails.InviteEmail.Content,
replaceWith, ok := template[v[1:len(v)-1]] app.storage.customEmails.InviteEmail.Variables,
if ok { nil,
content = strings.ReplaceAll(content, v, replaceWith.(string)) template,
} )
}
email, err = emailer.constructTemplate(email.Subject, content, app) email, err = emailer.constructTemplate(email.Subject, content, app)
} else { } else {
email.HTML, email.Text, err = emailer.construct(app, "invite_emails", "email_", template) email.HTML, email.Text, err = emailer.construct(app, "invite_emails", "email_", template)
@ -386,13 +384,12 @@ func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appCont
var err error var err error
template := emailer.expiryValues(code, invite, app, noSub) template := emailer.expiryValues(code, invite, app, noSub)
if app.storage.customEmails.InviteExpiry.Enabled { if app.storage.customEmails.InviteExpiry.Enabled {
content := app.storage.customEmails.InviteExpiry.Content content := templateEmail(
for _, v := range app.storage.customEmails.InviteExpiry.Variables { app.storage.customEmails.InviteExpiry.Content,
replaceWith, ok := template[v[1:len(v)-1]] app.storage.customEmails.InviteExpiry.Variables,
if ok { nil,
content = strings.ReplaceAll(content, v, replaceWith.(string)) template,
} )
}
email, err = emailer.constructTemplate(email.Subject, content, app) email, err = emailer.constructTemplate(email.Subject, content, app)
} else { } else {
email.HTML, email.Text, err = emailer.construct(app, "notifications", "expiry_", template) email.HTML, email.Text, err = emailer.construct(app, "notifications", "expiry_", template)
@ -441,13 +438,12 @@ func (emailer *Emailer) constructCreated(code, username, address string, invite
template := emailer.createdValues(code, username, address, invite, app, noSub) template := emailer.createdValues(code, username, address, invite, app, noSub)
var err error var err error
if app.storage.customEmails.UserCreated.Enabled { if app.storage.customEmails.UserCreated.Enabled {
content := app.storage.customEmails.UserCreated.Content content := templateEmail(
for _, v := range app.storage.customEmails.UserCreated.Variables { app.storage.customEmails.UserCreated.Content,
replaceWith, ok := template[v[1:len(v)-1]] app.storage.customEmails.UserCreated.Variables,
if ok { nil,
content = strings.ReplaceAll(content, v, replaceWith.(string)) template,
} )
}
email, err = emailer.constructTemplate(email.Subject, content, app) email, err = emailer.constructTemplate(email.Subject, content, app)
} else { } else {
email.HTML, email.Text, err = emailer.construct(app, "notifications", "created_", template) email.HTML, email.Text, err = emailer.construct(app, "notifications", "created_", template)
@ -516,13 +512,12 @@ func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext, noSub
template := emailer.resetValues(pwr, app, noSub) template := emailer.resetValues(pwr, app, noSub)
var err error var err error
if app.storage.customEmails.PasswordReset.Enabled { if app.storage.customEmails.PasswordReset.Enabled {
content := app.storage.customEmails.PasswordReset.Content content := templateEmail(
for _, v := range app.storage.customEmails.PasswordReset.Variables { app.storage.customEmails.PasswordReset.Content,
replaceWith, ok := template[v[1:len(v)-1]] app.storage.customEmails.PasswordReset.Variables,
if ok { nil,
content = strings.ReplaceAll(content, v, replaceWith.(string)) template,
} )
}
email, err = emailer.constructTemplate(email.Subject, content, app) email, err = emailer.constructTemplate(email.Subject, content, app)
} else { } else {
email.HTML, email.Text, err = emailer.construct(app, "password_resets", "email_", template) email.HTML, email.Text, err = emailer.construct(app, "password_resets", "email_", template)
@ -558,13 +553,12 @@ func (emailer *Emailer) constructDeleted(reason string, app *appContext, noSub b
var err error var err error
template := emailer.deletedValues(reason, app, noSub) template := emailer.deletedValues(reason, app, noSub)
if app.storage.customEmails.UserDeleted.Enabled { if app.storage.customEmails.UserDeleted.Enabled {
content := app.storage.customEmails.UserDeleted.Content content := templateEmail(
for _, v := range app.storage.customEmails.UserDeleted.Variables { app.storage.customEmails.UserDeleted.Content,
replaceWith, ok := template[v[1:len(v)-1]] app.storage.customEmails.UserDeleted.Variables,
if ok { nil,
content = strings.ReplaceAll(content, v, replaceWith.(string)) template,
} )
}
email, err = emailer.constructTemplate(email.Subject, content, app) email, err = emailer.constructTemplate(email.Subject, content, app)
} else { } else {
email.HTML, email.Text, err = emailer.construct(app, "deletion", "email_", template) email.HTML, email.Text, err = emailer.construct(app, "deletion", "email_", template)
@ -600,13 +594,12 @@ func (emailer *Emailer) constructDisabled(reason string, app *appContext, noSub
var err error var err error
template := emailer.disabledValues(reason, app, noSub) template := emailer.disabledValues(reason, app, noSub)
if app.storage.customEmails.UserDisabled.Enabled { if app.storage.customEmails.UserDisabled.Enabled {
content := app.storage.customEmails.UserDisabled.Content content := templateEmail(
for _, v := range app.storage.customEmails.UserDisabled.Variables { app.storage.customEmails.UserDisabled.Content,
replaceWith, ok := template[v[1:len(v)-1]] app.storage.customEmails.UserDisabled.Variables,
if ok { nil,
content = strings.ReplaceAll(content, v, replaceWith.(string)) template,
} )
}
email, err = emailer.constructTemplate(email.Subject, content, app) email, err = emailer.constructTemplate(email.Subject, content, app)
} else { } else {
email.HTML, email.Text, err = emailer.construct(app, "disable_enable", "disabled_", template) email.HTML, email.Text, err = emailer.construct(app, "disable_enable", "disabled_", template)
@ -642,13 +635,12 @@ func (emailer *Emailer) constructEnabled(reason string, app *appContext, noSub b
var err error var err error
template := emailer.enabledValues(reason, app, noSub) template := emailer.enabledValues(reason, app, noSub)
if app.storage.customEmails.UserEnabled.Enabled { if app.storage.customEmails.UserEnabled.Enabled {
content := app.storage.customEmails.UserEnabled.Content content := templateEmail(
for _, v := range app.storage.customEmails.UserEnabled.Variables { app.storage.customEmails.UserEnabled.Content,
replaceWith, ok := template[v[1:len(v)-1]] app.storage.customEmails.UserEnabled.Variables,
if ok { nil,
content = strings.ReplaceAll(content, v, replaceWith.(string)) template,
} )
}
email, err = emailer.constructTemplate(email.Subject, content, app) email, err = emailer.constructTemplate(email.Subject, content, app)
} else { } else {
email.HTML, email.Text, err = emailer.construct(app, "disable_enable", "enabled_", template) email.HTML, email.Text, err = emailer.construct(app, "disable_enable", "enabled_", template)
@ -677,6 +669,7 @@ func (emailer *Emailer) welcomeValues(username string, expiry time.Time, app *ap
template["username"] = username template["username"] = username
template["message"] = app.config.Section("email").Key("message").String() template["message"] = app.config.Section("email").Key("message").String()
exp := app.formatDatetime(expiry) exp := app.formatDatetime(expiry)
if !expiry.IsZero() {
if custom { if custom {
template["yourAccountWillExpire"] = exp template["yourAccountWillExpire"] = exp
} else if !expiry.IsZero() { } else if !expiry.IsZero() {
@ -685,6 +678,7 @@ func (emailer *Emailer) welcomeValues(username string, expiry time.Time, app *ap
}) })
} }
} }
}
return template return template
} }
@ -705,13 +699,12 @@ func (emailer *Emailer) constructWelcome(username string, expiry time.Time, app
}) })
} }
if app.storage.customEmails.WelcomeEmail.Enabled { if app.storage.customEmails.WelcomeEmail.Enabled {
content := app.storage.customEmails.WelcomeEmail.Content content := templateEmail(
for _, v := range app.storage.customEmails.WelcomeEmail.Variables { app.storage.customEmails.WelcomeEmail.Content,
replaceWith, ok := template[v[1:len(v)-1]] app.storage.customEmails.WelcomeEmail.Variables,
if ok { app.storage.customEmails.WelcomeEmail.Conditionals,
content = strings.ReplaceAll(content, v, replaceWith.(string)) template,
} )
}
email, err = emailer.constructTemplate(email.Subject, content, app) email, err = emailer.constructTemplate(email.Subject, content, app)
} else { } else {
email.HTML, email.Text, err = emailer.construct(app, "welcome_email", "email_", template) email.HTML, email.Text, err = emailer.construct(app, "welcome_email", "email_", template)
@ -741,13 +734,12 @@ func (emailer *Emailer) constructUserExpired(app *appContext, noSub bool) (*Emai
var err error var err error
template := emailer.userExpiredValues(app, noSub) template := emailer.userExpiredValues(app, noSub)
if app.storage.customEmails.UserExpired.Enabled { if app.storage.customEmails.UserExpired.Enabled {
content := app.storage.customEmails.UserExpired.Content content := templateEmail(
for _, v := range app.storage.customEmails.UserExpired.Variables { app.storage.customEmails.UserExpired.Content,
replaceWith, ok := template[v[1:len(v)-1]] app.storage.customEmails.UserExpired.Variables,
if ok { nil,
content = strings.ReplaceAll(content, v, replaceWith.(string)) template,
} )
}
email, err = emailer.constructTemplate(email.Subject, content, app) email, err = emailer.constructTemplate(email.Subject, content, app)
} else { } else {
email.HTML, email.Text, err = emailer.construct(app, "user_expiry", "email_", template) email.HTML, email.Text, err = emailer.construct(app, "user_expiry", "email_", template)

View File

@ -183,6 +183,8 @@
<div class="col flex-col content mt-half"> <div class="col flex-col content mt-half">
<span class="label supra" for="editor-variables" id="label-editor-variables">{{ .strings.variables }}</span> <span class="label supra" for="editor-variables" id="label-editor-variables">{{ .strings.variables }}</span>
<div id="editor-variables"></div> <div id="editor-variables"></div>
<span class="label supra" for="editor-conditionals" id="label-editor-conditionals">{{ .strings.conditionals }}</span>
<div id="editor-conditionals"></div>
<label class="label supra" for="textarea-editor">{{ .strings.message }}</label> <label class="label supra" for="textarea-editor">{{ .strings.message }}</label>
<textarea id="textarea-editor" class="textarea full-width flex-auto ~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> <p class="support mt-half mb-1">{{ .strings.markdownSupported }}</p>

View File

@ -49,6 +49,7 @@
"subject": "Email Subject", "subject": "Email Subject",
"message": "Message", "message": "Message",
"variables": "Variables", "variables": "Variables",
"conditionals": "Conditionals",
"preview": "Preview", "preview": "Preview",
"reset": "Reset", "reset": "Reset",
"edit": "Edit", "edit": "Edit",

View File

@ -216,6 +216,7 @@ type emailTestDTO struct {
type customEmailDTO struct { type customEmailDTO struct {
Content string `json:"content"` Content string `json:"content"`
Variables []string `json:"variables"` Variables []string `json:"variables"`
Conditionals []string `json:"conditionals"`
Values map[string]interface{} `json:"values"` Values map[string]interface{} `json:"values"`
HTML string `json:"html"` HTML string `json:"html"`
Plaintext string `json:"plaintext"` Plaintext string `json:"plaintext"`

View File

@ -46,6 +46,7 @@ type customEmail struct {
Enabled bool `json:"enabled,omitempty"` Enabled bool `json:"enabled,omitempty"`
Content string `json:"content"` Content string `json:"content"`
Variables []string `json:"variables,omitempty"` Variables []string `json:"variables,omitempty"`
Conditionals []string `json:"conditionals,omitempty"`
} }
// timePattern: %Y-%m-%dT%H:%M:%S.%f // timePattern: %Y-%m-%dT%H:%M:%S.%f

118
template.go Normal file
View File

@ -0,0 +1,118 @@
package main
import "fmt"
func truthy(val interface{}) bool {
switch v := val.(type) {
case string:
return v != ""
case bool:
return v
case int:
return v != 0
}
return false
}
// Templater for custom emails.
// Variables should be written as {varName}.
// If statements should be written as {if (!)varName}...{endif}.
// Strings are true if != "", ints are true if != 0.
func templateEmail(content string, variables []string, conditionals []string, values map[string]interface{}) string {
ifStart, ifEnd := -1, -1
ifTrue := false
invalidIf := false
previousEnd := -2
cStart, cEnd := -1, -1
varStart, varEnd := -1, -1
varName := ""
out := ""
for i, c := range content {
if c == '{' {
cStart = i + 1
for content[cStart] == ' ' {
cStart++
}
if content[cStart:cStart+3] == "if " {
varStart = cStart + 3
for content[varStart] == ' ' {
varStart++
}
}
if ifStart == -1 {
out += content[previousEnd+2 : i]
}
if content[cStart:cStart+5] != "endif" || invalidIf {
continue
}
ifEnd = i - 1
if ifTrue {
b := templateEmail(content[ifStart:ifEnd+1], variables, conditionals, values)
out += b
ifTrue = false
}
} else if c == '}' {
if varStart != -1 {
ifStart = i + 1
varEnd = i - 1
for content[varEnd] == ' ' {
varEnd--
}
varName = content[varStart : varEnd+1]
positive := true
if varName[0] == '!' {
positive = false
varName = varName[1:]
}
validVar := false
wrappedVarName := "{" + varName + "}"
for _, v := range conditionals {
if v == wrappedVarName {
validVar = true
break
}
}
if validVar {
ifTrue = positive == truthy(values[varName])
} else {
invalidIf = true
ifStart, ifEnd = -1, -1
}
varStart, varEnd = -1, -1
}
cEnd = i - 1
for content[cEnd] == ' ' {
cEnd--
}
previousEnd = i - 1
if content[cEnd-4:cEnd+1] == "endif" && !invalidIf {
continue
}
validVar := false
varName = content[cStart : cEnd+1]
cStart, cEnd = -1, -1
if ifStart != -1 {
continue
}
wrappedVarName := "{" + varName + "}"
for _, v := range variables {
if v == wrappedVarName {
validVar = true
break
}
}
if !validVar {
out += wrappedVarName
continue
}
out += fmt.Sprint(values[varName])
}
}
if previousEnd+1 != len(content)-1 {
out += content[previousEnd+2:]
}
if out == "" {
return content
}
return out
}

View File

@ -769,6 +769,7 @@ class ombiDefaults {
interface templateEmail { interface templateEmail {
content: string; content: string;
variables: string[]; variables: string[];
conditionals: string[];
values: { [key: string]: string }; values: { [key: string]: string };
html: string; html: string;
plaintext: string; plaintext: string;
@ -788,6 +789,8 @@ class EmailEditor {
private _header = document.getElementById("header-editor") as HTMLSpanElement; private _header = document.getElementById("header-editor") as HTMLSpanElement;
private _variables = document.getElementById("editor-variables") as HTMLDivElement; private _variables = document.getElementById("editor-variables") as HTMLDivElement;
private _variablesLabel = document.getElementById("label-editor-variables") as HTMLElement; private _variablesLabel = document.getElementById("label-editor-variables") as HTMLElement;
private _conditionals = document.getElementById("editor-conditionals") as HTMLDivElement;
private _conditionalsLabel = document.getElementById("label-editor-conditionals") as HTMLElement;
private _textArea = document.getElementById("textarea-editor") as HTMLTextAreaElement; private _textArea = document.getElementById("textarea-editor") as HTMLTextAreaElement;
private _preview = document.getElementById("editor-preview") as HTMLDivElement; private _preview = document.getElementById("editor-preview") as HTMLDivElement;
private _previewContent: HTMLElement; private _previewContent: HTMLElement;
@ -845,7 +848,7 @@ class EmailEditor {
this._variablesLabel.classList.remove("unfocused"); this._variablesLabel.classList.remove("unfocused");
} }
this._variables.innerHTML = innerHTML this._variables.innerHTML = innerHTML
const buttons = this._variables.querySelectorAll("span.button") as NodeListOf<HTMLSpanElement>; let buttons = this._variables.querySelectorAll("span.button") as NodeListOf<HTMLSpanElement>;
for (let i = 0; i < this._templ.variables.length; i++) { for (let i = 0; i < this._templ.variables.length; i++) {
buttons[i].innerHTML = `<span class="monospace">` + this._templ.variables[i] + `</span>`; buttons[i].innerHTML = `<span class="monospace">` + this._templ.variables[i] + `</span>`;
buttons[i].onclick = () => { buttons[i].onclick = () => {
@ -854,6 +857,28 @@ class EmailEditor {
// this._timeout = setTimeout(this.loadPreview, this._finishInterval); // this._timeout = setTimeout(this.loadPreview, this._finishInterval);
} }
} }
innerHTML = '';
for (let i = this._templ.conditionals.length-1; i >= 0; i--) {
let ci = i % colors.length;
innerHTML += '<span class="button ~' + colors[ci] +' !normal mb-1" style="margin-left: 0.25rem; margin-right: 0.25rem;"></span>'
}
if (this._templ.conditionals.length == 0) {
this._conditionalsLabel.classList.add("unfocused");
} else {
this._conditionalsLabel.classList.remove("unfocused");
}
this._conditionals.innerHTML = innerHTML
buttons = this._conditionals.querySelectorAll("span.button") as NodeListOf<HTMLSpanElement>;
for (let i = 0; i < this._templ.conditionals.length; i++) {
buttons[i].innerHTML = `<span class="monospace">{if ` + this._templ.conditionals[i].slice(1) + `</span>`;
buttons[i].onclick = () => {
this.insert(this._textArea, "{if " + this._templ.conditionals[i].slice(1) + "{endif}");
this.loadPreview();
// this._timeout = setTimeout(this.loadPreview, this._finishInterval);
}
}
window.modals.editor.show(); window.modals.editor.show();
} }
}) })