Group up existing components into elements

This commit is contained in:
2019-11-19 16:12:52 +09:00
parent dc4fc1410d
commit c542bcdcd6
77 changed files with 291 additions and 349 deletions

15
elements/AMV/AMV.pixy Normal file
View File

@ -0,0 +1,15 @@
component AMV(amv *arn.AMV, user *arn.User)
.amv.mountable
AMVVideo(amv)
AMVFooter(amv, user)
component AMVFooter(amv *arn.AMV, user *arn.User)
footer.amv-footer
if amv.Title.ByUser(user) == ""
a(href=amv.Link() + "/edit") untitled
else
a(href=amv.Link())= amv.Title.ByUser(user)
span posted
span.utc-date.no-tip(data-date=amv.Created)
span by
a(href=amv.Creator().Link())= amv.Creator().Nick + " "

11
elements/AMV/AMVMini.pixy Normal file
View File

@ -0,0 +1,11 @@
component AMVMini(amv *arn.AMV, user *arn.User)
.amv.mountable
AMVVideo(amv)
AMVMiniFooter(amv, user)
component AMVMiniFooter(amv *arn.AMV, user *arn.User)
footer.amv-footer
if amv.Title.ByUser(user) == ""
a(href=amv.Link() + "/edit") untitled
else
a(href=amv.Link())= amv.Title.ByUser(user)

View File

@ -0,0 +1,9 @@
component AMVVideo(amv *arn.AMV)
.video-container(id=amv.ID, data-api="/api/amv/" + amv.ID)
video.video.lazy.action(data-action="toggleFullscreen", data-trigger="dblclick", data-id=amv.ID)
source(data-src=amv.VideoLink(), data-type="video/webm")
//- button.media-play-button
//- RawIcon("play")
VideoControls(amv.ID, amv.Info.Duration)

View File

@ -0,0 +1,7 @@
component AnimeCard(anime *arn.Anime, note string, user *arn.User)
a.anime-card.mountable(href=anime.Link())
.anime-card-image-container
img.anime-card-image.lazy(data-src=anime.ImageLink("small"), data-webp="true", data-color=anime.AverageColor(), alt=anime.Title.ByUser(user))
.anime-card-info
.anime-card-info-main= anime.Title.ByUser(user)
.anime-card-info-details= note

View File

@ -0,0 +1,32 @@
.anime-cards
horizontal-wrap
.anime-card
card
> 600px
.anime-card
max-width 300px
.anime-card-image-container
width anime-image-small-width
height anime-image-small-width
img
width anime-image-small-width
height anime-image-small-width
border-radius ui-element-border-radius
object-fit cover
.anime-card-info
vertical
margin-left card-padding
font-size 0.95em
.anime-card-info-main
flex 1
line-height 1.3em
.anime-card-info-details
color text-color
opacity 0.5

View File

@ -0,0 +1,34 @@
component AnimeGrid(animes []*arn.Anime, user *arn.User)
#load-more-target.anime-grid
AnimeGridScrollable(animes, user)
component AnimeGridWithRelation(entries []*utils.AnimeWithRelatedAnime, user *arn.User)
#load-more-target.anime-grid
AnimeGridWithRelationScrollable(entries, user)
component AnimeGridScrollable(animes []*arn.Anime, user *arn.User)
each anime in animes
.anime-grid-cell(data-added=(user != nil && user.AnimeList().Contains(anime.ID)))
AnimeImageLink(anime, "medium", user)
AnimeGridButton(anime, user)
component AnimeImageLink(anime *arn.Anime, size string, user *arn.User)
a(href="/anime/" + anime.ID)
img.anime-grid-image.lazy(data-src=anime.ImageLink(size), data-webp="true", data-color=anime.AverageColor(), alt=anime.Title.Romaji)
.image-title
.image-title-text= anime.Title.ByUser(user)
component AnimeGridWithRelationScrollable(entries []*utils.AnimeWithRelatedAnime, user *arn.User)
each entry in entries
.anime-grid-cell(data-added=(user != nil && user.AnimeList().Contains(entry.Anime.ID)))
a(href="/anime/" + entry.Anime.ID)
img.anime-grid-image.lazy(data-src=entry.Anime.ImageLink("medium"), data-webp="true", data-color=entry.Anime.AverageColor(), alt=entry.Anime.Title.Romaji)
.image-title
.image-title-text= entry.Anime.Title.ByUser(user)
AnimeGridButton(entry.Anime, user)
component AnimeGridButton(anime *arn.Anime, user *arn.User)
if user != nil && !user.AnimeList().Contains(anime.ID)
button.anime-grid-add-button.action(data-action="addAnimeToCollection", data-trigger="click", data-api="/api/animelist/" + user.ID, data-anime-id=anime.ID, aria-label="Add anime to my list")
RawIcon("plus")

View File

