New user profile
This commit is contained in:
parent
a1c11a2eae
commit
d5dff615c1
@ -1,19 +1,15 @@
|
||||
component Comments(parent arn.PostParent, user *arn.User)
|
||||
.thread
|
||||
.posts
|
||||
if user == nil && parent.CountPosts() == 0
|
||||
p.no-data.mountable No comments have been written yet.
|
||||
else
|
||||
each post in parent.Posts()
|
||||
Postable(post, user, true, "", "")
|
||||
|
||||
if user != nil
|
||||
if arn.IsLocked(parent)
|
||||
footer.footer.mountable
|
||||
p.text-center= "This " + strings.ToLower(reflect.TypeOf(parent).Elem().Name()) + " is locked."
|
||||
else
|
||||
NewPostArea(user, "Comment")
|
||||
NewPostArea(parent, user, "Comment")
|
||||
|
||||
.buttons
|
||||
if !arn.IsLocked(parent)
|
||||
NewPostActions(reflect.TypeOf(parent).Elem().Name(), parent.GetID(), false)
|
||||
if user == nil && parent.CountPosts() == 0
|
||||
p.no-data.mountable No comments have been written yet.
|
||||
else
|
||||
each post in parent.PostsRelevantFirst(5)
|
||||
Postable(post, user, true, "", "")
|
@ -1,4 +1,4 @@
|
||||
component NewPostArea(user *arn.User, placeholder string)
|
||||
component NewPostArea(parent arn.PostParent, user *arn.User, placeholder string)
|
||||
#new-post.post.mountable
|
||||
.post-parent
|
||||
.post-author
|
||||
@ -6,11 +6,18 @@ component NewPostArea(user *arn.User, placeholder string)
|
||||
|
||||
textarea#new-post-text.post-content(placeholder=placeholder + "...", aria-label=placeholder)
|
||||
|
||||
component NewPostActions(parentType string, parentID string, cancelButton bool)
|
||||
#new-post-actions.buttons
|
||||
button#reply-button.mountable.action(data-action="createPost", data-trigger="click", data-parent-type=parentType, data-parent-id=parentID)
|
||||
if !arn.IsLocked(parent)
|
||||
NewPostActions(parent, false)
|
||||
|
||||
component NewPostActions(parent arn.PostParent, cancelButton bool)
|
||||
.buttons.new-post-actions
|
||||
button#reply-button.mountable.action(data-action="createPost", data-trigger="click", data-parent-type=parent.TypeName(), data-parent-id=parent.GetID())
|
||||
Icon("mail-reply")
|
||||
span Reply
|
||||
|
||||
if parent.TypeName() == "Post" || parent.TypeName() == "Thread"
|
||||
span= "Reply to " + parent.Creator().Nick
|
||||
else
|
||||
span Submit
|
||||
|
||||
if cancelButton
|
||||
button#reply-cancel-button.mountable.action(data-action="cancelReply", data-trigger="click")
|
||||
|
@ -1,5 +1,5 @@
|
||||
component Postable(post arn.Postable, user *arn.User, includeReplies bool, headerContent string, highlightAuthorID string)
|
||||
.post.mountable(id=strings.ToLower(post.Type()) + "-" + fmt.Sprint(post.GetID()), data-pro=post.Creator().IsPro(), data-api="/api/" + strings.ToLower(post.Type()) + "/" + post.GetID())
|
||||
.post.mountable(id=strings.ToLower(post.TypeName()) + "-" + fmt.Sprint(post.GetID()), data-pro=post.Creator().IsPro(), data-api="/api/" + strings.ToLower(post.TypeName()) + "/" + post.GetID())
|
||||
.post-parent
|
||||
.post-author
|
||||
Avatar(post.Creator())
|
||||
@ -12,7 +12,7 @@ component Postable(post arn.Postable, user *arn.User, includeReplies bool, heade
|
||||
|
||||
if user != nil && user.ID == post.Creator().ID
|
||||
.post-edit-interface
|
||||
if post.Type() == "Thread"
|
||||
if post.TypeName() == "Thread"
|
||||
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()
|
||||
@ -48,7 +48,7 @@ component Postable(post arn.Postable, user *arn.User, includeReplies bool, heade
|
||||
a.post-tool.post-edit.tip.action(data-action="editPost", data-trigger="click", data-id=post.GetID(), aria-label="Edit")
|
||||
Icon("pencil")
|
||||
|
||||
if post.Type() != "Thread"
|
||||
if post.TypeName() != "Thread"
|
||||
if user != nil && (user.Role == "admin" || user.Role == "editor")
|
||||
a.post-tool.post-delete.tip.action(data-action="deletePost", data-trigger="click", data-id=post.GetID(), aria-label="Delete")
|
||||
Icon("trash")
|
||||
|
@ -37,7 +37,7 @@ func fetchActivities(user *arn.User, followedOnly bool) []arn.Activity {
|
||||
return false
|
||||
}
|
||||
|
||||
if activity.Type() == "ActivityCreate" {
|
||||
if activity.TypeName() == "ActivityCreate" {
|
||||
obj := activity.(*arn.ActivityCreate).Object()
|
||||
|
||||
if obj == nil {
|
||||
@ -48,7 +48,7 @@ func fetchActivities(user *arn.User, followedOnly bool) []arn.Activity {
|
||||
return !isDraftable || !draft.GetIsDraft()
|
||||
}
|
||||
|
||||
if activity.Type() == "ActivityConsumeAnime" {
|
||||
if activity.TypeName() == "ActivityConsumeAnime" {
|
||||
return activity.(*arn.ActivityConsumeAnime).Anime() != nil
|
||||
}
|
||||
|
||||
|
@ -32,20 +32,20 @@ component Activity(activity arn.Activity, user *arn.User)
|
||||
.post-content
|
||||
.activity-header
|
||||
.activity-parent
|
||||
if activity.Type() == "ActivityCreate"
|
||||
if activity.TypeName() == "ActivityCreate"
|
||||
ActivityCreateTitle(activity.(*arn.ActivityCreate), user)
|
||||
else if activity.Type() == "ActivityConsumeAnime"
|
||||
else if activity.TypeName() == "ActivityConsumeAnime"
|
||||
ActivityConsumeAnimeTitle(activity.(*arn.ActivityConsumeAnime), user)
|
||||
|
||||
if user != nil && user.ID == activity.GetCreatedBy() && activity.Type() == "ActivityConsumeAnime"
|
||||
button.activity-action.tip.action(data-action="deleteObject", data-trigger="click", aria-label="Delete", data-return-path="/activity", data-confirm-type="activity", data-api=fmt.Sprintf("/api/%s/%s", strings.ToLower(activity.Type()), activity.GetID()))
|
||||
if user != nil && user.ID == activity.GetCreatedBy() && activity.TypeName() == "ActivityConsumeAnime"
|
||||
button.activity-action.tip.action(data-action="deleteObject", data-trigger="click", aria-label="Delete", data-return-path="/activity", data-confirm-type="activity", data-api=fmt.Sprintf("/api/%s/%s", strings.ToLower(activity.TypeName()), activity.GetID()))
|
||||
RawIcon("trash")
|
||||
|
||||
.activity-date.utc-date(data-date=activity.GetCreated())
|
||||
|
||||
if activity.Type() == "ActivityCreate"
|
||||
if activity.TypeName() == "ActivityCreate"
|
||||
ActivityCreateText(activity.(*arn.ActivityCreate), user)
|
||||
else if activity.Type() == "ActivityConsumeAnime"
|
||||
else if activity.TypeName() == "ActivityConsumeAnime"
|
||||
ActivityConsumeAnimeText(activity.(*arn.ActivityConsumeAnime), user)
|
||||
|
||||
component ActivityConsumeAnimeTitle(activity *arn.ActivityConsumeAnime, user *arn.User)
|
||||
|
@ -6,7 +6,6 @@
|
||||
align-items center
|
||||
margin 0.5rem
|
||||
default-transition
|
||||
transform scale(1)
|
||||
|
||||
:hover
|
||||
.image-title
|
||||
|
@ -33,5 +33,5 @@ component CompareMAL(comparisons []*utils.MALComparison, year string, status str
|
||||
.data-comparison-difference-detail= difference.DetailsA()
|
||||
.data-comparison-difference-detail= difference.DetailsB()
|
||||
|
||||
button.data-comparison-difference-ignore.action(data-action="newAnimeDiffIgnore", data-trigger="click", data-id=arn.CreateDifferenceID(comparison.Anime.ID, "mal", comparison.MALAnime.ID, difference.Type()), data-hash=difference.Hash())
|
||||
button.data-comparison-difference-ignore.action(data-action="newAnimeDiffIgnore", data-trigger="click", data-id=arn.CreateDifferenceID(comparison.Anime.ID, "mal", comparison.MALAnime.ID, difference.TypeName()), data-hash=difference.Hash())
|
||||
RawIcon("trash")
|
||||
|
@ -19,5 +19,5 @@ func ReplyUI(ctx *aero.Context) string {
|
||||
return ctx.Error(http.StatusNotFound, "Post not found", err)
|
||||
}
|
||||
|
||||
return ctx.HTML(components.NewPostArea(user, "Reply") + components.NewPostActions(post.Type(), post.ID, true))
|
||||
return ctx.HTML(components.NewPostArea(post, user, "Reply") + components.NewPostActions(post, true))
|
||||
}
|
||||
|
37
pages/profile/old.pixy
Normal file
37
pages/profile/old.pixy
Normal file
@ -0,0 +1,37 @@
|
||||
//- component ProfileTabs(viewUser *arn.User, uri string)
|
||||
//- .tabs.mountable.never-unmount
|
||||
//- Tab("Anime", "th", "/+" + viewUser.Nick)
|
||||
//- Tab("Characters", "child", "/+" + viewUser.Nick + "/characters/liked")
|
||||
//- Tab("Forum", "comment", "/+" + viewUser.Nick + "/forum/threads")
|
||||
//- Tab("Tracks", "music", "/+" + viewUser.Nick + "/soundtracks/liked")
|
||||
//- Tab("Quotes", "quote-left", "/+" + viewUser.Nick + "/quotes/liked")
|
||||
//- Tab("Stats", "area-chart", "/+" + viewUser.Nick + "/stats")
|
||||
//- Tab("Followers", "users", "/+" + viewUser.Nick + "/followers")
|
||||
|
||||
//- if strings.Contains(uri, "/soundtracks")
|
||||
//- .tabs
|
||||
//- Tab("Liked", "heart", "/+" + viewUser.Nick + "/soundtracks/liked")
|
||||
//- Tab("Added", "plus", "/+" + viewUser.Nick + "/soundtracks/added")
|
||||
|
||||
//- if strings.Contains(uri, "/quotes")
|
||||
//- .tabs
|
||||
//- Tab("Liked", "heart", "/+" + viewUser.Nick + "/quotes/liked")
|
||||
//- Tab("Added", "plus", "/+" + viewUser.Nick + "/quotes/added")
|
||||
|
||||
//- Anime shelf
|
||||
//- if len(animeList.Items) == 0
|
||||
//- p.no-data.mountable= viewUser.Nick + " hasn't added any anime yet."
|
||||
//- else
|
||||
//- .profile-watching-list.mountable
|
||||
//- each item in animeList.Items
|
||||
//- if item.Status == arn.AnimeListStatusWatching || item.Status == arn.AnimeListStatusCompleted
|
||||
//- a.profile-watching-list-item.tip(href=item.Anime().Link(), aria-label=item.Anime().Title.ByUser(user) + " (" + fmt.Sprint(item.Episodes) + " / " + arn.EpisodesToString(item.Anime().EpisodeCount) + ")")
|
||||
//- img.profile-watching-list-item-image.lazy(data-src=item.Anime().ImageLink("small"), data-webp="true", data-color=item.Anime().AverageColor(), alt=item.Anime().Title.ByUser(user), importance="high")
|
||||
|
||||
//- Footer
|
||||
//- .footer
|
||||
//- .buttons
|
||||
//- if user != nil && (user.Role == "admin" || user.Role == "editor")
|
||||
//- a.button.profile-action(href="/api/user/" + viewUser.ID, target="_blank", rel="noopener")
|
||||
//- Icon("search-plus")
|
||||
//- span JSON
|
@ -1,12 +1,19 @@
|
||||
package profile
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/aerogo/aero"
|
||||
"github.com/animenotifier/arn"
|
||||
"github.com/animenotifier/notify.moe/components"
|
||||
"github.com/animenotifier/notify.moe/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
maxCharacters = 6
|
||||
maxFriends = 7
|
||||
)
|
||||
|
||||
// Get user profile page.
|
||||
func Get(ctx *aero.Context) string {
|
||||
nick := ctx.Get("nick")
|
||||
@ -22,6 +29,8 @@ func Get(ctx *aero.Context) string {
|
||||
// Profile renders the user profile page of the given viewUser.
|
||||
func Profile(ctx *aero.Context, viewUser *arn.User) string {
|
||||
user := utils.GetUser(ctx)
|
||||
|
||||
// Anime list
|
||||
animeList := viewUser.AnimeList()
|
||||
|
||||
if user == nil || user.ID != viewUser.ID {
|
||||
@ -30,6 +39,10 @@ func Profile(ctx *aero.Context, viewUser *arn.User) string {
|
||||
|
||||
animeList.SortByRating()
|
||||
|
||||
// Genres
|
||||
topGenres := animeList.TopGenres(5)
|
||||
|
||||
// Open graph
|
||||
openGraph := &arn.OpenGraph{
|
||||
Tags: map[string]string{
|
||||
"og:title": viewUser.Nick,
|
||||
@ -46,7 +59,39 @@ func Profile(ctx *aero.Context, viewUser *arn.User) string {
|
||||
},
|
||||
}
|
||||
|
||||
ctx.Data = openGraph
|
||||
// Friends
|
||||
friends := viewUser.Follows().UsersWhoFollowBack()
|
||||
|
||||
return ctx.HTML(components.Profile(viewUser, user, animeList, ctx.URI()))
|
||||
arn.SortUsersFollowers(friends)
|
||||
|
||||
if len(friends) > maxFriends {
|
||||
friends = friends[:maxFriends]
|
||||
}
|
||||
|
||||
// Characters
|
||||
characters := []*arn.Character{}
|
||||
|
||||
for character := range arn.StreamCharacters() {
|
||||
if arn.Contains(character.Likes, viewUser.ID) {
|
||||
characters = append(characters, character)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(characters, func(i, j int) bool {
|
||||
aLikes := len(characters[i].Likes)
|
||||
bLikes := len(characters[j].Likes)
|
||||
|
||||
if aLikes == bLikes {
|
||||
return characters[i].Name.Canonical < characters[j].Name.Canonical
|
||||
}
|
||||
|
||||
return aLikes > bLikes
|
||||
})
|
||||
|
||||
if len(characters) > maxCharacters {
|
||||
characters = characters[:maxCharacters]
|
||||
}
|
||||
|
||||
ctx.Data = openGraph
|
||||
return ctx.HTML(components.Profile(viewUser, user, animeList, characters, friends, topGenres, ctx.URI()))
|
||||
}
|
||||
|
@ -1,48 +1,53 @@
|
||||
component Profile(viewUser *arn.User, user *arn.User, animeList *arn.AnimeList, uri string)
|
||||
ProfileHeader(viewUser, user, uri)
|
||||
component Profile(viewUser *arn.User, user *arn.User, animeList *arn.AnimeList, characters []*arn.Character, friends []*arn.User, topGenres []string, uri string)
|
||||
.profile
|
||||
ProfileHeader(viewUser, user, uri)
|
||||
|
||||
//- if len(animeList.Items) == 0
|
||||
//- p.no-data.mountable= viewUser.Nick + " hasn't added any anime yet."
|
||||
//- else
|
||||
//- .profile-watching-list.mountable
|
||||
//- each item in animeList.Items
|
||||
//- if item.Status == arn.AnimeListStatusWatching || item.Status == arn.AnimeListStatusCompleted
|
||||
//- a.profile-watching-list-item.tip(href=item.Anime().Link(), aria-label=item.Anime().Title.ByUser(user) + " (" + fmt.Sprint(item.Episodes) + " / " + arn.EpisodesToString(item.Anime().EpisodeCount) + ")")
|
||||
//- img.profile-watching-list-item-image.lazy(data-src=item.Anime().ImageLink("small"), data-webp="true", data-color=item.Anime().AverageColor(), alt=item.Anime().Title.ByUser(user), importance="high")
|
||||
.profile-columns
|
||||
.profile-column.profile-favorites
|
||||
.profile-section
|
||||
h3.profile-column-header.mountable Anime
|
||||
|
||||
//- .footer
|
||||
//- .buttons
|
||||
//- if user != nil && (user.Role == "admin" || user.Role == "editor")
|
||||
//- a.button.profile-action(href="/api/user/" + viewUser.ID, target="_blank", rel="noopener")
|
||||
//- Icon("search-plus")
|
||||
//- span JSON
|
||||
.profile-favorite-anime-container
|
||||
each item in animeList.Top(6)
|
||||
a.profile-favorite-anime.tip.mountable(href=item.Anime().Link(), aria-label=item.Anime().Title.ByUser(user), data-mountable-type="favorites")
|
||||
img.profile-favorite-anime-image.lazy(data-src=item.Anime().ImageLink("small"), data-webp=true, alt=item.Anime().Title.ByUser(user))
|
||||
|
||||
component ProfileTabs(viewUser *arn.User, uri string)
|
||||
.tabs.mountable.never-unmount
|
||||
Tab("Anime", "th", "/+" + viewUser.Nick)
|
||||
Tab("Characters", "child", "/+" + viewUser.Nick + "/characters/liked")
|
||||
Tab("Forum", "comment", "/+" + viewUser.Nick + "/forum/threads")
|
||||
Tab("Tracks", "music", "/+" + viewUser.Nick + "/soundtracks/liked")
|
||||
Tab("Quotes", "quote-left", "/+" + viewUser.Nick + "/quotes/liked")
|
||||
Tab("Stats", "area-chart", "/+" + viewUser.Nick + "/stats")
|
||||
Tab("Followers", "users", "/+" + viewUser.Nick + "/followers")
|
||||
.profile-section
|
||||
h3.profile-column-header.mountable(data-mountable-type="favorites") Characters
|
||||
|
||||
if strings.Contains(uri, "/soundtracks")
|
||||
.tabs
|
||||
Tab("Liked", "heart", "/+" + viewUser.Nick + "/soundtracks/liked")
|
||||
Tab("Added", "plus", "/+" + viewUser.Nick + "/soundtracks/added")
|
||||
.profile-favorite-characters-container
|
||||
each character in characters
|
||||
.mountable(data-mountable-type="favorites")
|
||||
CharacterSmall(character, user)
|
||||
//- a.profile-favorite-character.tip.mountable(href=character.Link(), aria-label=character.Name.ByUser(user), data-mountable-type="favorite-anime")
|
||||
//- img.profile-favorite-character-image.lazy(data-src=character.ImageLink("small"), data-webp=true, alt=character.Name.ByUser(user))
|
||||
|
||||
if strings.Contains(uri, "/quotes")
|
||||
.tabs
|
||||
Tab("Liked", "heart", "/+" + viewUser.Nick + "/quotes/liked")
|
||||
Tab("Added", "plus", "/+" + viewUser.Nick + "/quotes/added")
|
||||
.profile-column.profile-activity
|
||||
.profile-section
|
||||
h3.profile-column-header.mountable(data-mountable-type="activity") Activity
|
||||
Comments(viewUser, user)
|
||||
|
||||
.profile-column.profile-extra
|
||||
.profile-section
|
||||
h3.profile-column-header.mountable(data-mountable-type="extra") Genres
|
||||
|
||||
.anime-genres
|
||||
each genre in topGenres
|
||||
a.anime-genre.mountable(href="/genre/" + strings.ToLower(genre), data-mountable-type="extra")= genre
|
||||
|
||||
.profile-section
|
||||
h3.profile-column-header.mountable(data-mountable-type="extra") Friends
|
||||
|
||||
.profile-friends
|
||||
each friend in friends
|
||||
.profile-friend.mountable(data-mountable-type="extra")
|
||||
Avatar(friend)
|
||||
|
||||
component ProfileHeader(viewUser *arn.User, user *arn.User, uri string)
|
||||
ProfileHead(viewUser, user, uri)
|
||||
//- ProfileTabs(viewUser, uri)
|
||||
|
||||
component ProfileHead(viewUser *arn.User, user *arn.User, uri string)
|
||||
.profile
|
||||
.profile-head
|
||||
img.profile-cover.lazy(data-src=viewUser.CoverLink("large"), data-webp="true", alt="Cover image")
|
||||
|
||||
.profile-image-container.mountable.never-unmount
|
||||
@ -138,4 +143,3 @@ component ProfileHead(viewUser *arn.User, user *arn.User, uri string)
|
||||
button.profile-action.action.mountable.never-unmount(data-action="unfollowUser", data-trigger="click", data-api="/api/userfollows/" + user.ID + "/remove/" + viewUser.ID)
|
||||
Icon("user-times")
|
||||
span Unfollow
|
||||
|
@ -1,6 +1,6 @@
|
||||
const profile-image-size = 280px
|
||||
|
||||
.profile
|
||||
.profile-head
|
||||
vertical
|
||||
align-items center
|
||||
|
||||
@ -40,8 +40,59 @@ const profile-image-size = 280px
|
||||
color pro-color
|
||||
animation sk-pulse 1.5s infinite linear
|
||||
|
||||
.profile-columns
|
||||
vertical
|
||||
|
||||
.profile-column
|
||||
// border 1px solid red
|
||||
// height 100px
|
||||
padding calc(content-padding / 2)
|
||||
|
||||
.profile-column-header
|
||||
font-style bold
|
||||
margin-bottom 1rem
|
||||
|
||||
.profile-section
|
||||
margin-bottom 1rem
|
||||
|
||||
.profile-favorite-anime-container
|
||||
display grid
|
||||
grid-template-columns repeat(auto-fill, anime-image-small-width)
|
||||
grid-template-rows repeat(auto-fill, anime-image-small-height)
|
||||
grid-gap 0.5rem
|
||||
justify-content space-evenly
|
||||
|
||||
.profile-friends
|
||||
display grid
|
||||
grid-template-columns repeat(auto-fill, avatar-size)
|
||||
grid-template-rows repeat(auto-fill, avatar-size)
|
||||
grid-gap 0.5rem
|
||||
margin 0 0.5rem
|
||||
justify-content space-evenly
|
||||
|
||||
.profile-favorite-anime
|
||||
// anime-mini-item
|
||||
|
||||
.profile-favorite-anime-image
|
||||
// anime-mini-item-image
|
||||
border-radius ui-element-border-radius
|
||||
|
||||
.profile-favorite-characters-container
|
||||
display grid
|
||||
grid-template-columns repeat(auto-fill, character-image-small-width)
|
||||
grid-template-rows repeat(auto-fill, character-image-small-height)
|
||||
grid-gap 0.5rem
|
||||
justify-content space-evenly
|
||||
|
||||
// .profile-favorite-character
|
||||
// margin 0.25rem
|
||||
|
||||
// .profile-favorite-character-image
|
||||
|
||||
// border-radius ui-element-border-radius
|
||||
|
||||
> 740px
|
||||
.profile
|
||||
.profile-head
|
||||
horizontal
|
||||
align-items stretch
|
||||
|
||||
@ -61,6 +112,10 @@ const profile-image-size = 280px
|
||||
padding content-padding
|
||||
margin-top 0
|
||||
|
||||
.profile-columns
|
||||
display grid
|
||||
grid-template-columns 27% 46% 27%
|
||||
|
||||
.profile-pro-status
|
||||
position absolute
|
||||
right 0
|
||||
|
@ -30,7 +30,7 @@ func Anime(ctx *aero.Context) string {
|
||||
completed := animeList.FilterStatus(arn.AnimeListStatusCompleted)
|
||||
|
||||
// Genre affinity
|
||||
bestGenres := getBestGenres(animeList)
|
||||
bestGenres := animeList.TopGenres(bestGenreCount)
|
||||
|
||||
// Get all anime
|
||||
var tv []*arn.Anime
|
||||
|
@ -1,43 +0,0 @@
|
||||
package recommended
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/animenotifier/arn"
|
||||
)
|
||||
|
||||
// getBestGenres returns the most liked genres for the user's anime list.
|
||||
func getBestGenres(animeList *arn.AnimeList) []string {
|
||||
genreItems := animeList.Genres()
|
||||
genreAffinity := map[string]float64{}
|
||||
bestGenres := []string{}
|
||||
|
||||
for genre, animeListItems := range genreItems {
|
||||
affinity := 0.0
|
||||
|
||||
for _, item := range animeListItems {
|
||||
if item.Status != arn.AnimeListStatusCompleted {
|
||||
continue
|
||||
}
|
||||
|
||||
if item.Rating.Overall != 0 {
|
||||
affinity += item.Rating.Overall
|
||||
} else {
|
||||
affinity += 5.0
|
||||
}
|
||||
}
|
||||
|
||||
genreAffinity[genre] = affinity
|
||||
bestGenres = append(bestGenres, genre)
|
||||
}
|
||||
|
||||
sort.Slice(bestGenres, func(i, j int) bool {
|
||||
return genreAffinity[bestGenres[i]] > genreAffinity[bestGenres[j]]
|
||||
})
|
||||
|
||||
if len(bestGenres) > bestGenreCount {
|
||||
bestGenres = bestGenres[:bestGenreCount]
|
||||
}
|
||||
|
||||
return bestGenres
|
||||
}
|
@ -19,5 +19,5 @@ func ReplyUI(ctx *aero.Context) string {
|
||||
return ctx.Error(http.StatusNotFound, "Thread not found", err)
|
||||
}
|
||||
|
||||
return ctx.HTML(components.NewPostArea(user, "Reply") + components.NewPostActions(thread.Type(), thread.ID, true))
|
||||
return ctx.HTML(components.NewPostArea(thread, user, "Reply") + components.NewPostActions(thread, true))
|
||||
}
|
||||
|
@ -11,11 +11,11 @@ component Thread(thread *arn.Thread, user *arn.User)
|
||||
footer.footer.mountable
|
||||
p.text-center This topic is locked.
|
||||
else
|
||||
NewPostArea(user, "Reply")
|
||||
NewPostArea(thread, user, "Reply")
|
||||
|
||||
.buttons
|
||||
if !thread.Locked
|
||||
NewPostActions("Thread", thread.ID, false)
|
||||
NewPostActions(thread, false)
|
||||
|
||||
if user.Role == "admin" || user.Role == "editor"
|
||||
if thread.Locked
|
||||
|
@ -105,7 +105,7 @@ export async function reply(arn: AnimeNotifier, element: HTMLElement) {
|
||||
}
|
||||
|
||||
// Delete old reply button
|
||||
let oldPostActions = document.getElementById("new-post-actions")
|
||||
let oldPostActions = document.getElementsByClassName("new-post-actions")[0]
|
||||
|
||||
if(oldPostActions) {
|
||||
oldPostActions.remove()
|
||||
|
@ -159,6 +159,7 @@ export default class AnimeNotifier {
|
||||
Promise.resolve().then(() => this.lazyLoad()),
|
||||
Promise.resolve().then(() => this.displayLocalDates()),
|
||||
Promise.resolve().then(() => this.setSelectBoxValue()),
|
||||
Promise.resolve().then(() => this.textAreaFocus()),
|
||||
Promise.resolve().then(() => this.markPlayingSoundTrack()),
|
||||
Promise.resolve().then(() => this.assignActions()),
|
||||
Promise.resolve().then(() => this.updatePushUI()),
|
||||
@ -194,6 +195,26 @@ export default class AnimeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
textAreaFocus() {
|
||||
const newPostText = document.getElementById("new-post-text") as HTMLTextAreaElement
|
||||
|
||||
if(!newPostText || newPostText["has-input-listener"]) {
|
||||
return
|
||||
}
|
||||
|
||||
newPostText.addEventListener("input", () => {
|
||||
if(newPostText.value.length > 0) {
|
||||
const newPostActions = document.getElementsByClassName("new-post-actions")[0]
|
||||
newPostActions.classList.add("new-post-actions-enabled")
|
||||
} else {
|
||||
const newPostActions = document.getElementsByClassName("new-post-actions")[0]
|
||||
newPostActions.classList.remove("new-post-actions-enabled")
|
||||
}
|
||||
})
|
||||
|
||||
newPostText["has-input-listener"] = true
|
||||
}
|
||||
|
||||
async onIdle() {
|
||||
// Register event listeners
|
||||
document.addEventListener("keydown", this.onKeyDown.bind(this), false)
|
||||
@ -1071,6 +1092,7 @@ export default class AnimeNotifier {
|
||||
this.lazyLoad(findAllInside("lazy", element))
|
||||
this.mountMountables(findAllInside("mountable", element))
|
||||
this.assignTooltipOffsets(findAllInside("tip", element))
|
||||
this.textAreaFocus()
|
||||
}
|
||||
|
||||
scrollTo(target: HTMLElement) {
|
||||
|
@ -80,10 +80,18 @@ post-content-padding-y = 0.75rem
|
||||
align-items flex-start
|
||||
margin-right 0.5rem
|
||||
|
||||
#new-post-actions
|
||||
horizontal
|
||||
.new-post-actions
|
||||
justify-content flex-end
|
||||
opacity 0
|
||||
height 0
|
||||
transform translateY(-50%)
|
||||
transition all transition-speed ease
|
||||
|
||||
.new-post-actions-enabled
|
||||
opacity 1
|
||||
height auto
|
||||
margin-bottom 0.75rem
|
||||
transform translateY(0)
|
||||
|
||||
// Toolbar
|
||||
|
||||
|
@ -7,8 +7,8 @@ type CanonicalTitle struct {
|
||||
NumericHash uint64
|
||||
}
|
||||
|
||||
// Type returns the diff type.
|
||||
func (diff *CanonicalTitle) Type() string {
|
||||
// TypeName returns the diff type.
|
||||
func (diff *CanonicalTitle) TypeName() string {
|
||||
return "CanonicalTitle"
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,8 @@ type EndDate struct {
|
||||
NumericHash uint64
|
||||
}
|
||||
|
||||
// Type returns the diff type.
|
||||
func (diff *EndDate) Type() string {
|
||||
// TypeName returns the diff type.
|
||||
func (diff *EndDate) TypeName() string {
|
||||
return "EndDate"
|
||||
}
|
||||
|
||||
|
@ -9,8 +9,8 @@ type EpisodeCount struct {
|
||||
NumericHash uint64
|
||||
}
|
||||
|
||||
// Type returns the diff type.
|
||||
func (diff *EpisodeCount) Type() string {
|
||||
// TypeName returns the diff type.
|
||||
func (diff *EpisodeCount) TypeName() string {
|
||||
return "EpisodeCount"
|
||||
}
|
||||
|
||||
|
@ -9,8 +9,8 @@ type Genres struct {
|
||||
NumericHash uint64
|
||||
}
|
||||
|
||||
// Type returns the diff type.
|
||||
func (diff *Genres) Type() string {
|
||||
// TypeName returns the diff type.
|
||||
func (diff *Genres) TypeName() string {
|
||||
return "Genres"
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ package animediff
|
||||
|
||||
// Difference describes a difference between two anime.
|
||||
type Difference interface {
|
||||
Type() string
|
||||
TypeName() string
|
||||
Explanation() string
|
||||
DetailsA() string
|
||||
DetailsB() string
|
||||
|
@ -7,8 +7,8 @@ type JapaneseTitle struct {
|
||||
NumericHash uint64
|
||||
}
|
||||
|
||||
// Type returns the diff type.
|
||||
func (diff *JapaneseTitle) Type() string {
|
||||
// TypeName returns the diff type.
|
||||
func (diff *JapaneseTitle) TypeName() string {
|
||||
return "JapaneseTitle"
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,8 @@ type RomajiTitle struct {
|
||||
NumericHash uint64
|
||||
}
|
||||
|
||||
// Type returns the diff type.
|
||||
func (diff *RomajiTitle) Type() string {
|
||||
// TypeName returns the diff type.
|
||||
func (diff *RomajiTitle) TypeName() string {
|
||||
return "RomajiTitle"
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,8 @@ type StartDate struct {
|
||||
NumericHash uint64
|
||||
}
|
||||
|
||||
// Type returns the diff type.
|
||||
func (diff *StartDate) Type() string {
|
||||
// TypeName returns the diff type.
|
||||
func (diff *StartDate) TypeName() string {
|
||||
return "StartDate"
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,8 @@ type Status struct {
|
||||
NumericHash uint64
|
||||
}
|
||||
|
||||
// Type returns the diff type.
|
||||
func (diff *Status) Type() string {
|
||||
// TypeName returns the diff type.
|
||||
func (diff *Status) TypeName() string {
|
||||
return "Status"
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,8 @@ type Synopsis struct {
|
||||
NumericHash uint64
|
||||
}
|
||||
|
||||
// Type returns the diff type.
|
||||
func (diff *Synopsis) Type() string {
|
||||
// TypeName returns the diff type.
|
||||
func (diff *Synopsis) TypeName() string {
|
||||
return "Synopsis"
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user