Add nested posts UI (#5)
## Summary - Cut hunt, question, submission, voting, hiding, and profile flows over to unified posts - Render nested replies with permission-aware inline Reply/Edit controls and edited markers - Add post profile queries and the author index migration they depend on Co-authored-by: codegirl-007 <s.raide@gmail.com>
This commit was merged in pull request #5.
This commit is contained in:
@@ -1,28 +0,0 @@
|
||||
{{define "answer"}}
|
||||
<section id="answer-block" class="answer{{if .Answer}} is-in{{end}}">
|
||||
{{if .Answer}}
|
||||
<p class="answer-kicker">Shop response</p>
|
||||
<h2>Answer</h2>
|
||||
<p class="byline">{{.Answer.AuthorName}} · 22 years, Bay Area</p>
|
||||
<p class="answer-body">{{.Answer.Body}}</p>
|
||||
{{if isAdmin .User}}
|
||||
<details class="answer-editor">
|
||||
<summary>Edit answer</summary>
|
||||
<form class="answer-form" method="post" action="/questions/{{.Answer.QuestionID}}/answer"
|
||||
hx-post="/questions/{{.Answer.QuestionID}}/answer" hx-target="#answer-block" hx-swap="outerHTML">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRF}}">
|
||||
<label for="answer-body">Edit answer</label>
|
||||
<textarea id="answer-body" name="body" rows="8" required maxlength="12000">{{.Answer.Body}}</textarea>
|
||||
<div class="answer-form-actions">
|
||||
<button type="submit" class="btn btn-primary">Save answer</button>
|
||||
<button type="reset" class="btn btn-ghost"
|
||||
onclick="this.closest('details').removeAttribute('open')">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</details>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<p class="waiting">No answer yet. Check back after the hunt.</p>
|
||||
{{end}}
|
||||
</section>
|
||||
{{end}}
|
||||
@@ -1,6 +1,6 @@
|
||||
{{define "leaderboard"}}
|
||||
<ol id="leaderboard" class="board" start="1">
|
||||
{{if not .Questions}}
|
||||
{{if not .Posts}}
|
||||
<li class="empty">
|
||||
{{if eq .Date .Today}}
|
||||
<p class="empty-kicker">Queue empty</p>
|
||||
@@ -10,20 +10,20 @@
|
||||
{{end}}
|
||||
</li>
|
||||
{{else}}
|
||||
{{range $i, $q := .Questions}}
|
||||
{{range $i, $post := .Posts}}
|
||||
<li class="row">
|
||||
<span class="rank" aria-hidden="true">{{rank $i}}</span>
|
||||
{{template "vote" (voteCtx $.User $.CSRF "list" $.Date $q)}}
|
||||
{{template "vote" (voteCtx $.User $.CSRF "list" $.Date $post)}}
|
||||
<div class="row-body">
|
||||
<a class="q-title" href="/questions/{{$q.ID}}">{{$q.Title}}</a>
|
||||
<a class="q-title" href="/questions/{{$post.ID}}">{{$post.Title}}</a>
|
||||
<p class="meta">
|
||||
<span>{{$q.AuthorName}}</span>
|
||||
{{if $q.City}}<span class="dot" aria-hidden="true">·</span><span>{{$q.City}}</span>{{end}}
|
||||
{{if $q.Answered}}<span class="badge">Answered</span>{{end}}
|
||||
<span>{{$post.AuthorName}}</span>
|
||||
{{if $post.City}}<span class="dot" aria-hidden="true">·</span><span>{{$post.City}}</span>{{end}}
|
||||
{{if $post.Answered}}<span class="badge">Answered</span>{{end}}
|
||||
</p>
|
||||
{{if isAdmin $.User}}
|
||||
<form class="inline-hide" method="post" action="/questions/{{$q.ID}}/hide"
|
||||
hx-post="/questions/{{$q.ID}}/hide" hx-target="#leaderboard" hx-swap="outerHTML">
|
||||
<form class="inline-hide" method="post" action="/questions/{{$post.ID}}/hide"
|
||||
hx-post="/questions/{{$post.ID}}/hide" hx-target="#leaderboard" hx-swap="outerHTML">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRF}}">
|
||||
<input type="hidden" name="view" value="list">
|
||||
<button type="submit" class="linkish">Hide</button>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
{{define "postActions"}}
|
||||
<div class="post-actions">
|
||||
{{if canReply .User .Root}}
|
||||
<details class="post-composer">
|
||||
<summary>Reply</summary>
|
||||
<form class="post-form" method="post" action="/posts">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRF}}">
|
||||
<input type="hidden" name="parent_id" value="{{.Post.ID}}">
|
||||
<label for="reply-{{.Post.ID}}">Reply to {{.Post.AuthorName}}</label>
|
||||
<textarea id="reply-{{.Post.ID}}" name="body" rows="5" required maxlength="12000"></textarea>
|
||||
<div class="post-form-actions">
|
||||
<button type="submit" class="btn btn-primary">Post reply</button>
|
||||
<button type="reset" class="btn btn-ghost"
|
||||
onclick="this.closest('details').removeAttribute('open')">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</details>
|
||||
{{end}}
|
||||
{{if canEditPost .User .Post}}
|
||||
<details class="post-composer">
|
||||
<summary>Edit</summary>
|
||||
<form class="post-form" method="post" action="/posts/{{.Post.ID}}/edit">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRF}}">
|
||||
<label for="edit-{{.Post.ID}}">Edit post</label>
|
||||
<textarea id="edit-{{.Post.ID}}" name="body" rows="5" required
|
||||
maxlength="12000">{{.Post.Body}}</textarea>
|
||||
<div class="post-form-actions">
|
||||
<button type="submit" class="btn btn-primary">Save changes</button>
|
||||
<button type="reset" class="btn btn-ghost"
|
||||
onclick="this.closest('details').removeAttribute('open')">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</details>
|
||||
{{end}}
|
||||
{{if and (not .Post.ParentID) (isAdmin .User)}}
|
||||
<form class="post-hide" method="post" action="/questions/{{.Post.ID}}/hide">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRF}}">
|
||||
<button type="submit" class="linkish">Hide</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "threadReply"}}
|
||||
{{$ctx := .}}
|
||||
<article id="post-{{.Post.ID}}"
|
||||
class="thread-post thread-post-{{postDepth .Depth}}{{if eq .Post.AuthorRole "admin"}} is-shop{{end}}">
|
||||
<header class="post-head">
|
||||
<p class="post-kicker">{{postLabel .Post}}</p>
|
||||
<p class="post-meta">
|
||||
<span>{{.Post.AuthorName}}</span>
|
||||
<span class="dot" aria-hidden="true">·</span>
|
||||
<time datetime="{{.Post.CreatedAt}}">{{postTime .Post.CreatedAt}}</time>
|
||||
{{if isEdited .Post}}<span class="edited">Edited</span>{{end}}
|
||||
</p>
|
||||
</header>
|
||||
<p class="post-body">{{.Post.Body}}</p>
|
||||
{{template "postActions" .}}
|
||||
{{if .Post.Replies}}
|
||||
<div class="post-replies">
|
||||
{{range .Post.Replies}}
|
||||
{{template "threadReply" (postCtx $ctx.User $ctx.CSRF $ctx.Root . (add $ctx.Depth 1))}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</article>
|
||||
{{end}}
|
||||
@@ -1,26 +1,26 @@
|
||||
{{define "vote"}}
|
||||
<div id="vote-{{.Question.ID}}" class="vote">
|
||||
<div id="vote-{{.Post.ID}}" class="vote">
|
||||
{{if .User}}
|
||||
<form method="post" action="/questions/{{.Question.ID}}/vote"
|
||||
hx-post="/questions/{{.Question.ID}}/vote"
|
||||
{{if eq .View "list"}}hx-target="#leaderboard" hx-swap="outerHTML"{{else}}hx-target="#vote-{{.Question.ID}}" hx-swap="outerHTML"{{end}}>
|
||||
<form method="post" action="/questions/{{.Post.ID}}/vote"
|
||||
hx-post="/questions/{{.Post.ID}}/vote"
|
||||
{{if eq .View "list"}}hx-target="#leaderboard" hx-swap="outerHTML"{{else}}hx-target="#vote-{{.Post.ID}}" hx-swap="outerHTML"{{end}}>
|
||||
<input type="hidden" name="_csrf" value="{{.CSRF}}">
|
||||
{{if eq .Question.UserVote 1}}<input type="hidden" name="value" value="0">{{else}}<input type="hidden" name="value" value="1">{{end}}
|
||||
{{if eq .Post.UserVote 1}}<input type="hidden" name="value" value="0">{{else}}<input type="hidden" name="value" value="1">{{end}}
|
||||
<input type="hidden" name="view" value="{{.View}}">
|
||||
<input type="hidden" name="date" value="{{.Date}}">
|
||||
<button type="submit" class="vote-btn{{if eq .Question.UserVote 1}} is-up{{end}}" aria-label="Upvote" aria-pressed="{{if eq .Question.UserVote 1}}true{{else}}false{{end}}">
|
||||
<button type="submit" class="vote-btn{{if eq .Post.UserVote 1}} is-up{{end}}" aria-label="Upvote" aria-pressed="{{if eq .Post.UserVote 1}}true{{else}}false{{end}}">
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" aria-hidden="true"><path d="M9 3.5 15 12H3z" fill="currentColor"/></svg>
|
||||
</button>
|
||||
</form>
|
||||
<span class="score" aria-label="Net score {{.Question.Score}}">{{.Question.Score}}</span>
|
||||
<form method="post" action="/questions/{{.Question.ID}}/vote"
|
||||
hx-post="/questions/{{.Question.ID}}/vote"
|
||||
{{if eq .View "list"}}hx-target="#leaderboard" hx-swap="outerHTML"{{else}}hx-target="#vote-{{.Question.ID}}" hx-swap="outerHTML"{{end}}>
|
||||
<span class="score" aria-label="Net score {{.Post.Score}}">{{.Post.Score}}</span>
|
||||
<form method="post" action="/questions/{{.Post.ID}}/vote"
|
||||
hx-post="/questions/{{.Post.ID}}/vote"
|
||||
{{if eq .View "list"}}hx-target="#leaderboard" hx-swap="outerHTML"{{else}}hx-target="#vote-{{.Post.ID}}" hx-swap="outerHTML"{{end}}>
|
||||
<input type="hidden" name="_csrf" value="{{.CSRF}}">
|
||||
{{if eq .Question.UserVote -1}}<input type="hidden" name="value" value="0">{{else}}<input type="hidden" name="value" value="-1">{{end}}
|
||||
{{if eq .Post.UserVote -1}}<input type="hidden" name="value" value="0">{{else}}<input type="hidden" name="value" value="-1">{{end}}
|
||||
<input type="hidden" name="view" value="{{.View}}">
|
||||
<input type="hidden" name="date" value="{{.Date}}">
|
||||
<button type="submit" class="vote-btn{{if eq .Question.UserVote -1}} is-down{{end}}" aria-label="Downvote" aria-pressed="{{if eq .Question.UserVote -1}}true{{else}}false{{end}}">
|
||||
<button type="submit" class="vote-btn{{if eq .Post.UserVote -1}} is-down{{end}}" aria-label="Downvote" aria-pressed="{{if eq .Post.UserVote -1}}true{{else}}false{{end}}">
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" aria-hidden="true"><path d="M9 14.5 3 6h12z" fill="currentColor"/></svg>
|
||||
</button>
|
||||
</form>
|
||||
@@ -28,7 +28,7 @@
|
||||
<a class="vote-btn" href="/login" hx-get="/auth/prompt" hx-target="#flash" aria-label="Sign in to upvote">
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" aria-hidden="true"><path d="M9 3.5 15 12H3z" fill="currentColor"/></svg>
|
||||
</a>
|
||||
<span class="score" aria-label="Net score {{.Question.Score}}">{{.Question.Score}}</span>
|
||||
<span class="score" aria-label="Net score {{.Post.Score}}">{{.Post.Score}}</span>
|
||||
<a class="vote-btn" href="/login" hx-get="/auth/prompt" hx-target="#flash" aria-label="Sign in to downvote">
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" aria-hidden="true"><path d="M9 14.5 3 6h12z" fill="currentColor"/></svg>
|
||||
</a>
|
||||
|
||||
Reference in New Issue
Block a user