@ -0,0 +1,34 @@
.anime-grid
grid
.anime-grid-cell
grid-cell
saturate-up
shadow-up
default-transition
:hover
.image-title,
.anime-grid-add-button
opacity 1
< 450px
.anime-grid-cell
width 71px
height 100px
margin 0.3rem
.anime-grid-cell-hide
opacity 0.05
pointer-events none
.anime-grid-image
grid-image
.anime-grid-add-button
opacity 0
position absolute
top 5px
right 5px
padding 0.25rem
height auto

View File

@ -0,0 +1,7 @@
component AnimeList(animeListItems []*arn.AnimeListItem, nextIndex int, viewUser *arn.User, user *arn.User)
#load-more-target.anime-list
AnimeListScrollable(animeListItems, viewUser, user)
if nextIndex != -1
.buttons
LoadMore(nextIndex)

View File

@ -0,0 +1,36 @@
component AnimeListScrollable(animeListItems []*arn.AnimeListItem, viewUser *arn.User, user *arn.User)
each item in animeListItems
.anime-list-item.mountable(title=item.Notes, data-api="/api/animelist/" + viewUser.ID + "/field/Items[AnimeID=\"" + item.AnimeID + "\"]")
.anime-list-item-image-container(draggable="true")
a.anime-list-item-image-link(href=item.Anime().Link())
img.anime-list-item-image.lazy(data-src=item.Anime().ImageLink("small"), data-webp="true", data-color=item.Anime().AverageColor(), alt=item.Anime().Title.ByUser(user))
.anime-list-item-name(draggable="true")
a(href=item.Link(viewUser.Nick))= item.Anime().Title.ByUser(user)
//- .anime-list-item-actions
//- if user != nil && item.Status != arn.AnimeListStatusCompleted
//- if item.Anime().EpisodeByNumber(item.Episodes + 1) != nil
//- for _, link := range item.Anime().EpisodeByNumber(item.Episodes + 1).Links
//- a.tip(href=link, aria-label="Watch episode " + fmt.Sprint(item.Episodes + 1), target="_blank", rel="noopener")
//- RawIcon("eye")
.anime-list-item-airing-date
if item.Status != arn.AnimeListStatusCompleted && item.Anime().UpcomingEpisode() != nil
span.utc-airing-date(data-start-date=item.Anime().UpcomingEpisode().Episode.AiringDate.Start, data-end-date=item.Anime().UpcomingEpisode().Episode.AiringDate.End, data-episode-number=item.Anime().UpcomingEpisode().Episode.Number)
if item.Status != arn.AnimeListStatusCompleted
.anime-list-item-episodes
.anime-list-item-episodes-watched
.action(contenteditable=arn.SameUser(user, viewUser), data-field="Episodes", data-type="number", data-trigger="focusout", data-action="save")= item.Episodes
if item.Status == arn.AnimeListStatusWatching && user != nil && user.ID == viewUser.ID
.plus-episode.action(data-action="increaseEpisode", data-trigger="click") +
else
.plus-episode-dummy +
.anime-list-item-episodes-separator /
.anime-list-item-episodes-max= item.Anime().EpisodeCountString()
.anime-list-item-rating-container
.anime-list-item-rating.action.tip(contenteditable=arn.SameUser(user, viewUser), data-field="Rating.Overall", data-type="number", data-trigger="focusout", data-action="save", aria-label="O: " + utils.FormatRating(item.Rating.Overall) + " | S: " + utils.FormatRating(item.Rating.Story) + " | V: " + utils.FormatRating(item.Rating.Visuals) + " | M: " + utils.FormatRating(item.Rating.Soundtrack))= utils.FormatRating(item.Rating.Overall)

View File

@ -0,0 +1,9 @@
component AnimeListItems(animeListItems []*arn.AnimeListItem, nextIndex int, viewUser *arn.User, user *arn.User)
if len(animeListItems) == 0
if user != nil && user.ID == viewUser.ID
p.no-data.mountable= "You haven't added any anime to this list yet."
else
p.no-data.mountable= viewUser.Nick + " hasn't added any anime to this list yet."
else
.anime-list-container
AnimeList(animeListItems, nextIndex, viewUser, user)

View File

@ -0,0 +1,2 @@
component Avatar(user *arn.User)
CustomAvatar(user, user.Link(), user.Nick)

View File

@ -0,0 +1,5 @@
component AvatarNoLink(user *arn.User)
if user.HasAvatar()
img.user-image.lazy(data-src=user.AvatarLink("small"), data-webp="true", alt=user.Nick)
else
SVGAvatar(user)

View File

@ -0,0 +1,3 @@
component AvatarNoTip(user *arn.User)
a.user(href=user.Link(), title=user.Nick)
AvatarNoLink(user)

View File

@ -0,0 +1,7 @@
component CustomAvatar(user *arn.User, link string, title string)
a.user.tip(href=link, aria-label=title)
AvatarNoLink(user)
if user.IsPro()
.user-pro-icon
RawIcon("star")

View File

@ -0,0 +1,5 @@
component ProfileImage(user *arn.User)
if user.HasAvatar()
img.profile-image.lazy(data-src=user.AvatarLink("large"), data-webp="true", alt="Profile image", importance="high")
else
SVGProfileImage(user)

View File

