From 52d9cda61a40888ab633d6e788c93c2113afec24 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Mon, 22 Feb 2021 00:43:36 +0000 Subject: [PATCH] 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. --- README.md | 2 +- api.go | 99 ++++++++---------------------------------- email.go | 93 ++++++++++++++++++++++++++------------- models.go | 7 +++ package-lock.json | 19 ++++++-- package.json | 3 +- ts/modules/settings.ts | 63 +++++++++++++++++---------- ts/tsconfig.json | 4 +- 8 files changed, 150 insertions(+), 140 deletions(-) diff --git a/README.md b/README.md index 8ba8caf..ea61286 100644 --- a/README.md +++ b/README.md @@ -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 * 🔑 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. -* 📣 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. * Enables the usage of jfa-go by multiple people * 🌓 Customizable look diff --git a/api.go b/api.go index 5cdf5b7..859137c 100644 --- a/api.go +++ b/api.go @@ -1375,87 +1375,9 @@ func (app *appContext) SetEmailState(gc *gin.Context) { 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 -// @Param customEmail body customEmail true "Content = email (in markdown)." -// @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 +// @Success 200 {object} customEmailDTO // @Failure 400 {object} boolResponse // @Failure 500 {object} boolResponse // @Router /config/emails/{id} [get] @@ -1466,8 +1388,11 @@ func (app *appContext) GetEmail(gc *gin.Context) { var err error var msg *Email var variables []string + var values map[string]interface{} var writeVars func(variables []string) 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" { content = app.storage.customEmails.UserCreated.Content if content == "" { @@ -1478,6 +1403,7 @@ func (app *appContext) GetEmail(gc *gin.Context) { variables = app.storage.customEmails.UserCreated.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 } else if id == "InviteExpiry" { content = app.storage.customEmails.InviteExpiry.Content @@ -1489,6 +1415,7 @@ func (app *appContext) GetEmail(gc *gin.Context) { variables = app.storage.customEmails.InviteExpiry.Variables } writeVars = func(variables []string) { app.storage.customEmails.InviteExpiry.Variables = variables } + values = app.email.expiryValues("xxxxxx", Invite{}, app, false) // app.storage.customEmails.InviteExpiry = content } else if id == "PasswordReset" { content = app.storage.customEmails.PasswordReset.Content @@ -1500,6 +1427,7 @@ func (app *appContext) GetEmail(gc *gin.Context) { variables = app.storage.customEmails.PasswordReset.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 } else if id == "UserDeleted" { content = app.storage.customEmails.UserDeleted.Content @@ -1511,6 +1439,7 @@ func (app *appContext) GetEmail(gc *gin.Context) { variables = app.storage.customEmails.UserDeleted.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 } else if id == "InviteEmail" { content = app.storage.customEmails.InviteEmail.Content @@ -1522,6 +1451,7 @@ func (app *appContext) GetEmail(gc *gin.Context) { variables = app.storage.customEmails.InviteEmail.Variables } writeVars = func(variables []string) { app.storage.customEmails.InviteEmail.Variables = variables } + values = app.email.inviteValues("xxxxxx", Invite{}, app, false) // app.storage.customEmails.InviteEmail = content } else if id == "WelcomeEmail" { 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 } // app.storage.customEmails.WelcomeEmail = content + values = app.email.welcomeValues(username, app, false) } else if id == "EmailConfirmation" { content = app.storage.customEmails.EmailConfirmation.Content if content == "" { @@ -1544,6 +1475,7 @@ func (app *appContext) GetEmail(gc *gin.Context) { variables = app.storage.customEmails.EmailConfirmation.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 } else { respondBool(400, false, gc) @@ -1577,7 +1509,12 @@ func (app *appContext) GetEmail(gc *gin.Context) { respondBool(500, false, gc) return } - gc.JSON(200, customEmail{Content: content, Variables: variables}) + email, err := app.email.constructTemplate("", "
", 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. diff --git a/email.go b/email.go index 3ad3772..2e77af6 100644 --- a/email.go +++ b/email.go @@ -212,11 +212,7 @@ func (emailer *Emailer) construct(app *appContext, section, keyFragment string, return } -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 +func (emailer *Emailer) confirmationValues(code, username, key string, app *appContext, noSub bool) map[string]interface{} { template := map[string]interface{}{ "clickBelow": emailer.lang.EmailConfirmation.get("clickBelow"), "ifItWasNotYou": emailer.lang.Strings.get("ifItWasNotYou"), @@ -238,6 +234,15 @@ func (emailer *Emailer) constructConfirmation(code, username, key string, app *a template["confirmationURL"] = inviteLink 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 { content := app.storage.customEmails.EmailConfirmation.Content for _, v := range app.storage.customEmails.EmailConfirmation.Variables { @@ -274,10 +279,7 @@ func (emailer *Emailer) constructTemplate(subject, md string, app *appContext) ( return email, nil } -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")), - } +func (emailer *Emailer) inviteValues(code string, invite Invite, app *appContext, noSub bool) map[string]interface{} { expiry := invite.ValidTill d, t, expiresIn := emailer.formatExpiry(expiry, false, app.datePattern, app.timePattern) 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["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 if app.storage.customEmails.InviteEmail.Enabled { content := app.storage.customEmails.InviteEmail.Content @@ -323,10 +333,7 @@ func (emailer *Emailer) constructInvite(code string, invite Invite, app *appCont return email, nil } -func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appContext, noSub bool) (*Email, error) { - email := &Email{ - Subject: emailer.lang.InviteExpiry.get("title"), - } +func (emailer *Emailer) expiryValues(code string, invite Invite, app *appContext, noSub bool) map[string]interface{} { expiry := app.formatDatetime(invite.ValidTill) template := map[string]interface{}{ "inviteExpired": emailer.lang.InviteExpiry.get("inviteExpired"), @@ -339,7 +346,15 @@ func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appCont } else { 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 + template := emailer.expiryValues(code, invite, app, noSub) if app.storage.customEmails.InviteExpiry.Enabled { content := app.storage.customEmails.InviteExpiry.Content 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 } -func (emailer *Emailer) constructCreated(code, username, address string, invite Invite, app *appContext, noSub bool) (*Email, error) { - email := &Email{ - Subject: emailer.lang.UserCreated.get("title"), - } +func (emailer *Emailer) createdValues(code, username, address string, invite Invite, app *appContext, noSub bool) map[string]interface{} { template := map[string]interface{}{ "nameString": emailer.lang.Strings.get("name"), "addressString": emailer.lang.Strings.get("emailAddress"), @@ -389,6 +401,14 @@ func (emailer *Emailer) constructCreated(code, username, address string, invite template["time"] = created 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 if app.storage.customEmails.UserCreated.Enabled { content := app.storage.customEmails.UserCreated.Content @@ -408,10 +428,7 @@ func (emailer *Emailer) constructCreated(code, username, address string, invite return email, nil } -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")), - } +func (emailer *Emailer) resetValues(pwr PasswordReset, app *appContext, noSub bool) map[string]interface{} { d, t, expiresIn := emailer.formatExpiry(pwr.Expiry, true, app.datePattern, app.timePattern) message := app.config.Section("email").Key("message").String() template := map[string]interface{}{ @@ -438,6 +455,14 @@ func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext, noSub template["pin"] = pwr.Pin 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 if app.storage.customEmails.PasswordReset.Enabled { content := app.storage.customEmails.PasswordReset.Content @@ -457,10 +482,7 @@ func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext, noSub return email, nil } -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")), - } +func (emailer *Emailer) deletedValues(reason string, app *appContext, noSub bool) map[string]interface{} { template := map[string]interface{}{ "yourAccountWasDeleted": emailer.lang.UserDeleted.get("yourAccountWasDeleted"), "reasonString": emailer.lang.UserDeleted.get("reason"), @@ -475,7 +497,15 @@ func (emailer *Emailer) constructDeleted(reason string, app *appContext, noSub b template["reason"] = reason 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 + template := emailer.deletedValues(reason, app, noSub) if app.storage.customEmails.UserDeleted.Enabled { content := app.storage.customEmails.UserDeleted.Content 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 } -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")), - } +func (emailer *Emailer) welcomeValues(username string, app *appContext, noSub bool) map[string]interface{} { template := map[string]interface{}{ "welcome": emailer.lang.WelcomeEmail.get("welcome"), "youCanLoginWith": emailer.lang.WelcomeEmail.get("youCanLoginWith"), @@ -515,7 +542,15 @@ func (emailer *Emailer) constructWelcome(username string, app *appContext, noSub template["username"] = username 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 + template := emailer.welcomeValues(username, app, noSub) if app.storage.customEmails.WelcomeEmail.Enabled { content := app.storage.customEmails.WelcomeEmail.Content for _, v := range app.storage.customEmails.WelcomeEmail.Variables { diff --git a/models.go b/models.go index 39be8c7..40acc4a 100644 --- a/models.go +++ b/models.go @@ -189,3 +189,10 @@ type emailSetDTO struct { type emailTestDTO struct { 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"` +} diff --git a/package-lock.json b/package-lock.json index 0c79482..2d795b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,14 @@ "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": { "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", @@ -228,9 +236,9 @@ "integrity": "sha1-vfpzUplmTfr9NFKe1PhSKidf6lY=" }, "esbuild": { - "version": "0.8.49", - "resolved": "https://registry.npm.taobao.org/esbuild/download/esbuild-0.8.49.tgz", - "integrity": "sha1-PTP3GzlmYR+CLPTIOBFfP70W3vI=" + "version": "0.8.50", + "resolved": "https://registry.npm.taobao.org/esbuild/download/esbuild-0.8.50.tgz", + "integrity": "sha1-6/JP3gza0aNpeJ3W/XqCCwoB5Gw=" }, "escalade": { "version": "3.1.1", @@ -1463,6 +1471,11 @@ "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": { "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", diff --git a/package.json b/package.json index 70aedd8..4c05eb7 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,9 @@ }, "homepage": "https://github.com/hrfee/jfa-go#readme", "dependencies": { + "@ts-stack/markdown": "^1.3.0", "a17t": "^0.4.0", - "esbuild": "^0.8.49", + "esbuild": "^0.8.50", "lodash": "^4.17.19", "mjml": "^4.8.0", "remixicon": "^2.5.0", diff --git a/ts/modules/settings.ts b/ts/modules/settings.ts index 1ed804e..81840ac 100644 --- a/ts/modules/settings.ts +++ b/ts/modules/settings.ts @@ -1,4 +1,5 @@ import { _get, _post, toggleLoader } from "../modules/common.js"; +import { Marked } from "@ts-stack/markdown"; interface settingsBoolEvent extends Event { detail: boolean; @@ -680,6 +681,8 @@ interface Email { interface templateEmail { content: string; variables: string[]; + values: { [key: string]: string }; + html: string; } interface emailListEl { @@ -691,13 +694,14 @@ class EmailEditor { private _currentID: string; private _names: { [id: string]: emailListEl }; private _content: string; + private _templ: templateEmail; 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; + // private _timeout: number; + // private _finishInterval = 200; insert = (textarea: HTMLTextAreaElement, text: string) => { // https://kubyshkin.name/posts/insert-text-into-textarea-at-cursor-position <3 const isSuccess = document.execCommand("insertText", false, text); @@ -728,23 +732,25 @@ class EmailEditor { if (this._names[id] !== undefined) { this._header.textContent = this._names[id].name; } - const templ = req.response as templateEmail; - this._textArea.value = templ.content; + this._templ = req.response as templateEmail; + this._textArea.value = this._templ.content; + this._preview.innerHTML = this._templ.html; this.loadPreview(); - this._content = templ.content; + this._content = this._templ.content; const colors = ["info", "urge", "positive", "neutral"]; 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; innerHTML += '' } this._variables.innerHTML = innerHTML const buttons = this._variables.querySelectorAll("span.button") as NodeListOf; - for (let i = 0; i < templ.variables.length; i++) { - buttons[i].innerHTML = `` + templ.variables[i] + ``; + for (let i = 0; i < this._templ.variables.length; i++) { + buttons[i].innerHTML = `` + this._templ.variables[i] + ``; buttons[i].onclick = () => { - this.insert(this._textArea, templ.variables[i]); - this._timeout = setTimeout(this.loadPreview, this._finishInterval); + this.insert(this._textArea, this._templ.variables[i]); + this.loadPreview(); + // this._timeout = setTimeout(this.loadPreview, this._finishInterval); } } window.modals.editor.show(); @@ -752,15 +758,23 @@ class EmailEditor { }) } loadPreview = () => { - _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); + let content = this._textArea.value; + for (let variable of this._templ.variables) { + let value = this._templ.values[variable.slice(1, -1)]; + if (value === undefined) { value = variable; } + content = content.replace(new RegExp(variable, "g"), value); + } + content = Marked.parse(content) + document.getElementById("preview-content").innerHTML = content; + // _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 = () => { @@ -809,12 +823,13 @@ class EmailEditor { constructor() { this._textArea.onkeyup = () => { - clearTimeout(this._timeout); - this._timeout = setTimeout(this.loadPreview, this._finishInterval); - }; - this._textArea.onkeydown = () => { - clearTimeout(this._timeout); + // clearTimeout(this._timeout); + // this._timeout = setTimeout(this.loadPreview, this._finishInterval); + this.loadPreview(); }; + // this._textArea.onkeydown = () => { + // clearTimeout(this._timeout); + // }; this._form.onsubmit = (event: Event) => { if (this._textArea.value == this._content) { diff --git a/ts/tsconfig.json b/ts/tsconfig.json index 95903f5..3b3b207 100644 --- a/ts/tsconfig.json +++ b/ts/tsconfig.json @@ -3,6 +3,8 @@ "outDir": "../js", "target": "es6", "lib": ["dom", "es2017"], - "typeRoots": ["./node_modules/@types", "./typings"] + "typeRoots": ["./typings", "./node_modules/@types"], + "moduleResolution": "node", + "esModuleInterop": true } }