Add voting icons

This commit is contained in:
2024-10-17 14:33:40 +02:00
parent ffbbc709ce
commit d8bb6c30e9
5 changed files with 119 additions and 43 deletions
+16 -2
View File
@@ -37,10 +37,24 @@
<table> <table>
<tr> <tr>
<td> <td>
<a hx-post="/posts/{{post.id}}/upvotes" hx-target='.post-preview-details' hx-swap='outerHTML'>{{post.upvotes}}</a> <a hx-post="/posts/{{post.id}}/upvotes" hx-target='.post-preview-details' hx-swap='outerHTML'>
{{post.upvotes}}
{% if post.upvoted %}
<img src="/static/upvote_full.svg" />
{% else %}
<img src="/static/upvote_empty.svg" />
{% end %}
</a>
</td> </td>
<td> <td>
<a hx-post="/posts/{{post.id}}/downvotes" hx-target='.post-preview-details' hx-swap='outerHTML'>{{post.downvotes}}</a> <a hx-post="/posts/{{post.id}}/downvotes" hx-target='.post-preview-details' hx-swap='outerHTML'>
{{post.downvotes}}
{% if post.downvoted %}
<img src="/static/upvote_full.svg" class="rotate180" />
{% else %}
<img src="/static/upvote_empty.svg" class="rotate180" />
{% end %}
</a>
</td> </td>
<td> <td>
<span>by <a href="/users/{{post.author}}">{{post.author}}</a> at {{post.created_}}</span> <span>by <a href="/users/{{post.author}}">{{post.author}}</a> at {{post.created_}}</span>
+55 -37
View File
@@ -107,15 +107,19 @@ void free_endpoints(void)
sqlite3_close(db); sqlite3_close(db);
} }
void respond_with_post_details_html(ResponseBuilder *b, int details_post_id) void respond_with_post_details_html(ResponseBuilder *b, int details_post_id, string login_username)
{ {
sqlite3_stmt *stmt = sqlite3_utils_prepare(db, sqlite3_stmt *stmt = sqlite3_utils_prepare(db,
"SELECT P.author, " "SELECT P.author, "
" STRFTIME('%d/%m/%Y, %H.%M', P.created) AS created_, " " STRFTIME('%d/%m/%Y, %H.%M', P.created) AS created_, "
" (SELECT COUNT(*) FROM Comments AS C WHERE C.parent = P.id) AS num_comments, " " (SELECT COUNT(*) FROM Comments AS C WHERE C.parent = P.id) AS num_comments, "
" (SELECT COUNT(*) FROM PostVotes AS PV WHERE PV.post = P.id AND PV.up=1) AS upvotes, " " (SELECT COUNT(*) FROM PostVotes AS PV WHERE PV.post = P.id AND PV.up=1) AS upvotes, "
" (SELECT COUNT(*) FROM PostVotes AS PV WHERE PV.post = P.id AND PV.up=0) AS downvotes " " (SELECT COUNT(*) FROM PostVotes AS PV WHERE PV.post = P.id AND PV.up=0) AS downvotes, "
"FROM Posts AS P"); " EXISTS(SELECT * FROM PostVotes AS PV WHERE PV.post = P.id AND PV.up=1 AND PV.user=:s) AS upvoted, "
" EXISTS(SELECT * FROM PostVotes AS PV WHERE PV.post = P.id AND PV.up=0 AND PV.user=:s) AS downvoted "
"FROM Posts AS P",
login_username,
login_username);
if (stmt == NULL) { if (stmt == NULL) {
status_line(b, 500); status_line(b, 500);
return; return;
@@ -125,36 +129,46 @@ void respond_with_post_details_html(ResponseBuilder *b, int details_post_id)
int num_comments; int num_comments;
int upvotes; int upvotes;
int downvotes; int downvotes;
if (sqlite3_utils_fetch(stmt, "ssiii", &author, &created, &num_comments, &upvotes, &downvotes)) { int upvoted;
int downvoted;
if (sqlite3_utils_fetch(stmt, "ssiiiii", &author, &created, &num_comments, &upvotes, &downvotes, &upvoted, &downvoted)) {
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
status_line(b, 500); status_line(b, 500);
return; return;
} }
status_line(b, 200); status_line(b, 200);
append_content_f(b, append_content_f(b,
"<div class='post-preview-details'> \ "<div class='post-preview-details'>\
<table> \ <table>\
<tr> \ <tr>\
<td> \ <td>\
<a hx-post='/posts/%d/upvotes' hx-target='.post-preview-details' hx-swap='outerHTML'>%d</a> \ <a hx-post='/posts/%d/upvotes' hx-target='.post-preview-details' hx-swap='outerHTML'>\
</td> \ %d\
<td> \ <img src='%s' />\
<a hx-post='/posts/%d/downvotes' hx-target='.post-preview-details' hx-swap='outerHTML'>%d</a> \ </a>\
</td> \ </td>\
<td> \ <td>\
<span>by <a href='/users/%.*s'>%.*s</a> at %.*s</span> \ <a hx-post='/posts/%d/downvotes' hx-target='.post-preview-details' hx-swap='outerHTML'>\
</td> \ %d\
<td> \ <img src='%s' class='rotate180' />\
<span>%d comments</span> \ </a>\
</td> \ </td>\
</tr> \ <td>\
</table> \ <span>by <a href='/users/%.*s'>%.*s</a> at %.*s</span>\
</div>", </td>\
details_post_id, upvotes, details_post_id, downvotes, <td>\
(int) author.size, author.data, <span>%d comments</span>\
(int) author.size, author.data, </td>\
(int) created.size, created.data, </tr>\
num_comments); </table>\
</div>",
details_post_id, upvotes, upvoted ? "/static/upvote_full.svg" : "/static/upvote_empty.svg",
details_post_id, downvotes, downvoted ? "/static/upvote_full.svg" : "/static/upvote_empty.svg",
(int) author.size, author.data,
(int) author.size, author.data,
(int) created.size, created.data,
num_comments
);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
} }
@@ -175,23 +189,23 @@ static bool voting_endpoints(Request request, ResponseBuilder *b, string login_u
// Create the UP row // Create the UP row
if (sqlite3_utils_exec(db, "INSERT INTO PostVotes(user, post, up) VALUES (:s, :i, :i)", login_username, vote_post_id, up)) { if (sqlite3_utils_exec(db, "INSERT INTO PostVotes(user, post, up) VALUES (:s, :i, :i)", login_username, vote_post_id, up)) {
respond_with_post_details_html(b, vote_post_id); respond_with_post_details_html(b, vote_post_id, login_username);
return true; return true;
} }
// If we failed, maybe we need to delete the current UP row // If we failed, maybe we need to delete the current UP row
if (sqlite3_utils_exec(db, "DELETE FROM PostVotes WHERE up=:i AND user=:s AND post=:i", up, login_username, vote_post_id) && sqlite3_changes(db) > 0) { if (sqlite3_utils_exec(db, "DELETE FROM PostVotes WHERE up=:i AND user=:s AND post=:i", up, login_username, vote_post_id) && sqlite3_changes(db) > 0) {
respond_with_post_details_html(b, vote_post_id); respond_with_post_details_html(b, vote_post_id, login_username);
return true; return true;
} }
// If we failed, maybe we need to invert the UP row // If we failed, maybe we need to invert the UP row
if (sqlite3_utils_exec(db, "UPDATE PostVotes SET up=:i WHERE user=:s AND post=:i", up, login_username, vote_post_id)) { if (sqlite3_utils_exec(db, "UPDATE PostVotes SET up=:i WHERE user=:s AND post=:i", up, login_username, vote_post_id)) {
respond_with_post_details_html(b, vote_post_id); respond_with_post_details_html(b, vote_post_id, login_username);
return true; return true;
} }
respond_with_post_details_html(b, vote_post_id); respond_with_post_details_html(b, vote_post_id, login_username);
return true; return true;
} }
@@ -206,23 +220,23 @@ static bool voting_endpoints(Request request, ResponseBuilder *b, string login_u
// Create the UP row // Create the UP row
if (sqlite3_utils_exec(db, "INSERT INTO PostVotes(user, post, up) VALUES (:s, :i, :i)", login_username, vote_post_id, up)) { if (sqlite3_utils_exec(db, "INSERT INTO PostVotes(user, post, up) VALUES (:s, :i, :i)", login_username, vote_post_id, up)) {
respond_with_post_details_html(b, vote_post_id); respond_with_post_details_html(b, vote_post_id, login_username);
return true; return true;
} }
// If we failed, maybe we need to delete the current UP row // If we failed, maybe we need to delete the current UP row
if (sqlite3_utils_exec(db, "DELETE FROM PostVotes WHERE up=:i AND user=:s AND post=:i", up, login_username, vote_post_id) && sqlite3_changes(db) > 0) { if (sqlite3_utils_exec(db, "DELETE FROM PostVotes WHERE up=:i AND user=:s AND post=:i", up, login_username, vote_post_id) && sqlite3_changes(db) > 0) {
respond_with_post_details_html(b, vote_post_id); respond_with_post_details_html(b, vote_post_id, login_username);
return true; return true;
} }
// If we failed, maybe we need to invert the UP row // If we failed, maybe we need to invert the UP row
if (sqlite3_utils_exec(db, "UPDATE PostVotes SET up=:i WHERE user=:s AND post=:i", up, login_username, vote_post_id)) { if (sqlite3_utils_exec(db, "UPDATE PostVotes SET up=:i WHERE user=:s AND post=:i", up, login_username, vote_post_id)) {
respond_with_post_details_html(b, vote_post_id); respond_with_post_details_html(b, vote_post_id, login_username);
return true; return true;
} }
respond_with_post_details_html(b, vote_post_id); respond_with_post_details_html(b, vote_post_id, login_username);
return true; return true;
} }
@@ -394,8 +408,12 @@ void respond(Request request, ResponseBuilder *b)
" STRFTIME('%d/%m/%Y, %H.%M', P.created) AS created_, " " STRFTIME('%d/%m/%Y, %H.%M', P.created) AS created_, "
" (SELECT COUNT(*) FROM Comments AS C WHERE C.parent = P.id) AS num_comments, " " (SELECT COUNT(*) FROM Comments AS C WHERE C.parent = P.id) AS num_comments, "
" (SELECT COUNT(*) FROM PostVotes AS PV WHERE PV.post = P.id AND PV.up=1) AS upvotes, " " (SELECT COUNT(*) FROM PostVotes AS PV WHERE PV.post = P.id AND PV.up=1) AS upvotes, "
" (SELECT COUNT(*) FROM PostVotes AS PV WHERE PV.post = P.id AND PV.up=0) AS downvotes " " (SELECT COUNT(*) FROM PostVotes AS PV WHERE PV.post = P.id AND PV.up=0) AS downvotes, "
"FROM Posts AS P"); " EXISTS(SELECT * FROM PostVotes AS PV WHERE PV.post = P.id AND PV.up=1 AND PV.user = :s) AS upvoted, "
" EXISTS(SELECT * FROM PostVotes AS PV WHERE PV.post = P.id AND PV.up=0 AND PV.user = :s) AS downvoted "
"FROM Posts AS P",
login_username,
login_username);
if (stmt == NULL) { if (stmt == NULL) {
status_line(b, 500); status_line(b, 500);
return; return;
+46 -4
View File
@@ -96,8 +96,9 @@ form input[type=submit] {
.post-preview { .post-preview {
margin-top: 10px; margin-top: 10px;
border: 1px solid #ccc; border: 1px solid #404040;
border-radius: 3px; border-radius: 3px;
background: white;
} }
.post-preview-title { .post-preview-title {
@@ -105,27 +106,50 @@ form input[type=submit] {
} }
.post-preview-title a { .post-preview-title a {
color: white; color: #333;
font-size: 30px; font-size: 30px;
text-decoration: none; text-decoration: none;
} }
.post-preview-details { .post-preview-details {
height: 40px;
background: #eee;
border-top: 1px solid #ccc; border-top: 1px solid #ccc;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
} }
.post-preview-details table { .post-preview-details table {
border-collapse: collapse; border-collapse: collapse;
height: 100%;
width: 100%;
}
.post-preview-details table tr {
} }
.post-preview-details table td { .post-preview-details table td {
vertical-align: middle;
display: table-cell;
height: 100%;
border-right: 1px solid #ccc; border-right: 1px solid #ccc;
padding: 10px; }
.post-preview-details tr td:nth-child(1) {
width: 40px;
}
.post-preview-details tr td:nth-child(2) {
width: 40px;
}
.post-preview-details tr td:last-child {
width: 100px;
} }
.post-preview-details span, .post-preview-details span,
.post-preview-details a { .post-preview-details a {
color: #ccc; color: black;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
} }
@@ -141,4 +165,22 @@ form input[type=submit] {
background: #ccd6fb; background: #ccd6fb;
border-radius: 3px; border-radius: 3px;
margin-top: 10px; margin-top: 10px;
}
/* === MISC ======================================================== */
.rotate90 {
-webkit-transform: rotate(90deg);
-moz-transform: rotate(90deg);
-o-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}
.rotate180 {
-webkit-transform: rotate(180deg);
-moz-transform: rotate(180deg);
-o-transform: rotate(180deg);
-ms-transform: rotate(180deg);
transform: rotate(180deg);
} }
+1
View File
@@ -0,0 +1 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m12.781 2.375c-.381-.475-1.181-.475-1.562 0l-8 10c-.24.301-.286.712-.12 1.059.167.345.516.566.901.566h2 2v3 4c0 .553.447 1 1 1h6c.553 0 1-.447 1-1v-5-2h2 2c.385 0 .734-.221.901-.566.166-.347.12-.758-.12-1.059zm2.219 9.625h-1v1 3 4h-4v-3-4-1h-1-2.919l5.919-7.399 5.919 7.399z"/></svg>

After

Width:  |  Height:  |  Size: 375 B

+1
View File
@@ -0,0 +1 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m4 14h2 2v3 4c0 .553.447 1 1 1h6c.553 0 1-.447 1-1v-5-2h1 3c.385 0 .734-.221.901-.566.166-.347.12-.758-.12-1.059l-8-10c-.381-.475-1.181-.475-1.562 0l-8 10c-.24.301-.286.712-.12 1.059.167.345.516.566.901.566z"/></svg>

After

Width:  |  Height:  |  Size: 308 B