@ -0,0 +1,9 @@
component SVGAvatar(user *arn.User)
svg.user-image(viewBox="0 0 50 50")
circle.head(cx="25", cy="19", r="10")
circle.body(cx="25", cy="50", r="20")
if len(user.Nick) <= 6
text.svg-nick(x="25", y="44", text-anchor="middle")= user.Nick
else
text.svg-nick(x="25", y="44", text-anchor="middle")= user.Nick[:6]

View File

@ -0,0 +1,4 @@
component SVGProfileImage(user *arn.User)
svg.profile-image(viewBox="0 0 50 50", alt="Profile image")
circle.head(cx="25", cy="19", r="10")
circle.body(cx="25", cy="50", r="20")

View File

@ -0,0 +1,14 @@
component Comments(parent arn.PostParent, user *arn.User)
.posts
if user != nil
if arn.IsLocked(parent)
footer.footer.mountable
p.text-center= "This " + strings.ToLower(reflect.TypeOf(parent).Elem().Name()) + " is locked."
else if parent.TypeName() != "Group" || parent.(*arn.Group).FindMember(user.ID) != nil
NewPostArea(parent, user, "Comment")
//- if parent.CountPosts() == 0
//- p.no-data.mountable No comments have been written yet.
//- else
each post in parent.PostsRelevantFirst(5)
Postable(post, user, true, false, "")

3
elements/EditForm.pixy Normal file
View File

@ -0,0 +1,3 @@
component EditFormImagePreview(link string, imageURL string, webp bool, title string)
a.tip(href=link, target="_blank", aria-label=title)
img.lazy(data-src=imageURL, alt="Preview", data-webp=webp)

10
elements/Email.pixy Normal file
View File

@ -0,0 +1,10 @@
component NotificationEmail(notification *arn.Notification)
h2= notification.Message
p
img(src=notification.Icon, alt="Icon", style="width:125px; height:125px; object-fit: cover;")
p= notification.Message
if notification.Link != ""
a(href=notification.Link, target="_blank") View it on Anime Notifier

View File

@ -0,0 +1,2 @@
component ExternalMedia(media *arn.ExternalMedia)
iframe.lazy(data-src=media.EmbedLink(), title=media.Service + " media", allowfullscreen)

2
elements/Icon/Icon.pixy Normal file
View File

@ -0,0 +1,2 @@
component Icon(name string)
svg-icon.padded-icon(name=name, class="icon-" + name)

View File

@ -0,0 +1,10 @@
svg-icon
display inline-block
width 1em
height 1em
svg
display inherit
width 1em
height 1em
fill currentColor

View File

@ -0,0 +1,2 @@
component RawIcon(name string)
svg-icon(name=name, class="icon-" + name)

View File

@ -0,0 +1,5 @@
component ImportButton(url string)
.buttons.import-buttons
a.button.mountable(href=url)
Icon("check")
span Import

View File

@ -0,0 +1,11 @@
component InputBool(id string, value bool, label string, title string)
.widget-section
label= label + ":"
if value
button.action(id=id, data-action="disable", data-trigger="click", data-field=id, title=title)
Icon("toggle-on")
span ON
else
button.action(id=id, data-action="enable", data-trigger="click", data-field=id, title=title)
Icon("toggle-off")
span OFF

View File

@ -0,0 +1,9 @@
component InputColor(id string, variable string, label string)
.widget-section
label(for=id)= label
.color-picker-container
.widget-ui-element.color-picker.color-box.action(data-color="var(--" + variable + ")", data-variable=variable, data-action="pickColor", data-trigger="click")
button.tip(aria-label="Reset", disabled)
RawIcon("power-off")

View File

@ -0,0 +1,6 @@
component InputFileUpload(id string, label string, uploadType string, endpoint string)
.widget-section
label= label + ":"
button.action(id=id, data-action="selectFile", data-trigger="click", data-endpoint=endpoint, data-type=uploadType)
Icon("upload")
span Select file

View File

@ -0,0 +1,14 @@
component InputNumber(id string, value float64, label string, placeholder string, min string, max string, step string)
.widget-section
label(for=id)= label + ":"
input.widget-ui-element.action(id=id, data-field=id, type="number", value=value, min=min, max=max, step=step, placeholder=placeholder, title=placeholder, data-action="save", data-trigger="change")
component InputNumberWithButtons(id string, value float64, label string, placeholder string, min string, max string, step string)
.widget-section
label(for=id)= label + ":"
.number-input-container
input.widget-ui-element.action(id=id, data-field=id, type="number", value=value, min=min, max=max, step=step, placeholder=placeholder, title=placeholder, data-action="save", data-trigger="change")
button.action.tip(data-action="addNumber", data-trigger="click", data-id=id, data-add="1", aria-label="Increase by 1")
RawIcon("plus")
button.action.tip(data-action="addNumber", data-trigger="click", data-id=id, data-add="-1", aria-label="Decrease by 1")
RawIcon("minus")

View File

