From 39c5b41657cfd350bc79a18d60baedcf59bf5656 Mon Sep 17 00:00:00 2001 From: Dominic Ferrando Date: Sat, 19 Apr 2025 01:49:43 -0400 Subject: [PATCH] Implement reply rich rendering. Add jump to and highlight/hovering functionality for them --- internal/util/util.go | 32 ++++++++++++---- web/static/index.css | 42 +++++++++++++++------ web/static/index.js | 56 ++++++++++++++++++++++++++++ web/views/shared/layout.templ | 1 + web/views/thread.templ | 70 ++++++++++++++++++----------------- web/views/threads.templ | 4 +- 6 files changed, 151 insertions(+), 54 deletions(-) diff --git a/internal/util/util.go b/internal/util/util.go index abf4272..1aa42e1 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -1,19 +1,35 @@ package util import ( + "fmt" "html/template" "strings" ) func EnrichPost(body string) string { - lines := strings.Split(body, "\n") - for i, line := range lines { - if strings.HasPrefix(line, ">") { - escaped := template.HTMLEscapeString(line) - lines[i] = `` + escaped + `` - } else { - lines[i] = template.HTMLEscapeString(line) + var b strings.Builder + for _, raw := range strings.Split(body, "\n") { + var out string + switch { + case strings.HasPrefix(raw, ">>"): + postId := strings.TrimPrefix(raw, ">>") + esc := template.HTMLEscapeString(raw) + out = fmt.Sprintf( + `%s`, + postId, esc, + ) + case strings.HasPrefix(raw, ">"): + esc := template.HTMLEscapeString(raw) + out = `` + esc + `` + default: + out = template.HTMLEscapeString(raw) } + + b.WriteString(out) + b.WriteString("
") } - return strings.Join(lines, "
") + return b.String() } diff --git a/web/static/index.css b/web/static/index.css index 06ab7bb..75f84dd 100644 --- a/web/static/index.css +++ b/web/static/index.css @@ -111,6 +111,11 @@ body { margin-left: auto; } +#newPostBody { + width: 270px; + height: 100px; +} + .new-post-container { width: max-content; margin: auto; @@ -145,6 +150,23 @@ body { margin-right: 5px; } +.post-op { + margin-bottom: 10px; +} + +.post { + display: inline-block; + margin: 5x; + padding: 10px; + background-color: #D6DAF0; + border: 1px solid #282a2e; + margin-bottom: 10px; +} + +.post-highlighted { + background: #D6BAD0; +} + .post-header { display: flex; margin-bottom: 10px; @@ -174,23 +196,21 @@ body { line-height: 1.3; } -.op-post { - margin-bottom: 10px; -} - -.post { - display: inline-block; - margin: 5x; - padding: 10px; - background-color: #D6DAF0; - border: 1px solid #282a2e; - margin-bottom: 10px; +.post-num:hover { + cursor: pointer; + color: #ff0000; } .greentext { color: #789922 } +.reply-link { + color: #D00; + text-decoration: underline; + cursor: pointer; +} + /* GENERAL LAYOUT */ .container { diff --git a/web/static/index.js b/web/static/index.js index 915304f..4a6d449 100644 --- a/web/static/index.js +++ b/web/static/index.js @@ -1 +1,57 @@ const $ = (id) => document.getElementById(id); + +function isOffScreen(el) { + const rect = el.getBoundingClientRect(); + const vh = window.innerHeight || document.documentElement.clientHeight; + const vw = window.innerWidth || document.documentElement.clientWidth; + + return ( + rect.bottom < 0 || + rect.top > vh || + rect.right < 0 || + rect.left > vw + ); +} + +function insertAfter(referenceNode, newNode) { + referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); +} + +function onReplyLinkClick(e) { + const hoveringPost = document.getElementById("hoveringPost"); + hoveringPost?.remove(); +} + +function highlightPost(postId, e, status = true) { + const posts = Array.from(document.querySelectorAll("article")); + const postToHighlight = posts.find(p => +p.id.split("-")[1] === +postId); + if (!postToHighlight) return; + + const isOp = postToHighlight.id === posts[0]?.id; + if (isOffScreen(postToHighlight) || isOp) { + if (e.type === "mouseleave") { + const hoveringPost = document.querySelector("#hoveringPost") + hoveringPost.remove(); + } + else { + const postCopy = postToHighlight.cloneNode(true); + postCopy.style.position = "absolute"; + postCopy.id = "hoveringPost"; + + if (isOp) { + postCopy.classList.remove("post-op"); + postCopy.classList.add("post"); + } + + insertAfter(e.target, postCopy); + } + } + else { + if (status) { + postToHighlight.classList.add("post-highlighted"); + } + else { + postToHighlight.classList.remove("post-highlighted"); + } + } +} diff --git a/web/views/shared/layout.templ b/web/views/shared/layout.templ index e142551..2ac6698 100644 --- a/web/views/shared/layout.templ +++ b/web/views/shared/layout.templ @@ -8,6 +8,7 @@ templ Layout() { Comfychan + diff --git a/web/views/thread.templ b/web/views/thread.templ index e29cb5c..b56d968 100644 --- a/web/views/thread.templ +++ b/web/views/thread.templ @@ -8,24 +8,44 @@ import ( "strconv" ) +func getPostClass(isOp bool) string { + if isOp { + return "post-op" + } + return "post" +} + +templ ThreadPost(post database.Post, thread *database.Thread) { + {{ isOp := thread != nil }} +
+ if post.MediaPath != "" { +
+ +
+ } +
+ if isOp && thread.Subject != "" { +

{ thread.Subject } -

+ } + + + { post.CreatedAt.Format("15:04") } + >%d\n'", post.Id) } + class=" post-num" + > + No. { strconv.Itoa(post.Id) } + +
+

+ @templ.Raw(util.EnrichPost(post.Body)) +

+
+} + templ ThreadPosts(posts []database.Post) { for _, post := range (posts) { -
- if post.MediaPath != "" { -
- -
- } -
- - - { post.CreatedAt.Format("15:04") } - No. { strconv.Itoa(post.Id) } -
-

- @templ.Raw(util.EnrichPost(post.Body)) -

-
+ @ThreadPost(post, nil)
} } @@ -61,23 +81,7 @@ templ Thread(board database.Board, thread database.Thread, posts []database.Post
-
- if posts[0].MediaPath != "" { -
- -
- } -
-

{ thread.Subject } -

- - - { posts[0].CreatedAt.Format("15:04") } - No. { strconv.Itoa(posts[0].Id) } -
-

- @templ.Raw(util.EnrichPost(posts[0].Body)) -

-
+ @ThreadPost(posts[0], &thread)
@ThreadPosts(posts[1:])
diff --git a/web/views/threads.templ b/web/views/threads.templ index 812f6d7..2245f2c 100644 --- a/web/views/threads.templ +++ b/web/views/threads.templ @@ -14,10 +14,10 @@ templ ThreadsCatalog(previews []CatalogThreadPreview) {
for _, preview := range previews {
- + -

{ preview.Subject }

+

{ preview.Subject }

@templ.Raw(util.EnrichPost(preview.Body))