mirror of
https://github.com/hrfee/jfa-go.git
synced 2026-01-18 16:47:42 +01:00
invites: editable label, /invites/edit route
PATCH /invite/edit lets you edit an invite by giving new values for a subset of inviteDTO (EditableInviteDTO). Replaces /invite/profile and /invite/notify, and allows changing (user)label and user expiry as well as the previously customizable values through other routes. An edit button next to the code/label allows changing on the invites tab.
This commit is contained in:
228
api-invites.go
228
api-invites.go
@@ -186,6 +186,100 @@ func (app *appContext) SendInvite(gc *gin.Context) {
|
|||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Summary Edit an existing invite. Not all fields are modifiable.
|
||||||
|
// @Produce json
|
||||||
|
// @Param EditableInviteDTO body EditableInviteDTO true "Email address or Discord username"
|
||||||
|
// @Success 200 {object} boolResponse
|
||||||
|
// @Failure 500 {object} stringResponse
|
||||||
|
// @Failure 400 {object} stringResponse
|
||||||
|
// @Router /invites/edit [patch]
|
||||||
|
// @Security Bearer
|
||||||
|
// @tags Invites
|
||||||
|
func (app *appContext) EditInvite(gc *gin.Context) {
|
||||||
|
var req EditableInviteDTO
|
||||||
|
gc.BindJSON(&req)
|
||||||
|
inv, ok := app.storage.GetInvitesKey(req.Code)
|
||||||
|
if !ok {
|
||||||
|
msg := fmt.Sprintf(lm.InvalidInviteCode, req.Code)
|
||||||
|
app.err.Println(msg)
|
||||||
|
respond(400, msg, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
changed := false
|
||||||
|
|
||||||
|
if req.NotifyCreation != nil || req.NotifyExpiry != nil {
|
||||||
|
setNotify := map[string]bool{}
|
||||||
|
if req.NotifyExpiry != nil {
|
||||||
|
setNotify["notify-expiry"] = *req.NotifyExpiry
|
||||||
|
}
|
||||||
|
if req.NotifyCreation != nil {
|
||||||
|
setNotify["notify-creation"] = *req.NotifyCreation
|
||||||
|
}
|
||||||
|
ch, ok := app.SetNotify(&inv, setNotify, gc)
|
||||||
|
changed = changed || ch
|
||||||
|
if ch && !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.Profile != nil {
|
||||||
|
ch, ok := app.SetProfile(&inv, *req.Profile, gc)
|
||||||
|
changed = changed || ch
|
||||||
|
if ch && !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.Label != nil {
|
||||||
|
*req.Label = strings.TrimSpace(*req.Label)
|
||||||
|
changed = changed || (*req.Label != inv.Label)
|
||||||
|
inv.Label = *req.Label
|
||||||
|
}
|
||||||
|
if req.UserLabel != nil {
|
||||||
|
*req.UserLabel = strings.TrimSpace(*req.UserLabel)
|
||||||
|
changed = changed || (*req.UserLabel != inv.UserLabel)
|
||||||
|
inv.UserLabel = *req.UserLabel
|
||||||
|
}
|
||||||
|
if req.UserExpiry != nil {
|
||||||
|
changed = changed || (*req.UserExpiry != inv.UserExpiry)
|
||||||
|
inv.UserExpiry = *req.UserExpiry
|
||||||
|
if !inv.UserExpiry {
|
||||||
|
inv.UserMonths = 0
|
||||||
|
inv.UserDays = 0
|
||||||
|
inv.UserHours = 0
|
||||||
|
inv.UserMinutes = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.UserMonths != nil || req.UserDays != nil || req.UserHours != nil || req.UserMinutes != nil {
|
||||||
|
if inv.UserMonths == 0 &&
|
||||||
|
inv.UserDays == 0 &&
|
||||||
|
inv.UserHours == 0 &&
|
||||||
|
inv.UserMinutes == 0 {
|
||||||
|
changed = changed || (inv.UserExpiry != false)
|
||||||
|
inv.UserExpiry = false
|
||||||
|
}
|
||||||
|
if req.UserMonths != nil {
|
||||||
|
changed = changed || (*req.UserMonths != inv.UserMonths)
|
||||||
|
inv.UserMonths = *req.UserMonths
|
||||||
|
}
|
||||||
|
if req.UserDays != nil {
|
||||||
|
changed = changed || (*req.UserDays != inv.UserDays)
|
||||||
|
inv.UserDays = *req.UserDays
|
||||||
|
}
|
||||||
|
if req.UserHours != nil {
|
||||||
|
changed = changed || (*req.UserHours != inv.UserHours)
|
||||||
|
inv.UserHours = *req.UserHours
|
||||||
|
}
|
||||||
|
if req.UserMinutes != nil {
|
||||||
|
changed = changed || (*req.UserMinutes != inv.UserMinutes)
|
||||||
|
inv.UserMinutes = *req.UserMinutes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
app.storage.SetInvitesKey(inv.Code, inv)
|
||||||
|
}
|
||||||
|
respondBool(200, true, gc)
|
||||||
|
}
|
||||||
|
|
||||||
// sendInvite attempts to send an invite to the given email address or discord username.
|
// sendInvite attempts to send an invite to the given email address or discord username.
|
||||||
func (app *appContext) sendInvite(req sendInviteDTO, invite *Invite) (err error) {
|
func (app *appContext) sendInvite(req sendInviteDTO, invite *Invite) (err error) {
|
||||||
if !(app.config.Section("invite_emails").Key("enabled").MustBool(false)) {
|
if !(app.config.Section("invite_emails").Key("enabled").MustBool(false)) {
|
||||||
@@ -369,22 +463,24 @@ func (app *appContext) GetInvites(gc *gin.Context) {
|
|||||||
// years, months, days, hours, minutes, _ := timeDiff(inv.ValidTill, currentTime)
|
// years, months, days, hours, minutes, _ := timeDiff(inv.ValidTill, currentTime)
|
||||||
// months += years * 12
|
// months += years * 12
|
||||||
invite := inviteDTO{
|
invite := inviteDTO{
|
||||||
Code: inv.Code,
|
EditableInviteDTO: EditableInviteDTO{
|
||||||
|
Code: inv.Code,
|
||||||
|
Label: &inv.Label,
|
||||||
|
UserLabel: &inv.UserLabel,
|
||||||
|
Profile: &inv.Profile,
|
||||||
|
UserExpiry: &inv.UserExpiry,
|
||||||
|
UserMonths: &inv.UserMonths,
|
||||||
|
UserDays: &inv.UserDays,
|
||||||
|
UserHours: &inv.UserHours,
|
||||||
|
UserMinutes: &inv.UserMinutes,
|
||||||
|
},
|
||||||
ValidTill: inv.ValidTill.Unix(),
|
ValidTill: inv.ValidTill.Unix(),
|
||||||
// Months: months,
|
// Months: months,
|
||||||
// Days: days,
|
// Days: days,
|
||||||
// Hours: hours,
|
// Hours: hours,
|
||||||
// Minutes: minutes,
|
// Minutes: minutes,
|
||||||
UserExpiry: inv.UserExpiry,
|
Created: inv.Created.Unix(),
|
||||||
UserMonths: inv.UserMonths,
|
NoLimit: inv.NoLimit,
|
||||||
UserDays: inv.UserDays,
|
|
||||||
UserHours: inv.UserHours,
|
|
||||||
UserMinutes: inv.UserMinutes,
|
|
||||||
Created: inv.Created.Unix(),
|
|
||||||
Profile: inv.Profile,
|
|
||||||
NoLimit: inv.NoLimit,
|
|
||||||
Label: inv.Label,
|
|
||||||
UserLabel: inv.UserLabel,
|
|
||||||
}
|
}
|
||||||
if len(inv.UsedBy) != 0 {
|
if len(inv.UsedBy) != 0 {
|
||||||
invite.UsedBy = map[string]int64{}
|
invite.UsedBy = map[string]int64{}
|
||||||
@@ -420,10 +516,12 @@ func (app *appContext) GetInvites(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
if _, ok := inv.Notify[addressOrID]; ok {
|
if _, ok := inv.Notify[addressOrID]; ok {
|
||||||
if _, ok = inv.Notify[addressOrID]["notify-expiry"]; ok {
|
if _, ok = inv.Notify[addressOrID]["notify-expiry"]; ok {
|
||||||
invite.NotifyExpiry = inv.Notify[addressOrID]["notify-expiry"]
|
notifyExpiry := inv.Notify[addressOrID]["notify-expiry"]
|
||||||
|
invite.NotifyExpiry = ¬ifyExpiry
|
||||||
}
|
}
|
||||||
if _, ok = inv.Notify[addressOrID]["notify-creation"]; ok {
|
if _, ok = inv.Notify[addressOrID]["notify-creation"]; ok {
|
||||||
invite.NotifyCreation = inv.Notify[addressOrID]["notify-creation"]
|
notifyCreation := inv.Notify[addressOrID]["notify-creation"]
|
||||||
|
invite.NotifyCreation = ¬ifyCreation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -435,82 +533,54 @@ func (app *appContext) GetInvites(gc *gin.Context) {
|
|||||||
gc.JSON(200, resp)
|
gc.JSON(200, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Set profile for an invite
|
func (app *appContext) SetProfile(inv *Invite, name string, gc *gin.Context) (changed, ok bool) {
|
||||||
// @Produce json
|
changed = false
|
||||||
// @Param inviteProfileDTO body inviteProfileDTO true "Invite profile object"
|
ok = false
|
||||||
// @Success 200 {object} boolResponse
|
|
||||||
// @Failure 500 {object} stringResponse
|
|
||||||
// @Router /invites/profile [post]
|
|
||||||
// @Security Bearer
|
|
||||||
// @tags Invites
|
|
||||||
func (app *appContext) SetProfile(gc *gin.Context) {
|
|
||||||
var req inviteProfileDTO
|
|
||||||
gc.BindJSON(&req)
|
|
||||||
// "" means "Don't apply profile"
|
// "" means "Don't apply profile"
|
||||||
if _, ok := app.storage.GetProfileKey(req.Profile); !ok && req.Profile != "" {
|
if _, profileExists := app.storage.GetProfileKey(name); !profileExists && name != "" {
|
||||||
app.err.Printf(lm.FailedGetProfile, req.Profile)
|
app.err.Printf(lm.FailedGetProfile, name)
|
||||||
respond(500, "Profile not found", gc)
|
respond(500, "Profile not found", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inv, _ := app.storage.GetInvitesKey(req.Invite)
|
changed = name != inv.Profile
|
||||||
inv.Profile = req.Profile
|
inv.Profile = name
|
||||||
app.storage.SetInvitesKey(req.Invite, inv)
|
ok = true
|
||||||
respondBool(200, true, gc)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Set notification preferences for an invite.
|
func (app *appContext) SetNotify(inv *Invite, settings map[string]bool, gc *gin.Context) (changed, ok bool) {
|
||||||
// @Produce json
|
changed = false
|
||||||
// @Param setNotifyDTO body setNotifyDTO true "Map of invite codes to notification settings objects"
|
ok = false
|
||||||
// @Success 200
|
var address string
|
||||||
// @Failure 400 {object} stringResponse
|
jellyfinLogin := app.config.Section("ui").Key("jellyfin_login").MustBool(false)
|
||||||
// @Failure 500 {object} stringResponse
|
if jellyfinLogin {
|
||||||
// @Router /invites/notify [post]
|
var addressAvailable bool = app.getAddressOrName(gc.GetString("jfId")) != ""
|
||||||
// @Security Bearer
|
if !addressAvailable {
|
||||||
// @tags Other
|
app.err.Printf(lm.FailedGetContactMethod, gc.GetString("jfId"))
|
||||||
func (app *appContext) SetNotify(gc *gin.Context) {
|
respond(500, fmt.Sprintf(lm.FailedGetContactMethod, "admin"), gc)
|
||||||
var req map[string]map[string]bool
|
|
||||||
gc.BindJSON(&req)
|
|
||||||
changed := false
|
|
||||||
for code, settings := range req {
|
|
||||||
invite, ok := app.storage.GetInvitesKey(code)
|
|
||||||
if !ok {
|
|
||||||
msg := fmt.Sprintf(lm.InvalidInviteCode, code)
|
|
||||||
app.err.Println(msg)
|
|
||||||
respond(400, msg, gc)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var address string
|
address = gc.GetString("jfId")
|
||||||
jellyfinLogin := app.config.Section("ui").Key("jellyfin_login").MustBool(false)
|
} else {
|
||||||
if jellyfinLogin {
|
address = app.config.Section("ui").Key("email").String()
|
||||||
var addressAvailable bool = app.getAddressOrName(gc.GetString("jfId")) != ""
|
}
|
||||||
if !addressAvailable {
|
if inv.Notify == nil {
|
||||||
app.err.Printf(lm.FailedGetContactMethod, gc.GetString("jfId"))
|
inv.Notify = map[string]map[string]bool{}
|
||||||
respond(500, fmt.Sprintf(lm.FailedGetContactMethod, "admin"), gc)
|
}
|
||||||
return
|
if _, ok := inv.Notify[address]; !ok {
|
||||||
}
|
inv.Notify[address] = map[string]bool{}
|
||||||
address = gc.GetString("jfId")
|
} /*else {
|
||||||
} else {
|
if _, ok := invite.Notify[address]["notify-expiry"]; !ok {
|
||||||
address = app.config.Section("ui").Key("email").String()
|
*/
|
||||||
}
|
for _, notifyType := range []string{"notify-expiry", "notify-creation"} {
|
||||||
if invite.Notify == nil {
|
if _, ok := settings[notifyType]; ok && inv.Notify[address][notifyType] != settings[notifyType] {
|
||||||
invite.Notify = map[string]map[string]bool{}
|
inv.Notify[address][notifyType] = settings[notifyType]
|
||||||
}
|
app.debug.Printf(lm.SetAdminNotify, notifyType, settings[notifyType], address)
|
||||||
if _, ok := invite.Notify[address]; !ok {
|
changed = true
|
||||||
invite.Notify[address] = map[string]bool{}
|
|
||||||
} /*else {
|
|
||||||
if _, ok := invite.Notify[address]["notify-expiry"]; !ok {
|
|
||||||
*/
|
|
||||||
for _, notifyType := range []string{"notify-expiry", "notify-creation"} {
|
|
||||||
if _, ok := settings[notifyType]; ok && invite.Notify[address][notifyType] != settings[notifyType] {
|
|
||||||
invite.Notify[address][notifyType] = settings[notifyType]
|
|
||||||
app.debug.Printf(lm.SetAdminNotify, notifyType, settings[notifyType], address)
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if changed {
|
|
||||||
app.storage.SetInvitesKey(code, invite)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ok = true
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Delete an invite.
|
// @Summary Delete an invite.
|
||||||
|
|||||||
31
models.go
31
models.go
@@ -79,11 +79,6 @@ type sendInviteDTO struct {
|
|||||||
SendTo string `json:"send-to" example:"jeff@jellyf.in"` // Send invite to this address or discord name
|
SendTo string `json:"send-to" example:"jeff@jellyf.in"` // Send invite to this address or discord name
|
||||||
}
|
}
|
||||||
|
|
||||||
type inviteProfileDTO struct {
|
|
||||||
Invite string `json:"invite" example:"slakdaslkdl2342"` // Invite to apply to
|
|
||||||
Profile string `json:"profile" example:"DefaultProfile"` // Profile to use
|
|
||||||
}
|
|
||||||
|
|
||||||
type profileDTO struct {
|
type profileDTO struct {
|
||||||
Admin bool `json:"admin" example:"false"` // Whether profile has admin rights or not
|
Admin bool `json:"admin" example:"false"` // Whether profile has admin rights or not
|
||||||
LibraryAccess string `json:"libraries" example:"all"` // Number of libraries profile has access to
|
LibraryAccess string `json:"libraries" example:"all"` // Number of libraries profile has access to
|
||||||
@@ -115,25 +110,29 @@ type newProfileDTO struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type inviteDTO struct {
|
type inviteDTO struct {
|
||||||
Code string `json:"code" example:"sajdlj23423j23"` // Invite code
|
EditableInviteDTO
|
||||||
ValidTill int64 `json:"valid_till" example:"1617737207510"` // Unix timestamp of expiry
|
ValidTill int64 `json:"valid_till" example:"1617737207510"` // Unix timestamp of expiry
|
||||||
UserExpiry bool `json:"user_expiry"` // Whether or not user expiry is enabled
|
|
||||||
UserMonths int `json:"user_months,omitempty" example:"1"` // Number of months till user expiry
|
|
||||||
UserDays int `json:"user_days,omitempty" example:"1"` // Number of days till user expiry
|
|
||||||
UserHours int `json:"user_hours,omitempty" example:"2"` // Number of hours till user expiry
|
|
||||||
UserMinutes int `json:"user_minutes,omitempty" example:"3"` // Number of minutes till user expiry
|
|
||||||
Created int64 `json:"created" example:"1617737207510"` // Date of creation
|
Created int64 `json:"created" example:"1617737207510"` // Date of creation
|
||||||
Profile string `json:"profile" example:"DefaultProfile"` // Profile used on this invite
|
|
||||||
UsedBy map[string]int64 `json:"used_by,omitempty"` // Users who have used this invite mapped to their creation time in Epoch/Unix time
|
UsedBy map[string]int64 `json:"used_by,omitempty"` // Users who have used this invite mapped to their creation time in Epoch/Unix time
|
||||||
NoLimit bool `json:"no_limit"` // If true, invite can be used any number of times
|
NoLimit bool `json:"no_limit"` // If true, invite can be used any number of times
|
||||||
RemainingUses int `json:"remaining_uses,omitempty"` // Remaining number of uses (if applicable)
|
RemainingUses int `json:"remaining_uses,omitempty"` // Remaining number of uses (if applicable)
|
||||||
SendTo string `json:"send_to,omitempty"` // DEPRECATED Email/Discord username the invite was sent to (if applicable)
|
SendTo string `json:"send_to,omitempty"` // DEPRECATED Email/Discord username the invite was sent to (if applicable)
|
||||||
SentTo SentToList `json:"sent_to,omitempty"` // Email/Discord usernames attempts were made to send this invite to, and a failure reason if failed.
|
SentTo SentToList `json:"sent_to,omitempty"` // Email/Discord usernames attempts were made to send this invite to, and a failure reason if failed.
|
||||||
|
}
|
||||||
|
|
||||||
NotifyExpiry bool `json:"notify_expiry,omitempty"` // Whether to notify the requesting user of expiry or not
|
type EditableInviteDTO struct {
|
||||||
NotifyCreation bool `json:"notify_creation,omitempty"` // Whether to notify the requesting user of account creation or not
|
Code string `json:"code" example:"sajdlj23423j23"` // Invite code
|
||||||
Label string `json:"label,omitempty" example:"For Friends"` // Optional label for the invite
|
|
||||||
UserLabel string `json:"user_label,omitempty" example:"Friend"` // Label to apply to users created w/ this invite.
|
NotifyExpiry *bool `json:"notify_expiry,omitempty"` // Whether to notify the requesting user of expiry or not
|
||||||
|
NotifyCreation *bool `json:"notify_creation,omitempty"` // Whether to notify the requesting user of account creation or not
|
||||||
|
Label *string `json:"label,omitempty" example:"For Friends"` // Optional label for the invite
|
||||||
|
UserLabel *string `json:"user_label,omitempty" example:"Friend"` // Label to apply to users created w/ this invite.
|
||||||
|
Profile *string `json:"profile" example:"DefaultProfile"` // Profile used on this invite
|
||||||
|
UserExpiry *bool `json:"user_expiry"` // Whether or not user expiry is enabled
|
||||||
|
UserMonths *int `json:"user_months,omitempty" example:"1"` // Number of months till user expiry
|
||||||
|
UserDays *int `json:"user_days,omitempty" example:"1"` // Number of days till user expiry
|
||||||
|
UserHours *int `json:"user_hours,omitempty" example:"2"` // Number of hours till user expiry
|
||||||
|
UserMinutes *int `json:"user_minutes,omitempty" example:"3"` // Number of minutes till user expiry
|
||||||
}
|
}
|
||||||
|
|
||||||
type getInvitesDTO struct {
|
type getInvitesDTO struct {
|
||||||
|
|||||||
@@ -212,8 +212,8 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
|||||||
api.GET(p+"/invites/count", app.GetInviteCount)
|
api.GET(p+"/invites/count", app.GetInviteCount)
|
||||||
api.GET(p+"/invites/count/used", app.GetInviteUsedCount)
|
api.GET(p+"/invites/count/used", app.GetInviteUsedCount)
|
||||||
api.DELETE(p+"/invites", app.DeleteInvite)
|
api.DELETE(p+"/invites", app.DeleteInvite)
|
||||||
api.POST(p+"/invites/profile", app.SetProfile)
|
|
||||||
api.POST(p+"/invites/send", app.SendInvite)
|
api.POST(p+"/invites/send", app.SendInvite)
|
||||||
|
api.PATCH(p+"/invites/edit", app.EditInvite)
|
||||||
api.GET(p+"/profiles", app.GetProfiles)
|
api.GET(p+"/profiles", app.GetProfiles)
|
||||||
api.GET(p+"/profiles/names", app.GetProfileNames)
|
api.GET(p+"/profiles/names", app.GetProfileNames)
|
||||||
api.GET(p+"/profiles/raw/:name", app.GetRawProfile)
|
api.GET(p+"/profiles/raw/:name", app.GetRawProfile)
|
||||||
@@ -221,7 +221,6 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
|||||||
api.POST(p+"/profiles/default", app.SetDefaultProfile)
|
api.POST(p+"/profiles/default", app.SetDefaultProfile)
|
||||||
api.POST(p+"/profiles", app.CreateProfile)
|
api.POST(p+"/profiles", app.CreateProfile)
|
||||||
api.DELETE(p+"/profiles", app.DeleteProfile)
|
api.DELETE(p+"/profiles", app.DeleteProfile)
|
||||||
api.POST(p+"/invites/notify", app.SetNotify)
|
|
||||||
api.POST(p+"/users/emails", app.ModifyEmails)
|
api.POST(p+"/users/emails", app.ModifyEmails)
|
||||||
api.POST(p+"/users/labels", app.ModifyLabels)
|
api.POST(p+"/users/labels", app.ModifyLabels)
|
||||||
api.POST(p+"/users/accounts-admin", app.SetAccountsAdmin)
|
api.POST(p+"/users/accounts-admin", app.SetAccountsAdmin)
|
||||||
|
|||||||
@@ -142,6 +142,8 @@ export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHt
|
|||||||
|
|
||||||
export const _put = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => _req("PUT", url, data, onreadystatechange, response, statusHandler, noConnectionError);
|
export const _put = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => _req("PUT", url, data, onreadystatechange, response, statusHandler, noConnectionError);
|
||||||
|
|
||||||
|
export const _patch = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => _req("PATCH", url, data, onreadystatechange, response, statusHandler, noConnectionError);
|
||||||
|
|
||||||
export function _delete(url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void {
|
export function _delete(url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
if (window.pages) { url = window.pages.Base + url; }
|
if (window.pages) { url = window.pages.Base + url; }
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { _get, _post, _delete, toClipboard, toggleLoader, toDateString, SetupCopyButton, addLoader, removeLoader, DateCountdown } from "../modules/common.js";
|
import { _get, _post, _delete, _patch, toClipboard, toggleLoader, toDateString, SetupCopyButton, addLoader, removeLoader, DateCountdown } from "../modules/common.js";
|
||||||
import { DiscordSearch, DiscordUser, newDiscordSearch } from "../modules/discord.js";
|
import { DiscordSearch, DiscordUser, newDiscordSearch } from "../modules/discord.js";
|
||||||
import { reloadProfileNames } from "../modules/profiles.js";
|
import { reloadProfileNames } from "../modules/profiles.js";
|
||||||
|
import { HiddenInputField } from "./ui.js";
|
||||||
|
|
||||||
declare var window: GlobalWindow;
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
@@ -14,16 +15,18 @@ export const generateCodeLink = (code: string): string => {
|
|||||||
|
|
||||||
class DOMInvite implements Invite {
|
class DOMInvite implements Invite {
|
||||||
updateNotify = (checkbox: HTMLInputElement) => {
|
updateNotify = (checkbox: HTMLInputElement) => {
|
||||||
let state: { [code: string]: { [type: string]: boolean } } = {};
|
let state = {
|
||||||
|
code: this.code,
|
||||||
|
notify_expiry: this.notify_expiry,
|
||||||
|
notify_creation: this.notify_creation
|
||||||
|
};
|
||||||
let revertChanges: () => void;
|
let revertChanges: () => void;
|
||||||
if (checkbox.classList.contains("inv-notify-expiry")) {
|
if (checkbox.classList.contains("inv-notify-expiry")) {
|
||||||
revertChanges = () => { this.notify_expiry = !this.notify_expiry };
|
revertChanges = () => { this.notify_expiry = !this.notify_expiry };
|
||||||
state[this.code] = { "notify-expiry": this.notify_expiry };
|
|
||||||
} else {
|
} else {
|
||||||
revertChanges = () => { this.notify_creation = !this.notify_creation };
|
revertChanges = () => { this.notify_creation = !this.notify_creation };
|
||||||
state[this.code] = { "notify-creation": this.notify_creation };
|
|
||||||
}
|
}
|
||||||
_post("/invites/notify", state, (req: XMLHttpRequest) => {
|
_patch("/invites/edit", state, (req: XMLHttpRequest) => {
|
||||||
if (req.readyState == 4 && !(req.status == 200 || req.status == 204)) {
|
if (req.readyState == 4 && !(req.status == 200 || req.status == 204)) {
|
||||||
revertChanges();
|
revertChanges();
|
||||||
}
|
}
|
||||||
@@ -38,18 +41,6 @@ class DOMInvite implements Invite {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
private _label: string = "";
|
|
||||||
get label(): string { return this._label; }
|
|
||||||
set label(label: string) {
|
|
||||||
this._label = label;
|
|
||||||
const linkEl = this._codeArea.querySelector("a") as HTMLAnchorElement;
|
|
||||||
if (label == "") {
|
|
||||||
linkEl.textContent = this.code.replace(/-/g, '-');
|
|
||||||
} else {
|
|
||||||
linkEl.textContent = label;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _userLabel: string = "";
|
private _userLabel: string = "";
|
||||||
get user_label(): string { return this._userLabel; }
|
get user_label(): string { return this._userLabel; }
|
||||||
set user_label(label: string) {
|
set user_label(label: string) {
|
||||||
@@ -67,16 +58,26 @@ class DOMInvite implements Invite {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _label: string = "";
|
||||||
|
get label(): string { return this._label; }
|
||||||
|
set label(label: string) {
|
||||||
|
this._label = label;
|
||||||
|
if (label == "") {
|
||||||
|
this.code = this.code;
|
||||||
|
} else {
|
||||||
|
this._labelEditor.value = label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _code: string = "None";
|
private _code: string = "None";
|
||||||
get code(): string { return this._code; }
|
get code(): string { return this._code; }
|
||||||
set code(code: string) {
|
set code(code: string) {
|
||||||
this._code = code;
|
this._code = code;
|
||||||
this._codeLink = generateCodeLink(code);
|
this._codeLink = generateCodeLink(code);
|
||||||
const linkEl = this._codeArea.querySelector("a") as HTMLAnchorElement;
|
|
||||||
if (this.label == "") {
|
if (this.label == "") {
|
||||||
linkEl.textContent = code.replace(/-/g, '-');
|
this._labelEditor.value = code.replace(/-/g, '-');
|
||||||
}
|
}
|
||||||
linkEl.href = this._codeLink;
|
this._linkEl.href = this._codeLink;
|
||||||
}
|
}
|
||||||
private _codeLink: string;
|
private _codeLink: string;
|
||||||
|
|
||||||
@@ -311,8 +312,9 @@ class DOMInvite implements Invite {
|
|||||||
const select = this._left.querySelector("select") as HTMLSelectElement;
|
const select = this._left.querySelector("select") as HTMLSelectElement;
|
||||||
const previous = this.profile;
|
const previous = this.profile;
|
||||||
let profile = select.value;
|
let profile = select.value;
|
||||||
if (profile == "noProfile") { profile = ""; }
|
let state = {code: this.code};
|
||||||
_post("/invites/profile", { "invite": this.code, "profile": profile }, (req: XMLHttpRequest) => {
|
if (profile != "noProfile") { state["profile"] = profile };
|
||||||
|
_patch("/invites/edit", state, (req: XMLHttpRequest) => {
|
||||||
if (req.readyState == 4) {
|
if (req.readyState == 4) {
|
||||||
if (!(req.status == 200 || req.status == 204)) {
|
if (!(req.status == 200 || req.status == 204)) {
|
||||||
select.value = previous || "noProfile";
|
select.value = previous || "noProfile";
|
||||||
@@ -323,6 +325,22 @@ class DOMInvite implements Invite {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _setLabel = () => {
|
||||||
|
const newLabel = this._labelEditor.value.trim();
|
||||||
|
const old = this.label;
|
||||||
|
this.label = newLabel;
|
||||||
|
let state = {
|
||||||
|
code: this.code,
|
||||||
|
label: newLabel
|
||||||
|
};
|
||||||
|
_patch("/invites/edit", state, (req: XMLHttpRequest) => {
|
||||||
|
if (req.readyState != 4) return;
|
||||||
|
if (req.status != 200 && req.status != 204) {
|
||||||
|
this.label = old;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private _container: HTMLDivElement;
|
private _container: HTMLDivElement;
|
||||||
|
|
||||||
private _header: HTMLDivElement;
|
private _header: HTMLDivElement;
|
||||||
@@ -335,6 +353,10 @@ class DOMInvite implements Invite {
|
|||||||
private _right: HTMLDivElement;
|
private _right: HTMLDivElement;
|
||||||
private _userTable: HTMLDivElement;
|
private _userTable: HTMLDivElement;
|
||||||
|
|
||||||
|
private _linkContainer: HTMLElement;
|
||||||
|
private _linkEl: HTMLAnchorElement;
|
||||||
|
private _labelEditor: HiddenInputField;
|
||||||
|
|
||||||
private _detailsToggle: HTMLInputElement;
|
private _detailsToggle: HTMLInputElement;
|
||||||
|
|
||||||
// whether the details card is expanded.
|
// whether the details card is expanded.
|
||||||
@@ -413,11 +435,22 @@ class DOMInvite implements Invite {
|
|||||||
this._codeArea.classList.add("flex", "flex-row", "flex-wrap", "justify-between", "w-full", "items-center", "gap-2", "truncate");
|
this._codeArea.classList.add("flex", "flex-row", "flex-wrap", "justify-between", "w-full", "items-center", "gap-2", "truncate");
|
||||||
this._codeArea.innerHTML = `
|
this._codeArea.innerHTML = `
|
||||||
<div class="flex items-center gap-x-4 gap-y-2 truncate">
|
<div class="flex items-center gap-x-4 gap-y-2 truncate">
|
||||||
<a class="invite-link text-black dark:text-white font-mono bg-inherit truncate" href=""></a>
|
<a class="invite-link-container text-black dark:text-white font-mono bg-inherit truncate"></a>
|
||||||
<button class="invite-copy-button"></button>
|
<button class="invite-copy-button"></button>
|
||||||
</div>
|
</div>
|
||||||
<span>${window.lang.var("strings", "inviteExpiresInTime", "<span class=\"inv-duration\"></span>")}</span>
|
<span>${window.lang.var("strings", "inviteExpiresInTime", "<span class=\"inv-duration\"></span>")}</span>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
this._linkContainer = this._codeArea.getElementsByClassName("invite-link-container")[0] as HTMLElement;
|
||||||
|
this._labelEditor = new HiddenInputField({
|
||||||
|
container: this._linkContainer,
|
||||||
|
buttonOnLeft: false,
|
||||||
|
customContainerHTML: `<a class="hidden-input-content invite-link text-black dark:text-white font-mono bg-inherit truncate"></a>`,
|
||||||
|
clickAwayShouldSave: true,
|
||||||
|
onSet: this._setLabel
|
||||||
|
});
|
||||||
|
this._linkEl = this._linkContainer.getElementsByClassName("invite-link")[0] as HTMLAnchorElement;
|
||||||
|
|
||||||
const copyButton = this._codeArea.getElementsByClassName("invite-copy-button")[0] as HTMLButtonElement;
|
const copyButton = this._codeArea.getElementsByClassName("invite-copy-button")[0] as HTMLButtonElement;
|
||||||
SetupCopyButton(copyButton, this._codeLink);
|
SetupCopyButton(copyButton, this._codeLink);
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,14 @@ export class HiddenInputField {
|
|||||||
this._toggle.onclick = () => {
|
this._toggle.onclick = () => {
|
||||||
this.editing = !this.editing;
|
this.editing = !this.editing;
|
||||||
};
|
};
|
||||||
|
this._input.addEventListener("keypress", (e: KeyboardEvent) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
this._toggle.click();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.setEditing(false, true);
|
this.setEditing(false, true);
|
||||||
}
|
}
|
||||||
@@ -66,6 +74,7 @@ export class HiddenInputField {
|
|||||||
this._toggle.classList.add(HiddenInputField.saveClass);
|
this._toggle.classList.add(HiddenInputField.saveClass);
|
||||||
this._toggle.classList.remove(HiddenInputField.editClass);
|
this._toggle.classList.remove(HiddenInputField.editClass);
|
||||||
this._input.classList.remove("hidden");
|
this._input.classList.remove("hidden");
|
||||||
|
this._input.focus();
|
||||||
this._content.classList.add("hidden");
|
this._content.classList.add("hidden");
|
||||||
} else {
|
} else {
|
||||||
document.removeEventListener("click", this.outerClickListener);
|
document.removeEventListener("click", this.outerClickListener);
|
||||||
|
|||||||
Reference in New Issue
Block a user