From 30ffd5ad6fd236fc53d70e8ee08ba543100df1ce Mon Sep 17 00:00:00 2001 From: ElnuDev Date: Fri, 22 Jul 2022 12:25:57 -0700 Subject: [PATCH] Make replies and sharing work --- demo/soudan.js | 57 ++++++++++++++++++++++++++++++++++++++----------- demo/style.css | 21 +++++++++++++++--- src/database.rs | 7 +++--- src/main.rs | 20 ++++++++++++++++- 4 files changed, 85 insertions(+), 20 deletions(-) diff --git a/demo/soudan.js b/demo/soudan.js index 20dc586..f047c83 100644 --- a/demo/soudan.js +++ b/demo/soudan.js @@ -1,12 +1,19 @@ +function commentForm(parent) { + return ` +
+ + + + + + +
`; +} +function commentDisplay(comment, replies) { + return `
${typeof replies === "string" ? `` : ""}${comment.author ? comment.author : "Anonymous"} commented ${moment(new Date(comment.timestamp * 1000)).fromNow()}:
${md.render(comment.text)}
${typeof replies === "string" ? `
${replies ? replies : ""}
` : ""}
`; +} document.getElementById("soudan").innerHTML = `

Make a comment

-
- - - - - - -
+${commentForm()}

Comments

`; const md = window.markdownit().disable("image"); @@ -15,7 +22,8 @@ const form = document.getElementById("soudan-comment-form"); const commentContainer = document.getElementById("soudan-comments"); const commentContainerHeader = document.getElementById("soudan-comments-header"); const contentId = document.querySelector("meta[name=\"soudan-content-id\"]").getAttribute("content"); -form.addEventListener("submit", e => { + +function submitForm(form, e) { let data = { url: window.location.href, comment: { contentId } @@ -23,6 +31,9 @@ form.addEventListener("submit", e => { new FormData(form).forEach((value, key) => { data.comment[key] = value === "" ? null : value; }); + if (data.comment.parent) { + data.comment.parent = parseInt(data.comment.parent); + } fetch(url, { method: "POST", body: JSON.stringify(data), @@ -36,9 +47,9 @@ form.addEventListener("submit", e => { reloadComments(); }) e.preventDefault(); -}); +} -function reloadComments() { +function reloadComments(jump) { fetch(`${url}/${contentId}`) .then(response => { return response.json().then(json => { @@ -52,11 +63,31 @@ function reloadComments() { html = "

No comments yet! Be the first to make one.

"; } else { comments.forEach(comment => { - html += `
${comment.author ? comment.author : "Anonymous"} commented ${moment(new Date(comment.timestamp * 1000)).fromNow()}:
${md.render(comment.text)}
`; + if (comment.replies) { + let replies = ""; + comment.replies.forEach(reply => { + replies += commentDisplay(reply); + }); + html += commentDisplay(comment, replies); + } else { + html += commentDisplay(comment, ""); + } }); } commentContainer.innerHTML = html; + if (jump && window.location.hash) { + const target = document.getElementById(window.location.hash.substring(1)); + if (target) { + window.scrollTo(0, target.offsetTop); + target.classList.add("soudan-highlighted"); + } + } }); } -reloadComments(); +function reply(id) { + const replies = document.getElementById(id).querySelector(".soudan-replies"); + replies.innerHTML = `

Reply

${commentForm(id)}${replies.innerHTML}`; +} + +reloadComments(true); diff --git a/demo/style.css b/demo/style.css index 598e37a..4d7a2e3 100644 --- a/demo/style.css +++ b/demo/style.css @@ -1,10 +1,25 @@ -#soudan-comments > div { +.soudan-comment { display: flex; gap: 0.5em; } +.soudan-comment > div { + width: 100%; +} +@keyframes soudan-highlighted { + from { background: rgba(255, 255, 0, 0.25); } + to { background: rgba(255, 255, 0, 0); } +} +.soudan-highlighted { + background: rgba(255, 255, 0, 0.25); + border-radius: 0.25em; + animation-name: soudan-highlighted; + animation-duration: 4s; + animation-delay: 2s; + animation-fill-mode: forwards; +} .soudan-avatar { border-radius: 100%; - width: 80px; - height: 80px; + width: 60px; + height: 60px; background-color: gray; } diff --git a/src/database.rs b/src/database.rs index 063f6b9..97736b8 100644 --- a/src/database.rs +++ b/src/database.rs @@ -32,7 +32,7 @@ impl Database { .query_map([], |row| { let id = row.get::>(0)?.unwrap(); let replies = self.conn - .prepare(&format!("SELECT id, author, email, text, timestamp FROM comment WHERE parent={id} ORDER BY timestamp DESC"))? + .prepare(&format!("SELECT id, author, email, text, timestamp FROM comment WHERE parent={id}"))? .query_map([], |row| { Ok(Comment { id: row.get(0)?, @@ -62,12 +62,13 @@ impl Database { pub fn create_comment(&self, comment: &Comment) -> Result<()> { self.conn.execute( - "INSERT INTO comment (author, email, text, content_id) VALUES (?1, ?2, ?3, ?4)", + "INSERT INTO comment (author, email, text, content_id, parent) VALUES (?1, ?2, ?3, ?4, ?5)", params![ &comment.author, &comment.email, &comment.text, - &comment.content_id + &comment.content_id, + &comment.parent, ], )?; Ok(()) diff --git a/src/main.rs b/src/main.rs index 5db64e5..20891c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -123,7 +123,25 @@ async fn post_comment( Ok(database) => database, Err(response) => return response, }; - database.create_comment(&comment).unwrap(); + if let Some(parent) = comment.parent { + 'outer2: loop { + match database.get_comments(&comment.content_id) { + Ok(comments) => for other_comment in comments.iter() { + if other_comment.id.unwrap() == parent { + if other_comment.parent.is_none() { + break 'outer2; + } + break; + } + }, + Err(_) => return HttpResponse::InternalServerError().reason("failed to get comments").finish(), + } + return HttpResponse::BadRequest().reason("invalid comment parent").finish(); + } + } + if let Err(_) = database.create_comment(&comment) { + return HttpResponse::InternalServerError().reason("failed to create comment").finish(); + } HttpResponse::Ok().into() } Err(_) => HttpResponse::BadRequest().reason("failed to parse request body").finish(),