Introduced length limits
This commit is contained in:
parent
9338f7bdeb
commit
3ef41b1cdd
6
arn/limits/limits.go
Normal file
6
arn/limits/limits.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package limits
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultTextMaxLength = 100
|
||||||
|
DefaultTextAreaMaxLength = 20000
|
||||||
|
)
|
@ -25,7 +25,7 @@ func AMVs(originalTerm string, maxLength int) []*arn.AMV {
|
|||||||
text := strings.ToLower(amv.Title.Canonical)
|
text := strings.ToLower(amv.Title.Canonical)
|
||||||
similarity := stringutils.AdvancedStringSimilarity(term, text)
|
similarity := stringutils.AdvancedStringSimilarity(term, text)
|
||||||
|
|
||||||
if similarity >= MinimumStringSimilarity {
|
if similarity >= MinStringSimilarity {
|
||||||
results = append(results, &Result{
|
results = append(results, &Result{
|
||||||
obj: amv,
|
obj: amv,
|
||||||
similarity: similarity,
|
similarity: similarity,
|
||||||
@ -36,7 +36,7 @@ func AMVs(originalTerm string, maxLength int) []*arn.AMV {
|
|||||||
text = strings.ToLower(amv.Title.Native)
|
text = strings.ToLower(amv.Title.Native)
|
||||||
similarity = stringutils.AdvancedStringSimilarity(term, text)
|
similarity = stringutils.AdvancedStringSimilarity(term, text)
|
||||||
|
|
||||||
if similarity >= MinimumStringSimilarity {
|
if similarity >= MinStringSimilarity {
|
||||||
results = append(results, &Result{
|
results = append(results, &Result{
|
||||||
obj: amv,
|
obj: amv,
|
||||||
similarity: similarity,
|
similarity: similarity,
|
||||||
|
@ -1,16 +1,24 @@
|
|||||||
package search
|
package search
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/aerogo/flow"
|
"github.com/aerogo/flow"
|
||||||
"github.com/animenotifier/notify.moe/arn"
|
"github.com/animenotifier/notify.moe/arn"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MinimumStringSimilarity is the minimum JaroWinkler distance we accept for search results.
|
// MinStringSimilarity is the minimum JaroWinkler distance we accept for search results.
|
||||||
const MinimumStringSimilarity = 0.89
|
const MinStringSimilarity = 0.89
|
||||||
|
|
||||||
|
// MaxSearchTermLength defines how long the search term can be.
|
||||||
|
const MaxSearchTermLength = 100
|
||||||
|
|
||||||
// popularityDamping reduces the factor of popularity in search results.
|
// popularityDamping reduces the factor of popularity in search results.
|
||||||
const popularityDamping = 0.0009
|
const popularityDamping = 0.0009
|
||||||
|
|
||||||
|
// ErrTermTooLong is used when the search term is too long
|
||||||
|
var ErrTermTooLong = fmt.Errorf("Search term is too long (maximum %d characters)", MaxSearchTermLength)
|
||||||
|
|
||||||
// Result ...
|
// Result ...
|
||||||
type Result struct {
|
type Result struct {
|
||||||
obj interface{}
|
obj interface{}
|
||||||
@ -23,6 +31,10 @@ func All(term string, maxUsers, maxAnime, maxPosts, maxThreads, maxTracks, maxCh
|
|||||||
return nil, nil, nil, nil, nil, nil, nil, nil
|
return nil, nil, nil, nil, nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(term) > MaxSearchTermLength {
|
||||||
|
return nil, nil, nil, nil, nil, nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
userResults []*arn.User
|
userResults []*arn.User
|
||||||
animeResults []*arn.Anime
|
animeResults []*arn.Anime
|
||||||
|
@ -42,7 +42,7 @@ func Anime(originalTerm string, maxLength int) []*arn.Anime {
|
|||||||
// Canonical title
|
// Canonical title
|
||||||
similarity := check(anime.Title.Canonical)
|
similarity := check(anime.Title.Canonical)
|
||||||
|
|
||||||
if similarity >= MinimumStringSimilarity {
|
if similarity >= MinStringSimilarity {
|
||||||
add(anime, similarity)
|
add(anime, similarity)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@ func Anime(originalTerm string, maxLength int) []*arn.Anime {
|
|||||||
// English
|
// English
|
||||||
similarity = check(anime.Title.English)
|
similarity = check(anime.Title.English)
|
||||||
|
|
||||||
if similarity >= MinimumStringSimilarity {
|
if similarity >= MinStringSimilarity {
|
||||||
add(anime, similarity)
|
add(anime, similarity)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -58,7 +58,7 @@ func Anime(originalTerm string, maxLength int) []*arn.Anime {
|
|||||||
// Romaji
|
// Romaji
|
||||||
similarity = check(anime.Title.Romaji)
|
similarity = check(anime.Title.Romaji)
|
||||||
|
|
||||||
if similarity >= MinimumStringSimilarity {
|
if similarity >= MinStringSimilarity {
|
||||||
add(anime, similarity)
|
add(anime, similarity)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -67,7 +67,7 @@ func Anime(originalTerm string, maxLength int) []*arn.Anime {
|
|||||||
for _, synonym := range anime.Title.Synonyms {
|
for _, synonym := range anime.Title.Synonyms {
|
||||||
similarity := check(synonym)
|
similarity := check(synonym)
|
||||||
|
|
||||||
if similarity >= MinimumStringSimilarity {
|
if similarity >= MinStringSimilarity {
|
||||||
add(anime, similarity)
|
add(anime, similarity)
|
||||||
goto nextAnime
|
goto nextAnime
|
||||||
}
|
}
|
||||||
@ -76,7 +76,7 @@ func Anime(originalTerm string, maxLength int) []*arn.Anime {
|
|||||||
// Japanese
|
// Japanese
|
||||||
similarity = check(anime.Title.Japanese)
|
similarity = check(anime.Title.Japanese)
|
||||||
|
|
||||||
if similarity >= MinimumStringSimilarity {
|
if similarity >= MinStringSimilarity {
|
||||||
add(anime, similarity)
|
add(anime, similarity)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ func Companies(originalTerm string, maxLength int) []*arn.Company {
|
|||||||
text := strings.ToLower(stringutils.RemoveSpecialCharacters(company.Name.English))
|
text := strings.ToLower(stringutils.RemoveSpecialCharacters(company.Name.English))
|
||||||
similarity := stringutils.AdvancedStringSimilarity(term, text)
|
similarity := stringutils.AdvancedStringSimilarity(term, text)
|
||||||
|
|
||||||
if similarity >= MinimumStringSimilarity {
|
if similarity >= MinStringSimilarity {
|
||||||
results = append(results, &Result{
|
results = append(results, &Result{
|
||||||
obj: company,
|
obj: company,
|
||||||
similarity: similarity,
|
similarity: similarity,
|
||||||
|
@ -25,7 +25,7 @@ func SoundTracks(originalTerm string, maxLength int) []*arn.SoundTrack {
|
|||||||
text := strings.ToLower(track.Title.Canonical)
|
text := strings.ToLower(track.Title.Canonical)
|
||||||
similarity := stringutils.AdvancedStringSimilarity(term, text)
|
similarity := stringutils.AdvancedStringSimilarity(term, text)
|
||||||
|
|
||||||
if similarity >= MinimumStringSimilarity {
|
if similarity >= MinStringSimilarity {
|
||||||
results = append(results, &Result{
|
results = append(results, &Result{
|
||||||
obj: track,
|
obj: track,
|
||||||
similarity: similarity,
|
similarity: similarity,
|
||||||
@ -36,7 +36,7 @@ func SoundTracks(originalTerm string, maxLength int) []*arn.SoundTrack {
|
|||||||
text = strings.ToLower(track.Title.Native)
|
text = strings.ToLower(track.Title.Native)
|
||||||
similarity = stringutils.AdvancedStringSimilarity(term, text)
|
similarity = stringutils.AdvancedStringSimilarity(term, text)
|
||||||
|
|
||||||
if similarity >= MinimumStringSimilarity {
|
if similarity >= MinStringSimilarity {
|
||||||
results = append(results, &Result{
|
results = append(results, &Result{
|
||||||
obj: track,
|
obj: track,
|
||||||
similarity: similarity,
|
similarity: similarity,
|
||||||
|
@ -23,7 +23,7 @@ func Users(originalTerm string, maxLength int) []*arn.User {
|
|||||||
// Similarity check
|
// Similarity check
|
||||||
similarity := stringutils.AdvancedStringSimilarity(term, text)
|
similarity := stringutils.AdvancedStringSimilarity(term, text)
|
||||||
|
|
||||||
if similarity < MinimumStringSimilarity {
|
if similarity < MinStringSimilarity {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
component FuzzySearch(value string)
|
component FuzzySearch(value string)
|
||||||
input#search.action(data-action="search", data-trigger="input", type="search", autocomplete="off", autocorrect="off", autocapitalize="none", spellcheck="false", placeholder="Search...", title="Shortcut: F", value=value)
|
input#search.action(data-action="search", data-trigger="input", type="search", autocomplete="off", autocorrect="off", autocapitalize="none", spellcheck="false", placeholder="Search...", title="Shortcut: F", maxlength="100", value=value)
|
@ -1,7 +1,7 @@
|
|||||||
component InputText(id string, value string, label string, placeholder string)
|
component InputText(id string, value string, label string, placeholder string, maxLength int)
|
||||||
.widget-section
|
.widget-section
|
||||||
label(for=id)= label + ":"
|
label(for=id)= label + ":"
|
||||||
input.widget-ui-element.action(id=id, data-field=id, type="text", value=value, placeholder=placeholder, title=placeholder, data-action="save", data-trigger="change")
|
input.widget-ui-element.action(id=id, data-field=id, type="text", value=value, placeholder=placeholder, title=placeholder, data-action="save", data-trigger="change", maxlength=maxLength)
|
||||||
|
|
||||||
component InputBool(id string, value bool, label string, title string)
|
component InputBool(id string, value bool, label string, title string)
|
||||||
.widget-section
|
.widget-section
|
||||||
@ -16,10 +16,10 @@ component InputBool(id string, value bool, label string, title string)
|
|||||||
span OFF
|
span OFF
|
||||||
//- input.widget-ui-element.action(id=id, data-field=id, type="checkbox", value=value, checked=value, data-action="save", data-trigger="change")
|
//- input.widget-ui-element.action(id=id, data-field=id, type="checkbox", value=value, checked=value, data-action="save", data-trigger="change")
|
||||||
|
|
||||||
component InputTextArea(id string, value string, label string, placeholder string)
|
component InputTextArea(id string, value string, label string, placeholder string, maxLength int)
|
||||||
.widget-section
|
.widget-section
|
||||||
label(for=id)= label + ":"
|
label(for=id)= label + ":"
|
||||||
textarea.widget-ui-element.action(id=id, data-field=id, placeholder=placeholder, title=placeholder, data-action="save", data-trigger="change")= value
|
textarea.widget-ui-element.action(id=id, data-field=id, placeholder=placeholder, title=placeholder, data-action="save", data-trigger="change", maxlength=maxLength)= value
|
||||||
|
|
||||||
component InputNumber(id string, value float64, label string, placeholder string, min string, max string, step string)
|
component InputNumber(id string, value float64, label string, placeholder string, min string, max string, step string)
|
||||||
.widget-section
|
.widget-section
|
||||||
|
@ -4,7 +4,7 @@ component NewPostArea(parent arn.PostParent, user *arn.User, placeholder string)
|
|||||||
.post-author
|
.post-author
|
||||||
Avatar(user)
|
Avatar(user)
|
||||||
|
|
||||||
textarea#new-post-text.post-content(placeholder=placeholder + "...", aria-label=placeholder)
|
textarea#new-post-text.post-content(placeholder=placeholder + "...", aria-label=placeholder, maxlength=limits.DefaultTextAreaMaxLength)
|
||||||
|
|
||||||
if !arn.IsLocked(parent)
|
if !arn.IsLocked(parent)
|
||||||
NewPostActions(parent, false)
|
NewPostActions(parent, false)
|
||||||
|
@ -40,7 +40,7 @@ component Postable(post arn.Postable, user *arn.User, includeReplies bool, showP
|
|||||||
if post.TypeName() == "Thread"
|
if post.TypeName() == "Thread"
|
||||||
input.post-title-input.hidden(id="title-" + post.GetID(), value=post.TitleByUser(user), type="text", placeholder="Thread title")
|
input.post-title-input.hidden(id="title-" + post.GetID(), value=post.TitleByUser(user), type="text", placeholder="Thread title")
|
||||||
|
|
||||||
textarea.post-text-input.hidden(id="source-" + post.GetID())= post.GetText()
|
textarea.post-text-input.hidden(id="source-" + post.GetID(), maxlength=limits.DefaultTextAreaMaxLength)= post.GetText()
|
||||||
|
|
||||||
.buttons.hidden(id="edit-toolbar-" + post.GetID())
|
.buttons.hidden(id="edit-toolbar-" + post.GetID())
|
||||||
a.button.post-save.action(data-action="savePost", data-trigger="click", data-id=post.GetID())
|
a.button.post-save.action(data-action="savePost", data-trigger="click", data-id=post.GetID())
|
||||||
|
@ -26,7 +26,7 @@ component AnimeListItem(viewUser *arn.User, item *arn.AnimeListItem, anime *arn.
|
|||||||
InputBool("Private", item.Private, "Private", "Hidden entry")
|
InputBool("Private", item.Private, "Private", "Hidden entry")
|
||||||
|
|
||||||
.mountable
|
.mountable
|
||||||
InputTextArea("Notes", item.Notes, "Notes", "Your notes")
|
InputTextArea("Notes", item.Notes, "Notes", "Your notes", 2000)
|
||||||
|
|
||||||
.buttons.mountable
|
.buttons.mountable
|
||||||
a.button.mountable(href="/+" + viewUser.Nick + "/animelist/" + item.Status)
|
a.button.mountable(href="/+" + viewUser.Nick + "/animelist/" + item.Status)
|
||||||
|
@ -5,7 +5,7 @@ component NewThread(user *arn.User)
|
|||||||
.widget
|
.widget
|
||||||
input#title.widget-ui-element(type="text", placeholder="Title")
|
input#title.widget-ui-element(type="text", placeholder="Title")
|
||||||
|
|
||||||
textarea#text.widget-ui-element(placeholder="Content")
|
textarea#text.widget-ui-element(placeholder="Content", maxlength=limits.DefaultTextAreaMaxLength)
|
||||||
|
|
||||||
select#tag.widget-ui-element(value="general")
|
select#tag.widget-ui-element(value="general")
|
||||||
option(value="general") General
|
option(value="general") General
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
component MultiSearch
|
component MultiSearch
|
||||||
textarea.action(data-action="multiSearchAnime", data-trigger="change", placeholder="Paste multiple anime titles here, one per line")
|
textarea.action(data-action="multiSearchAnime", data-trigger="change", placeholder="Paste multiple anime titles here, one per line", maxlength=limits.DefaultTextAreaMaxLength)
|
||||||
#multi-search-anime
|
#multi-search-anime
|
@ -1,7 +1,7 @@
|
|||||||
package search
|
package search
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"net/http"
|
||||||
|
|
||||||
"github.com/animenotifier/notify.moe/utils"
|
"github.com/animenotifier/notify.moe/utils"
|
||||||
|
|
||||||
@ -24,9 +24,12 @@ const (
|
|||||||
// Get search page.
|
// Get search page.
|
||||||
func Get(ctx aero.Context) error {
|
func Get(ctx aero.Context) error {
|
||||||
term := ctx.Get("term")
|
term := ctx.Get("term")
|
||||||
term = strings.TrimPrefix(term, "/")
|
|
||||||
user := utils.GetUser(ctx)
|
user := utils.GetUser(ctx)
|
||||||
|
|
||||||
|
if len(term) > search.MaxSearchTermLength {
|
||||||
|
ctx.SetStatus(http.StatusRequestEntityTooLarge)
|
||||||
|
}
|
||||||
|
|
||||||
users, animes, posts, threads, tracks, characters, amvs, companies := search.All(
|
users, animes, posts, threads, tracks, characters, amvs, companies := search.All(
|
||||||
term,
|
term,
|
||||||
maxUsers,
|
maxUsers,
|
||||||
@ -50,9 +53,12 @@ func GetEmptySearch(ctx aero.Context) error {
|
|||||||
// Anime search.
|
// Anime search.
|
||||||
func Anime(ctx aero.Context) error {
|
func Anime(ctx aero.Context) error {
|
||||||
term := ctx.Get("term")
|
term := ctx.Get("term")
|
||||||
term = strings.TrimPrefix(term, "/")
|
|
||||||
user := utils.GetUser(ctx)
|
user := utils.GetUser(ctx)
|
||||||
|
|
||||||
|
if len(term) > search.MaxSearchTermLength {
|
||||||
|
ctx.SetStatus(http.StatusRequestEntityTooLarge)
|
||||||
|
}
|
||||||
|
|
||||||
animes := search.Anime(term, maxAnime)
|
animes := search.Anime(term, maxAnime)
|
||||||
return ctx.HTML(components.AnimeSearchResults(animes, user))
|
return ctx.HTML(components.AnimeSearchResults(animes, user))
|
||||||
}
|
}
|
||||||
@ -60,9 +66,12 @@ func Anime(ctx aero.Context) error {
|
|||||||
// Characters search.
|
// Characters search.
|
||||||
func Characters(ctx aero.Context) error {
|
func Characters(ctx aero.Context) error {
|
||||||
term := ctx.Get("term")
|
term := ctx.Get("term")
|
||||||
term = strings.TrimPrefix(term, "/")
|
|
||||||
user := utils.GetUser(ctx)
|
user := utils.GetUser(ctx)
|
||||||
|
|
||||||
|
if len(term) > search.MaxSearchTermLength {
|
||||||
|
ctx.SetStatus(http.StatusRequestEntityTooLarge)
|
||||||
|
}
|
||||||
|
|
||||||
characters := search.Characters(term, maxCharacters)
|
characters := search.Characters(term, maxCharacters)
|
||||||
return ctx.HTML(components.CharacterSearchResults(characters, user))
|
return ctx.HTML(components.CharacterSearchResults(characters, user))
|
||||||
}
|
}
|
||||||
@ -70,29 +79,38 @@ func Characters(ctx aero.Context) error {
|
|||||||
// Posts search.
|
// Posts search.
|
||||||
func Posts(ctx aero.Context) error {
|
func Posts(ctx aero.Context) error {
|
||||||
term := ctx.Get("term")
|
term := ctx.Get("term")
|
||||||
term = strings.TrimPrefix(term, "/")
|
|
||||||
user := utils.GetUser(ctx)
|
user := utils.GetUser(ctx)
|
||||||
posts := search.Posts(term, maxPosts)
|
|
||||||
|
|
||||||
|
if len(term) > search.MaxSearchTermLength {
|
||||||
|
ctx.SetStatus(http.StatusRequestEntityTooLarge)
|
||||||
|
}
|
||||||
|
|
||||||
|
posts := search.Posts(term, maxPosts)
|
||||||
return ctx.HTML(components.PostsSearchResults(posts, user))
|
return ctx.HTML(components.PostsSearchResults(posts, user))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Threads search.
|
// Threads search.
|
||||||
func Threads(ctx aero.Context) error {
|
func Threads(ctx aero.Context) error {
|
||||||
term := ctx.Get("term")
|
term := ctx.Get("term")
|
||||||
term = strings.TrimPrefix(term, "/")
|
|
||||||
user := utils.GetUser(ctx)
|
user := utils.GetUser(ctx)
|
||||||
threads := search.Threads(term, maxThreads)
|
|
||||||
|
|
||||||
|
if len(term) > search.MaxSearchTermLength {
|
||||||
|
ctx.SetStatus(http.StatusRequestEntityTooLarge)
|
||||||
|
}
|
||||||
|
|
||||||
|
threads := search.Threads(term, maxThreads)
|
||||||
return ctx.HTML(components.ThreadsSearchResults(threads, user))
|
return ctx.HTML(components.ThreadsSearchResults(threads, user))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SoundTracks search.
|
// SoundTracks search.
|
||||||
func SoundTracks(ctx aero.Context) error {
|
func SoundTracks(ctx aero.Context) error {
|
||||||
term := ctx.Get("term")
|
term := ctx.Get("term")
|
||||||
term = strings.TrimPrefix(term, "/")
|
|
||||||
user := utils.GetUser(ctx)
|
user := utils.GetUser(ctx)
|
||||||
|
|
||||||
|
if len(term) > search.MaxSearchTermLength {
|
||||||
|
ctx.SetStatus(http.StatusRequestEntityTooLarge)
|
||||||
|
}
|
||||||
|
|
||||||
tracks := search.SoundTracks(term, maxSoundTracks)
|
tracks := search.SoundTracks(term, maxSoundTracks)
|
||||||
return ctx.HTML(components.SoundTrackSearchResults(tracks, user))
|
return ctx.HTML(components.SoundTrackSearchResults(tracks, user))
|
||||||
}
|
}
|
||||||
@ -100,9 +118,12 @@ func SoundTracks(ctx aero.Context) error {
|
|||||||
// AMVs search.
|
// AMVs search.
|
||||||
func AMVs(ctx aero.Context) error {
|
func AMVs(ctx aero.Context) error {
|
||||||
term := ctx.Get("term")
|
term := ctx.Get("term")
|
||||||
term = strings.TrimPrefix(term, "/")
|
|
||||||
user := utils.GetUser(ctx)
|
user := utils.GetUser(ctx)
|
||||||
|
|
||||||
|
if len(term) > search.MaxSearchTermLength {
|
||||||
|
ctx.SetStatus(http.StatusRequestEntityTooLarge)
|
||||||
|
}
|
||||||
|
|
||||||
amvs := search.AMVs(term, maxAMVs)
|
amvs := search.AMVs(term, maxAMVs)
|
||||||
return ctx.HTML(components.AMVSearchResults(amvs, user))
|
return ctx.HTML(components.AMVSearchResults(amvs, user))
|
||||||
}
|
}
|
||||||
@ -110,7 +131,10 @@ func AMVs(ctx aero.Context) error {
|
|||||||
// Users search.
|
// Users search.
|
||||||
func Users(ctx aero.Context) error {
|
func Users(ctx aero.Context) error {
|
||||||
term := ctx.Get("term")
|
term := ctx.Get("term")
|
||||||
term = strings.TrimPrefix(term, "/")
|
|
||||||
|
if len(term) > search.MaxSearchTermLength {
|
||||||
|
ctx.SetStatus(http.StatusRequestEntityTooLarge)
|
||||||
|
}
|
||||||
|
|
||||||
users := search.Users(term, maxUsers)
|
users := search.Users(term, maxUsers)
|
||||||
return ctx.HTML(components.UserSearchResults(users))
|
return ctx.HTML(components.UserSearchResults(users))
|
||||||
@ -119,7 +143,10 @@ func Users(ctx aero.Context) error {
|
|||||||
// Companies search.
|
// Companies search.
|
||||||
func Companies(ctx aero.Context) error {
|
func Companies(ctx aero.Context) error {
|
||||||
term := ctx.Get("term")
|
term := ctx.Get("term")
|
||||||
term = strings.TrimPrefix(term, "/")
|
|
||||||
|
if len(term) > search.MaxSearchTermLength {
|
||||||
|
ctx.SetStatus(http.StatusRequestEntityTooLarge)
|
||||||
|
}
|
||||||
|
|
||||||
companies := search.Companies(term, maxCompanies)
|
companies := search.Companies(term, maxCompanies)
|
||||||
return ctx.HTML(components.CompanySearchResults(companies))
|
return ctx.HTML(components.CompanySearchResults(companies))
|
||||||
|
@ -9,20 +9,20 @@ component SettingsAccounts(user *arn.User)
|
|||||||
Icon("cubes")
|
Icon("cubes")
|
||||||
span Accounts
|
span Accounts
|
||||||
|
|
||||||
InputText("Accounts.AniList.Nick", user.Accounts.AniList.Nick, "AniList", "Your username on anilist.co")
|
InputText("Accounts.AniList.Nick", user.Accounts.AniList.Nick, "AniList", "Your username on anilist.co", 30)
|
||||||
InputText("Accounts.Kitsu.Nick", user.Accounts.Kitsu.Nick, "Kitsu", "Your username on kitsu.io")
|
InputText("Accounts.Kitsu.Nick", user.Accounts.Kitsu.Nick, "Kitsu", "Your username on kitsu.io", 30)
|
||||||
InputText("Accounts.MyAnimeList.Nick", user.Accounts.MyAnimeList.Nick, "MyAnimeList", "Your username on myanimelist.net")
|
InputText("Accounts.MyAnimeList.Nick", user.Accounts.MyAnimeList.Nick, "MyAnimeList", "Your username on myanimelist.net", 30)
|
||||||
InputText("Accounts.Discord.Nick", user.Accounts.Discord.Nick, "Discord", "Your username on Discord")
|
InputText("Accounts.Discord.Nick", user.Accounts.Discord.Nick, "Discord", "Your username on Discord", 30)
|
||||||
|
|
||||||
.widget.mountable(data-api="/api/user/" + user.ID)
|
.widget.mountable(data-api="/api/user/" + user.ID)
|
||||||
h3.widget-title
|
h3.widget-title
|
||||||
Icon("gamepad")
|
Icon("gamepad")
|
||||||
span Games
|
span Games
|
||||||
|
|
||||||
InputText("Accounts.FinalFantasyXIV.Nick", user.Accounts.FinalFantasyXIV.Nick, "Final Fantasy XIV", "Your character name on FFXIV")
|
InputText("Accounts.FinalFantasyXIV.Nick", user.Accounts.FinalFantasyXIV.Nick, "Final Fantasy XIV", "Your character name on FFXIV", 30)
|
||||||
InputSelection("Accounts.FinalFantasyXIV.Server", user.Accounts.FinalFantasyXIV.Server, "Final Fantasy XIV - World", "Your server/world on FFXIV", arn.DataLists["ffxiv-servers"])
|
InputSelection("Accounts.FinalFantasyXIV.Server", user.Accounts.FinalFantasyXIV.Server, "Final Fantasy XIV - World", "Your server/world on FFXIV", arn.DataLists["ffxiv-servers"])
|
||||||
InputText("Accounts.Osu.Nick", user.Accounts.Osu.Nick, "Osu", "Your username on osu.ppy.sh")
|
InputText("Accounts.Osu.Nick", user.Accounts.Osu.Nick, "Osu", "Your username on osu.ppy.sh", 30)
|
||||||
InputText("Accounts.Overwatch.BattleTag", user.Accounts.Overwatch.BattleTag, "Overwatch", "Your battletag on Overwatch")
|
InputText("Accounts.Overwatch.BattleTag", user.Accounts.Overwatch.BattleTag, "Overwatch", "Your battletag on Overwatch", 30)
|
||||||
|
|
||||||
.widget.mountable
|
.widget.mountable
|
||||||
h3.widget-title
|
h3.widget-title
|
||||||
|
@ -10,8 +10,8 @@ component SettingsInfo(user *arn.User)
|
|||||||
span Info
|
span Info
|
||||||
|
|
||||||
InputSelection("Gender", user.Gender, "Gender", "Your gender", arn.DataLists["genders"])
|
InputSelection("Gender", user.Gender, "Gender", "Your gender", arn.DataLists["genders"])
|
||||||
InputText("BirthDay", user.BirthDay, "Birthday", "YYYY-MM-DD")
|
InputText("BirthDay", user.BirthDay, "Birthday", "YYYY-MM-DD", len("YYYY-MM-DD"))
|
||||||
InputText("Website", user.Website, "Website", "Your homepage")
|
InputText("Website", user.Website, "Website", "Your homepage", 100)
|
||||||
|
|
||||||
.widget.mountable(data-api="/api/settings/" + user.ID)
|
.widget.mountable(data-api="/api/settings/" + user.ID)
|
||||||
h3.widget-title
|
h3.widget-title
|
||||||
|
@ -9,39 +9,14 @@ component SettingsPersonal(user *arn.User)
|
|||||||
Icon("user")
|
Icon("user")
|
||||||
span Personal
|
span Personal
|
||||||
|
|
||||||
InputText("Nick", user.Nick, "Username", "Your username on notify.moe")
|
InputText("Nick", user.Nick, "Username", "Your username on notify.moe", 25)
|
||||||
InputTextArea("Introduction", user.Introduction, "Introduction", "Tell us a little bit about yourself")
|
InputTextArea("Introduction", user.Introduction, "Introduction", "Tell us a little bit about yourself", 2000)
|
||||||
|
|
||||||
.widget.mountable(data-api="/api/settings/" + user.ID)
|
.widget.mountable(data-api="/api/settings/" + user.ID)
|
||||||
h3.widget-title
|
h3.widget-title
|
||||||
Icon("camera")
|
Icon("camera")
|
||||||
span Avatar
|
span Avatar
|
||||||
|
|
||||||
//- .widget-section
|
|
||||||
//- label(for="Avatar.Source") Source:
|
|
||||||
//- select.widget-ui-element.action(id="Avatar.Source", data-field="Avatar.Source", value=user.Settings().Avatar.Source, data-action="save", data-trigger="change")
|
|
||||||
//- option(value="") Automatic
|
|
||||||
//- option(value="Gravatar") Gravatar
|
|
||||||
//- option(value="URL") Link
|
|
||||||
//- option(value="FileSystem") Upload
|
|
||||||
|
|
||||||
//- //- URL input
|
|
||||||
//- if user.Settings().Avatar.Source == "URL"
|
|
||||||
//- InputText("Avatar.SourceURL", user.Settings().Avatar.SourceURL, "Link", "Post the link to the image here")
|
|
||||||
|
|
||||||
//- //- Gravatar preview image
|
|
||||||
//- if user.Settings().Avatar.Source == "Gravatar" || (user.Settings().Avatar.Source == "" && user.Avatar.Source == "Gravatar")
|
|
||||||
//- .profile-image-container.avatar-preview
|
|
||||||
//- img.profile-image.mountable(src=user.Gravatar(), alt="Gravatar (" + user.Email + ")", title="Gravatar (" + user.Email + ")")
|
|
||||||
|
|
||||||
//- //- URL preview image
|
|
||||||
//- if user.Settings().Avatar.Source == "URL" && user.Settings().Avatar.SourceURL != ""
|
|
||||||
//- .profile-image-container.avatar-preview
|
|
||||||
//- img.profile-image.mountable(src=strings.Replace(user.Settings().Avatar.SourceURL, "http://", "https://", 1), alt="Avatar preview")
|
|
||||||
|
|
||||||
//- //- File upload
|
|
||||||
//- if user.Settings().Avatar.Source == "FileSystem"
|
|
||||||
|
|
||||||
AvatarInput(user)
|
AvatarInput(user)
|
||||||
|
|
||||||
.widget.mountable(data-api="/api/settings/" + user.ID)
|
.widget.mountable(data-api="/api/settings/" + user.ID)
|
||||||
|
@ -10,7 +10,7 @@ component Welcome(user *arn.User)
|
|||||||
|
|
||||||
.mountable(data-api="/api/user/" + user.ID)
|
.mountable(data-api="/api/user/" + user.ID)
|
||||||
//- [^\\W\\s\\d]{1,24}[A-Za-z]{1}
|
//- [^\\W\\s\\d]{1,24}[A-Za-z]{1}
|
||||||
InputText("Nick", user.CleanNick(), "Nick", "Your username on notify.moe")
|
InputText("Nick", user.CleanNick(), "Nick", "Your username on notify.moe", 25)
|
||||||
|
|
||||||
.footer.mountable
|
.footer.mountable
|
||||||
p Only letters and underscore.
|
p Only letters and underscore.
|
||||||
@ -26,7 +26,7 @@ component Welcome(user *arn.User)
|
|||||||
p.welcome-text.mountable Write a little introduction for your profile.
|
p.welcome-text.mountable Write a little introduction for your profile.
|
||||||
|
|
||||||
.mountable(data-api="/api/user/" + user.ID)
|
.mountable(data-api="/api/user/" + user.ID)
|
||||||
InputTextArea("Introduction", user.Introduction, "Introduction", "Tell us a little bit about yourself")
|
InputTextArea("Introduction", user.Introduction, "Introduction", "Tell us a little bit about yourself", 2000)
|
||||||
|
|
||||||
.footer.mountable
|
.footer.mountable
|
||||||
p Markdown allowed.
|
p Markdown allowed.
|
||||||
|
@ -34,9 +34,6 @@ const fetchOptions: RequestInit = {
|
|||||||
credentials: "same-origin"
|
credentials: "same-origin"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error message
|
|
||||||
const searchErrorMessage = "Looks like the website is offline, please retry later."
|
|
||||||
|
|
||||||
// Speech recognition
|
// Speech recognition
|
||||||
let recognition: SpeechRecognition
|
let recognition: SpeechRecognition
|
||||||
|
|
||||||
@ -122,7 +119,7 @@ export async function search(arn: AnimeNotifier, search: HTMLInputElement, evt?:
|
|||||||
// Start searching anime
|
// Start searching anime
|
||||||
fetch("/_/anime-search/" + term, fetchOptions)
|
fetch("/_/anime-search/" + term, fetchOptions)
|
||||||
.then(showResponseInElement(arn, url, "anime", results["anime"]))
|
.then(showResponseInElement(arn, url, "anime", results["anime"]))
|
||||||
.catch(_ => arn.statusMessage.showError(searchErrorMessage))
|
.catch(err => arn.statusMessage.showError(err))
|
||||||
|
|
||||||
requestIdleCallback(() => {
|
requestIdleCallback(() => {
|
||||||
// Check that the term hasn't changed in the meantime
|
// Check that the term hasn't changed in the meantime
|
||||||
@ -138,7 +135,7 @@ export async function search(arn: AnimeNotifier, search: HTMLInputElement, evt?:
|
|||||||
|
|
||||||
fetch(`/_/${key}-search/` + term, fetchOptions)
|
fetch(`/_/${key}-search/` + term, fetchOptions)
|
||||||
.then(showResponseInElement(arn, url, key, results[key]))
|
.then(showResponseInElement(arn, url, key, results[key]))
|
||||||
.catch(_ => arn.statusMessage.showError(searchErrorMessage))
|
.catch(err => arn.statusMessage.showError(err))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/aerogo/api"
|
"github.com/aerogo/api"
|
||||||
"github.com/animenotifier/notify.moe/arn"
|
"github.com/animenotifier/notify.moe/arn"
|
||||||
|
"github.com/animenotifier/notify.moe/arn/limits"
|
||||||
"github.com/animenotifier/notify.moe/components"
|
"github.com/animenotifier/notify.moe/components"
|
||||||
"github.com/animenotifier/notify.moe/utils"
|
"github.com/animenotifier/notify.moe/utils"
|
||||||
)
|
)
|
||||||
@ -214,7 +215,13 @@ func renderStringField(b io.StringWriter, v *reflect.Value, field reflect.Struct
|
|||||||
b.WriteString(components.InputSelection(idPrefix+field.Name, fieldValue.String(), field.Name, field.Tag.Get("tooltip"), values))
|
b.WriteString(components.InputSelection(idPrefix+field.Name, fieldValue.String(), field.Name, field.Tag.Get("tooltip"), values))
|
||||||
|
|
||||||
case field.Tag.Get("type") == "textarea":
|
case field.Tag.Get("type") == "textarea":
|
||||||
b.WriteString(components.InputTextArea(idPrefix+field.Name, fieldValue.String(), field.Name, field.Tag.Get("tooltip")))
|
maxLength, err := strconv.Atoi(field.Tag.Get("maxLength"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
maxLength = limits.DefaultTextAreaMaxLength
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteString(components.InputTextArea(idPrefix+field.Name, fieldValue.String(), field.Name, field.Tag.Get("tooltip"), maxLength))
|
||||||
|
|
||||||
case field.Tag.Get("type") == "upload":
|
case field.Tag.Get("type") == "upload":
|
||||||
endpoint := field.Tag.Get("endpoint")
|
endpoint := field.Tag.Get("endpoint")
|
||||||
@ -224,7 +231,13 @@ func renderStringField(b io.StringWriter, v *reflect.Value, field reflect.Struct
|
|||||||
b.WriteString(components.InputFileUpload(idPrefix+field.Name, field.Name, field.Tag.Get("filetype"), endpoint))
|
b.WriteString(components.InputFileUpload(idPrefix+field.Name, field.Name, field.Tag.Get("filetype"), endpoint))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
b.WriteString(components.InputText(idPrefix+field.Name, fieldValue.String(), field.Name, field.Tag.Get("tooltip")))
|
maxLength, err := strconv.Atoi(field.Tag.Get("maxLength"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
maxLength = limits.DefaultTextMaxLength
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteString(components.InputText(idPrefix+field.Name, fieldValue.String(), field.Name, field.Tag.Get("tooltip"), maxLength))
|
||||||
}
|
}
|
||||||
|
|
||||||
if showPreview {
|
if showPreview {
|
||||||
|
Loading…
Reference in New Issue
Block a user