@ -0,0 +1,6 @@
component InputSelection(id string, value string, label string, placeholder string, options []*arn.Option)
.widget-section
label(for=id)= label + ":"
select.widget-ui-element.action(id=id, data-field=id, value=value, title=placeholder, data-action="save", data-trigger="change")
each option in options
option(value=option.Value)= option.Label

View File

@ -0,0 +1,13 @@
component InputTags(id string, value []string, label string, tooltip string)
.widget-section
label(for=id)= label + ":"
.tags(id=id)
for index, tag := range value
.tag.tag-edit.action(contenteditable="true", data-action="save", data-trigger="focusout", data-field=id + "[" + strconv.Itoa(index) + "]")= tag
button.tag-remove.action(data-action="arrayRemove", data-trigger="click", data-field=id, data-index=index)
RawIcon("trash")
button.tag-add.action(data-action="arrayAppend", data-trigger="click", data-field=id, title="Add more")
RawIcon("plus")
p!= tooltip

View File

@ -0,0 +1,4 @@
component InputText(id string, value string, label string, placeholder string, maxLength int)
.widget-section
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", maxlength=maxLength)

View File

@ -0,0 +1,4 @@
component InputTextArea(id string, value string, label string, placeholder string, maxLength int)
.widget-section
label(for=id)= label + ":"
textarea.widget-ui-element.action(id=id, data-field=id, placeholder=placeholder, title=placeholder, data-action="save", data-trigger="change", maxlength=maxLength)= value

View File

@ -0,0 +1,87 @@
input, textarea, button, .button, select
ui-element
font-family inherit
font-size 1em
line-height 1.25em
color text-color
input, textarea, select
input-focus
width 100%
:disabled
ui-disabled
input, select
padding 0.5rem 1rem
input
height input-height
[pattern]
:focus
:invalid
border-color red !important
:valid
border-color green !important
:active
transform translateY(3px)
.color-picker-container
horizontal
.color-picker
ui-element
flex 1
height input-height
margin-right content-padding-half
:hover
cursor pointer
:active
transform translateY(3px)
button, .button
horizontal
padding 0rem 1rem
color button-color
align-items center
pointer-events all
height input-height
button-hover
:disabled
ui-disabled
select
appearance none
-webkit-appearance none
-moz-appearance none
option
color text-color
background bg-color
label
width 100%
padding 0.5rem 0
text-align left
textarea
padding 0.4em 0.8em
line-height 1.5em
min-height 10rem
transition none
.number-input-container
horizontal
button
justify-content center
margin-left 0.2rem
width input-height
height input-height

View File

@ -0,0 +1,45 @@
.widget
vertical
flex 1
margin content-padding-half
.widget-section
vertical
width 100%
.widget-section-with-preview
horizontal
.widget-section-preview
display flex
justify-content center
align-items center
margin-left 1rem
img
anime-mini-item-image
.widget-title
horizontal
align-items center
padding-bottom 0.5rem
border-bottom 1px solid rgba(0, 0, 0, 0.1)
// We need !important here to overwrite the h3:first-child rule
margin 1rem 0 !important
.widget-ui-element
vertical-wrap
ui-element
transition border transition-speed ease, background transition-speed ease, transform transition-speed ease, color transition-speed ease
margin-bottom 1rem
padding 0.5rem 1rem
width 100%
// max-width 700px
.widget-form
width 100%
max-width 650px
margin 0 auto
.indent
margin-left 1rem

12
elements/Japanese.pixy Normal file
View File

@ -0,0 +1,12 @@
component Japanese(text string)
if stringutils.ContainsUnicodeLetters(text)
for _, token := range arn.JapaneseTokenizer.Tokenize(text)
if token.Furigana
a.japanese(href="http://jisho.org/search/" + token.Original, target="_blank", rel="noopener")
ruby(title=token.Romaji)= token.Original
rt.furigana= token.Hiragana
else
ruby.japanese(title=token.Romaji)= token.Original
rt.furigana
else
span.japanese= text

29
elements/Like.pixy Normal file
View File

@ -0,0 +1,29 @@
component LikeButton(label string, icon string, typeName string, likeable arn.Likeable, user *arn.User)
if user == nil
button.tip.action(aria-label="Login to like this " + typeName)
Icon(icon)
span= label
else
if likeable.LikedBy(user.ID)
button.tip.action(data-api="/api" + likeable.Link(), data-action="unlike", data-trigger="click", aria-label="Click to unlike this " + typeName)
Icon(icon)
span= label
else
button.tip.action(data-api="/api" + likeable.Link(), data-action="like", data-trigger="click", aria-label="Click to like this " + typeName)
Icon(icon + "-o")
span= label
component LikeTab(label string, icon string, typeName string, likeable arn.Likeable, user *arn.User)
if user == nil
.tab.action(aria-label=label, title="Login to like this " + typeName)
Icon(icon)
span.tab-text= label
else
if likeable.LikedBy(user.ID)
.tab.action(data-api="/api" + likeable.Link(), data-action="unlike", data-trigger="click", aria-label=label, title="Click to unlike this " + typeName)
Icon(icon)
span.tab-text= label
else
.tab.action(data-api="/api" + likeable.Link(), data-action="like", data-trigger="click", aria-label=label, title="Click to like this " + typeName)
Icon(icon + "-o")
span.tab-text= label

