diff --git a/api.go b/api.go index 2038817..9292b48 100644 --- a/api.go +++ b/api.go @@ -1469,6 +1469,7 @@ func (app *appContext) GetUsers(gc *gin.Context) { if email, ok := app.storage.emails[jfUser.ID]; ok { user.Email = email.Addr user.NotifyThroughEmail = email.Contact + user.Label = email.Label } expiry, ok := app.storage.users[jfUser.ID] if ok { @@ -1579,6 +1580,44 @@ func (app *appContext) DeleteOmbiProfile(gc *gin.Context) { respondBool(204, true, gc) } +// @Summary Modify user's labels, which show next to their name in the accounts tab. +// @Produce json +// @Param modifyEmailsDTO body modifyEmailsDTO true "Map of userIDs to labels" +// @Success 204 {object} boolResponse +// @Failure 500 {object} boolResponse +// @Router /users/labels [post] +// @Security Bearer +// @tags Users +func (app *appContext) ModifyLabels(gc *gin.Context) { + var req modifyEmailsDTO + gc.BindJSON(&req) + app.debug.Println("Label modification requested") + users, status, err := app.jf.GetUsers(false) + if !(status == 200 || status == 204) || err != nil { + app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err) + respond(500, "Couldn't get users", gc) + return + } + for _, jfUser := range users { + id := jfUser.ID + if label, ok := req[id]; ok { + addr := "" + contact := true + if oldEmail, ok := app.storage.emails[id]; ok { + addr = oldEmail.Addr + contact = oldEmail.Contact + } + app.storage.emails[id] = EmailAddress{Addr: addr, Contact: contact, Label: label} + } + } + if err := app.storage.storeEmails(); err != nil { + app.err.Printf("Failed to store email list: %v", err) + respondBool(500, false, gc) + } + app.info.Println("Email list modified") + respondBool(204, true, gc) +} + // @Summary Modify user's email addresses. // @Produce json // @Param modifyEmailsDTO body modifyEmailsDTO true "Map of userIDs to email addresses" diff --git a/html/admin.html b/html/admin.html index 2a68159..455f9d6 100644 --- a/html/admin.html +++ b/html/admin.html @@ -340,15 +340,15 @@ {{ if .telegramEnabled }} diff --git a/models.go b/models.go index 7318953..e220da5 100644 --- a/models.go +++ b/models.go @@ -145,6 +145,7 @@ type respUser struct { NotifyThroughDiscord bool `json:"notify_discord"` Matrix string `json:"matrix"` // Matrix ID (if known) NotifyThroughMatrix bool `json:"notify_matrix"` + Label string `json:"label"` // Label of user, shown next to their name. } type getUsersDTO struct { diff --git a/router.go b/router.go index 789f264..bd66b48 100644 --- a/router.go +++ b/router.go @@ -160,6 +160,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) { api.DELETE(p+"/profiles", app.DeleteProfile) api.POST(p+"/invites/notify", app.SetNotify) api.POST(p+"/users/emails", app.ModifyEmails) + api.POST(p+"/users/labels", app.ModifyLabels) // api.POST(p + "/setDefaults", app.SetDefaults) api.POST(p+"/users/settings", app.ApplySettings) api.POST(p+"/users/announce", app.Announce) diff --git a/storage.go b/storage.go index a39a90b..f7f133a 100644 --- a/storage.go +++ b/storage.go @@ -52,6 +52,7 @@ type DiscordUser struct { type EmailAddress struct { Addr string + Label string // User Label. Contact bool } diff --git a/ts/modules/accounts.ts b/ts/modules/accounts.ts index d94a412..2ffade6 100644 --- a/ts/modules/accounts.ts +++ b/ts/modules/accounts.ts @@ -20,6 +20,7 @@ interface User { discord_id: string; matrix: string; notify_matrix: boolean; + label: string; } interface getPinResponse { @@ -60,6 +61,9 @@ class user implements User { private _lastActive: HTMLTableDataCellElement; private _lastActiveUnix: number; private _notifyDropdown: HTMLDivElement; + private _label: HTMLInputElement; + private _userLabel: string; + private _labelEditButton: HTMLElement; id = ""; private _selected: boolean; @@ -380,6 +384,19 @@ class user implements User { } } + get label(): string { return this._userLabel; } + set label(l: string) { + console.log(l); + this._userLabel = l ? l : ""; + this._label.innerHTML = l ? l : ""; + this._labelEditButton.classList.add("ri-edit-line"); + this._labelEditButton.classList.remove("ri-check-line"); + if (!l) { + this._label.classList.remove("chip", "~gray"); + } else { + this._label.classList.add("chip", "~gray", "mr-2"); + } + } private _checkEvent = new CustomEvent("accountCheckEvent"); private _uncheckEvent = new CustomEvent("accountUncheckEvent"); @@ -387,7 +404,7 @@ class user implements User { this._row = document.createElement("tr") as HTMLTableRowElement; let innerHTML = ` -
+
`; if (window.telegramEnabled) { @@ -411,6 +428,7 @@ class user implements User { `; this._row.innerHTML = innerHTML; const emailEditor = ``; + const labelEditor = ``; this._check = this._row.querySelector("input[type=checkbox]") as HTMLInputElement; this._username = this._row.querySelector(".accounts-username") as HTMLSpanElement; this._admin = this._row.querySelector(".accounts-admin") as HTMLSpanElement; @@ -422,11 +440,13 @@ class user implements User { this._matrix = this._row.querySelector(".accounts-matrix") as HTMLTableDataCellElement; this._expiry = this._row.querySelector(".accounts-expiry") as HTMLTableDataCellElement; this._lastActive = this._row.querySelector(".accounts-last-active") as HTMLTableDataCellElement; + this._label = this._row.querySelector(".accounts-label-container") as HTMLInputElement; + this._labelEditButton = this._row.querySelector(".accounts-label-edit") as HTMLElement; this._check.onchange = () => { this.selected = this._check.checked; } this._notifyDropdown = this._constructDropdown(); - const toggleStealthInput = () => { + const toggleEmailInput = () => { if (this._emailEditButton.classList.contains("ri-edit-line")) { this._email.innerHTML = emailEditor; this._email.querySelector("input").value = this._emailAddress; @@ -438,21 +458,52 @@ class user implements User { this._emailEditButton.classList.toggle("ri-check-line"); this._emailEditButton.classList.toggle("ri-edit-line"); }; - const outerClickListener = (event: Event) => { + const emailClickListener = (event: Event) => { if (!(event.target instanceof HTMLElement && (this._email.contains(event.target) || this._emailEditButton.contains(event.target)))) { - toggleStealthInput(); + toggleEmailInput(); this.email = this.email; - document.removeEventListener("click", outerClickListener); + document.removeEventListener("click", emailClickListener); } }; this._emailEditButton.onclick = () => { if (this._emailEditButton.classList.contains("ri-edit-line")) { - document.addEventListener('click', outerClickListener); + document.addEventListener('click', emailClickListener); } else { this._updateEmail(); - document.removeEventListener('click', outerClickListener); + document.removeEventListener('click', emailClickListener); } - toggleStealthInput(); + toggleEmailInput(); + }; + + const toggleLabelInput = () => { + if (this._labelEditButton.classList.contains("ri-edit-line")) { + this._label.innerHTML = labelEditor; + const input = this._label.querySelector("input"); + input.value = this._userLabel; + input.placeholder = window.lang.strings("label"); + this._label.classList.remove("ml-2"); + this._labelEditButton.classList.add("ri-check-line"); + this._labelEditButton.classList.remove("ri-edit-line"); + } else { + this._updateLabel(); + this._email.classList.add("ml-2"); + } + }; + + const labelClickListener = (event: Event) => { + if (!(event.target instanceof HTMLElement && (this._label.contains(event.target) || this._labelEditButton.contains(event.target)))) { + toggleLabelInput(); + document.removeEventListener("click", labelClickListener); + } + }; + + this._labelEditButton.onclick = () => { + if (this._labelEditButton.classList.contains("ri-edit-line")) { + document.addEventListener('click', labelClickListener); + } else { + document.removeEventListener('click', labelClickListener); + } + toggleLabelInput(); }; this.update(user); @@ -462,6 +513,21 @@ class user implements User { this.last_active = this.last_active; }); } + + private _updateLabel = () => { + let oldLabel = this.label; + this.label = this._label.querySelector("input").value; + let send = {}; + send[this.id] = this.label; + _post("/users/labels", send, (req: XMLHttpRequest) => { + if (req.readyState == 4) { + if (req.status != 204) { + this.label = oldLabel; + window.notifications.customError("labelChanged", window.lang.notif("errorUnknown")); + } + } + }); + }; private _updateEmail = () => { let oldEmail = this.email; @@ -544,6 +610,7 @@ class user implements User { this.notify_matrix = user.notify_matrix; this.notify_email = user.notify_email; this.discord_id = user.discord_id; + this.label = user.label; } asElement = (): HTMLTableRowElement => { return this._row; }