Threaded comments (Reply UI)
This commit is contained in:
parent
08497f0c37
commit
960b1e4b92
@ -1,7 +1,17 @@
|
|||||||
component NewPostArea(user *arn.User, placeholder string)
|
component NewPostArea(user *arn.User, placeholder string)
|
||||||
.post.mountable
|
#new-post.post.mountable
|
||||||
.post-parent
|
.post-parent
|
||||||
.post-author
|
.post-author
|
||||||
Avatar(user)
|
Avatar(user)
|
||||||
|
|
||||||
textarea#new-post.post-content(placeholder=placeholder + "...", aria-label=placeholder)
|
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
|
@ -14,7 +14,9 @@ component Postable(post arn.Postable, user *arn.User, includeReplies bool, heade
|
|||||||
.post-edit-interface
|
.post-edit-interface
|
||||||
if post.Type() == "Thread"
|
if post.Type() == "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()
|
||||||
|
|
||||||
.buttons.hidden(id="edit-toolbar-" + post.GetID())
|
.buttons.hidden(id="edit-toolbar-" + post.GetID())
|
||||||
a.button.post-save.action(data-action="savePost", data-trigger="click", data-id=post.GetID())
|
a.button.post-save.action(data-action="savePost", data-trigger="click", data-id=post.GetID())
|
||||||
Icon("save")
|
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")
|
//- a.post-tool.post-edit.tip(href=post.Link() + "/edit", aria-label="Edit")
|
||||||
//- Icon("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")
|
Icon("reply")
|
||||||
|
|
||||||
if user.ID == post.Creator().ID
|
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")
|
a.post-tool.post-permalink.tip(href=post.Link(), aria-label="Link")
|
||||||
Icon("link")
|
Icon("link")
|
||||||
|
|
||||||
.replies
|
.replies(id="replies-" + post.GetID())
|
||||||
if includeReplies
|
if includeReplies
|
||||||
each reply in post.Posts()
|
each reply in post.Posts()
|
||||||
Postable(reply, user, true, "", highlightAuthorID)
|
Postable(reply, user, true, "", highlightAuthorID)
|
||||||
|
@ -3,8 +3,7 @@ component ActivityFeed(entries []*arn.EditLogEntry, user *arn.User)
|
|||||||
|
|
||||||
.activities
|
.activities
|
||||||
each entry in entries
|
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)
|
component ActivityPost(post arn.Postable, user *arn.User)
|
||||||
if post.Parent() != nil
|
if post.Parent() != nil
|
||||||
|
@ -2,10 +2,4 @@
|
|||||||
vertical
|
vertical
|
||||||
width 100%
|
width 100%
|
||||||
max-width forum-width
|
max-width forum-width
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
|
|
||||||
// .activity
|
|
||||||
// margin-bottom 1rem
|
|
||||||
|
|
||||||
.activity-header
|
|
||||||
font-size 0.9rem
|
|
@ -3,6 +3,9 @@ package apiroutes
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/animenotifier/notify.moe/pages/post"
|
||||||
|
"github.com/animenotifier/notify.moe/pages/thread"
|
||||||
|
|
||||||
"github.com/aerogo/aero"
|
"github.com/aerogo/aero"
|
||||||
|
|
||||||
"github.com/aerogo/layout"
|
"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/next/soundtrack", soundtrack.Next)
|
||||||
app.Get("/api/character/:id/ranking", character.Ranking)
|
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
|
// SoundTrack
|
||||||
app.Post("/api/soundtrack/:id/download", soundtrack.Download)
|
app.Post("/api/soundtrack/:id/download", soundtrack.Download)
|
||||||
|
|
||||||
|
@ -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"))
|
|
||||||
}
|
|
23
pages/post/reply-ui.go
Normal file
23
pages/post/reply-ui.go
Normal file
@ -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))
|
||||||
|
}
|
23
pages/thread/reply-ui.go
Normal file
23
pages/thread/reply-ui.go
Normal file
@ -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))
|
||||||
|
}
|
@ -15,9 +15,7 @@ component Thread(thread *arn.Thread, user *arn.User)
|
|||||||
|
|
||||||
.buttons
|
.buttons
|
||||||
if !thread.Locked
|
if !thread.Locked
|
||||||
button.mountable.action(data-action="createPost", data-trigger="click", data-parent-type="Thread", data-parent-id=thread.ID)
|
NewPostActions("Thread", thread.ID)
|
||||||
Icon("mail-reply")
|
|
||||||
span Reply
|
|
||||||
|
|
||||||
if user.Role == "admin" || user.Role == "editor"
|
if user.Role == "admin" || user.Role == "editor"
|
||||||
if thread.Locked
|
if thread.Locked
|
||||||
|
@ -11,6 +11,9 @@
|
|||||||
vertical
|
vertical
|
||||||
margin-bottom 1.75rem
|
margin-bottom 1.75rem
|
||||||
|
|
||||||
|
:last-child
|
||||||
|
margin-bottom 0
|
||||||
|
|
||||||
.post-author
|
.post-author
|
||||||
margin-bottom 0.25rem
|
margin-bottom 0.25rem
|
||||||
|
|
||||||
@ -25,9 +28,12 @@
|
|||||||
margin-top 0.75rem
|
margin-top 0.75rem
|
||||||
margin-left content-padding
|
margin-left content-padding
|
||||||
|
|
||||||
|
:empty
|
||||||
|
margin-top 0
|
||||||
|
|
||||||
> 600px
|
> 600px
|
||||||
.post
|
.post
|
||||||
margin-bottom 0
|
margin-bottom 0.75rem
|
||||||
|
|
||||||
.post-author
|
.post-author
|
||||||
margin-bottom 0
|
margin-bottom 0
|
||||||
|
@ -58,7 +58,7 @@ export function deletePost(arn: AnimeNotifier, element: HTMLElement) {
|
|||||||
|
|
||||||
// Create post
|
// Create post
|
||||||
export function createPost(arn: AnimeNotifier, element: HTMLElement) {
|
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 {parentId, parentType} = element.dataset
|
||||||
|
|
||||||
let post = {
|
let post = {
|
||||||
@ -92,8 +92,40 @@ export function createThread(arn: AnimeNotifier) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reply to a post
|
// 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
|
// Lock thread
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import AnimeNotifier from "../AnimeNotifier"
|
import AnimeNotifier from "../AnimeNotifier"
|
||||||
import { delay, requestIdleCallback, findAllInside } from "../Utils"
|
import { delay, requestIdleCallback } from "../Utils"
|
||||||
|
|
||||||
// Search page reference
|
// Search page reference
|
||||||
var emptySearchHTML = ""
|
var emptySearchHTML = ""
|
||||||
@ -172,20 +172,10 @@ function showResponseInElement(arn: AnimeNotifier, url: string, typeName: string
|
|||||||
}
|
}
|
||||||
|
|
||||||
await arn.innerHTML(element, html)
|
await arn.innerHTML(element, html)
|
||||||
|
arn.onNewContent(element)
|
||||||
showSearchResults(arn, 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) {
|
export function searchBySpeech(arn: AnimeNotifier, element: HTMLElement) {
|
||||||
if(recognition) {
|
if(recognition) {
|
||||||
recognition.stop()
|
recognition.stop()
|
||||||
|
@ -9,9 +9,9 @@ import Analytics from "./Analytics"
|
|||||||
import SideBar from "./SideBar"
|
import SideBar from "./SideBar"
|
||||||
import InfiniteScroller from "./InfiniteScroller"
|
import InfiniteScroller from "./InfiniteScroller"
|
||||||
import ServiceWorkerManager from "./ServiceWorkerManager"
|
import ServiceWorkerManager from "./ServiceWorkerManager"
|
||||||
import { displayAiringDate, displayDate, displayTime } from "./DateView"
|
|
||||||
import { findAll, canUseWebP, requestIdleCallback, swapElements, delay } from "./Utils"
|
|
||||||
import { checkNewVersionDelayed } from "./NewVersionCheck"
|
import { checkNewVersionDelayed } from "./NewVersionCheck"
|
||||||
|
import { displayAiringDate, displayDate, displayTime } from "./DateView"
|
||||||
|
import { findAll, canUseWebP, requestIdleCallback, swapElements, delay, findAllInside } from "./Utils"
|
||||||
import * as actions from "./Actions"
|
import * as actions from "./Actions"
|
||||||
|
|
||||||
export default class AnimeNotifier {
|
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) {
|
scrollTo(target: HTMLElement) {
|
||||||
const duration = 250.0
|
const duration = 250.0
|
||||||
const fullSin = Math.PI / 2
|
const fullSin = Math.PI / 2
|
||||||
|
@ -83,6 +83,11 @@ 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
|
||||||
|
horizontal
|
||||||
|
justify-content flex-end
|
||||||
|
margin-bottom 0.75rem
|
||||||
|
|
||||||
// Toolbar
|
// Toolbar
|
||||||
|
|
||||||
.post-toolbar
|
.post-toolbar
|
||||||
|
Loading…
Reference in New Issue
Block a user