4
elements/LoadMore.pixy Normal file
View File

@ -0,0 +1,4 @@
component LoadMore(index int)
button#load-more-button.action(data-action="loadMore", data-trigger="click", data-index=index)
Icon("refresh")
span Load more

View File

@ -0,0 +1,11 @@
component LoadingAnimation
#loading.sk-cube-grid.fade
.sk-cube.hide
.sk-cube
.sk-cube.hide
.sk-cube
.sk-cube.sk-cube-center
.sk-cube
.sk-cube.hide
.sk-cube
.sk-cube.hide

View File

@ -0,0 +1,42 @@
const loading-anim-duration = 0.8s
const loading-anim-size = 24px
#loading
position fixed
bottom 1.15rem
right 1.15rem
z-index 1
pointer-events none
.sk-cube-grid
horizontal-wrap
width loading-anim-size
height loading-anim-size
transform rotateZ(0deg)
animation sk-rotate loading-anim-duration infinite linear
.sk-cube
width 33.3%
height 33.3%
background-color loading-anim-color
opacity 0.7
border-radius 100%
animation sk-pulse loading-anim-duration infinite linear
.sk-cube-center
opacity 1.0
.hide
visibility hidden
animation sk-rotate
0%
transform rotateZ(0deg)
100%
transform rotateZ(360deg)
animation sk-pulse
0%, 100%
transform scale3D(0.4, 0.4, 0.4)
50%
transform scale3D(0.9, 0.9, 0.9)

25
elements/NewPostArea.pixy Normal file
View File

@ -0,0 +1,25 @@
component NewPostArea(parent arn.PostParent, user *arn.User, placeholder string)
#new-post.post.mountable
.post-parent
.post-author
Avatar(user)
textarea#new-post-text.post-content(placeholder=placeholder + "...", aria-label=placeholder, maxlength=limits.DefaultTextAreaMaxLength)
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")
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")
Icon("close")
span Cancel

View File

@ -0,0 +1,75 @@
component Postable(post arn.Postable, user *arn.User, includeReplies bool, showParent bool, highlightAuthorID string)
.post.mountable(id=fmt.Sprintf("%s-%s", strings.ToLower(post.TypeName()), post.GetID()), data-pro=post.Creator().IsPro(), data-api=fmt.Sprintf("/api/%s/%s", strings.ToLower(post.TypeName()), post.GetID()))
.post-parent
.post-author
Avatar(post.Creator())
.post-box(data-highlight=post.Creator().ID == highlightAuthorID)
.post-header
.post-header-info
a(href=post.Creator().Link())= post.Creator().Nick
if showParent
if post.TypeName() == "Thread"
span in
a(href=post.Link())= post.TitleByUser(user)
else if post.GetParentType() == "User"
if post.GetParentID() != post.Creator().ID
span to
a(href=post.Parent().Link())= post.Parent().TitleByUser(user)
else if post.GetParentType() != ""
span in
a(href=post.Parent().Link())= post.Parent().TitleByUser(user)
if user != nil
if user.ID == post.Creator().ID
button.post-action.post-header-action.tip.action(data-action="editPost", data-trigger="click", data-id=post.GetID(), aria-label="Edit")
RawIcon("pencil")
if post.TypeName() != "Thread"
if user != nil && (user.Role == "admin" || user.Role == "editor")
button.post-action.post-header-action.tip.action(data-action="deletePost", data-trigger="click", data-id=post.GetID(), aria-label="Delete")
RawIcon("trash")
a.post-action.post-header-action.tip(href=post.Link(), aria-label="Link")
RawIcon("link")
.post-date.utc-date(data-date=post.GetCreated())
.post-content(id="render-" + post.GetID())!= post.HTML()
if user != nil && user.ID == post.Creator().ID
.post-edit-interface
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(), maxlength=limits.DefaultTextAreaMaxLength)= post.GetText()
.buttons.hidden(id="edit-toolbar-" + post.GetID())
a.button.post-save.action(data-action="savePost", data-trigger="click", data-id=post.GetID())
Icon("save")
span Save
a.button.post-cancel-edit.action(data-action="editPost", data-trigger="click", data-id=post.GetID())
Icon("close")
span Cancel
.post-toolbar
if user != nil
button.post-action.post-toolbar-action.tip.action(data-post-id=post.GetID(), aria-label="Reply", data-action="reply", data-trigger="click")
Icon("reply")
span= post.CountPosts()
if user != nil && post.LikedBy(user.ID)
button.post-action.post-toolbar-action.tip.action(id="unlike-" + post.GetID(), aria-label="Unlike", data-action="unlike", data-trigger="click", data-like="true")
Icon("heart")
span= post.CountLikes()
else
button.post-action.post-toolbar-action.tip.action(id="like-" + post.GetID(), aria-label="Like", data-action="like", data-trigger="click", data-like="false")
Icon("heart-o")
span= post.CountLikes()
.replies(id="replies-" + post.GetID())
if includeReplies
each reply in post.Posts()
Postable(reply, user, true, false, highlightAuthorID)

