From 960b1e4b929c032b9cf738d379b09cb7884f7b62 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 5 Nov 2018 20:57:37 +0900 Subject: [PATCH] Threaded comments (Reply UI) --- mixins/NewPostArea.pixy | 14 ++++++++++-- mixins/Postable.pixy | 6 +++-- pages/activity/activity.pixy | 3 +-- pages/activity/activity.scarlet | 8 +------ pages/index/apiroutes/apiroutes.go | 9 ++++++++ pages/post/newpostarea.go | 13 ----------- pages/post/reply-ui.go | 23 +++++++++++++++++++ pages/thread/reply-ui.go | 23 +++++++++++++++++++ pages/thread/thread.pixy | 4 +--- pages/thread/thread.scarlet | 8 ++++++- scripts/Actions/Forum.ts | 36 ++++++++++++++++++++++++++++-- scripts/Actions/Search.ts | 14 ++---------- scripts/AnimeNotifier.ts | 13 +++++++++-- styles/forum.scarlet | 5 +++++ 14 files changed, 133 insertions(+), 46 deletions(-) delete mode 100644 pages/post/newpostarea.go create mode 100644 pages/post/reply-ui.go create mode 100644 pages/thread/reply-ui.go diff --git a/mixins/NewPostArea.pixy b/mixins/NewPostArea.pixy index 826adb2c..b267374c 100644 --- a/mixins/NewPostArea.pixy +++ b/mixins/NewPostArea.pixy @@ -1,7 +1,17 @@ component NewPostArea(user *arn.User, placeholder string) - .post.mountable + #new-post.post.mountable .post-parent .post-author Avatar(user) - textarea#new-post.post-content(placeholder=placeholder + "...", aria-label=placeholder) \ No newline at end of file + textarea#new-post-text.post-content(placeholder=placeholder + "...", aria-label=placeholder) + +component NewPostActions(parentType string, parentID string) + #new-post-actions.buttons + button#reply-button.mountable.action(data-action="createPost", data-trigger="click", data-parent-type=parentType, data-parent-id=parentID) + Icon("mail-reply") + span Reply + + button#reply-cancel-button.mountable.action(data-action="cancelReply", data-trigger="click") + Icon("close") + span Cancel \ No newline at end of file diff --git a/mixins/Postable.pixy b/mixins/Postable.pixy index c18bc167..98b34857 100644 --- a/mixins/Postable.pixy +++ b/mixins/Postable.pixy @@ -14,7 +14,9 @@ component Postable(post arn.Postable, user *arn.User, includeReplies bool, heade .post-edit-interface if post.Type() == "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() + .buttons.hidden(id="edit-toolbar-" + post.GetID()) a.button.post-save.action(data-action="savePost", data-trigger="click", data-id=post.GetID()) Icon("save") @@ -43,7 +45,7 @@ component Postable(post arn.Postable, user *arn.User, includeReplies bool, heade //- a.post-tool.post-edit.tip(href=post.Link() + "/edit", aria-label="Edit") //- Icon("edit") - a.post-tool.post-reply.tip.action(id="reply-" + post.GetID(), aria-label="Reply", data-action="reply", data-trigger="click") + a.post-tool.post-reply.tip.action(data-post-id=post.GetID(), aria-label="Reply", data-action="reply", data-trigger="click") Icon("reply") if user.ID == post.Creator().ID @@ -58,7 +60,7 @@ component Postable(post arn.Postable, user *arn.User, includeReplies bool, heade a.post-tool.post-permalink.tip(href=post.Link(), aria-label="Link") Icon("link") - .replies + .replies(id="replies-" + post.GetID()) if includeReplies each reply in post.Posts() Postable(reply, user, true, "", highlightAuthorID) diff --git a/pages/activity/activity.pixy b/pages/activity/activity.pixy index a966e1b4..81f04fe0 100644 --- a/pages/activity/activity.pixy +++ b/pages/activity/activity.pixy @@ -3,8 +3,7 @@ component ActivityFeed(entries []*arn.EditLogEntry, user *arn.User) .activities each entry in entries - .activity - ActivityPost(entry.Object().(arn.Postable), user) + ActivityPost(entry.Object().(arn.Postable), user) component ActivityPost(post arn.Postable, user *arn.User) if post.Parent() != nil diff --git a/pages/activity/activity.scarlet b/pages/activity/activity.scarlet index cedd67fc..1f3ed4f1 100644 --- a/pages/activity/activity.scarlet +++ b/pages/activity/activity.scarlet @@ -2,10 +2,4 @@ vertical width 100% max-width forum-width - margin 0 auto - -// .activity -// margin-bottom 1rem - -.activity-header - font-size 0.9rem \ No newline at end of file + margin 0 auto \ No newline at end of file diff --git a/pages/index/apiroutes/apiroutes.go b/pages/index/apiroutes/apiroutes.go index 3331067d..d615ee43 100644 --- a/pages/index/apiroutes/apiroutes.go +++ b/pages/index/apiroutes/apiroutes.go @@ -3,6 +3,9 @@ package apiroutes import ( "strings" + "github.com/animenotifier/notify.moe/pages/post" + "github.com/animenotifier/notify.moe/pages/thread" + "github.com/aerogo/aero" "github.com/aerogo/layout" @@ -39,6 +42,12 @@ func Register(l *layout.Layout, app *aero.Application) { app.Get("/api/next/soundtrack", soundtrack.Next) app.Get("/api/character/:id/ranking", character.Ranking) + // Thread + app.Get("/api/thread/:id/reply/ui", thread.ReplyUI) + + // Post + app.Get("/api/post/:id/reply/ui", post.ReplyUI) + // SoundTrack app.Post("/api/soundtrack/:id/download", soundtrack.Download) diff --git a/pages/post/newpostarea.go b/pages/post/newpostarea.go deleted file mode 100644 index a485811a..00000000 --- a/pages/post/newpostarea.go +++ /dev/null @@ -1,13 +0,0 @@ -package post - -import ( - "github.com/aerogo/aero" - "github.com/animenotifier/notify.moe/components" - "github.com/animenotifier/notify.moe/utils" -) - -// NewPostArea renders a new post area. -func NewPostArea(ctx *aero.Context) string { - user := utils.GetUser(ctx) - return ctx.HTML(components.NewPostArea(user, "Reply")) -} diff --git a/pages/post/reply-ui.go b/pages/post/reply-ui.go new file mode 100644 index 00000000..c96101be --- /dev/null +++ b/pages/post/reply-ui.go @@ -0,0 +1,23 @@ +package post + +import ( + "net/http" + + "github.com/aerogo/aero" + "github.com/animenotifier/arn" + "github.com/animenotifier/notify.moe/components" + "github.com/animenotifier/notify.moe/utils" +) + +// ReplyUI renders a new post area. +func ReplyUI(ctx *aero.Context) string { + id := ctx.Get("id") + user := utils.GetUser(ctx) + post, err := arn.GetPost(id) + + if err != nil { + return ctx.Error(http.StatusNotFound, "Post not found", err) + } + + return ctx.HTML(components.NewPostArea(user, "Reply") + components.NewPostActions(post.Type(), post.ID)) +} diff --git a/pages/thread/reply-ui.go b/pages/thread/reply-ui.go new file mode 100644 index 00000000..43afe342 --- /dev/null +++ b/pages/thread/reply-ui.go @@ -0,0 +1,23 @@ +package thread + +import ( + "net/http" + + "github.com/aerogo/aero" + "github.com/animenotifier/arn" + "github.com/animenotifier/notify.moe/components" + "github.com/animenotifier/notify.moe/utils" +) + +// ReplyUI renders a new post area. +func ReplyUI(ctx *aero.Context) string { + id := ctx.Get("id") + user := utils.GetUser(ctx) + thread, err := arn.GetThread(id) + + if err != nil { + return ctx.Error(http.StatusNotFound, "Thread not found", err) + } + + return ctx.HTML(components.NewPostArea(user, "Reply") + components.NewPostActions(thread.Type(), thread.ID)) +} diff --git a/pages/thread/thread.pixy b/pages/thread/thread.pixy index 406be548..1840460e 100644 --- a/pages/thread/thread.pixy +++ b/pages/thread/thread.pixy @@ -15,9 +15,7 @@ component Thread(thread *arn.Thread, user *arn.User) .buttons if !thread.Locked - button.mountable.action(data-action="createPost", data-trigger="click", data-parent-type="Thread", data-parent-id=thread.ID) - Icon("mail-reply") - span Reply + NewPostActions("Thread", thread.ID) if user.Role == "admin" || user.Role == "editor" if thread.Locked diff --git a/pages/thread/thread.scarlet b/pages/thread/thread.scarlet index c23212f5..749b312b 100644 --- a/pages/thread/thread.scarlet +++ b/pages/thread/thread.scarlet @@ -11,6 +11,9 @@ vertical margin-bottom 1.75rem + :last-child + margin-bottom 0 + .post-author margin-bottom 0.25rem @@ -25,9 +28,12 @@ margin-top 0.75rem margin-left content-padding + :empty + margin-top 0 + > 600px .post - margin-bottom 0 + margin-bottom 0.75rem .post-author margin-bottom 0 diff --git a/scripts/Actions/Forum.ts b/scripts/Actions/Forum.ts index a417a073..68f80aa3 100644 --- a/scripts/Actions/Forum.ts +++ b/scripts/Actions/Forum.ts @@ -58,7 +58,7 @@ export function deletePost(arn: AnimeNotifier, element: HTMLElement) { // Create post export function createPost(arn: AnimeNotifier, element: HTMLElement) { - let textarea = document.getElementById("new-post") as HTMLTextAreaElement + let textarea = document.getElementById("new-post-text") as HTMLTextAreaElement let {parentId, parentType} = element.dataset let post = { @@ -92,8 +92,40 @@ export function createThread(arn: AnimeNotifier) { } // Reply to a post -export function reply(arn: AnimeNotifier, element: HTMLElement) { +export async function reply(arn: AnimeNotifier, element: HTMLElement) { + let apiEndpoint = arn.findAPIEndpoint(element) + let repliesId = `replies-${element.dataset.postId}` + let replies = document.getElementById(repliesId) + // Delete old reply area + let oldReplyArea = document.getElementById("new-post") + + if(oldReplyArea) { + oldReplyArea.remove() + } + + // Delete old reply button + let oldPostActions = document.getElementById("new-post-actions") + + if(oldPostActions) { + oldPostActions.remove() + } + + // Fetch new reply UI + try { + let response = await fetch(`${apiEndpoint}/reply/ui`) + let html = await response.text() + replies.innerHTML = html + replies.innerHTML + arn.onNewContent(replies) + arn.assignActions() + } catch(err) { + arn.statusMessage.showError(err) + } +} + +// Cancel replying to a post +export function cancelReply(arn: AnimeNotifier, element: HTMLElement) { + arn.reloadContent() } // Lock thread diff --git a/scripts/Actions/Search.ts b/scripts/Actions/Search.ts index b5dd6491..6571d024 100644 --- a/scripts/Actions/Search.ts +++ b/scripts/Actions/Search.ts @@ -1,5 +1,5 @@ import AnimeNotifier from "../AnimeNotifier" -import { delay, requestIdleCallback, findAllInside } from "../Utils" +import { delay, requestIdleCallback } from "../Utils" // Search page reference var emptySearchHTML = "" @@ -172,20 +172,10 @@ function showResponseInElement(arn: AnimeNotifier, url: string, typeName: string } await arn.innerHTML(element, html) - - showSearchResults(arn, element) + arn.onNewContent(element) } } -export function showSearchResults(arn: AnimeNotifier, element: HTMLElement) { - // Do the same as for the content loaded event, - // except here we are limiting it to the element. - arn.app.ajaxify(element.getElementsByTagName("a")) - arn.lazyLoad(findAllInside("lazy", element)) - arn.mountMountables(findAllInside("mountable", element)) - arn.assignTooltipOffsets(findAllInside("tip", element)) -} - export function searchBySpeech(arn: AnimeNotifier, element: HTMLElement) { if(recognition) { recognition.stop() diff --git a/scripts/AnimeNotifier.ts b/scripts/AnimeNotifier.ts index 91a5e783..af3a2da4 100644 --- a/scripts/AnimeNotifier.ts +++ b/scripts/AnimeNotifier.ts @@ -9,9 +9,9 @@ import Analytics from "./Analytics" import SideBar from "./SideBar" import InfiniteScroller from "./InfiniteScroller" import ServiceWorkerManager from "./ServiceWorkerManager" -import { displayAiringDate, displayDate, displayTime } from "./DateView" -import { findAll, canUseWebP, requestIdleCallback, swapElements, delay } from "./Utils" import { checkNewVersionDelayed } from "./NewVersionCheck" +import { displayAiringDate, displayDate, displayTime } from "./DateView" +import { findAll, canUseWebP, requestIdleCallback, swapElements, delay, findAllInside } from "./Utils" import * as actions from "./Actions" export default class AnimeNotifier { @@ -954,6 +954,15 @@ export default class AnimeNotifier { }) } + onNewContent(element: HTMLElement) { + // Do the same as for the content loaded event, + // except here we are limiting it to the element. + this.app.ajaxify(element.getElementsByTagName("a")) + this.lazyLoad(findAllInside("lazy", element)) + this.mountMountables(findAllInside("mountable", element)) + this.assignTooltipOffsets(findAllInside("tip", element)) + } + scrollTo(target: HTMLElement) { const duration = 250.0 const fullSin = Math.PI / 2 diff --git a/styles/forum.scarlet b/styles/forum.scarlet index aabe0e3d..d6abf5b5 100644 --- a/styles/forum.scarlet +++ b/styles/forum.scarlet @@ -83,6 +83,11 @@ post-content-padding-y = 0.75rem align-items flex-start margin-right 0.5rem +#new-post-actions + horizontal + justify-content flex-end + margin-bottom 0.75rem + // Toolbar .post-toolbar