mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-23 01:20:11 +00:00
Move email rendering to browser
the email preview no longer has a delay after each change. This also avoids a race condition in which the email currently being edited could be actually sent.
This commit is contained in:
parent
d1b1b90de3
commit
52d9cda61a
@ -19,7 +19,7 @@ I chose to rewrite the python [jellyfin-accounts](https://github.com/hrfee/jelly
|
|||||||
* Email addresses can optionally be used instead of usernames
|
* Email addresses can optionally be used instead of usernames
|
||||||
* 🔑 Password resets: When user's forget their passwords and request a change in Jellyfin, jfa-go reads the PIN from the created file and sends it straight to the user via email.
|
* 🔑 Password resets: When user's forget their passwords and request a change in Jellyfin, jfa-go reads the PIN from the created file and sends it straight to the user via email.
|
||||||
* Notifications: Get notified when someone creates an account, or an invite expires.
|
* Notifications: Get notified when someone creates an account, or an invite expires.
|
||||||
* 📣 Announcements: Bulk email you users with announcements about your server.
|
* 📣 Announcements: Bulk email your users with announcements about your server.
|
||||||
* Authentication via Jellyfin: Instead of using separate credentials for jfa-go and Jellyfin, jfa-go can use it as the authentication provider.
|
* Authentication via Jellyfin: Instead of using separate credentials for jfa-go and Jellyfin, jfa-go can use it as the authentication provider.
|
||||||
* Enables the usage of jfa-go by multiple people
|
* Enables the usage of jfa-go by multiple people
|
||||||
* 🌓 Customizable look
|
* 🌓 Customizable look
|
||||||
|
99
api.go
99
api.go
@ -1375,87 +1375,9 @@ func (app *appContext) SetEmailState(gc *gin.Context) {
|
|||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Render and return an email for testing purposes.
|
// @Summary Returns the custom email (generating it if not set) and list of used variables in it.
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param customEmail body customEmail true "Content = email (in markdown)."
|
// @Success 200 {object} customEmailDTO
|
||||||
// @Success 200 {object} Email
|
|
||||||
// @Failure 400 {object} boolResponse
|
|
||||||
// @Failure 500 {object} boolResponse
|
|
||||||
// @Router /config/emails/{id}/test [post]
|
|
||||||
// @tags Configuration
|
|
||||||
func (app *appContext) GetTestEmail(gc *gin.Context) {
|
|
||||||
var req customEmail
|
|
||||||
gc.BindJSON(&req)
|
|
||||||
if req.Content == "" {
|
|
||||||
app.debug.Println("Test failed: Content was empty")
|
|
||||||
respondBool(400, false, gc)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
id := gc.Param("id")
|
|
||||||
var msg *Email
|
|
||||||
var err error
|
|
||||||
var cache customEmail
|
|
||||||
var restore func(cache customEmail)
|
|
||||||
username := app.storage.lang.Email[app.storage.lang.chosenEmailLang].Strings.get("username")
|
|
||||||
emailAddress := app.storage.lang.Email[app.storage.lang.chosenEmailLang].Strings.get("emailAddress")
|
|
||||||
if id == "UserCreated" {
|
|
||||||
cache = app.storage.customEmails.UserCreated
|
|
||||||
restore = func(cache customEmail) { app.storage.customEmails.UserCreated = cache }
|
|
||||||
app.storage.customEmails.UserCreated.Content = req.Content
|
|
||||||
app.storage.customEmails.UserCreated.Enabled = true
|
|
||||||
msg, err = app.email.constructCreated("xxxxxx", username, emailAddress, Invite{}, app, false)
|
|
||||||
} else if id == "InviteExpiry" {
|
|
||||||
cache = app.storage.customEmails.InviteExpiry
|
|
||||||
restore = func(cache customEmail) { app.storage.customEmails.InviteExpiry = cache }
|
|
||||||
app.storage.customEmails.InviteExpiry.Content = req.Content
|
|
||||||
app.storage.customEmails.InviteExpiry.Enabled = true
|
|
||||||
msg, err = app.email.constructExpiry("xxxxxx", Invite{}, app, false)
|
|
||||||
} else if id == "PasswordReset" {
|
|
||||||
cache = app.storage.customEmails.PasswordReset
|
|
||||||
restore = func(cache customEmail) { app.storage.customEmails.PasswordReset = cache }
|
|
||||||
app.storage.customEmails.PasswordReset.Content = req.Content
|
|
||||||
app.storage.customEmails.PasswordReset.Enabled = true
|
|
||||||
msg, err = app.email.constructReset(PasswordReset{Pin: "12-34-56", Username: username}, app, false)
|
|
||||||
} else if id == "UserDeleted" {
|
|
||||||
cache = app.storage.customEmails.UserDeleted
|
|
||||||
restore = func(cache customEmail) { app.storage.customEmails.UserDeleted = cache }
|
|
||||||
app.storage.customEmails.UserDeleted.Content = req.Content
|
|
||||||
app.storage.customEmails.UserDeleted.Enabled = true
|
|
||||||
msg, err = app.email.constructDeleted(app.storage.lang.Email[app.storage.lang.chosenEmailLang].UserDeleted.get("reason"), app, false)
|
|
||||||
} else if id == "InviteEmail" {
|
|
||||||
cache = app.storage.customEmails.InviteEmail
|
|
||||||
restore = func(cache customEmail) { app.storage.customEmails.InviteEmail = cache }
|
|
||||||
app.storage.customEmails.InviteEmail.Content = req.Content
|
|
||||||
app.storage.customEmails.InviteEmail.Enabled = true
|
|
||||||
msg, err = app.email.constructInvite("xxxxxx", Invite{}, app, false)
|
|
||||||
} else if id == "WelcomeEmail" {
|
|
||||||
cache = app.storage.customEmails.WelcomeEmail
|
|
||||||
restore = func(cache customEmail) { app.storage.customEmails.WelcomeEmail = cache }
|
|
||||||
app.storage.customEmails.WelcomeEmail.Content = req.Content
|
|
||||||
app.storage.customEmails.WelcomeEmail.Enabled = true
|
|
||||||
msg, err = app.email.constructWelcome(username, app, false)
|
|
||||||
} else if id == "EmailConfirmation" {
|
|
||||||
cache = app.storage.customEmails.EmailConfirmation
|
|
||||||
restore = func(cache customEmail) { app.storage.customEmails.EmailConfirmation = cache }
|
|
||||||
app.storage.customEmails.EmailConfirmation.Content = req.Content
|
|
||||||
app.storage.customEmails.EmailConfirmation.Enabled = true
|
|
||||||
msg, err = app.email.constructConfirmation("xxxxxx", username, "xxxxxx", app, false)
|
|
||||||
} else {
|
|
||||||
respondBool(400, false, gc)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
restore(cache)
|
|
||||||
if err != nil {
|
|
||||||
respondBool(500, false, gc)
|
|
||||||
app.err.Printf("Failed to construct test email: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gc.JSON(200, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Summary Returns the boilerplate email and list of used variables in it.
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {object} customEmail
|
|
||||||
// @Failure 400 {object} boolResponse
|
// @Failure 400 {object} boolResponse
|
||||||
// @Failure 500 {object} boolResponse
|
// @Failure 500 {object} boolResponse
|
||||||
// @Router /config/emails/{id} [get]
|
// @Router /config/emails/{id} [get]
|
||||||
@ -1466,8 +1388,11 @@ 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 values map[string]interface{}
|
||||||
var writeVars func(variables []string)
|
var writeVars func(variables []string)
|
||||||
newEmail := false
|
newEmail := false
|
||||||
|
username := app.storage.lang.Email[app.storage.lang.chosenEmailLang].Strings.get("username")
|
||||||
|
emailAddress := app.storage.lang.Email[app.storage.lang.chosenEmailLang].Strings.get("emailAddress")
|
||||||
if id == "UserCreated" {
|
if id == "UserCreated" {
|
||||||
content = app.storage.customEmails.UserCreated.Content
|
content = app.storage.customEmails.UserCreated.Content
|
||||||
if content == "" {
|
if content == "" {
|
||||||
@ -1478,6 +1403,7 @@ func (app *appContext) GetEmail(gc *gin.Context) {
|
|||||||
variables = app.storage.customEmails.UserCreated.Variables
|
variables = app.storage.customEmails.UserCreated.Variables
|
||||||
}
|
}
|
||||||
writeVars = func(variables []string) { app.storage.customEmails.UserCreated.Variables = variables }
|
writeVars = func(variables []string) { app.storage.customEmails.UserCreated.Variables = variables }
|
||||||
|
values = app.email.createdValues("xxxxxx", username, emailAddress, Invite{}, app, false)
|
||||||
// app.storage.customEmails.UserCreated = content
|
// app.storage.customEmails.UserCreated = content
|
||||||
} else if id == "InviteExpiry" {
|
} else if id == "InviteExpiry" {
|
||||||
content = app.storage.customEmails.InviteExpiry.Content
|
content = app.storage.customEmails.InviteExpiry.Content
|
||||||
@ -1489,6 +1415,7 @@ func (app *appContext) GetEmail(gc *gin.Context) {
|
|||||||
variables = app.storage.customEmails.InviteExpiry.Variables
|
variables = app.storage.customEmails.InviteExpiry.Variables
|
||||||
}
|
}
|
||||||
writeVars = func(variables []string) { app.storage.customEmails.InviteExpiry.Variables = variables }
|
writeVars = func(variables []string) { app.storage.customEmails.InviteExpiry.Variables = variables }
|
||||||
|
values = app.email.expiryValues("xxxxxx", Invite{}, app, false)
|
||||||
// app.storage.customEmails.InviteExpiry = content
|
// app.storage.customEmails.InviteExpiry = content
|
||||||
} else if id == "PasswordReset" {
|
} else if id == "PasswordReset" {
|
||||||
content = app.storage.customEmails.PasswordReset.Content
|
content = app.storage.customEmails.PasswordReset.Content
|
||||||
@ -1500,6 +1427,7 @@ func (app *appContext) GetEmail(gc *gin.Context) {
|
|||||||
variables = app.storage.customEmails.PasswordReset.Variables
|
variables = app.storage.customEmails.PasswordReset.Variables
|
||||||
}
|
}
|
||||||
writeVars = func(variables []string) { app.storage.customEmails.PasswordReset.Variables = variables }
|
writeVars = func(variables []string) { app.storage.customEmails.PasswordReset.Variables = variables }
|
||||||
|
values = app.email.resetValues(PasswordReset{Pin: "12-34-56", Username: username}, app, false)
|
||||||
// app.storage.customEmails.PasswordReset = content
|
// app.storage.customEmails.PasswordReset = content
|
||||||
} else if id == "UserDeleted" {
|
} else if id == "UserDeleted" {
|
||||||
content = app.storage.customEmails.UserDeleted.Content
|
content = app.storage.customEmails.UserDeleted.Content
|
||||||
@ -1511,6 +1439,7 @@ func (app *appContext) GetEmail(gc *gin.Context) {
|
|||||||
variables = app.storage.customEmails.UserDeleted.Variables
|
variables = app.storage.customEmails.UserDeleted.Variables
|
||||||
}
|
}
|
||||||
writeVars = func(variables []string) { app.storage.customEmails.UserDeleted.Variables = variables }
|
writeVars = func(variables []string) { app.storage.customEmails.UserDeleted.Variables = variables }
|
||||||
|
values = app.email.deletedValues(app.storage.lang.Email[app.storage.lang.chosenEmailLang].UserDeleted.get("reason"), app, false)
|
||||||
// app.storage.customEmails.UserDeleted = content
|
// app.storage.customEmails.UserDeleted = content
|
||||||
} else if id == "InviteEmail" {
|
} else if id == "InviteEmail" {
|
||||||
content = app.storage.customEmails.InviteEmail.Content
|
content = app.storage.customEmails.InviteEmail.Content
|
||||||
@ -1522,6 +1451,7 @@ func (app *appContext) GetEmail(gc *gin.Context) {
|
|||||||
variables = app.storage.customEmails.InviteEmail.Variables
|
variables = app.storage.customEmails.InviteEmail.Variables
|
||||||
}
|
}
|
||||||
writeVars = func(variables []string) { app.storage.customEmails.InviteEmail.Variables = variables }
|
writeVars = func(variables []string) { app.storage.customEmails.InviteEmail.Variables = variables }
|
||||||
|
values = app.email.inviteValues("xxxxxx", Invite{}, app, false)
|
||||||
// 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
|
||||||
@ -1534,6 +1464,7 @@ func (app *appContext) GetEmail(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
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, app, false)
|
||||||
} else if id == "EmailConfirmation" {
|
} else if id == "EmailConfirmation" {
|
||||||
content = app.storage.customEmails.EmailConfirmation.Content
|
content = app.storage.customEmails.EmailConfirmation.Content
|
||||||
if content == "" {
|
if content == "" {
|
||||||
@ -1544,6 +1475,7 @@ func (app *appContext) GetEmail(gc *gin.Context) {
|
|||||||
variables = app.storage.customEmails.EmailConfirmation.Variables
|
variables = app.storage.customEmails.EmailConfirmation.Variables
|
||||||
}
|
}
|
||||||
writeVars = func(variables []string) { app.storage.customEmails.EmailConfirmation.Variables = variables }
|
writeVars = func(variables []string) { app.storage.customEmails.EmailConfirmation.Variables = variables }
|
||||||
|
values = app.email.confirmationValues("xxxxxx", username, "xxxxxx", app, false)
|
||||||
// app.storage.customEmails.EmailConfirmation = content
|
// app.storage.customEmails.EmailConfirmation = content
|
||||||
} else {
|
} else {
|
||||||
respondBool(400, false, gc)
|
respondBool(400, false, gc)
|
||||||
@ -1577,7 +1509,12 @@ func (app *appContext) GetEmail(gc *gin.Context) {
|
|||||||
respondBool(500, false, gc)
|
respondBool(500, false, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
gc.JSON(200, customEmail{Content: content, Variables: variables})
|
email, err := app.email.constructTemplate("", "<div id=\"preview-content\"></div>", app)
|
||||||
|
if err != nil {
|
||||||
|
respondBool(500, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gc.JSON(200, customEmailDTO{Content: content, Variables: variables, Values: values, HTML: email.HTML})
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Logout by deleting refresh token from cookies.
|
// @Summary Logout by deleting refresh token from cookies.
|
||||||
|
93
email.go
93
email.go
@ -212,11 +212,7 @@ func (emailer *Emailer) construct(app *appContext, section, keyFragment string,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) constructConfirmation(code, username, key string, app *appContext, noSub bool) (*Email, error) {
|
func (emailer *Emailer) confirmationValues(code, username, key string, app *appContext, noSub bool) map[string]interface{} {
|
||||||
email := &Email{
|
|
||||||
Subject: app.config.Section("email_confirmation").Key("subject").MustString(emailer.lang.EmailConfirmation.get("title")),
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
template := map[string]interface{}{
|
template := map[string]interface{}{
|
||||||
"clickBelow": emailer.lang.EmailConfirmation.get("clickBelow"),
|
"clickBelow": emailer.lang.EmailConfirmation.get("clickBelow"),
|
||||||
"ifItWasNotYou": emailer.lang.Strings.get("ifItWasNotYou"),
|
"ifItWasNotYou": emailer.lang.Strings.get("ifItWasNotYou"),
|
||||||
@ -238,6 +234,15 @@ func (emailer *Emailer) constructConfirmation(code, username, key string, app *a
|
|||||||
template["confirmationURL"] = inviteLink
|
template["confirmationURL"] = inviteLink
|
||||||
template["message"] = message
|
template["message"] = message
|
||||||
}
|
}
|
||||||
|
return template
|
||||||
|
}
|
||||||
|
|
||||||
|
func (emailer *Emailer) constructConfirmation(code, username, key string, app *appContext, noSub bool) (*Email, error) {
|
||||||
|
email := &Email{
|
||||||
|
Subject: app.config.Section("email_confirmation").Key("subject").MustString(emailer.lang.EmailConfirmation.get("title")),
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
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 := app.storage.customEmails.EmailConfirmation.Content
|
||||||
for _, v := range app.storage.customEmails.EmailConfirmation.Variables {
|
for _, v := range app.storage.customEmails.EmailConfirmation.Variables {
|
||||||
@ -274,10 +279,7 @@ func (emailer *Emailer) constructTemplate(subject, md string, app *appContext) (
|
|||||||
return email, nil
|
return email, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) constructInvite(code string, invite Invite, app *appContext, noSub bool) (*Email, error) {
|
func (emailer *Emailer) inviteValues(code string, invite Invite, app *appContext, noSub bool) map[string]interface{} {
|
||||||
email := &Email{
|
|
||||||
Subject: app.config.Section("email_confirmation").Key("subject").MustString(emailer.lang.InviteEmail.get("title")),
|
|
||||||
}
|
|
||||||
expiry := invite.ValidTill
|
expiry := invite.ValidTill
|
||||||
d, t, expiresIn := emailer.formatExpiry(expiry, false, app.datePattern, app.timePattern)
|
d, t, expiresIn := emailer.formatExpiry(expiry, false, app.datePattern, app.timePattern)
|
||||||
message := app.config.Section("email").Key("message").String()
|
message := app.config.Section("email").Key("message").String()
|
||||||
@ -304,6 +306,14 @@ func (emailer *Emailer) constructInvite(code string, invite Invite, app *appCont
|
|||||||
template["inviteURL"] = inviteLink
|
template["inviteURL"] = inviteLink
|
||||||
template["message"] = message
|
template["message"] = message
|
||||||
}
|
}
|
||||||
|
return template
|
||||||
|
}
|
||||||
|
|
||||||
|
func (emailer *Emailer) constructInvite(code string, invite Invite, app *appContext, noSub bool) (*Email, error) {
|
||||||
|
email := &Email{
|
||||||
|
Subject: app.config.Section("email_confirmation").Key("subject").MustString(emailer.lang.InviteEmail.get("title")),
|
||||||
|
}
|
||||||
|
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 := app.storage.customEmails.InviteEmail.Content
|
||||||
@ -323,10 +333,7 @@ func (emailer *Emailer) constructInvite(code string, invite Invite, app *appCont
|
|||||||
return email, nil
|
return email, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appContext, noSub bool) (*Email, error) {
|
func (emailer *Emailer) expiryValues(code string, invite Invite, app *appContext, noSub bool) map[string]interface{} {
|
||||||
email := &Email{
|
|
||||||
Subject: emailer.lang.InviteExpiry.get("title"),
|
|
||||||
}
|
|
||||||
expiry := app.formatDatetime(invite.ValidTill)
|
expiry := app.formatDatetime(invite.ValidTill)
|
||||||
template := map[string]interface{}{
|
template := map[string]interface{}{
|
||||||
"inviteExpired": emailer.lang.InviteExpiry.get("inviteExpired"),
|
"inviteExpired": emailer.lang.InviteExpiry.get("inviteExpired"),
|
||||||
@ -339,7 +346,15 @@ func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appCont
|
|||||||
} else {
|
} else {
|
||||||
template["expiredAt"] = emailer.lang.InviteExpiry.template("expiredAt", tmpl{"code": template["code"].(string), "time": template["time"].(string)})
|
template["expiredAt"] = emailer.lang.InviteExpiry.template("expiredAt", tmpl{"code": template["code"].(string), "time": template["time"].(string)})
|
||||||
}
|
}
|
||||||
|
return template
|
||||||
|
}
|
||||||
|
|
||||||
|
func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appContext, noSub bool) (*Email, error) {
|
||||||
|
email := &Email{
|
||||||
|
Subject: emailer.lang.InviteExpiry.get("title"),
|
||||||
|
}
|
||||||
var err error
|
var err error
|
||||||
|
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 := app.storage.customEmails.InviteExpiry.Content
|
||||||
for _, v := range app.storage.customEmails.InviteExpiry.Variables {
|
for _, v := range app.storage.customEmails.InviteExpiry.Variables {
|
||||||
@ -358,10 +373,7 @@ func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appCont
|
|||||||
return email, nil
|
return email, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) constructCreated(code, username, address string, invite Invite, app *appContext, noSub bool) (*Email, error) {
|
func (emailer *Emailer) createdValues(code, username, address string, invite Invite, app *appContext, noSub bool) map[string]interface{} {
|
||||||
email := &Email{
|
|
||||||
Subject: emailer.lang.UserCreated.get("title"),
|
|
||||||
}
|
|
||||||
template := map[string]interface{}{
|
template := map[string]interface{}{
|
||||||
"nameString": emailer.lang.Strings.get("name"),
|
"nameString": emailer.lang.Strings.get("name"),
|
||||||
"addressString": emailer.lang.Strings.get("emailAddress"),
|
"addressString": emailer.lang.Strings.get("emailAddress"),
|
||||||
@ -389,6 +401,14 @@ func (emailer *Emailer) constructCreated(code, username, address string, invite
|
|||||||
template["time"] = created
|
template["time"] = created
|
||||||
template["notificationNotice"] = emailer.lang.UserCreated.get("notificationNotice")
|
template["notificationNotice"] = emailer.lang.UserCreated.get("notificationNotice")
|
||||||
}
|
}
|
||||||
|
return template
|
||||||
|
}
|
||||||
|
|
||||||
|
func (emailer *Emailer) constructCreated(code, username, address string, invite Invite, app *appContext, noSub bool) (*Email, error) {
|
||||||
|
email := &Email{
|
||||||
|
Subject: emailer.lang.UserCreated.get("title"),
|
||||||
|
}
|
||||||
|
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 := app.storage.customEmails.UserCreated.Content
|
||||||
@ -408,10 +428,7 @@ func (emailer *Emailer) constructCreated(code, username, address string, invite
|
|||||||
return email, nil
|
return email, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext, noSub bool) (*Email, error) {
|
func (emailer *Emailer) resetValues(pwr PasswordReset, app *appContext, noSub bool) map[string]interface{} {
|
||||||
email := &Email{
|
|
||||||
Subject: app.config.Section("password_resets").Key("subject").MustString(emailer.lang.PasswordReset.get("title")),
|
|
||||||
}
|
|
||||||
d, t, expiresIn := emailer.formatExpiry(pwr.Expiry, true, app.datePattern, app.timePattern)
|
d, t, expiresIn := emailer.formatExpiry(pwr.Expiry, true, app.datePattern, app.timePattern)
|
||||||
message := app.config.Section("email").Key("message").String()
|
message := app.config.Section("email").Key("message").String()
|
||||||
template := map[string]interface{}{
|
template := map[string]interface{}{
|
||||||
@ -438,6 +455,14 @@ func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext, noSub
|
|||||||
template["pin"] = pwr.Pin
|
template["pin"] = pwr.Pin
|
||||||
template["message"] = message
|
template["message"] = message
|
||||||
}
|
}
|
||||||
|
return template
|
||||||
|
}
|
||||||
|
|
||||||
|
func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext, noSub bool) (*Email, error) {
|
||||||
|
email := &Email{
|
||||||
|
Subject: app.config.Section("password_resets").Key("subject").MustString(emailer.lang.PasswordReset.get("title")),
|
||||||
|
}
|
||||||
|
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 := app.storage.customEmails.PasswordReset.Content
|
||||||
@ -457,10 +482,7 @@ func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext, noSub
|
|||||||
return email, nil
|
return email, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) constructDeleted(reason string, app *appContext, noSub bool) (*Email, error) {
|
func (emailer *Emailer) deletedValues(reason string, app *appContext, noSub bool) map[string]interface{} {
|
||||||
email := &Email{
|
|
||||||
Subject: app.config.Section("deletion").Key("subject").MustString(emailer.lang.UserDeleted.get("title")),
|
|
||||||
}
|
|
||||||
template := map[string]interface{}{
|
template := map[string]interface{}{
|
||||||
"yourAccountWasDeleted": emailer.lang.UserDeleted.get("yourAccountWasDeleted"),
|
"yourAccountWasDeleted": emailer.lang.UserDeleted.get("yourAccountWasDeleted"),
|
||||||
"reasonString": emailer.lang.UserDeleted.get("reason"),
|
"reasonString": emailer.lang.UserDeleted.get("reason"),
|
||||||
@ -475,7 +497,15 @@ func (emailer *Emailer) constructDeleted(reason string, app *appContext, noSub b
|
|||||||
template["reason"] = reason
|
template["reason"] = reason
|
||||||
template["message"] = app.config.Section("email").Key("message").String()
|
template["message"] = app.config.Section("email").Key("message").String()
|
||||||
}
|
}
|
||||||
|
return template
|
||||||
|
}
|
||||||
|
|
||||||
|
func (emailer *Emailer) constructDeleted(reason string, app *appContext, noSub bool) (*Email, error) {
|
||||||
|
email := &Email{
|
||||||
|
Subject: app.config.Section("deletion").Key("subject").MustString(emailer.lang.UserDeleted.get("title")),
|
||||||
|
}
|
||||||
var err error
|
var err error
|
||||||
|
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 := app.storage.customEmails.UserDeleted.Content
|
||||||
for _, v := range app.storage.customEmails.UserDeleted.Variables {
|
for _, v := range app.storage.customEmails.UserDeleted.Variables {
|
||||||
@ -494,10 +524,7 @@ func (emailer *Emailer) constructDeleted(reason string, app *appContext, noSub b
|
|||||||
return email, nil
|
return email, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) constructWelcome(username string, app *appContext, noSub bool) (*Email, error) {
|
func (emailer *Emailer) welcomeValues(username string, app *appContext, noSub bool) map[string]interface{} {
|
||||||
email := &Email{
|
|
||||||
Subject: app.config.Section("welcome_email").Key("subject").MustString(emailer.lang.WelcomeEmail.get("title")),
|
|
||||||
}
|
|
||||||
template := map[string]interface{}{
|
template := map[string]interface{}{
|
||||||
"welcome": emailer.lang.WelcomeEmail.get("welcome"),
|
"welcome": emailer.lang.WelcomeEmail.get("welcome"),
|
||||||
"youCanLoginWith": emailer.lang.WelcomeEmail.get("youCanLoginWith"),
|
"youCanLoginWith": emailer.lang.WelcomeEmail.get("youCanLoginWith"),
|
||||||
@ -515,7 +542,15 @@ func (emailer *Emailer) constructWelcome(username string, app *appContext, noSub
|
|||||||
template["username"] = username
|
template["username"] = username
|
||||||
template["message"] = app.config.Section("email").Key("message").String()
|
template["message"] = app.config.Section("email").Key("message").String()
|
||||||
}
|
}
|
||||||
|
return template
|
||||||
|
}
|
||||||
|
|
||||||
|
func (emailer *Emailer) constructWelcome(username string, app *appContext, noSub bool) (*Email, error) {
|
||||||
|
email := &Email{
|
||||||
|
Subject: app.config.Section("welcome_email").Key("subject").MustString(emailer.lang.WelcomeEmail.get("title")),
|
||||||
|
}
|
||||||
var err error
|
var err error
|
||||||
|
template := emailer.welcomeValues(username, app, noSub)
|
||||||
if app.storage.customEmails.WelcomeEmail.Enabled {
|
if app.storage.customEmails.WelcomeEmail.Enabled {
|
||||||
content := app.storage.customEmails.WelcomeEmail.Content
|
content := app.storage.customEmails.WelcomeEmail.Content
|
||||||
for _, v := range app.storage.customEmails.WelcomeEmail.Variables {
|
for _, v := range app.storage.customEmails.WelcomeEmail.Variables {
|
||||||
|
@ -189,3 +189,10 @@ type emailSetDTO struct {
|
|||||||
type emailTestDTO struct {
|
type emailTestDTO struct {
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type customEmailDTO struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
Variables []string `json:"variables"`
|
||||||
|
Values map[string]interface{} `json:"values"`
|
||||||
|
HTML string `json:"html"`
|
||||||
|
}
|
||||||
|
19
package-lock.json
generated
19
package-lock.json
generated
@ -12,6 +12,14 @@
|
|||||||
"regenerator-runtime": "^0.13.4"
|
"regenerator-runtime": "^0.13.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@ts-stack/markdown": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/@ts-stack/markdown/download/@ts-stack/markdown-1.3.0.tgz",
|
||||||
|
"integrity": "sha1-OdkuDifo9w6Ba3L/EzaO6HWWe+o=",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "14.14.16",
|
"version": "14.14.16",
|
||||||
"resolved": "https://registry.npm.taobao.org/@types/node/download/@types/node-14.14.16.tgz?cache=0&sync_timestamp=1608756036972&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fnode%2Fdownload%2F%40types%2Fnode-14.14.16.tgz",
|
"resolved": "https://registry.npm.taobao.org/@types/node/download/@types/node-14.14.16.tgz?cache=0&sync_timestamp=1608756036972&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fnode%2Fdownload%2F%40types%2Fnode-14.14.16.tgz",
|
||||||
@ -228,9 +236,9 @@
|
|||||||
"integrity": "sha1-vfpzUplmTfr9NFKe1PhSKidf6lY="
|
"integrity": "sha1-vfpzUplmTfr9NFKe1PhSKidf6lY="
|
||||||
},
|
},
|
||||||
"esbuild": {
|
"esbuild": {
|
||||||
"version": "0.8.49",
|
"version": "0.8.50",
|
||||||
"resolved": "https://registry.npm.taobao.org/esbuild/download/esbuild-0.8.49.tgz",
|
"resolved": "https://registry.npm.taobao.org/esbuild/download/esbuild-0.8.50.tgz",
|
||||||
"integrity": "sha1-PTP3GzlmYR+CLPTIOBFfP70W3vI="
|
"integrity": "sha1-6/JP3gza0aNpeJ3W/XqCCwoB5Gw="
|
||||||
},
|
},
|
||||||
"escalade": {
|
"escalade": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
@ -1463,6 +1471,11 @@
|
|||||||
"safe-buffer": "~5.1.0"
|
"safe-buffer": "~5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/tslib/download/tslib-2.1.0.tgz?cache=0&sync_timestamp=1609887446200&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftslib%2Fdownload%2Ftslib-2.1.0.tgz",
|
||||||
|
"integrity": "sha1-2mCGDxwuyqVwOrfTm8Bba/mIuXo="
|
||||||
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "4.1.3",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npm.taobao.org/typescript/download/typescript-4.1.3.tgz?cache=0&sync_timestamp=1609830171931&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftypescript%2Fdownload%2Ftypescript-4.1.3.tgz",
|
"resolved": "https://registry.npm.taobao.org/typescript/download/typescript-4.1.3.tgz?cache=0&sync_timestamp=1609830171931&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftypescript%2Fdownload%2Ftypescript-4.1.3.tgz",
|
||||||
|
@ -17,8 +17,9 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/hrfee/jfa-go#readme",
|
"homepage": "https://github.com/hrfee/jfa-go#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ts-stack/markdown": "^1.3.0",
|
||||||
"a17t": "^0.4.0",
|
"a17t": "^0.4.0",
|
||||||
"esbuild": "^0.8.49",
|
"esbuild": "^0.8.50",
|
||||||
"lodash": "^4.17.19",
|
"lodash": "^4.17.19",
|
||||||
"mjml": "^4.8.0",
|
"mjml": "^4.8.0",
|
||||||
"remixicon": "^2.5.0",
|
"remixicon": "^2.5.0",
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { _get, _post, toggleLoader } from "../modules/common.js";
|
import { _get, _post, toggleLoader } from "../modules/common.js";
|
||||||
|
import { Marked } from "@ts-stack/markdown";
|
||||||
|
|
||||||
interface settingsBoolEvent extends Event {
|
interface settingsBoolEvent extends Event {
|
||||||
detail: boolean;
|
detail: boolean;
|
||||||
@ -680,6 +681,8 @@ interface Email {
|
|||||||
interface templateEmail {
|
interface templateEmail {
|
||||||
content: string;
|
content: string;
|
||||||
variables: string[];
|
variables: string[];
|
||||||
|
values: { [key: string]: string };
|
||||||
|
html: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface emailListEl {
|
interface emailListEl {
|
||||||
@ -691,13 +694,14 @@ class EmailEditor {
|
|||||||
private _currentID: string;
|
private _currentID: string;
|
||||||
private _names: { [id: string]: emailListEl };
|
private _names: { [id: string]: emailListEl };
|
||||||
private _content: string;
|
private _content: string;
|
||||||
|
private _templ: templateEmail;
|
||||||
private _form = document.getElementById("form-editor") as HTMLFormElement;
|
private _form = document.getElementById("form-editor") as HTMLFormElement;
|
||||||
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 _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 _timeout: number;
|
// private _timeout: number;
|
||||||
private _finishInterval = 1000;
|
// private _finishInterval = 200;
|
||||||
|
|
||||||
insert = (textarea: HTMLTextAreaElement, text: string) => { // https://kubyshkin.name/posts/insert-text-into-textarea-at-cursor-position <3
|
insert = (textarea: HTMLTextAreaElement, text: string) => { // https://kubyshkin.name/posts/insert-text-into-textarea-at-cursor-position <3
|
||||||
const isSuccess = document.execCommand("insertText", false, text);
|
const isSuccess = document.execCommand("insertText", false, text);
|
||||||
@ -728,23 +732,25 @@ class EmailEditor {
|
|||||||
if (this._names[id] !== undefined) {
|
if (this._names[id] !== undefined) {
|
||||||
this._header.textContent = this._names[id].name;
|
this._header.textContent = this._names[id].name;
|
||||||
}
|
}
|
||||||
const templ = req.response as templateEmail;
|
this._templ = req.response as templateEmail;
|
||||||
this._textArea.value = templ.content;
|
this._textArea.value = this._templ.content;
|
||||||
|
this._preview.innerHTML = this._templ.html;
|
||||||
this.loadPreview();
|
this.loadPreview();
|
||||||
this._content = templ.content;
|
this._content = this._templ.content;
|
||||||
const colors = ["info", "urge", "positive", "neutral"];
|
const colors = ["info", "urge", "positive", "neutral"];
|
||||||
let innerHTML = '';
|
let innerHTML = '';
|
||||||
for (let i = 0; i < templ.variables.length; i++) {
|
for (let i = 0; i < this._templ.variables.length; i++) {
|
||||||
let ci = i % colors.length;
|
let ci = i % colors.length;
|
||||||
innerHTML += '<span class="button ~' + colors[ci] +' !normal mb-1" style="margin-left: 0.25rem; margin-right: 0.25rem;"></span>'
|
innerHTML += '<span class="button ~' + colors[ci] +' !normal mb-1" style="margin-left: 0.25rem; margin-right: 0.25rem;"></span>'
|
||||||
}
|
}
|
||||||
this._variables.innerHTML = innerHTML
|
this._variables.innerHTML = innerHTML
|
||||||
const buttons = this._variables.querySelectorAll("span.button") as NodeListOf<HTMLSpanElement>;
|
const buttons = this._variables.querySelectorAll("span.button") as NodeListOf<HTMLSpanElement>;
|
||||||
for (let i = 0; i < templ.variables.length; i++) {
|
for (let i = 0; i < this._templ.variables.length; i++) {
|
||||||
buttons[i].innerHTML = `<span class="monospace">` + templ.variables[i] + `</span>`;
|
buttons[i].innerHTML = `<span class="monospace">` + this._templ.variables[i] + `</span>`;
|
||||||
buttons[i].onclick = () => {
|
buttons[i].onclick = () => {
|
||||||
this.insert(this._textArea, templ.variables[i]);
|
this.insert(this._textArea, this._templ.variables[i]);
|
||||||
this._timeout = setTimeout(this.loadPreview, this._finishInterval);
|
this.loadPreview();
|
||||||
|
// this._timeout = setTimeout(this.loadPreview, this._finishInterval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window.modals.editor.show();
|
window.modals.editor.show();
|
||||||
@ -752,15 +758,23 @@ class EmailEditor {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
loadPreview = () => {
|
loadPreview = () => {
|
||||||
_post("/config/emails/" + this._currentID + "/test", { "content": this._textArea.value }, (req: XMLHttpRequest) => {
|
let content = this._textArea.value;
|
||||||
if (req.readyState == 4) {
|
for (let variable of this._templ.variables) {
|
||||||
if (req.status != 200) {
|
let value = this._templ.values[variable.slice(1, -1)];
|
||||||
window.notifications.customError("loadTemplateError", window.lang.notif("errorFailureCheckLogs"));
|
if (value === undefined) { value = variable; }
|
||||||
return;
|
content = content.replace(new RegExp(variable, "g"), value);
|
||||||
}
|
}
|
||||||
this._preview.innerHTML = (req.response as Email).html;
|
content = Marked.parse(content)
|
||||||
}
|
document.getElementById("preview-content").innerHTML = content;
|
||||||
}, true);
|
// _post("/config/emails/" + this._currentID + "/test", { "content": this._textArea.value }, (req: XMLHttpRequest) => {
|
||||||
|
// if (req.readyState == 4) {
|
||||||
|
// if (req.status != 200) {
|
||||||
|
// window.notifications.customError("loadTemplateError", window.lang.notif("errorFailureCheckLogs"));
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// this._preview.innerHTML = (req.response as Email).html;
|
||||||
|
// }
|
||||||
|
// }, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
showList = () => {
|
showList = () => {
|
||||||
@ -809,12 +823,13 @@ class EmailEditor {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this._textArea.onkeyup = () => {
|
this._textArea.onkeyup = () => {
|
||||||
clearTimeout(this._timeout);
|
// clearTimeout(this._timeout);
|
||||||
this._timeout = setTimeout(this.loadPreview, this._finishInterval);
|
// this._timeout = setTimeout(this.loadPreview, this._finishInterval);
|
||||||
};
|
this.loadPreview();
|
||||||
this._textArea.onkeydown = () => {
|
|
||||||
clearTimeout(this._timeout);
|
|
||||||
};
|
};
|
||||||
|
// this._textArea.onkeydown = () => {
|
||||||
|
// clearTimeout(this._timeout);
|
||||||
|
// };
|
||||||
|
|
||||||
this._form.onsubmit = (event: Event) => {
|
this._form.onsubmit = (event: Event) => {
|
||||||
if (this._textArea.value == this._content) {
|
if (this._textArea.value == this._content) {
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
"outDir": "../js",
|
"outDir": "../js",
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"lib": ["dom", "es2017"],
|
"lib": ["dom", "es2017"],
|
||||||
"typeRoots": ["./node_modules/@types", "./typings"]
|
"typeRoots": ["./typings", "./node_modules/@types"],
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"esModuleInterop": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user