mirror of
https://github.com/hrfee/jfa-go.git
synced 2026-01-18 16:47:42 +01:00
invites: add /invites/send, store more details in new SentTo field
deprecated SendTo string field for SentTo, which holds successful send addresses, and failures with a reason that isn't plain text. Will soon add an interface for sending invites after their creation. For #444 (ha).
This commit is contained in:
132
api-invites.go
132
api-invites.go
@@ -157,6 +157,90 @@ func (app *appContext) sendAdminExpiryNotification(data Invite) *sync.WaitGroup
|
|||||||
return &wait
|
return &wait
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Summary Send an existing invite to an email address or discord user.
|
||||||
|
// @Produce json
|
||||||
|
// @Param SendInviteDTO body SendInviteDTO true "Email address or Discord username"
|
||||||
|
// @Success 200 {object} boolResponse
|
||||||
|
// @Failure 500 {object} stringResponse
|
||||||
|
// @Router /invites/send [post]
|
||||||
|
// @Security Bearer
|
||||||
|
// @tags Invites
|
||||||
|
func (app *appContext) SendInvite(gc *gin.Context) {
|
||||||
|
var req SendInviteDTO
|
||||||
|
gc.BindJSON(&req)
|
||||||
|
inv, ok := app.storage.GetInvitesKey(req.Invite)
|
||||||
|
if !ok {
|
||||||
|
app.err.Printf(lm.FailedGetInvite, req.Invite, lm.NotFound)
|
||||||
|
respond(500, "Invite not found", gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := app.sendInvite(req.sendInviteDTO, &inv)
|
||||||
|
// Even if failed, some error info might have been stored in the invite.
|
||||||
|
app.storage.SetInvitesKey(req.Invite, inv)
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf(lm.FailedSendInviteMessage, req.Invite, req.SendTo, err)
|
||||||
|
respond(500, err.Error(), gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
app.info.Printf(lm.SentInviteMessage, req.Invite, req.SendTo)
|
||||||
|
respondBool(200, true, gc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendInvite attempts to send an invite to the given email address or discord username.
|
||||||
|
func (app *appContext) sendInvite(req sendInviteDTO, invite *Invite) (err error) {
|
||||||
|
if !(app.config.Section("invite_emails").Key("enabled").MustBool(false)) {
|
||||||
|
// app.err.Printf(lm.FailedSendInviteMessage, invite.Code, req.SendTo, errors.New(lm.InviteMessagesDisabled))
|
||||||
|
err = errors.New(lm.InviteMessagesDisabled)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
discord := ""
|
||||||
|
if discordEnabled && (!strings.Contains(req.SendTo, "@") || strings.HasPrefix(req.SendTo, "@")) {
|
||||||
|
users := app.discord.GetUsers(req.SendTo)
|
||||||
|
if len(users) == 0 {
|
||||||
|
invite.SentTo.Failed = append(invite.SentTo.Failed, SendFailure{
|
||||||
|
Address: req.SendTo,
|
||||||
|
Reason: NoUser,
|
||||||
|
})
|
||||||
|
err = fmt.Errorf(lm.InvalidAddress, req.SendTo)
|
||||||
|
return err
|
||||||
|
} else if len(users) > 1 {
|
||||||
|
invite.SentTo.Failed = append(invite.SentTo.Failed, SendFailure{
|
||||||
|
Address: req.SendTo,
|
||||||
|
Reason: MultiUser,
|
||||||
|
})
|
||||||
|
err = fmt.Errorf(lm.InvalidAddress, req.SendTo)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
discord = users[0].User.ID
|
||||||
|
}
|
||||||
|
var msg *Message
|
||||||
|
msg, err = app.email.constructInvite(invite, false)
|
||||||
|
if err != nil {
|
||||||
|
// Slight misuse of the template
|
||||||
|
invite.SentTo.Failed = append(invite.SentTo.Failed, SendFailure{
|
||||||
|
Address: req.SendTo,
|
||||||
|
Reason: CheckLogs,
|
||||||
|
})
|
||||||
|
// app.err.Printf(lm.FailedConstructInviteMessage, req.SendTo, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if discord != "" {
|
||||||
|
err = app.discord.SendDM(msg, discord)
|
||||||
|
} else {
|
||||||
|
err = app.email.send(msg, req.SendTo)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
invite.SentTo.Failed = append(invite.SentTo.Failed, SendFailure{
|
||||||
|
Address: req.SendTo,
|
||||||
|
Reason: CheckLogs,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
// app.err.Println(invite.SendTo)
|
||||||
|
}
|
||||||
|
invite.SentTo.Success = append(invite.SentTo.Success, req.SendTo)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// @Summary Create a new invite.
|
// @Summary Create a new invite.
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param generateInviteDTO body generateInviteDTO true "New invite request object"
|
// @Param generateInviteDTO body generateInviteDTO true "New invite request object"
|
||||||
@@ -198,48 +282,11 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
invite.ValidTill = validTill
|
invite.ValidTill = validTill
|
||||||
if req.SendTo != "" {
|
if req.SendTo != "" {
|
||||||
if !(app.config.Section("invite_emails").Key("enabled").MustBool(false)) {
|
err := app.sendInvite(req.sendInviteDTO, &invite)
|
||||||
app.err.Printf(lm.FailedSendInviteMessage, invite.Code, req.SendTo, errors.New(lm.InviteMessagesDisabled))
|
if err != nil {
|
||||||
|
app.err.Printf(lm.FailedSendInviteMessage, invite.Code, req.SendTo, err)
|
||||||
} else {
|
} else {
|
||||||
addressValid := false
|
app.info.Printf(lm.SentInviteMessage, invite.Code, req.SendTo)
|
||||||
discord := ""
|
|
||||||
if discordEnabled && (!strings.Contains(req.SendTo, "@") || strings.HasPrefix(req.SendTo, "@")) {
|
|
||||||
users := app.discord.GetUsers(req.SendTo)
|
|
||||||
if len(users) == 0 {
|
|
||||||
invite.SendTo = fmt.Sprintf(lm.FailedSendToTooltipNoUser, req.SendTo)
|
|
||||||
} else if len(users) > 1 {
|
|
||||||
invite.SendTo = fmt.Sprintf(lm.FailedSendToTooltipMultiUser, req.SendTo)
|
|
||||||
} else {
|
|
||||||
invite.SendTo = req.SendTo
|
|
||||||
addressValid = true
|
|
||||||
discord = users[0].User.ID
|
|
||||||
}
|
|
||||||
} else if emailEnabled {
|
|
||||||
addressValid = true
|
|
||||||
invite.SendTo = req.SendTo
|
|
||||||
}
|
|
||||||
if addressValid {
|
|
||||||
msg, err := app.email.constructInvite(invite, false)
|
|
||||||
if err != nil {
|
|
||||||
// Slight misuse of the template
|
|
||||||
invite.SendTo = fmt.Sprintf(lm.FailedConstructInviteMessage, req.SendTo, err)
|
|
||||||
|
|
||||||
app.err.Printf(lm.FailedConstructInviteMessage, invite.Code, err)
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
if discord != "" {
|
|
||||||
err = app.discord.SendDM(msg, discord)
|
|
||||||
} else {
|
|
||||||
err = app.email.send(msg, req.SendTo)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
invite.SendTo = fmt.Sprintf(lm.FailedSendInviteMessage, invite.Code, req.SendTo, err)
|
|
||||||
app.err.Println(invite.SendTo)
|
|
||||||
} else {
|
|
||||||
app.info.Printf(lm.SentInviteMessage, invite.Code, req.SendTo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if req.Profile != "" {
|
if req.Profile != "" {
|
||||||
@@ -357,6 +404,9 @@ func (app *appContext) GetInvites(gc *gin.Context) {
|
|||||||
if inv.RemainingUses != 0 {
|
if inv.RemainingUses != 0 {
|
||||||
invite.RemainingUses = inv.RemainingUses
|
invite.RemainingUses = inv.RemainingUses
|
||||||
}
|
}
|
||||||
|
if len(inv.SentTo.Success) != 0 || len(inv.SentTo.Failed) != 0 {
|
||||||
|
invite.SentTo = inv.SentTo
|
||||||
|
}
|
||||||
if inv.SendTo != "" {
|
if inv.SendTo != "" {
|
||||||
invite.SendTo = inv.SendTo
|
invite.SendTo = inv.SendTo
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ func (app *appContext) GetCustomMessageTemplate(gc *gin.Context) {
|
|||||||
case "ExpiryReminder":
|
case "ExpiryReminder":
|
||||||
msg, err = app.email.constructExpiryReminder("", time.Now().AddDate(0, 0, 3), true)
|
msg, err = app.email.constructExpiryReminder("", time.Now().AddDate(0, 0, 3), true)
|
||||||
case "InviteEmail":
|
case "InviteEmail":
|
||||||
msg, err = app.email.constructInvite(Invite{Code: ""}, true)
|
msg, err = app.email.constructInvite(&Invite{Code: ""}, true)
|
||||||
case "WelcomeEmail":
|
case "WelcomeEmail":
|
||||||
msg, err = app.email.constructWelcome("", time.Time{}, true)
|
msg, err = app.email.constructWelcome("", time.Time{}, true)
|
||||||
case "EmailConfirmation":
|
case "EmailConfirmation":
|
||||||
|
|||||||
12
discord.go
12
discord.go
@@ -738,7 +738,6 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
|
|||||||
|
|
||||||
var invname *dg.Member = nil
|
var invname *dg.Member = nil
|
||||||
invname, err = d.bot.GuildMember(d.guildID, recipient.ID)
|
invname, err = d.bot.GuildMember(d.guildID, recipient.ID)
|
||||||
invite.SendTo = invname.User.Username
|
|
||||||
|
|
||||||
if err == nil && !(d.app.config.Section("invite_emails").Key("enabled").MustBool(false)) {
|
if err == nil && !(d.app.config.Section("invite_emails").Key("enabled").MustBool(false)) {
|
||||||
err = errors.New(lm.InviteMessagesDisabled)
|
err = errors.New(lm.InviteMessagesDisabled)
|
||||||
@@ -746,11 +745,14 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
|
|||||||
|
|
||||||
var msg *Message
|
var msg *Message
|
||||||
if err == nil {
|
if err == nil {
|
||||||
msg, err = d.app.email.constructInvite(invite, false)
|
msg, err = d.app.email.constructInvite(&invite, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Print extra message, ideally we'd just print this, or get rid of it though.
|
// Print extra message, ideally we'd just print this, or get rid of it though.
|
||||||
invite.SendTo = fmt.Sprintf(lm.FailedConstructInviteMessage, invite.Code, err)
|
invite.SentTo.Failed = append(invite.SentTo.Failed, SendFailure{
|
||||||
d.app.err.Println(invite.SendTo)
|
Address: invname.User.Username,
|
||||||
|
Reason: CheckLogs,
|
||||||
|
})
|
||||||
|
d.app.err.Printf(lm.FailedConstructInviteMessage, invite.Code, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -760,12 +762,12 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
|
|||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
d.app.info.Printf(lm.SentInviteMessage, invite.Code, RenderDiscordUsername(recipient))
|
d.app.info.Printf(lm.SentInviteMessage, invite.Code, RenderDiscordUsername(recipient))
|
||||||
|
invite.SentTo.Success = append(invite.SentTo.Success, invname.User.Username)
|
||||||
sendResponse("sentInvite")
|
sendResponse("sentInvite")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
invite.SendTo = fmt.Sprintf(lm.FailedSendInviteMessage, invite.Code, RenderDiscordUsername(recipient), err)
|
invite.SendTo = fmt.Sprintf(lm.FailedSendInviteMessage, invite.Code, RenderDiscordUsername(recipient), err)
|
||||||
d.app.err.Println(invite.SendTo)
|
|
||||||
sendResponse("sentInviteFailure")
|
sendResponse("sentInviteFailure")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
email.go
2
email.go
@@ -379,7 +379,7 @@ func (emailer *Emailer) constructConfirmation(code, username, key string, placeh
|
|||||||
return emailer.construct(contentInfo, cc, template)
|
return emailer.construct(contentInfo, cc, template)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) constructInvite(invite Invite, placeholders bool) (*Message, error) {
|
func (emailer *Emailer) constructInvite(invite *Invite, placeholders bool) (*Message, error) {
|
||||||
expiry := invite.ValidTill
|
expiry := invite.ValidTill
|
||||||
d, t, expiresIn := emailer.formatExpiry(expiry, false)
|
d, t, expiresIn := emailer.formatExpiry(expiry, false)
|
||||||
inviteLink := fmt.Sprintf("%s%s/%s", ExternalURI(nil), PAGES.Form, invite.Code)
|
inviteLink := fmt.Sprintf("%s%s/%s", ExternalURI(nil), PAGES.Form, invite.Code)
|
||||||
|
|||||||
@@ -266,18 +266,20 @@
|
|||||||
<div id="modal-announce" class="modal">
|
<div id="modal-announce" class="modal">
|
||||||
<form class="relative mx-auto my-[10%] w-4/5 lg:w-2/3 content card" id="form-announce" href="">
|
<form class="relative mx-auto my-[10%] w-4/5 lg:w-2/3 content card" id="form-announce" href="">
|
||||||
<span class="heading"><span id="header-announce"></span> <span class="modal-close">×</span></span>
|
<span class="heading"><span id="header-announce"></span> <span class="modal-close">×</span></span>
|
||||||
<div class="flex flex-col md:flex-row">
|
<div class="flex flex-col md:flex-row gap-4">
|
||||||
<div class="col card ~neutral @low">
|
<div class="card ~neutral @low w-1/2 grow">
|
||||||
<div id="announce-details">
|
<div id="announce-details" class="flex flex-col gap-2">
|
||||||
<span class="label supra" for="editor-variables" id="label-editor-variables">{{ .strings.variables }}</span>
|
<span class="label supra" for="editor-variables" id="label-editor-variables">{{ .strings.variables }}</span>
|
||||||
<div id="announce-variables">
|
<div id="announce-variables" class="flex flex-row flex-wrap gap-2">
|
||||||
<span class="button ~urge @low mb-2 mt-4" id="announce-variables-username" style="margin-left: 0.25rem; margin-right: 0.25rem;"><span class="font-mono bg-inherit">{username}</span></span>
|
<span class="button ~urge @low" id="announce-variables-username"><span class="font-mono bg-inherit">{username}</span></span>
|
||||||
</div>
|
</div>
|
||||||
<label class="label supra" for="announce-subject"> {{ .strings.subject }}</label>
|
<label class="label supra" for="announce-subject"> {{ .strings.subject }}</label>
|
||||||
<input type="text" id="announce-subject" class="input ~neutral @low mb-2 mt-4">
|
<input type="text" id="announce-subject" class="input ~neutral @low">
|
||||||
<label class="label supra" for="textarea-announce">{{ .strings.message }}</label>
|
<label class="label supra" for="textarea-announce">{{ .strings.message }}</label>
|
||||||
<textarea id="textarea-announce" class="textarea full-width ~neutral @low mt-4 font-mono"></textarea>
|
<textarea id="textarea-announce" class="textarea full-width ~neutral @low font-mono"></textarea>
|
||||||
<p class="support mt-4 mb-2">{{ .strings.markdownSupported }}</p>
|
<p class="support">{{ .strings.markdownSupported }}</p>
|
||||||
|
<p class="support editor-syntax-description">{{ .strings.syntaxDescription }}</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<label class="label unfocused" id="announce-name"><p class="supra">{{ .strings.name }}</p>
|
<label class="label unfocused" id="announce-name"><p class="supra">{{ .strings.name }}</p>
|
||||||
<input type="text" class="input ~neutral @low mb-2 mt-4">
|
<input type="text" class="input ~neutral @low mb-2 mt-4">
|
||||||
@@ -291,9 +293,9 @@
|
|||||||
<span class="button ~info @low center supra" id="save-announce">{{ .strings.saveAsTemplate }}</span>
|
<span class="button ~info @low center supra" id="save-announce">{{ .strings.saveAsTemplate }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col card ~neutral @low">
|
<div class="card ~neutral @low flex flex-col gap-2 min-w-lg">
|
||||||
<span class="subheading supra">{{ .strings.preview }}</span>
|
<span class="subheading supra">{{ .strings.preview }}</span>
|
||||||
<div class="mt-8" id="announce-preview"></div>
|
<div id="announce-preview"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -319,8 +321,8 @@
|
|||||||
<div id="modal-editor" class="modal">
|
<div id="modal-editor" class="modal">
|
||||||
<form class="relative mx-auto my-[10%] w-4/5 lg:w-2/3 content card" id="form-editor" href="">
|
<form class="relative mx-auto my-[10%] w-4/5 lg:w-2/3 content card" id="form-editor" href="">
|
||||||
<span class="heading"><span id="header-editor"></span> <span class="modal-close">×</span></span>
|
<span class="heading"><span id="header-editor"></span> <span class="modal-close">×</span></span>
|
||||||
<div class="row">
|
<div class="flex flex-col md:flex-row gap-4">
|
||||||
<div class="col card ~neutral @low flex flex-col gap-2 justify-between">
|
<div class="card ~neutral @low w-1/2 grow flex flex-col gap-2 justify-between">
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<aside class="aside sm ~urge dark:~d_info @low" id="aside-editor"></aside>
|
<aside class="aside sm ~urge dark:~d_info @low" id="aside-editor"></aside>
|
||||||
<label class="label supra" for="editor-variables" id="label-editor-variables">{{ .strings.variables }}</label>
|
<label class="label supra" for="editor-variables" id="label-editor-variables">{{ .strings.variables }}</label>
|
||||||
@@ -329,16 +331,17 @@
|
|||||||
<div id="editor-conditionals"></div>
|
<div id="editor-conditionals"></div>
|
||||||
<label class="label supra" for="textarea-editor">{{ .strings.message }}</label>
|
<label class="label supra" for="textarea-editor">{{ .strings.message }}</label>
|
||||||
<textarea id="textarea-editor" class="textarea full-width flex-auto ~neutral @low font-mono"></textarea>
|
<textarea id="textarea-editor" class="textarea full-width flex-auto ~neutral @low font-mono"></textarea>
|
||||||
|
<p class="support">{{ .strings.markdownSupported }}</p>
|
||||||
|
<p class="support editor-syntax-description">{{ .strings.syntaxDescription }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<p class="support">{{ .strings.markdownSupported }}</p>
|
|
||||||
<label class="w-full">
|
<label class="w-full">
|
||||||
<input type="submit" class="unfocused">
|
<input type="submit" class="unfocused">
|
||||||
<span class="button ~urge @low w-full supra submit">{{ .strings.submit }}</span>
|
<span class="button ~urge @low w-full supra submit">{{ .strings.submit }}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col card ~neutral @low flex flex-col gap-2">
|
<div class="card ~neutral @low flex flex-col gap-2 min-w-lg">
|
||||||
<span class="subheading supra">{{ .strings.preview }}</span>
|
<span class="subheading supra">{{ .strings.preview }}</span>
|
||||||
<div id="editor-preview"></div>
|
<div id="editor-preview"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"warning": "Warning",
|
"warning": "Warning",
|
||||||
"inviteInfiniteUsesWarning": "invites with infinite uses can be used abusively",
|
"inviteInfiniteUsesWarning": "invites with infinite uses can be used abusively",
|
||||||
"inviteSendToEmail": "Send to",
|
"inviteSendToEmail": "Send to",
|
||||||
|
"sentTo": "Sent to",
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
"apply": "Apply",
|
"apply": "Apply",
|
||||||
"select": "Select",
|
"select": "Select",
|
||||||
@@ -223,7 +224,7 @@
|
|||||||
"restartRequired": "Restart required",
|
"restartRequired": "Restart required",
|
||||||
"required": "Required",
|
"required": "Required",
|
||||||
"syntax": "Syntax",
|
"syntax": "Syntax",
|
||||||
"syntaxDescription": "Variables denoted as {varname}. If statements can evaluate truthfulness (e.g. {if messageAddress}Message sent to {messageAddress}{end}) or make basic comparisons (e.g. {if profile == \"Friends\"}Friend{else if profile != \"Admins\"}User{end})"
|
"syntaxDescription": "Variables denoted as {variable}. If statements can evaluate truthfulness (e.g. {ifTruth}) or make basic comparisons (e.g. {ifCompare})"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"pathCopied": "Full path copied to clipboard.",
|
"pathCopied": "Full path copied to clipboard.",
|
||||||
@@ -261,6 +262,7 @@
|
|||||||
"errorLoadOmbiUsers": "Failed to load ombi users.",
|
"errorLoadOmbiUsers": "Failed to load ombi users.",
|
||||||
"errorChangedEmailAddress": "Couldn't change email address of {n}.",
|
"errorChangedEmailAddress": "Couldn't change email address of {n}.",
|
||||||
"errorFailureCheckLogs": "Failed (check console/logs)",
|
"errorFailureCheckLogs": "Failed (check console/logs)",
|
||||||
|
"errorCheckLogs": "Check console/logs",
|
||||||
"errorPartialFailureCheckLogs": "Partial failure (check console/logs)",
|
"errorPartialFailureCheckLogs": "Partial failure (check console/logs)",
|
||||||
"errorUserCreated": "Failed to create user {n}.",
|
"errorUserCreated": "Failed to create user {n}.",
|
||||||
"errorSendWelcomeEmail": "Failed to send welcome message (check console/logs)",
|
"errorSendWelcomeEmail": "Failed to send welcome message (check console/logs)",
|
||||||
@@ -272,7 +274,10 @@
|
|||||||
"errorInvalidJSON": "Invalid JSON.",
|
"errorInvalidJSON": "Invalid JSON.",
|
||||||
"updateAvailable": "A new update is available, check settings.",
|
"updateAvailable": "A new update is available, check settings.",
|
||||||
"noUpdatesAvailable": "No new updates available.",
|
"noUpdatesAvailable": "No new updates available.",
|
||||||
"runTask": "Triggered task."
|
"runTask": "Triggered task.",
|
||||||
|
"errorMultiUser": "Multiple matching users found",
|
||||||
|
"errorNoUser": "No matching user found",
|
||||||
|
"errorInvalidAddress": "Invalid address/name"
|
||||||
},
|
},
|
||||||
"quantityStrings": {
|
"quantityStrings": {
|
||||||
"modifySettingsFor": {
|
"modifySettingsFor": {
|
||||||
|
|||||||
@@ -43,7 +43,8 @@
|
|||||||
"referrals": "Referrals",
|
"referrals": "Referrals",
|
||||||
"inviteRemainingUses": "Remaining uses",
|
"inviteRemainingUses": "Remaining uses",
|
||||||
"internal": "Internal",
|
"internal": "Internal",
|
||||||
"external": "External"
|
"external": "External",
|
||||||
|
"failed": "Failed"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"errorLoginBlank": "The username and/or password were left blank.",
|
"errorLoginBlank": "The username and/or password were left blank.",
|
||||||
|
|||||||
@@ -109,9 +109,11 @@ const (
|
|||||||
GenerateInvite = "Generating new invite"
|
GenerateInvite = "Generating new invite"
|
||||||
FailedGenerateInvite = "Failed to generate new invite: %v"
|
FailedGenerateInvite = "Failed to generate new invite: %v"
|
||||||
InvalidInviteCode = "Invalid invite code \"%s\""
|
InvalidInviteCode = "Invalid invite code \"%s\""
|
||||||
|
FailedGetInvite = "Failed to get invite \"%s\": %v"
|
||||||
|
|
||||||
FailedSendToTooltipNoUser = "Failed: \"%s\" not found"
|
FailedSendToTooltipNoUser = "Failed: \"%s\" not found"
|
||||||
FailedSendToTooltipMultiUser = "Failed: \"%s\" linked to multiple users"
|
FailedSendToTooltipMultiUser = "Failed: \"%s\" linked to multiple users"
|
||||||
|
InvalidAddress = "invalid address \"%s\""
|
||||||
|
|
||||||
FailedParseTime = "Failed to parse time value: %v"
|
FailedParseTime = "Failed to parse time value: %v"
|
||||||
|
|
||||||
|
|||||||
71
models.go
71
models.go
@@ -52,16 +52,16 @@ type enableDisableUserDTO struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type generateInviteDTO struct {
|
type generateInviteDTO struct {
|
||||||
Months int `json:"months" example:"0"` // Number of months
|
Months int `json:"months" example:"0"` // Number of months
|
||||||
Days int `json:"days" example:"1"` // Number of days
|
Days int `json:"days" example:"1"` // Number of days
|
||||||
Hours int `json:"hours" example:"2"` // Number of hours
|
Hours int `json:"hours" example:"2"` // Number of hours
|
||||||
Minutes int `json:"minutes" example:"3"` // Number of minutes
|
Minutes int `json:"minutes" example:"3"` // Number of minutes
|
||||||
UserExpiry bool `json:"user-expiry"` // Whether or not user expiry is enabled
|
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
|
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
|
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
|
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
|
UserMinutes int `json:"user-minutes,omitempty" example:"3"` // Number of minutes till user expiry
|
||||||
SendTo string `json:"send-to" example:"jeff@jellyf.in"` // Send invite to this address or discord name
|
sendInviteDTO
|
||||||
MultipleUses bool `json:"multiple-uses" example:"true"` // Allow multiple uses
|
MultipleUses bool `json:"multiple-uses" example:"true"` // Allow multiple uses
|
||||||
NoLimit bool `json:"no-limit" example:"false"` // No invite use limit
|
NoLimit bool `json:"no-limit" example:"false"` // No invite use limit
|
||||||
RemainingUses int `json:"remaining-uses" example:"5"` // Remaining invite uses
|
RemainingUses int `json:"remaining-uses" example:"5"` // Remaining invite uses
|
||||||
@@ -70,6 +70,15 @@ type generateInviteDTO struct {
|
|||||||
UserLabel string `json:"user_label,omitempty" example:"Friend"` // Label to apply to users created w/ this invite.
|
UserLabel string `json:"user_label,omitempty" example:"Friend"` // Label to apply to users created w/ this invite.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SendInviteDTO struct {
|
||||||
|
Invite string `json:"invite" example:"slakdaslkdl2342"` // Invite to apply to
|
||||||
|
sendInviteDTO
|
||||||
|
}
|
||||||
|
|
||||||
|
type sendInviteDTO struct {
|
||||||
|
SendTo string `json:"send-to" example:"jeff@jellyf.in"` // Send invite to this address or discord name
|
||||||
|
}
|
||||||
|
|
||||||
type inviteProfileDTO struct {
|
type inviteProfileDTO struct {
|
||||||
Invite string `json:"invite" example:"slakdaslkdl2342"` // Invite to apply to
|
Invite string `json:"invite" example:"slakdaslkdl2342"` // Invite to apply to
|
||||||
Profile string `json:"profile" example:"DefaultProfile"` // Profile to use
|
Profile string `json:"profile" example:"DefaultProfile"` // Profile to use
|
||||||
@@ -106,26 +115,28 @@ type newProfileDTO struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type inviteDTO struct {
|
type inviteDTO struct {
|
||||||
Code string `json:"code" example:"sajdlj23423j23"` // Invite code
|
Code string `json:"code" example:"sajdlj23423j23"` // Invite code
|
||||||
Months int `json:"months" example:"1"` // Number of months till expiry
|
Months int `json:"months" example:"1"` // Number of months till expiry
|
||||||
Days int `json:"days" example:"1"` // Number of days till expiry
|
Days int `json:"days" example:"1"` // Number of days till expiry
|
||||||
Hours int `json:"hours" example:"2"` // Number of hours till expiry
|
Hours int `json:"hours" example:"2"` // Number of hours till expiry
|
||||||
Minutes int `json:"minutes" example:"3"` // Number of minutes till expiry
|
Minutes int `json:"minutes" example:"3"` // Number of minutes till expiry
|
||||||
UserExpiry bool `json:"user-expiry"` // Whether or not user expiry is enabled
|
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
|
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
|
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
|
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
|
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
|
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,omitempty"` // If true, invite can be used any number of times
|
NoLimit bool `json:"no-limit,omitempty"` // 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"` // 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)
|
||||||
NotifyExpiry bool `json:"notify-expiry,omitempty"` // Whether to notify the requesting user of expiry or not
|
SentTo SentToList `json:"sent_to,omitempty"` // Email/Discord usernames attempts were made to send this invite to, and a failure reason if failed.
|
||||||
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
|
NotifyExpiry bool `json:"notify-expiry,omitempty"` // Whether to notify the requesting user of expiry or not
|
||||||
UserLabel string `json:"user_label,omitempty" example:"Friend"` // Label to apply to users created w/ this invite.
|
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.
|
||||||
}
|
}
|
||||||
|
|
||||||
type getInvitesDTO struct {
|
type getInvitesDTO struct {
|
||||||
|
|||||||
7
package-lock.json
generated
7
package-lock.json
generated
@@ -10,6 +10,7 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@af-utils/scrollend-polyfill": "^0.0.14",
|
"@af-utils/scrollend-polyfill": "^0.0.14",
|
||||||
|
"@hrfee/simpletemplate": "^1.1.0",
|
||||||
"@ts-stack/markdown": "^1.4.0",
|
"@ts-stack/markdown": "^1.4.0",
|
||||||
"@types/node": "^20.3.0",
|
"@types/node": "^20.3.0",
|
||||||
"@webcoder49/code-input": "^2.7.2",
|
"@webcoder49/code-input": "^2.7.2",
|
||||||
@@ -482,6 +483,12 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@hrfee/simpletemplate": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hrfee/simpletemplate/-/simpletemplate-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-5H4/y7CegE4twstRPip/ms+OGUb+BPyVt+hriEpY88lTWW4I6jxzHFHmz6NDZmVyRa4RVIEVBxvtwk3RXKJVwA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@isaacs/cliui": {
|
"node_modules/@isaacs/cliui": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"homepage": "https://github.com/hrfee/jfa-go#readme",
|
"homepage": "https://github.com/hrfee/jfa-go#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@af-utils/scrollend-polyfill": "^0.0.14",
|
"@af-utils/scrollend-polyfill": "^0.0.14",
|
||||||
|
"@hrfee/simpletemplate": "^1.1.0",
|
||||||
"@ts-stack/markdown": "^1.4.0",
|
"@ts-stack/markdown": "^1.4.0",
|
||||||
"@types/node": "^20.3.0",
|
"@types/node": "^20.3.0",
|
||||||
"@webcoder49/code-input": "^2.7.2",
|
"@webcoder49/code-input": "^2.7.2",
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
|||||||
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/profile", app.SetProfile)
|
||||||
|
api.POST(p+"/invites/send", app.SendInvite)
|
||||||
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)
|
||||||
|
|||||||
45
storage.go
45
storage.go
@@ -769,20 +769,39 @@ type JellyseerrTemplate struct {
|
|||||||
Notifications jellyseerr.NotificationsTemplate `json:"notifications,omitempty"`
|
Notifications jellyseerr.NotificationsTemplate `json:"notifications,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SendFailureReason = string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CheckLogs SendFailureReason = "CheckLogs"
|
||||||
|
NoUser = "NoUser"
|
||||||
|
MultiUser = "MultiUser"
|
||||||
|
InvalidAddress = "InvalidAddress"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SendFailure struct {
|
||||||
|
Address string `json:"address"`
|
||||||
|
Reason SendFailureReason `json:"reason"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SentToList struct {
|
||||||
|
Success []string `json:"success"`
|
||||||
|
Failed []SendFailure `json:"failed"`
|
||||||
|
}
|
||||||
|
|
||||||
type Invite struct {
|
type Invite struct {
|
||||||
Code string `badgerhold:"key"`
|
Code string `badgerhold:"key"`
|
||||||
Created time.Time `json:"created"`
|
Created time.Time `json:"created"`
|
||||||
NoLimit bool `json:"no-limit"`
|
NoLimit bool `json:"no-limit"`
|
||||||
RemainingUses int `json:"remaining-uses"`
|
RemainingUses int `json:"remaining-uses"`
|
||||||
ValidTill time.Time `json:"valid_till"`
|
ValidTill time.Time `json:"valid_till"`
|
||||||
UserExpiry bool `json:"user-duration"`
|
UserExpiry bool `json:"user-duration"`
|
||||||
UserMonths int `json:"user-months,omitempty"`
|
UserMonths int `json:"user-months,omitempty"`
|
||||||
UserDays int `json:"user-days,omitempty"`
|
UserDays int `json:"user-days,omitempty"`
|
||||||
UserHours int `json:"user-hours,omitempty"`
|
UserHours int `json:"user-hours,omitempty"`
|
||||||
UserMinutes int `json:"user-minutes,omitempty"`
|
UserMinutes int `json:"user-minutes,omitempty"`
|
||||||
SendTo string `json:"email"`
|
SendTo string `json:"email"` // deprecated: use SentTo now.
|
||||||
// Used to be stored as formatted time, now as Unix.
|
SentTo SentToList `json:"sent-to,omitempty"`
|
||||||
UsedBy [][]string `json:"used-by"`
|
UsedBy [][]string `json:"used-by"` // Used to be stored as formatted time, now as Unix.
|
||||||
Notify map[string]map[string]bool `json:"notify"`
|
Notify map[string]map[string]bool `json:"notify"`
|
||||||
Profile string `json:"profile"`
|
Profile string `json:"profile"`
|
||||||
Label string `json:"label,omitempty"`
|
Label string `json:"label,omitempty"`
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { ThemeManager } from "./modules/theme.js";
|
|||||||
import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
|
import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
|
||||||
import { Modal } from "./modules/modal.js";
|
import { Modal } from "./modules/modal.js";
|
||||||
import { Tabs, Tab } from "./modules/tabs.js";
|
import { Tabs, Tab } from "./modules/tabs.js";
|
||||||
import { inviteList, createInvite } from "./modules/invites.js";
|
import { DOMInviteList, createInvite } from "./modules/invites.js";
|
||||||
import { accountsList } from "./modules/accounts.js";
|
import { accountsList } from "./modules/accounts.js";
|
||||||
import { settingsList } from "./modules/settings.js";
|
import { settingsList } from "./modules/settings.js";
|
||||||
import { activityList } from "./modules/activity.js";
|
import { activityList } from "./modules/activity.js";
|
||||||
@@ -105,7 +105,7 @@ var accounts = new accountsList();
|
|||||||
|
|
||||||
var activity = new activityList();
|
var activity = new activityList();
|
||||||
|
|
||||||
window.invites = new inviteList();
|
window.invites = new DOMInviteList();
|
||||||
|
|
||||||
var settings = new settingsList();
|
var settings = new settingsList();
|
||||||
|
|
||||||
|
|||||||
@@ -1247,6 +1247,12 @@ export class accountsList extends PaginatedList {
|
|||||||
this._search.showHideSearchOptionsHeader();
|
this._search.showHideSearchOptionsHeader();
|
||||||
|
|
||||||
this.registerURLListener();
|
this.registerURLListener();
|
||||||
|
|
||||||
|
// Get rid of nasty CSS
|
||||||
|
window.modals.announce.onclose = () => {
|
||||||
|
const preview = document.getElementById("announce-preview") as HTMLDivElement;
|
||||||
|
preview.textContent = ``;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reload = (callback?: (resp: paginatedDTO) => void) => {
|
reload = (callback?: (resp: paginatedDTO) => void) => {
|
||||||
|
|||||||
@@ -123,20 +123,55 @@ class DOMInvite implements Invite {
|
|||||||
} else {
|
} else {
|
||||||
chip.classList.add("button");
|
chip.classList.add("button");
|
||||||
chip.parentElement.classList.add("h-full");
|
chip.parentElement.classList.add("h-full");
|
||||||
if (address.includes("Failed")) {
|
if (address.includes(window.lang.strings("failed"))) {
|
||||||
icon.classList.remove("ri-mail-line");
|
icon.classList.remove("ri-mail-line");
|
||||||
icon.classList.add("ri-mail-close-line");
|
icon.classList.add("ri-mail-close-line");
|
||||||
chip.classList.remove("~neutral");
|
chip.classList.remove("~neutral");
|
||||||
chip.classList.add("~critical");
|
chip.classList.add("~critical");
|
||||||
} else {
|
} else {
|
||||||
address = "Sent to " + address;
|
|
||||||
icon.classList.remove("ri-mail-close-line");
|
icon.classList.remove("ri-mail-close-line");
|
||||||
icon.classList.add("ri-mail-line");
|
icon.classList.add("ri-mail-line");
|
||||||
chip.classList.remove("~critical");
|
chip.classList.remove("~critical");
|
||||||
chip.classList.add("~neutral");
|
chip.classList.add("~neutral");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tooltip.textContent = address;
|
// innerHTML as the newer sent_to re-uses this with HTML.
|
||||||
|
tooltip.innerHTML = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _sent_to: SentToList;
|
||||||
|
get sent_to(): SentToList { return this._sent_to; }
|
||||||
|
set sent_to(v: SentToList) {
|
||||||
|
this._sent_to = v;
|
||||||
|
if (!v || !(v.success || v.failed)) return;
|
||||||
|
let text = "";
|
||||||
|
if (v.success && v.success.length > 0) {
|
||||||
|
text += window.lang.strings("sentTo") + ": " + v.success.join(", ") + " <br>"
|
||||||
|
}
|
||||||
|
if (v.failed && v.failed.length > 0) {
|
||||||
|
text += window.lang.strings("failed") + ": " + v.failed.map((el: SendFailure) => {
|
||||||
|
let err: string;
|
||||||
|
switch (el.reason) {
|
||||||
|
case "CheckLogs":
|
||||||
|
err = window.lang.notif("errorCheckLogs");
|
||||||
|
break;
|
||||||
|
case "NoUser":
|
||||||
|
err = window.lang.notif("errorNoUser");
|
||||||
|
break;
|
||||||
|
case "MultiUser":
|
||||||
|
err = window.lang.notif("errorMultiUser");
|
||||||
|
break;
|
||||||
|
case "InvalidAddress":
|
||||||
|
err = window.lang.notif("errorInvalidAddress");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
err = el.reason;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return el.address + " (" + err + ")";
|
||||||
|
}).join(", ");
|
||||||
|
}
|
||||||
|
if (text.length != 0) this.send_to = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _usedBy: { [name: string]: number };
|
private _usedBy: { [name: string]: number };
|
||||||
@@ -455,6 +490,7 @@ class DOMInvite implements Invite {
|
|||||||
this.code = invite.code;
|
this.code = invite.code;
|
||||||
this.created = invite.created;
|
this.created = invite.created;
|
||||||
this.send_to = invite.send_to;
|
this.send_to = invite.send_to;
|
||||||
|
this.sent_to = invite.sent_to;
|
||||||
this.expiresIn = invite.expiresIn;
|
this.expiresIn = invite.expiresIn;
|
||||||
if (window.notificationsEnabled) {
|
if (window.notificationsEnabled) {
|
||||||
this.notifyCreation = invite.notifyCreation;
|
this.notifyCreation = invite.notifyCreation;
|
||||||
@@ -477,7 +513,7 @@ class DOMInvite implements Invite {
|
|||||||
remove = () => { this._container.remove(); }
|
remove = () => { this._container.remove(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
export class inviteList implements inviteList {
|
export class DOMInviteList implements InviteList {
|
||||||
private _list: HTMLDivElement;
|
private _list: HTMLDivElement;
|
||||||
private _empty: boolean;
|
private _empty: boolean;
|
||||||
// since invite reload sends profiles, this event it broadcast so the createInvite object can load them.
|
// since invite reload sends profiles, this event it broadcast so the createInvite object can load them.
|
||||||
@@ -493,7 +529,7 @@ export class inviteList implements inviteList {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public static readonly _inviteURLEvent = "invite-url";
|
public static readonly _inviteURLEvent = "invite-url";
|
||||||
registerURLListener = () => document.addEventListener(inviteList._inviteURLEvent, (event: CustomEvent) => {
|
registerURLListener = () => document.addEventListener(DOMInviteList._inviteURLEvent, (event: CustomEvent) => {
|
||||||
this.focusInvite(event.detail);
|
this.focusInvite(event.detail);
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -587,12 +623,14 @@ export class inviteList implements inviteList {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const inviteURLEvent = (id: string) => { return new CustomEvent(inviteList._inviteURLEvent, {"detail": id}) };
|
export const inviteURLEvent = (id: string) => { return new CustomEvent(DOMInviteList._inviteURLEvent, {"detail": id}) };
|
||||||
|
|
||||||
function parseInvite(invite: { [f: string]: string | number | { [name: string]: number } | boolean }): Invite {
|
// FIXME: Please, i beg you, get rid of this horror!
|
||||||
|
function parseInvite(invite: { [f: string]: string | number | { [name: string]: number } | boolean | SentToList }): Invite {
|
||||||
let parsed: Invite = {};
|
let parsed: Invite = {};
|
||||||
parsed.code = invite["code"] as string;
|
parsed.code = invite["code"] as string;
|
||||||
parsed.send_to = invite["send_to"] as string || "";
|
parsed.send_to = invite["send_to"] as string || "";
|
||||||
|
parsed.sent_to = invite["sent_to"] as SentToList || null;
|
||||||
parsed.label = invite["label"] as string || "";
|
parsed.label = invite["label"] as string || "";
|
||||||
parsed.user_label = invite["user_label"] as string || "";
|
parsed.user_label = invite["user_label"] as string || "";
|
||||||
let time = "";
|
let time = "";
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { _get } from "../modules/common.js";
|
import { _get } from "../modules/common.js";
|
||||||
|
import { Template } from "@hrfee/simpletemplate";
|
||||||
|
|
||||||
interface Meta {
|
interface Meta {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -39,6 +40,15 @@ export class lang implements Lang {
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template = (sect: string, key: string, subs: { [key: string]: any }): string => {
|
||||||
|
if (sect == "quantityStrings" || sect == "meta") { return ""; }
|
||||||
|
const map = new Map<string, any>();
|
||||||
|
for (let key of Object.keys(subs)) { map.set(key, subs[key]); }
|
||||||
|
const [out, err] = Template(this._lang[sect][key], map);
|
||||||
|
if (err != null) throw err;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
quantity = (key: string, number: number): string => {
|
quantity = (key: string, number: number): string => {
|
||||||
if (number == 1) {
|
if (number == 1) {
|
||||||
return this._lang.quantityStrings[key].singular.replace("{n}", ""+number)
|
return this._lang.quantityStrings[key].singular.replace("{n}", ""+number)
|
||||||
|
|||||||
@@ -1626,7 +1626,8 @@ class MessageEditor {
|
|||||||
} else {
|
} else {
|
||||||
for (let i = this._templ.conditionals.length-1; i >= 0; i--) {
|
for (let i = this._templ.conditionals.length-1; i >= 0; i--) {
|
||||||
let ci = i % colors.length;
|
let ci = i % colors.length;
|
||||||
innerHTML += '<span class="button ~' + colors[ci] +' @low mb-4" style="margin-left: 0.25rem; margin-right: 0.25rem;"></span>'
|
// FIXME: Store full color strings (with ~) so tailwind sees them.
|
||||||
|
innerHTML += '<span class="button ~' + colors[ci] +' @low"></span>'
|
||||||
}
|
}
|
||||||
this._conditionalsLabel.classList.remove("unfocused");
|
this._conditionalsLabel.classList.remove("unfocused");
|
||||||
this._conditionals.innerHTML = innerHTML
|
this._conditionals.innerHTML = innerHTML
|
||||||
@@ -1754,6 +1755,20 @@ class MessageEditor {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const descriptions = document.getElementsByClassName("editor-syntax-description") as HTMLCollectionOf<HTMLParagraphElement>;
|
||||||
|
for (let el of descriptions) {
|
||||||
|
el.innerHTML = window.lang.template("strings", "syntaxDescription", {
|
||||||
|
"variable": `<span class="font-mono font-bold">{varname}</span>`,
|
||||||
|
"ifTruth": `<span class="font-mono font-bold">{if address}Message sent to {address}{end}</span>`,
|
||||||
|
"ifCompare": `<span class="font-mono font-bold">{if profile == "Friends"}Friend{else if profile != "Admins"}User{end}</span>`
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get rid of nasty CSS
|
||||||
|
window.modals.editor.onclose = () => {
|
||||||
|
this._preview.textContent = ``;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ declare interface GlobalWindow extends Window {
|
|||||||
transitionEvent: string;
|
transitionEvent: string;
|
||||||
animationEvent: string;
|
animationEvent: string;
|
||||||
tabs: Tabs;
|
tabs: Tabs;
|
||||||
invites: inviteList;
|
invites: InviteList;
|
||||||
notifications: NotificationBox;
|
notifications: NotificationBox;
|
||||||
language: string;
|
language: string;
|
||||||
lang: Lang;
|
lang: Lang;
|
||||||
@@ -61,6 +61,42 @@ declare interface GlobalWindow extends Window {
|
|||||||
loginAppearance: string;
|
loginAppearance: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare interface InviteList {
|
||||||
|
empty: boolean;
|
||||||
|
invites: { [code: string]: Invite }
|
||||||
|
add: (invite: Invite) => void;
|
||||||
|
reload: (callback?: () => void) => void;
|
||||||
|
isInviteURL: () => boolean;
|
||||||
|
loadInviteURL: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare interface Invite {
|
||||||
|
code?: string;
|
||||||
|
expiresIn?: string;
|
||||||
|
remainingUses?: string;
|
||||||
|
send_to?: string; // DEPRECATED: use sent_to instead.
|
||||||
|
sent_to?: SentToList;
|
||||||
|
usedBy?: { [name: string]: number };
|
||||||
|
created?: number;
|
||||||
|
notifyExpiry?: boolean;
|
||||||
|
notifyCreation?: boolean;
|
||||||
|
profile?: string;
|
||||||
|
label?: string;
|
||||||
|
user_label?: string;
|
||||||
|
userExpiry?: boolean;
|
||||||
|
userExpiryTime?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare interface SendFailure {
|
||||||
|
address: string;
|
||||||
|
reason: "CheckLogs" | "NoUser" | "MultiUser" | "InvalidAddress";
|
||||||
|
}
|
||||||
|
|
||||||
|
declare interface SentToList {
|
||||||
|
success: string[];
|
||||||
|
failed: SendFailure[];
|
||||||
|
}
|
||||||
|
|
||||||
declare interface Update {
|
declare interface Update {
|
||||||
version: string;
|
version: string;
|
||||||
commit: string;
|
commit: string;
|
||||||
@@ -83,6 +119,7 @@ declare interface Lang {
|
|||||||
strings: (key: string) => string;
|
strings: (key: string) => string;
|
||||||
notif: (key: string) => string;
|
notif: (key: string) => string;
|
||||||
var: (sect: string, key: string, ...subs: string[]) => string;
|
var: (sect: string, key: string, ...subs: string[]) => string;
|
||||||
|
template: (sect: string, key: string, subs: { [key: string]: string }) => string;
|
||||||
quantity: (key: string, number: number) => string;
|
quantity: (key: string, number: number) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,31 +168,6 @@ declare interface Modals {
|
|||||||
backups?: Modal;
|
backups?: Modal;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Invite {
|
|
||||||
code?: string;
|
|
||||||
expiresIn?: string;
|
|
||||||
remainingUses?: string;
|
|
||||||
send_to?: string;
|
|
||||||
usedBy?: { [name: string]: number };
|
|
||||||
created?: number;
|
|
||||||
notifyExpiry?: boolean;
|
|
||||||
notifyCreation?: boolean;
|
|
||||||
profile?: string;
|
|
||||||
label?: string;
|
|
||||||
user_label?: string;
|
|
||||||
userExpiry?: boolean;
|
|
||||||
userExpiryTime?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface inviteList {
|
|
||||||
empty: boolean;
|
|
||||||
invites: { [code: string]: Invite }
|
|
||||||
add: (invite: Invite) => void;
|
|
||||||
reload: (callback?: () => void) => void;
|
|
||||||
isInviteURL: () => boolean;
|
|
||||||
loadInviteURL: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface paginatedDTO {
|
interface paginatedDTO {
|
||||||
last_page: boolean;
|
last_page: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user