Introduced length limits

This commit is contained in:
Eduard Urbach 2019-08-31 16:52:42 +09:00
parent 9338f7bdeb
commit 3ef41b1cdd
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
21 changed files with 110 additions and 80 deletions

6
arn/limits/limits.go Normal file
View File

@ -0,0 +1,6 @@
package limits
const (
DefaultTextMaxLength = 100
DefaultTextAreaMaxLength = 20000
)

View File

@ -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,

View File

@ -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

View File

@ -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
} }

View File

@ -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,

View File

@ -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,

View File

@ -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
} }

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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())

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -9,38 +9,13 @@ 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)

View File

@ -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.

View File

@ -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) {

View File

@ -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 {