View File

@ -0,0 +1,109 @@
post-content-padding-y = 0.75rem
.post-author
horizontal
justify-content center
align-items flex-start
margin-right post-avatar-text-margin
.post-header
horizontal
horizontal-line-bottom
.post-header-info
flex 1
clip-long-text
.post-action
display flex
align-items center
border none
background none
padding 0
height auto
color hsla(text-color-h, text-color-s, text-color-l, 0.5)
:hover
color link-hover-color
background none
.post-header-action
opacity 0
margin-right 0.5rem
.post-date
color hsla(text-color-h, text-color-s, text-color-l, 0.5)
white-space nowrap
.post-box
ui-element
flex-grow 1
padding 0.75rem 1rem
position relative
overflow hidden
h1, h2, h3
font-weight bold
text-align left
line-height 1.5em
margin 1rem 0
h1
font-size 1.5rem
h2
font-size 1.3rem
h3
font-size 1.1rem
img
max-width 100%
max-height 450px
width 100%
object-fit cover
:hover
.post-header-action
opacity 1
.post-toolbar
horizontal
justify-content flex-end
margin-top typography-margin
padding-top typography-margin
border-top 1px solid reverse-light-color
.post-toolbar-action
margin-left 1rem
[data-like="true"]
color hsla(0, 90%, 50%, 1)
.new-post-actions
justify-content flex-end
opacity 0
height 0
transform translateY(-50%)
transition all transition-speed ease
button
pointer-events none
.new-post-actions-enabled
opacity 1
height auto
margin-bottom 0.75rem
transform translateY(0)
button
pointer-events all
.post-edit-interface
vertical
.post-title-input
margin-bottom post-content-padding-y
.post-text-input
min-height 200px

View File

@ -0,0 +1,5 @@
component PostableList(postables []arn.Postable, user *arn.User)
.thread
.posts
each post in postables
Postable(post, user, false, false, "")

14
elements/Quote/Quote.pixy Normal file
View File

@ -0,0 +1,14 @@
component Quote(quote *arn.Quote, user *arn.User)
.quote.mountable
QuoteContent(quote, user)
QuoteFooter(quote)
component QuoteContent(quote *arn.Quote, user *arn.User)
.quote-content
a.quotation(href=quote.Link())
QuoteText(quote)
QuoteCharacter(quote, user)
component QuoteText(quote *arn.Quote)
blockquote!= utils.RenderQuoteText(quote.Text.English)

View File

@ -0,0 +1,56 @@
const quote-margin = 1rem
.quote
vertical
flex 1
flex-basis 500px
margin quote-margin 0
> 500px
.quote
margin quote-margin
.quote-content
vertical
ui-element
border-left 5px solid quote-side-border-color !important
box-shadow shadow-light
.quote-line
// ...
.quote-character
horizontal
align-self flex-end
margin 0 1em 1em 0
.character
margin 0
.quote-footer
media-footer
blockquote
flex-grow 1
padding 1em
p
line-height 2em
quotes "\201C""\201D"
color text-color
:before
color quote-color
content open-quote
font-size 4em
line-height 0.1em
margin-right 0.25em
vertical-align -0.4em
:after
color quote-color
content close-quote
font-size 4em
line-height 0.1em
margin-left 0.25em
vertical-align -0.4em

View File

@ -0,0 +1,4 @@
component QuoteCharacter(quote *arn.Quote, user *arn.User)
if quote.CharacterID != "" && quote.Character() != nil
footer.quote-character
CharacterSmall(quote.Character(), user)

View File

@ -0,0 +1,6 @@
component QuoteFooter(quote *arn.Quote)
footer.quote-footer
span posted
span.utc-date.no-tip(data-date=quote.Created)
span by
a(href=quote.Creator().Link())= quote.Creator().Nick

View File

@ -0,0 +1,14 @@
component QuotePreview(quote *arn.Quote, user *arn.User)
.quote.mountable
QuoteContentPreview(quote, user)
QuoteFooter(quote)
component QuoteContentPreview(quote *arn.Quote, user *arn.User)
.quote-content
a.quotation(href=quote.Link())
QuoteTextPreview(quote)
QuoteCharacter(quote, user)
component QuoteTextPreview(quote *arn.Quote)
blockquote!= utils.RenderQuoteText(utils.CutLongDescription(quote.Text.English))

5
elements/Rating.pixy Normal file
View File

@ -0,0 +1,5 @@
component Rating(value float64, userCount int, user *arn.User)
if user == nil
.anime-rating.tip(aria-label="Rated by " + stringutils.Plural(userCount, "user"))= fmt.Sprintf("%.1f", value)
else
.anime-rating.tip(aria-label="Rated by " + stringutils.Plural(userCount, "user"))= fmt.Sprintf("%." + strconv.Itoa(user.Settings().Format.RatingsPrecision) + "f", value)

View File

