diff --git a/api.go b/api.go index fa41da0..bce2359 100644 --- a/api.go +++ b/api.go @@ -806,15 +806,37 @@ func (app *appContext) Announce(gc *gin.Context) { respondBool(400, false, gc) return } - msg, err := app.email.constructTemplate(req.Subject, req.Message, app) - if err != nil { - app.err.Printf("Failed to construct announcement messages: %v", err) - respondBool(500, false, gc) - return - } else if err := app.sendByID(msg, req.Users...); err != nil { - app.err.Printf("Failed to send announcement messages: %v", err) - respondBool(500, false, gc) - return + // Generally, we only need to construct once. If {username} is included, however, this needs to be done for each user. + unique := strings.Contains(req.Message, "{username}") + if unique { + for _, userID := range req.Users { + user, status, err := app.jf.UserByID(userID, false) + if status != 200 || err != nil { + app.err.Printf("Failed to get user with ID \"%s\" (%d): %v", userID, status, err) + continue + } + msg, err := app.email.constructTemplate(req.Subject, req.Message, app, user.Name) + if err != nil { + app.err.Printf("Failed to construct announcement message: %v", err) + respondBool(500, false, gc) + return + } else if err := app.sendByID(msg, userID); err != nil { + app.err.Printf("Failed to send announcement message: %v", err) + respondBool(500, false, gc) + return + } + } + } else { + msg, err := app.email.constructTemplate(req.Subject, req.Message, app) + if err != nil { + app.err.Printf("Failed to construct announcement messages: %v", err) + respondBool(500, false, gc) + return + } else if err := app.sendByID(msg, req.Users...); err != nil { + app.err.Printf("Failed to send announcement messages: %v", err) + respondBool(500, false, gc) + return + } } app.info.Println("Sent announcement messages") respondBool(200, true, gc) diff --git a/email.go b/email.go index 76b9d92..29bf260 100644 --- a/email.go +++ b/email.go @@ -337,7 +337,12 @@ func (emailer *Emailer) constructConfirmation(code, username, key string, app *a return email, nil } -func (emailer *Emailer) constructTemplate(subject, md string, app *appContext) (*Message, error) { +// username is optional, but should only be passed once. +func (emailer *Emailer) constructTemplate(subject, md string, app *appContext, username ...string) (*Message, error) { + if len(username) != 0 { + md = templateEmail(md, []string{"{username}"}, nil, map[string]interface{}{"username": username[0]}) + subject = templateEmail(subject, []string{"{username}"}, nil, map[string]interface{}{"username": username[0]}) + } email := &Message{Subject: subject} html := markdown.ToHTML([]byte(md), nil, renderer) text := stripMarkdown(md) diff --git a/go.sum b/go.sum index 7318009..4d8a81d 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,7 @@ github.com/bwmarrin/discordgo v0.23.2 h1:BzrtTktixGHIu9Tt7dEE6diysEF9HWnXeHuoJEt github.com/bwmarrin/discordgo v0.23.2/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -62,6 +63,7 @@ github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSl github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA= github.com/getlantern/systray v1.1.0 h1:U0wCEqseLi2ok1fE6b88gJklzriavPJixZysZPkZd/Y= github.com/getlantern/systray v1.1.0/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc= github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= @@ -226,6 +228,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -273,8 +276,10 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY github.com/ugorji/go/codec v1.1.13/go.mod h1:oNVt3Dq+FO91WNQ/9JnHKQP2QJxTzoN7wCBFCq1OeuU= github.com/ugorji/go/codec v1.2.0 h1:As6RccOIlbm9wHuWYMlB30dErcI+4WiKWsYsmPkyrUw= github.com/ugorji/go/codec v1.2.0/go.mod h1:dXvG35r7zTX6QImXOSFhGMmKtX+wJ7VTWzGvYQGIjBs= +github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw= github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE= diff --git a/html/admin.html b/html/admin.html index 5b42891..568f5c7 100644 --- a/html/admin.html +++ b/html/admin.html @@ -164,6 +164,10 @@
+ {{ .strings.variables }} +
+ {username} +
diff --git a/ts/modules/accounts.ts b/ts/modules/accounts.ts index dec6c15..04e1f0b 100644 --- a/ts/modules/accounts.ts +++ b/ts/modules/accounts.ts @@ -1,4 +1,4 @@ -import { _get, _post, _delete, toggleLoader, addLoader, removeLoader, toDateString } from "../modules/common.js"; +import { _get, _post, _delete, toggleLoader, addLoader, removeLoader, toDateString, insertText } from "../modules/common.js"; import { templateEmail } from "../modules/settings.js"; import { Marked } from "@ts-stack/markdown"; import { stripMarkdown } from "../modules/stripmd.js"; @@ -1254,6 +1254,11 @@ export class accountsList { }); this._announceSaveButton.onclick = this.saveAnnouncement; + const announceVarUsername = document.getElementById("announce-variables-username") as HTMLSpanElement; + announceVarUsername.onclick = () => { + insertText(this._announceTextarea, announceVarUsername.children[0].textContent); + this.loadPreview(); + }; } reload = () => { diff --git a/ts/modules/common.ts b/ts/modules/common.ts index 05b6bcb..7d1e2e4 100644 --- a/ts/modules/common.ts +++ b/ts/modules/common.ts @@ -202,3 +202,24 @@ export function removeLoader(el: HTMLElement, small: boolean = true) { if (dot) { dot.remove(); } } } + +export function insertText(textarea: HTMLTextAreaElement, text: string) { + // https://kubyshkin.name/posts/insert-text-into-textarea-at-cursor-position <3 + const isSuccess = document.execCommand("insertText", false, text); + + // Firefox (non-standard method) + if (!isSuccess && typeof textarea.setRangeText === "function") { + const start = textarea.selectionStart; + textarea.setRangeText(text); + // update cursor to be at the end of insertion + textarea.selectionStart = textarea.selectionEnd = start + text.length; + + // Notify any possible listeners of the change + const e = document.createEvent("UIEvent"); + e.initEvent("input", true, false); + textarea.dispatchEvent(e); + textarea.focus(); + } +} + + diff --git a/ts/modules/settings.ts b/ts/modules/settings.ts index 8a639aa..7360e31 100644 --- a/ts/modules/settings.ts +++ b/ts/modules/settings.ts @@ -1,4 +1,4 @@ -import { _get, _post, toggleLoader, addLoader, removeLoader } from "../modules/common.js"; +import { _get, _post, toggleLoader, addLoader, removeLoader, insertText } from "../modules/common.js"; import { Marked } from "@ts-stack/markdown"; import { stripMarkdown } from "../modules/stripmd.js"; @@ -850,24 +850,6 @@ class EmailEditor { // 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); - - // Firefox (non-standard method) - if (!isSuccess && typeof textarea.setRangeText === "function") { - const start = textarea.selectionStart; - textarea.setRangeText(text); - // update cursor to be at the end of insertion - textarea.selectionStart = textarea.selectionEnd = start + text.length; - - // Notify any possible listeners of the change - const e = document.createEvent("UIEvent"); - e.initEvent("input", true, false); - textarea.dispatchEvent(e); - textarea.focus(); - } - } - loadEditor = (id: string) => { this._currentID = id; _get("/config/emails/" + id, null, (req: XMLHttpRequest) => { @@ -905,7 +887,7 @@ class EmailEditor { for (let i = 0; i < this._templ.variables.length; i++) { buttons[i].innerHTML = `` + this._templ.variables[i] + ``; buttons[i].onclick = () => { - this.insert(this._textArea, this._templ.variables[i]); + insertText(this._textArea, this._templ.variables[i]); this.loadPreview(); // this._timeout = setTimeout(this.loadPreview, this._finishInterval); } @@ -925,7 +907,7 @@ class EmailEditor { for (let i = 0; i < this._templ.conditionals.length; i++) { buttons[i].innerHTML = `{if ` + this._templ.conditionals[i].slice(1) + ``; buttons[i].onclick = () => { - this.insert(this._textArea, "{if " + this._templ.conditionals[i].slice(1) + "{endif}"); + insertText(this._textArea, "{if " + this._templ.conditionals[i].slice(1) + "{endif}"); this.loadPreview(); // this._timeout = setTimeout(this.loadPreview, this._finishInterval); }