diff --git a/mixins/Quote.pixy b/mixins/Quote.pixy new file mode 100644 index 00000000..3b3e950f --- /dev/null +++ b/mixins/Quote.pixy @@ -0,0 +1,23 @@ +component Quote(quote *arn.Quote) + .quote.mountable(id=quote.ID) + QuoteContent(quote) + QuoteFooter(quote) + +component QuoteContent(quote *arn.Quote) + .quote-content + a.quotation.ajax(href="/quote/" + quote.ID) + blockquote + p + q= quote.Description + if quote.Character() != nil + footer.quote-character + span= "by" + a.ajax(href="/character/" + quote.Character().ID)= quote.Character().Name + CharacterSmall(quote.Character()) + +component QuoteFooter(quote *arn.Quote) + .quote-footer + span posted + span.utc-date(data-date=quote.Created) + span by + a.ajax(href=quote.Creator().Link())= quote.Creator().Nick + " " diff --git a/pages/index.go b/pages/index.go index 481ac3ed..c0daf691 100644 --- a/pages/index.go +++ b/pages/index.go @@ -107,7 +107,10 @@ func Configure(app *aero.Application) { // Quotes l.Page("/quote/:id", quote.Get) l.Page("/quote/:id/edit", quote.Edit) - l.Page("/quotes", quotes.Get) + l.Page("/quotes", quotes.Latest) + l.Page("/quotes/from/:index", quotes.LatestFrom) + l.Page("/quotes/best", quotes.Best) + l.Page("/quotes/best/from/:index", quotes.BestFrom) // Calendar l.Page("/calendar", calendar.Get) diff --git a/pages/quote/edit.go b/pages/quote/edit.go index a741e1b9..e7f72841 100644 --- a/pages/quote/edit.go +++ b/pages/quote/edit.go @@ -28,5 +28,9 @@ func Edit(ctx *aero.Context) string { }, } + if quote.Character() != nil { + ctx.Data.(*arn.OpenGraph).Tags["og:image"] = quote.Character().Image + } + return ctx.HTML(components.QuoteTabs(quote, user) + editform.Render(quote, "Edit quote", user)) } diff --git a/pages/quote/quote.go b/pages/quote/quote.go index 882d67fd..36090c86 100644 --- a/pages/quote/quote.go +++ b/pages/quote/quote.go @@ -19,7 +19,7 @@ func Get(ctx *aero.Context) string { return ctx.Error(http.StatusNotFound, "Quote not found", err) } - character, err := arn.GetCharacter(quote.Character) + character, err := arn.GetCharacter(quote.CharacterId) if err != nil { return ctx.Error(http.StatusNotFound, "Quote not found", err) } diff --git a/pages/quotes/best.go b/pages/quotes/best.go new file mode 100644 index 00000000..e86df8ea --- /dev/null +++ b/pages/quotes/best.go @@ -0,0 +1,66 @@ +package quotes + +import ( + "net/http" + "strconv" + + "github.com/aerogo/aero" + "github.com/animenotifier/arn" + "github.com/animenotifier/notify.moe/components" + "github.com/animenotifier/notify.moe/utils" +) + +// Best renders the quotes page ordered by the most favorites first. +func Best(ctx *aero.Context) string { + user := utils.GetUser(ctx) + + quotes := arn.FilterQuotes(func(track *arn.Quote) bool { + return !track.IsDraft && len(track.Description) > 0 + }) + + arn.SortQuotesPopularFirst(quotes) + + if len(quotes) > maxQuotes { + quotes = quotes[:maxQuotes] + } + + return ctx.HTML(components.Quotes(quotes, maxQuotes, user)) +} + +// BestFrom renders the quotes from the given index. +func BestFrom(ctx *aero.Context) string { + user := utils.GetUser(ctx) + index, err := ctx.GetInt("index") + + if err != nil { + return ctx.Error(http.StatusBadRequest, "Invalid start index", err) + } + + allQuotes := arn.FilterQuotes(func(track *arn.Quote) bool { + return !track.IsDraft && len(track.Description) > 0 + }) + + if index < 0 || index >= len(allQuotes) { + return ctx.Error(http.StatusBadRequest, "Invalid start index (maximum is "+strconv.Itoa(len(allQuotes))+")", nil) + } + + arn.SortQuotesPopularFirst(allQuotes) + + quotes := allQuotes[index:] + + if len(quotes) > maxQuotes { + quotes = quotes[:maxQuotes] + } + + nextIndex := index + maxQuotes + + if nextIndex >= len(allQuotes) { + // End of data - no more scrolling + ctx.Response().Header().Set("X-LoadMore-Index", "-1") + } else { + // Send the index for the next request + ctx.Response().Header().Set("X-LoadMore-Index", strconv.Itoa(nextIndex)) + } + + return ctx.HTML(components.QuotesScrollable(quotes, user)) +} diff --git a/pages/quotes/quotes.go b/pages/quotes/quotes.go index ce6e4ff4..35b13068 100644 --- a/pages/quotes/quotes.go +++ b/pages/quotes/quotes.go @@ -5,12 +5,62 @@ import ( "github.com/animenotifier/arn" "github.com/animenotifier/notify.moe/components" "github.com/animenotifier/notify.moe/utils" + "net/http" + "strconv" ) -// Get renders the quotes page. -func Get(ctx *aero.Context) string { +const maxQuotes = 12 + +// Latest renders the quotes page. +func Latest(ctx *aero.Context) string { user := utils.GetUser(ctx) - quotes := arn.AllQuotes() - return ctx.HTML(components.Quotes(quotes, user)) + quotes := arn.FilterQuotes(func(track *arn.Quote) bool { + return !track.IsDraft && len(track.Description) > 0 + }) + + arn.SortQuotesLatestFirst(quotes) + + if len(quotes) > maxQuotes { + quotes = quotes[:maxQuotes] + } + return ctx.HTML(components.Quotes(quotes, maxQuotes, user)) +} + +// LatestFrom renders the quotes from the given index. +func LatestFrom(ctx *aero.Context) string { + user := utils.GetUser(ctx) + index, err := ctx.GetInt("index") + + if err != nil { + return ctx.Error(http.StatusBadRequest, "Invalid start index", err) + } + + allQuotes := arn.FilterQuotes(func(track *arn.Quote) bool { + return !track.IsDraft && len(track.Description) > 0 + }) + + if index < 0 || index >= len(allQuotes) { + return ctx.Error(http.StatusBadRequest, "Invalid start index (maximum is "+strconv.Itoa(len(allQuotes))+")", nil) + } + + arn.SortQuotesLatestFirst(allQuotes) + + quotes := allQuotes[index:] + + if len(quotes) > maxQuotes { + quotes = quotes[:maxQuotes] + } + + nextIndex := index + maxQuotes + + if nextIndex >= len(allQuotes) { + // End of data - no more scrolling + ctx.Response().Header().Set("X-LoadMore-Index", "-1") + } else { + // Send the index for the next request + ctx.Response().Header().Set("X-LoadMore-Index", strconv.Itoa(nextIndex)) + } + + return ctx.HTML(components.QuotesScrollable(quotes, user)) } diff --git a/pages/quotes/quotes.pixy b/pages/quotes/quotes.pixy index 74f9cb56..7d947aa5 100644 --- a/pages/quotes/quotes.pixy +++ b/pages/quotes/quotes.pixy @@ -1,6 +1,8 @@ -component Quotes(quotes []*arn.Quote, user *arn.User) +component Quotes(quotes []*arn.Quote, quotesPerPage int, user *arn.User) h1.page-title Quotes + QuotesTabs + .corner-buttons if user != nil if user.DraftIndex().QuoteID == "" @@ -12,6 +14,18 @@ component Quotes(quotes []*arn.Quote, user *arn.User) Icon("pencil") span Edit draft - ul - each quote in quotes - li= quote.Description \ No newline at end of file + #load-more-target.quotes + QuotesScrollable(quotes, user) + + if len(quotes) == quotesPerPage + .buttons + LoadMore(quotesPerPage) + +component QuotesScrollable(quotes []*arn.Quote, user *arn.User) + each quote in quotes + Quote(quote) + +component QuotesTabs + .tabs + Tab("Latest", "quote-left", "/quotes") + Tab("Best", "heart", "/quotes/best") \ No newline at end of file diff --git a/pages/quotes/quotes.scarlet b/pages/quotes/quotes.scarlet new file mode 100644 index 00000000..99e99da8 --- /dev/null +++ b/pages/quotes/quotes.scarlet @@ -0,0 +1,87 @@ +.quotes + horizontal-wrap + justify-content space-around + +.quote + vertical + flex 1 + flex-basis 500px + padding 1rem + +.quote-content + vertical + border-radius 3px + border-left 5px solid quote-side-border-color + overflow hidden + box-shadow shadow-light + align-items stretch + align-content stretch + +.quote-character + horizontal + align-self flex-end + align-items baseline + justify-content space-around + padding 0 1em 1em 0 + + span + opacity 0.65 + + a + display inline + margin-left 0.5em + + +.quote-footer + text-align center + margin-bottom 1rem + margin-top 0.4rem + font-size 0.9em + + span + opacity 0.65 + +.quotation + height 100% + display flex + flex-grow 1 + align-items stretch + align-content stretch + +blockquote + flex-grow 1 + padding 1em + + p + line-height 2em + + q + quotes "\201C""\201D" + + :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 + + +.character + display none !important + +> 800px + .character + margin 0.5em 0 0 0.5em !important + display block !important + + .quote-character a + display none \ No newline at end of file diff --git a/scripts/Actions/Theme.ts b/scripts/Actions/Theme.ts index 79ddd84d..851d967e 100644 --- a/scripts/Actions/Theme.ts +++ b/scripts/Actions/Theme.ts @@ -35,7 +35,9 @@ let dark = { "post-like-color": "var(--link-color)", "post-unlike-color": "var(--link-color)", - "post-permalink-color": "var(--link-color)" + "post-permalink-color": "var(--link-color)", + + "quote-color" : "var(--text-color)" } // Toggle theme diff --git a/styles/include/config.scarlet b/styles/include/config.scarlet index 21941f6d..0e8b60da 100644 --- a/styles/include/config.scarlet +++ b/styles/include/config.scarlet @@ -59,6 +59,10 @@ nav-link-hover-slide-color = main-color // nav-link-color = rgb(160, 160, 160) // nav-link-hover-color = rgb(80, 80, 80) +// Quote +quote-color = hsl(0, 0%, 45%) +quote-side-border-color = quote-color + // Forum post-like-color = green !important post-unlike-color = rgb(255, 32, 12) !important diff --git a/styles/include/dark.scarlet b/styles/include/dark.scarlet index b55c961d..a0207da9 100644 --- a/styles/include/dark.scarlet +++ b/styles/include/dark.scarlet @@ -34,4 +34,7 @@ // // Forum // post-like-color = link-color // post-unlike-color = link-color -// post-permalink-color = link-color \ No newline at end of file +// post-permalink-color = link-color + +// // Quote +// quote-color = text-color \ No newline at end of file