New user profile
This commit is contained in:
parent
a1c11a2eae
commit
d5dff615c1
@ -1,19 +1,15 @@
|
|||||||
component Comments(parent arn.PostParent, user *arn.User)
|
component Comments(parent arn.PostParent, user *arn.User)
|
||||||
.thread
|
.thread
|
||||||
.posts
|
.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 user != nil
|
||||||
if arn.IsLocked(parent)
|
if arn.IsLocked(parent)
|
||||||
footer.footer.mountable
|
footer.footer.mountable
|
||||||
p.text-center= "This " + strings.ToLower(reflect.TypeOf(parent).Elem().Name()) + " is locked."
|
p.text-center= "This " + strings.ToLower(reflect.TypeOf(parent).Elem().Name()) + " is locked."
|
||||||
else
|
else
|
||||||
NewPostArea(user, "Comment")
|
NewPostArea(parent, user, "Comment")
|
||||||
|
|
||||||
.buttons
|
if user == nil && parent.CountPosts() == 0
|
||||||
if !arn.IsLocked(parent)
|
p.no-data.mountable No comments have been written yet.
|
||||||
NewPostActions(reflect.TypeOf(parent).Elem().Name(), parent.GetID(), false)
|
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
|
#new-post.post.mountable
|
||||||
.post-parent
|
.post-parent
|
||||||
.post-author
|
.post-author
|
||||||
@ -6,11 +6,18 @@ component NewPostArea(user *arn.User, placeholder string)
|
|||||||
|
|
||||||
textarea#new-post-text.post-content(placeholder=placeholder + "...", aria-label=placeholder)
|
textarea#new-post-text.post-content(placeholder=placeholder + "...", aria-label=placeholder)
|
||||||
|
|
||||||
component NewPostActions(parentType string, parentID string, cancelButton bool)
|
if !arn.IsLocked(parent)
|
||||||
#new-post-actions.buttons
|
NewPostActions(parent, false)
|
||||||
button#reply-button.mountable.action(data-action="createPost", data-trigger="click", data-parent-type=parentType, data-parent-id=parentID)
|
|
||||||
|
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")
|
Icon("mail-reply")
|
||||||
span Reply
|
|
||||||
|
if parent.TypeName() == "Post" || parent.TypeName() == "Thread"
|
||||||
|
span= "Reply to " + parent.Creator().Nick
|
||||||
|
else
|
||||||
|
span Submit
|
||||||
|
|
||||||
if cancelButton
|
if cancelButton
|
||||||
button#reply-cancel-button.mountable.action(data-action="cancelReply", data-trigger="click")
|
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)
|
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-parent
|
||||||
.post-author
|
.post-author
|
||||||
Avatar(post.Creator())
|
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
|
if user != nil && user.ID == post.Creator().ID
|
||||||
.post-edit-interface
|
.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")
|
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())= 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")
|
a.post-tool.post-edit.tip.action(data-action="editPost", data-trigger="click", data-id=post.GetID(), aria-label="Edit")
|
||||||
Icon("pencil")
|
Icon("pencil")
|
||||||
|
|
||||||
if post.Type() != "Thread"
|
if post.TypeName() != "Thread"
|
||||||
if user != nil && (user.Role == "admin" || user.Role == "editor")
|
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")
|
a.post-tool.post-delete.tip.action(data-action="deletePost", data-trigger="click", data-id=post.GetID(), aria-label="Delete")
|
||||||
Icon("trash")
|
Icon("trash")
|
||||||
|
@ -37,7 +37,7 @@ func fetchActivities(user *arn.User, followedOnly bool) []arn.Activity {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if activity.Type() == "ActivityCreate" {
|
if activity.TypeName() == "ActivityCreate" {
|
||||||
obj := activity.(*arn.ActivityCreate).Object()
|
obj := activity.(*arn.ActivityCreate).Object()
|
||||||
|
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
@ -48,7 +48,7 @@ func fetchActivities(user *arn.User, followedOnly bool) []arn.Activity {
|
|||||||
return !isDraftable || !draft.GetIsDraft()
|
return !isDraftable || !draft.GetIsDraft()
|
||||||
}
|
}
|
||||||
|
|
||||||
if activity.Type() == "ActivityConsumeAnime" {
|
if activity.TypeName() == "ActivityConsumeAnime" {
|
||||||
return activity.(*arn.ActivityConsumeAnime).Anime() != nil
|
return activity.(*arn.ActivityConsumeAnime).Anime() != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,20 +32,20 @@ component Activity(activity arn.Activity, user *arn.User)
|
|||||||
.post-content
|
.post-content
|
||||||
.activity-header
|
.activity-header
|
||||||
.activity-parent
|
.activity-parent
|
||||||
if activity.Type() == "ActivityCreate"
|
if activity.TypeName() == "ActivityCreate"
|
||||||
ActivityCreateTitle(activity.(*arn.ActivityCreate), user)
|
ActivityCreateTitle(activity.(*arn.ActivityCreate), user)
|
||||||
else if activity.Type() == "ActivityConsumeAnime"
|
else if activity.TypeName() == "ActivityConsumeAnime"
|
||||||
ActivityConsumeAnimeTitle(activity.(*arn.ActivityConsumeAnime), user)
|
ActivityConsumeAnimeTitle(activity.(*arn.ActivityConsumeAnime), user)
|
||||||
|
|
||||||
if user != nil && user.ID == activity.GetCreatedBy() && activity.Type() == "ActivityConsumeAnime"
|
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.Type()), activity.GetID()))
|
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")
|
RawIcon("trash")
|
||||||
|
|
||||||
.activity-date.utc-date(data-date=activity.GetCreated())
|
.activity-date.utc-date(data-date=activity.GetCreated())
|
||||||
|
|
||||||
if activity.Type() == "ActivityCreate"
|
if activity.TypeName() == "ActivityCreate"
|
||||||
ActivityCreateText(activity.(*arn.ActivityCreate), user)
|
ActivityCreateText(activity.(*arn.ActivityCreate), user)
|
||||||
else if activity.Type() == "ActivityConsumeAnime"
|
else if activity.TypeName() == "ActivityConsumeAnime"
|
||||||
ActivityConsumeAnimeText(activity.(*arn.ActivityConsumeAnime), user)
|
ActivityConsumeAnimeText(activity.(*arn.ActivityConsumeAnime), user)
|
||||||
|
|
||||||
component ActivityConsumeAnimeTitle(activity *arn.ActivityConsumeAnime, user *arn.User)
|
component ActivityConsumeAnimeTitle(activity *arn.ActivityConsumeAnime, user *arn.User)
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
align-items center
|
align-items center
|
||||||
margin 0.5rem
|
margin 0.5rem
|
||||||
default-transition
|
default-transition
|
||||||
transform scale(1)
|
|
||||||
|
|
||||||
:hover
|
:hover
|
||||||
.image-title
|
.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.DetailsA()
|
||||||
.data-comparison-difference-detail= difference.DetailsB()
|
.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")
|
RawIcon("trash")
|
||||||
|
@ -19,5 +19,5 @@ func ReplyUI(ctx *aero.Context) string {
|
|||||||
return ctx.Error(http.StatusNotFound, "Post not found", err)
|
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
|
package profile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/aerogo/aero"
|
"github.com/aerogo/aero"
|
||||||
"github.com/animenotifier/arn"
|
"github.com/animenotifier/arn"
|
||||||
"github.com/animenotifier/notify.moe/components"
|
"github.com/animenotifier/notify.moe/components"
|
||||||
"github.com/animenotifier/notify.moe/utils"
|
"github.com/animenotifier/notify.moe/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxCharacters = 6
|
||||||
|
maxFriends = 7
|
||||||
|
)
|
||||||
|
|
||||||
// Get user profile page.
|
// Get user profile page.
|
||||||
func Get(ctx *aero.Context) string {
|
func Get(ctx *aero.Context) string {
|
||||||
nick := ctx.Get("nick")
|
nick := ctx.Get("nick")
|
||||||
@ -22,6 +29,8 @@ func Get(ctx *aero.Context) string {
|
|||||||
// Profile renders the user profile page of the given viewUser.
|
// Profile renders the user profile page of the given viewUser.
|
||||||
func Profile(ctx *aero.Context, viewUser *arn.User) string {
|
func Profile(ctx *aero.Context, viewUser *arn.User) string {
|
||||||
user := utils.GetUser(ctx)
|
user := utils.GetUser(ctx)
|
||||||
|
|
||||||
|
// Anime list
|
||||||
animeList := viewUser.AnimeList()
|
animeList := viewUser.AnimeList()
|
||||||
|
|
||||||
if user == nil || user.ID != viewUser.ID {
|
if user == nil || user.ID != viewUser.ID {
|
||||||
@ -30,6 +39,10 @@ func Profile(ctx *aero.Context, viewUser *arn.User) string {
|
|||||||
|
|
||||||
animeList.SortByRating()
|
animeList.SortByRating()
|
||||||
|
|
||||||
|
// Genres
|
||||||
|
topGenres := animeList.TopGenres(5)
|
||||||
|
|
||||||
|
// Open graph
|
||||||
openGraph := &arn.OpenGraph{
|
openGraph := &arn.OpenGraph{
|
||||||
Tags: map[string]string{
|
Tags: map[string]string{
|
||||||
"og:title": viewUser.Nick,
|
"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)
|
component Profile(viewUser *arn.User, user *arn.User, animeList *arn.AnimeList, characters []*arn.Character, friends []*arn.User, topGenres []string, uri string)
|
||||||
ProfileHeader(viewUser, user, uri)
|
.profile
|
||||||
|
ProfileHeader(viewUser, user, uri)
|
||||||
|
|
||||||
//- if len(animeList.Items) == 0
|
.profile-columns
|
||||||
//- p.no-data.mountable= viewUser.Nick + " hasn't added any anime yet."
|
.profile-column.profile-favorites
|
||||||
//- else
|
.profile-section
|
||||||
//- .profile-watching-list.mountable
|
h3.profile-column-header.mountable Anime
|
||||||
//- each item in animeList.Items
|
|
||||||
//- if item.Status == arn.AnimeListStatusWatching || item.Status == arn.AnimeListStatusCompleted
|
.profile-favorite-anime-container
|
||||||
//- 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) + ")")
|
each item in animeList.Top(6)
|
||||||
//- 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")
|
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))
|
||||||
|
|
||||||
//- .footer
|
.profile-section
|
||||||
//- .buttons
|
h3.profile-column-header.mountable(data-mountable-type="favorites") Characters
|
||||||
//- if user != nil && (user.Role == "admin" || user.Role == "editor")
|
|
||||||
//- a.button.profile-action(href="/api/user/" + viewUser.ID, target="_blank", rel="noopener")
|
.profile-favorite-characters-container
|
||||||
//- Icon("search-plus")
|
each character in characters
|
||||||
//- span JSON
|
.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))
|
||||||
|
|
||||||
component ProfileTabs(viewUser *arn.User, uri string)
|
.profile-column.profile-activity
|
||||||
.tabs.mountable.never-unmount
|
.profile-section
|
||||||
Tab("Anime", "th", "/+" + viewUser.Nick)
|
h3.profile-column-header.mountable(data-mountable-type="activity") Activity
|
||||||
Tab("Characters", "child", "/+" + viewUser.Nick + "/characters/liked")
|
Comments(viewUser, user)
|
||||||
Tab("Forum", "comment", "/+" + viewUser.Nick + "/forum/threads")
|
|
||||||
Tab("Tracks", "music", "/+" + viewUser.Nick + "/soundtracks/liked")
|
.profile-column.profile-extra
|
||||||
Tab("Quotes", "quote-left", "/+" + viewUser.Nick + "/quotes/liked")
|
.profile-section
|
||||||
Tab("Stats", "area-chart", "/+" + viewUser.Nick + "/stats")
|
h3.profile-column-header.mountable(data-mountable-type="extra") Genres
|
||||||
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")
|
.anime-genres
|
||||||
.tabs
|
each genre in topGenres
|
||||||
Tab("Liked", "heart", "/+" + viewUser.Nick + "/quotes/liked")
|
a.anime-genre.mountable(href="/genre/" + strings.ToLower(genre), data-mountable-type="extra")= genre
|
||||||
Tab("Added", "plus", "/+" + viewUser.Nick + "/quotes/added")
|
|
||||||
|
|
||||||
|
.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)
|
component ProfileHeader(viewUser *arn.User, user *arn.User, uri string)
|
||||||
ProfileHead(viewUser, user, uri)
|
ProfileHead(viewUser, user, uri)
|
||||||
//- ProfileTabs(viewUser, uri)
|
|
||||||
|
|
||||||
component ProfileHead(viewUser *arn.User, user *arn.User, uri string)
|
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")
|
img.profile-cover.lazy(data-src=viewUser.CoverLink("large"), data-webp="true", alt="Cover image")
|
||||||
|
|
||||||
.profile-image-container.mountable.never-unmount
|
.profile-image-container.mountable.never-unmount
|
||||||
@ -137,5 +142,4 @@ component ProfileHead(viewUser *arn.User, user *arn.User, uri string)
|
|||||||
else
|
else
|
||||||
button.profile-action.action.mountable.never-unmount(data-action="unfollowUser", data-trigger="click", data-api="/api/userfollows/" + user.ID + "/remove/" + viewUser.ID)
|
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")
|
Icon("user-times")
|
||||||
span Unfollow
|
span Unfollow
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
const profile-image-size = 280px
|
const profile-image-size = 280px
|
||||||
|
|
||||||
.profile
|
.profile-head
|
||||||
vertical
|
vertical
|
||||||
align-items center
|
align-items center
|
||||||
|
|
||||||
@ -40,8 +40,59 @@ const profile-image-size = 280px
|
|||||||
color pro-color
|
color pro-color
|
||||||
animation sk-pulse 1.5s infinite linear
|
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
|
> 740px
|
||||||
.profile
|
.profile-head
|
||||||
horizontal
|
horizontal
|
||||||
align-items stretch
|
align-items stretch
|
||||||
|
|
||||||
@ -61,6 +112,10 @@ const profile-image-size = 280px
|
|||||||
padding content-padding
|
padding content-padding
|
||||||
margin-top 0
|
margin-top 0
|
||||||
|
|
||||||
|
.profile-columns
|
||||||
|
display grid
|
||||||
|
grid-template-columns 27% 46% 27%
|
||||||
|
|
||||||
.profile-pro-status
|
.profile-pro-status
|
||||||
position absolute
|
position absolute
|
||||||
right 0
|
right 0
|
||||||
|
@ -30,7 +30,7 @@ func Anime(ctx *aero.Context) string {
|
|||||||
completed := animeList.FilterStatus(arn.AnimeListStatusCompleted)
|
completed := animeList.FilterStatus(arn.AnimeListStatusCompleted)
|
||||||
|
|
||||||
// Genre affinity
|
// Genre affinity
|
||||||
bestGenres := getBestGenres(animeList)
|
bestGenres := animeList.TopGenres(bestGenreCount)
|
||||||
|
|
||||||
// Get all anime
|
// Get all anime
|
||||||
var tv []*arn.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.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
|
footer.footer.mountable
|
||||||
p.text-center This topic is locked.
|
p.text-center This topic is locked.
|
||||||
else
|
else
|
||||||
NewPostArea(user, "Reply")
|
NewPostArea(thread, user, "Reply")
|
||||||
|
|
||||||
.buttons
|
.buttons
|
||||||
if !thread.Locked
|
if !thread.Locked
|
||||||
NewPostActions("Thread", thread.ID, false)
|
NewPostActions(thread, false)
|
||||||
|
|
||||||
if user.Role == "admin" || user.Role == "editor"
|
if user.Role == "admin" || user.Role == "editor"
|
||||||
if thread.Locked
|
if thread.Locked
|
||||||
|
@ -105,7 +105,7 @@ export async function reply(arn: AnimeNotifier, element: HTMLElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete old reply button
|
// Delete old reply button
|
||||||
let oldPostActions = document.getElementById("new-post-actions")
|
let oldPostActions = document.getElementsByClassName("new-post-actions")[0]
|
||||||
|
|
||||||
if(oldPostActions) {
|
if(oldPostActions) {
|
||||||
oldPostActions.remove()
|
oldPostActions.remove()
|
||||||
|
@ -159,6 +159,7 @@ export default class AnimeNotifier {
|
|||||||
Promise.resolve().then(() => this.lazyLoad()),
|
Promise.resolve().then(() => this.lazyLoad()),
|
||||||
Promise.resolve().then(() => this.displayLocalDates()),
|
Promise.resolve().then(() => this.displayLocalDates()),
|
||||||
Promise.resolve().then(() => this.setSelectBoxValue()),
|
Promise.resolve().then(() => this.setSelectBoxValue()),
|
||||||
|
Promise.resolve().then(() => this.textAreaFocus()),
|
||||||
Promise.resolve().then(() => this.markPlayingSoundTrack()),
|
Promise.resolve().then(() => this.markPlayingSoundTrack()),
|
||||||
Promise.resolve().then(() => this.assignActions()),
|
Promise.resolve().then(() => this.assignActions()),
|
||||||
Promise.resolve().then(() => this.updatePushUI()),
|
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() {
|
async onIdle() {
|
||||||
// Register event listeners
|
// Register event listeners
|
||||||
document.addEventListener("keydown", this.onKeyDown.bind(this), false)
|
document.addEventListener("keydown", this.onKeyDown.bind(this), false)
|
||||||
@ -1071,6 +1092,7 @@ export default class AnimeNotifier {
|
|||||||
this.lazyLoad(findAllInside("lazy", element))
|
this.lazyLoad(findAllInside("lazy", element))
|
||||||
this.mountMountables(findAllInside("mountable", element))
|
this.mountMountables(findAllInside("mountable", element))
|
||||||
this.assignTooltipOffsets(findAllInside("tip", element))
|
this.assignTooltipOffsets(findAllInside("tip", element))
|
||||||
|
this.textAreaFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollTo(target: HTMLElement) {
|
scrollTo(target: HTMLElement) {
|
||||||
|
@ -80,10 +80,18 @@ post-content-padding-y = 0.75rem
|
|||||||
align-items flex-start
|
align-items flex-start
|
||||||
margin-right 0.5rem
|
margin-right 0.5rem
|
||||||
|
|
||||||
#new-post-actions
|
.new-post-actions
|
||||||
horizontal
|
|
||||||
justify-content flex-end
|
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
|
margin-bottom 0.75rem
|
||||||
|
transform translateY(0)
|
||||||
|
|
||||||
// Toolbar
|
// Toolbar
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ type CanonicalTitle struct {
|
|||||||
NumericHash uint64
|
NumericHash uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type returns the diff type.
|
// TypeName returns the diff type.
|
||||||
func (diff *CanonicalTitle) Type() string {
|
func (diff *CanonicalTitle) TypeName() string {
|
||||||
return "CanonicalTitle"
|
return "CanonicalTitle"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ type EndDate struct {
|
|||||||
NumericHash uint64
|
NumericHash uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type returns the diff type.
|
// TypeName returns the diff type.
|
||||||
func (diff *EndDate) Type() string {
|
func (diff *EndDate) TypeName() string {
|
||||||
return "EndDate"
|
return "EndDate"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,8 +9,8 @@ type EpisodeCount struct {
|
|||||||
NumericHash uint64
|
NumericHash uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type returns the diff type.
|
// TypeName returns the diff type.
|
||||||
func (diff *EpisodeCount) Type() string {
|
func (diff *EpisodeCount) TypeName() string {
|
||||||
return "EpisodeCount"
|
return "EpisodeCount"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,8 +9,8 @@ type Genres struct {
|
|||||||
NumericHash uint64
|
NumericHash uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type returns the diff type.
|
// TypeName returns the diff type.
|
||||||
func (diff *Genres) Type() string {
|
func (diff *Genres) TypeName() string {
|
||||||
return "Genres"
|
return "Genres"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ package animediff
|
|||||||
|
|
||||||
// Difference describes a difference between two anime.
|
// Difference describes a difference between two anime.
|
||||||
type Difference interface {
|
type Difference interface {
|
||||||
Type() string
|
TypeName() string
|
||||||
Explanation() string
|
Explanation() string
|
||||||
DetailsA() string
|
DetailsA() string
|
||||||
DetailsB() string
|
DetailsB() string
|
||||||
|
@ -7,8 +7,8 @@ type JapaneseTitle struct {
|
|||||||
NumericHash uint64
|
NumericHash uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type returns the diff type.
|
// TypeName returns the diff type.
|
||||||
func (diff *JapaneseTitle) Type() string {
|
func (diff *JapaneseTitle) TypeName() string {
|
||||||
return "JapaneseTitle"
|
return "JapaneseTitle"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ type RomajiTitle struct {
|
|||||||
NumericHash uint64
|
NumericHash uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type returns the diff type.
|
// TypeName returns the diff type.
|
||||||
func (diff *RomajiTitle) Type() string {
|
func (diff *RomajiTitle) TypeName() string {
|
||||||
return "RomajiTitle"
|
return "RomajiTitle"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ type StartDate struct {
|
|||||||
NumericHash uint64
|
NumericHash uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type returns the diff type.
|
// TypeName returns the diff type.
|
||||||
func (diff *StartDate) Type() string {
|
func (diff *StartDate) TypeName() string {
|
||||||
return "StartDate"
|
return "StartDate"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ type Status struct {
|
|||||||
NumericHash uint64
|
NumericHash uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type returns the diff type.
|
// TypeName returns the diff type.
|
||||||
func (diff *Status) Type() string {
|
func (diff *Status) TypeName() string {
|
||||||
return "Status"
|
return "Status"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ type Synopsis struct {
|
|||||||
NumericHash uint64
|
NumericHash uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type returns the diff type.
|
// TypeName returns the diff type.
|
||||||
func (diff *Synopsis) Type() string {
|
func (diff *Synopsis) TypeName() string {
|
||||||
return "Synopsis"
|
return "Synopsis"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user