Make replies and sharing work

main
Elnu 2 years ago
parent 42d980f4cb
commit 30ffd5ad6f

@ -1,12 +1,19 @@
function commentForm(parent) {
return `
<form id="soudan-comment-form" onsubmit="submitForm(this, event)">
<label for="author">Name:</label> <input type="text" name="author" placeholder="Anonymous">
<label for="email">Email:</label> <input type="email" name="email">
<label for="text">Comment:</label>
<textarea name="text" required></textarea>
<input type="hidden" name="parent"${parent ? ` value=${parent}` : ""}>
<input type="submit">
</form>`;
}
function commentDisplay(comment, replies) {
return `<div id="${comment.id}" class="soudan-comment"><img class="soudan-avatar" src="https://www.gravatar.com/avatar/${comment.gravatar}"><div>${typeof replies === "string" ? `<button title="Reply" style="float: right; margin-left: 0.5em" onclick="this.disabled = true; reply(${comment.id})"><svg xmlns="http://www.w3.org/2000/svg" style="width: 20px" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M7.707 3.293a1 1 0 010 1.414L5.414 7H11a7 7 0 017 7v2a1 1 0 11-2 0v-2a5 5 0 00-5-5H5.414l2.293 2.293a1 1 0 11-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" /></svg></button>` : ""}<button title="Share" style="float: right" onclick="navigator.clipboard.writeText('${document.location.origin}${document.location.pathname}#${comment.id}'); alert('Copied comment share link to clipboard!')"><svg xmlns="http://www.w3.org/2000/svg" style="diplay: inline; width: 20px" viewBox="0 0 20 20" fill="currentColor"><path d="M15 8a3 3 0 10-2.977-2.63l-4.94 2.47a3 3 0 100 4.319l4.94 2.47a3 3 0 10.895-1.789l-4.94-2.47a3.027 3.027 0 000-.74l4.94-2.47C13.456 7.68 14.19 8 15 8z" /></svg></button><b>${comment.author ? comment.author : "Anonymous"}</b> commented ${moment(new Date(comment.timestamp * 1000)).fromNow()}:<br><div>${md.render(comment.text)}</div>${typeof replies === "string" ? `<div class="soudan-replies">${replies ? replies : ""}</div>` : ""}</div></div>`;
}
document.getElementById("soudan").innerHTML = `<h3>Make a comment</h3> document.getElementById("soudan").innerHTML = `<h3>Make a comment</h3>
<form id="soudan-comment-form"> ${commentForm()}
<label for="author">Name:</label> <input type="text" name="author" placeholder="Anonymous">
<label for="email">Email:</label> <input type="email" name="email">
<label for="text">Comment:</label>
<textarea name="text" required></textarea>
<input type="hidden" name="parent">
<input type="submit">
</form>
<h3 id="soudan-comments-header">Comments</h3> <h3 id="soudan-comments-header">Comments</h3>
<div id="soudan-comments"></div>`; <div id="soudan-comments"></div>`;
const md = window.markdownit().disable("image"); const md = window.markdownit().disable("image");
@ -15,7 +22,8 @@ const form = document.getElementById("soudan-comment-form");
const commentContainer = document.getElementById("soudan-comments"); const commentContainer = document.getElementById("soudan-comments");
const commentContainerHeader = document.getElementById("soudan-comments-header"); const commentContainerHeader = document.getElementById("soudan-comments-header");
const contentId = document.querySelector("meta[name=\"soudan-content-id\"]").getAttribute("content"); const contentId = document.querySelector("meta[name=\"soudan-content-id\"]").getAttribute("content");
form.addEventListener("submit", e => {
function submitForm(form, e) {
let data = { let data = {
url: window.location.href, url: window.location.href,
comment: { contentId } comment: { contentId }
@ -23,6 +31,9 @@ form.addEventListener("submit", e => {
new FormData(form).forEach((value, key) => { new FormData(form).forEach((value, key) => {
data.comment[key] = value === "" ? null : value; data.comment[key] = value === "" ? null : value;
}); });
if (data.comment.parent) {
data.comment.parent = parseInt(data.comment.parent);
}
fetch(url, { fetch(url, {
method: "POST", method: "POST",
body: JSON.stringify(data), body: JSON.stringify(data),
@ -36,9 +47,9 @@ form.addEventListener("submit", e => {
reloadComments(); reloadComments();
}) })
e.preventDefault(); e.preventDefault();
}); }
function reloadComments() { function reloadComments(jump) {
fetch(`${url}/${contentId}`) fetch(`${url}/${contentId}`)
.then(response => { .then(response => {
return response.json().then(json => { return response.json().then(json => {
@ -52,11 +63,31 @@ function reloadComments() {
html = "<p>No comments yet! Be the first to make one.</p>"; html = "<p>No comments yet! Be the first to make one.</p>";
} else { } else {
comments.forEach(comment => { comments.forEach(comment => {
html += `<div><img class="soudan-avatar" src="https://www.gravatar.com/avatar/${comment.gravatar}"><div><b>${comment.author ? comment.author : "Anonymous"}</b> commented ${moment(new Date(comment.timestamp * 1000)).fromNow()}:<br><div>${md.render(comment.text)}</div></div></div>`; if (comment.replies) {
let replies = "";
comment.replies.forEach(reply => {
replies += commentDisplay(reply);
});
html += commentDisplay(comment, replies);
} else {
html += commentDisplay(comment, "");
}
}); });
} }
commentContainer.innerHTML = html; 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 = `<h3>Reply</h3>${commentForm(id)}${replies.innerHTML}`;
}
reloadComments(true);

@ -1,10 +1,25 @@
#soudan-comments > div { .soudan-comment {
display: flex; display: flex;
gap: 0.5em; 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 { .soudan-avatar {
border-radius: 100%; border-radius: 100%;
width: 80px; width: 60px;
height: 80px; height: 60px;
background-color: gray; background-color: gray;
} }

@ -32,7 +32,7 @@ impl Database {
.query_map([], |row| { .query_map([], |row| {
let id = row.get::<usize, Option<i64>>(0)?.unwrap(); let id = row.get::<usize, Option<i64>>(0)?.unwrap();
let replies = self.conn 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| { .query_map([], |row| {
Ok(Comment { Ok(Comment {
id: row.get(0)?, id: row.get(0)?,
@ -62,12 +62,13 @@ impl Database {
pub fn create_comment(&self, comment: &Comment) -> Result<()> { pub fn create_comment(&self, comment: &Comment) -> Result<()> {
self.conn.execute( 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![ params![
&comment.author, &comment.author,
&comment.email, &comment.email,
&comment.text, &comment.text,
&comment.content_id &comment.content_id,
&comment.parent,
], ],
)?; )?;
Ok(()) Ok(())

@ -123,7 +123,25 @@ async fn post_comment(
Ok(database) => database, Ok(database) => database,
Err(response) => return response, 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() HttpResponse::Ok().into()
} }
Err(_) => HttpResponse::BadRequest().reason("failed to parse request body").finish(), Err(_) => HttpResponse::BadRequest().reason("failed to parse request body").finish(),

Loading…
Cancel
Save