@ -0,0 +1,2 @@
component Search(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", maxlength="100", value=value)

View File

@ -0,0 +1,36 @@
#search
background transparent
border none !important
box-shadow none !important
font-size 1em
padding 0
width 0
height auto
flex-grow 1
opacity 0.5
-webkit-appearance none
:focus
color link-color
opacity 1.0
::-webkit-search-cancel-button
display none
// Microphone icon
.speech-input
display none
opacity 0
default-transition
:hover
cursor pointer
opacity 1
.speech-input-available
display block
opacity 0.25
.speech-listening
color link-hover-color
opacity 1

36
elements/SocialMedia.pixy Normal file
View File

@ -0,0 +1,36 @@
component SocialMediaLinks
a.footer-element(href="https://discord.gg/0kimAmMCeXGXuzNF", target="_blank", rel="noopener")
Icon("discord")
span Discord
a.footer-element(href="https://www.facebook.com/animenotifier", target="_blank", rel="noopener")
Icon("facebook")
span Facebook
a.footer-element(href="https://twitter.com/animenotifier", target="_blank", rel="noopener")
Icon("twitter")
span Twitter
a.footer-element(href="https://patreon.com/eduardurbach", target="_blank", rel="noopener")
Icon("patreon")
span Patreon
a.footer-element(href="https://github.com/animenotifier/notify.moe", target="_blank", rel="noopener")
Icon("github")
span GitHub
component SocialMediaButtons
a.social-media-button.circle-1(href="https://discord.gg/0kimAmMCeXGXuzNF", title="Discord", target="_blank", rel="noopener")
RawIcon("discord")
a.social-media-button.circle-2(href="https://www.facebook.com/animenotifier", title="Facebook", target="_blank", rel="noopener")
RawIcon("facebook-square")
a.social-media-button.circle-3(href="https://twitter.com/animenotifier", title="Twitter", target="_blank", rel="noopener")
RawIcon("twitter-square")
a.social-media-button.circle-4(href="https://patreon.com/eduardurbach", title="Patreon", target="_blank", rel="noopener")
RawIcon("patreon")
a.social-media-button.circle-5(href="https://github.com/animenotifier/notify.moe", title="GitHub", target="_blank", rel="noopener")
RawIcon("github")

View File

@ -0,0 +1,39 @@
component SoundTrack(track *arn.SoundTrack, user *arn.User)
.soundtrack.mountable(id=track.ID)
SoundTrackContent(track, user)
SoundTrackFooter(track, user)
component SoundTrackContent(track *arn.SoundTrack, user *arn.User)
.soundtrack-content
if track.MainAnime() != nil
a.soundtrack-anime-link(href="/anime/" + track.MainAnime().ID, title=track.MainAnime().Title.ByUser(user))
img.soundtrack-anime-image.lazy(data-src=track.MainAnime().ImageLink("medium"), data-webp="true", data-color=track.MainAnime().AverageColor(), alt=track.MainAnime().Title.Canonical)
SoundTrackMedia(track)
component SoundTrackMedia(track *arn.SoundTrack)
if track.File != "" && track.HasMediaByService("Youtube")
.soundtrack-media
.media-play-area.action(data-action="toggleAudio", data-trigger="click", data-audio-src="https://notify.moe/audio/" + track.File, data-media-id=track.ID)
img.media-image.lazy(data-src="https://img.youtube.com/vi/" + track.MediaByService("Youtube")[0].ServiceID + "/0.jpg", alt=track.Title)
button.media-play-button(aria-label="Play soundtrack")
RawIcon("play")
.media-visualizer
.visualizer-box.visualizer-box-1
.visualizer-box.visualizer-box-2
.visualizer-box.visualizer-box-3
else if len(track.Media) > 0
ExternalMedia(track.Media[0])
component SoundTrackFooter(track *arn.SoundTrack, user *arn.User)
footer.soundtrack-footer
if track.Title.ByUser(user) == ""
a(href=track.Link() + "/edit") untitled
else
a(href=track.Link())= track.Title.ByUser(user)
span posted
span.utc-date.no-tip(data-date=track.Created)
span by
a(href=track.Creator().Link())= track.Creator().Nick + " "

View File

@ -0,0 +1,11 @@
component SoundTrackMini(track *arn.SoundTrack, user *arn.User)
.soundtrack.mountable(id=track.ID)
SoundTrackContent(track, user)
SoundTrackMiniFooter(track, user)
component SoundTrackMiniFooter(track *arn.SoundTrack, user *arn.User)
footer.soundtrack-footer
if track.Title.ByUser(user) == ""
a(href=track.Link() + "/edit") untitled
else
a(href=track.Link())= track.Title.ByUser(user)

View File

@ -0,0 +1,5 @@
component StatusMessage
#status-message.fade.fade-out
#status-message-text
a.status-message-action.action(href="#", data-trigger="click", data-action="closeStatusMessage", aria-label="Close status message")
RawIcon("close")

View File

@ -0,0 +1,33 @@
#status-message
horizontal
position fixed
bottom 0
left 0
width 100%
padding 0.5rem content-padding
pointer-events none
z-index 1000
&.fade-out
z-index 1
#status-message-text
flex 1
text-align center
line-height content-line-height
.status-message-action
color white !important
pointer-events auto !important
display flex
justify-content center
align-items center
margin-left 0.5rem
.error-message
color white
background-color hsl(0, 75%, 50%)
.info-message
color white
background tab-active-background

7
elements/StatusTabs.pixy Normal file
View File

@ -0,0 +1,7 @@
component StatusTabs(urlPrefix string, statusLists map[string]*arn.AnimeList)
.tabs
TabWithCount("Watching", len(statusLists[arn.AnimeListStatusWatching].Items), "play", urlPrefix + "/watching")
TabWithCount("Completed", len(statusLists[arn.AnimeListStatusCompleted].Items), "check", urlPrefix + "/completed")
TabWithCount("Planned", len(statusLists[arn.AnimeListStatusPlanned].Items), "forward", urlPrefix + "/planned")
TabWithCount("On Hold", len(statusLists[arn.AnimeListStatusHold].Items), "pause", urlPrefix + "/hold")
TabWithCount("Dropped", len(statusLists[arn.AnimeListStatusDropped].Items), "stop", urlPrefix + "/dropped")

4
elements/Tab/Tab.pixy Normal file
View File

@ -0,0 +1,4 @@
component Tab(label string, icon string, url string)
a.tab.action(href=url, data-action="diff", data-trigger="click", aria-label=label, dropzone="move")
Icon(icon)
span.tab-text= label

57
elements/Tab/Tab.scarlet Normal file
View File

@ -0,0 +1,57 @@
const tab-padding-x = 1rem
.tab
horizontal
align-items center
color text-color
padding 0.5rem tab-padding-x
background-color tab-background
border ui-border
border-left none
white-space nowrap
:hover
color text-color
background-color tab-hover-background
cursor pointer
:active
transform none
&.active
background-color tab-active-background
color tab-active-color
:first-child
border-left ui-border
border-top-left-radius ui-element-border-radius
border-bottom-left-radius ui-element-border-radius
:last-child
border-top-right-radius ui-element-border-radius
border-bottom-right-radius ui-element-border-radius
.tab-count
margin-left typography-margin
opacity 0.5
< 920px
.tab
padding 0.75rem tab-padding-x
.padded-icon
margin-right 0
.tab-text,
.tab-count
display none
.tabs
horizontal
justify-content center
margin content-padding calc(content-padding / 4)
margin-top 0
.tab-groups
horizontal
justify-content center

View File

@ -0,0 +1,5 @@
component TabWithCount(label string, count int, icon string, url string)
a.tab.action(href=url, data-action="diff", data-trigger="click", aria-label=label, dropzone="move")
Icon(icon)
span.tab-text= label
span.tab-count= count

13
elements/ThreadLink.pixy Normal file
View File

@ -0,0 +1,13 @@
component ThreadLink(thread *arn.Thread)
.thread-link.mountable(data-sticky=thread.Sticky)
.post-author.thread-author
Avatar(thread.Creator())
.thread-content-container
.thread-content
if thread.Sticky != 0
Icon("thumb-tack")
a.thread-link-title(href="/thread/" + thread.ID)= thread.Title
.spacer
.thread-reply-count= len(thread.PostIDs)
.thread-icons
Icon(arn.GetForumIcon(thread.Tags[0]))

View File

@ -0,0 +1,7 @@
component UserCard(user *arn.User, note string)
a.user-card.mountable(href=user.Link(), data-pro=user.IsPro())
.user-card-image-container
AvatarNoLink(user)
.user-card-info
.user-card-info-main= user.Nick
.user-card-info-details= note

View File

@ -0,0 +1,33 @@
.user-cards
horizontal-wrap
justify-content center
.user-card
card
> 600px
.user-card
max-width 230px
.user-card-image-container
width avatar-size
height avatar-size
img
width avatar-size
height avatar-size
border-radius ui-element-border-radius
box-shadow none !important
.user-card-info
vertical
margin-left card-padding
overflow hidden
.user-card-info-main
clip-long-text
.user-card-info-details
clip-long-text
color text-color
opacity 0.5

View File

@ -0,0 +1,19 @@
component VideoControls(containerID string, duration time.Duration)
.video-controls
button.video-control.video-control-play.action(data-action="togglePlayVideo", data-trigger="click", data-media-id=containerID, aria-label="Play")
RawIcon("play")
button.video-control.video-control-pause.action(data-action="togglePlayVideo", data-trigger="click", data-media-id=containerID, aria-label="Pause")
RawIcon("pause")
.video-progress-clickable
.video-progress-container
.video-progress
.video-time= fmt.Sprintf("%d:%02d", int(duration.Minutes()), int(duration.Seconds()) % 60)
//- button.video-control.action(data-action="like", data-trigger="click")
//- RawIcon("heart-o")
button.video-control.video-control-fullscreen.action(data-action="toggleFullscreen", data-trigger="click", data-id=containerID, aria-label="Fullscreen")
RawIcon("fullscreen")