Allow for comment deletion (#363)
* Allow for comment deletion Receive and emit deletion activity Add button to delete comment * Remove debug print and fix copy-past typo * Improve style of comment deletion button
This commit is contained in:
parent
0df9c4d400
commit
5c5cf36b0d
|
@ -1,4 +1,4 @@
|
||||||
use activitypub::{activity::Create, link, object::Note};
|
use activitypub::{activity::{Create, Delete}, link, object::{Note, Tombstone}};
|
||||||
use chrono::{self, NaiveDateTime};
|
use chrono::{self, NaiveDateTime};
|
||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
@ -7,7 +7,7 @@ use instance::Instance;
|
||||||
use mentions::Mention;
|
use mentions::Mention;
|
||||||
use notifications::*;
|
use notifications::*;
|
||||||
use plume_common::activity_pub::{
|
use plume_common::activity_pub::{
|
||||||
inbox::{FromActivity, Notify},
|
inbox::{FromActivity, Notify, Deletable},
|
||||||
Id, IntoId, PUBLIC_VISIBILTY,
|
Id, IntoId, PUBLIC_VISIBILTY,
|
||||||
};
|
};
|
||||||
use plume_common::utils;
|
use plume_common::utils;
|
||||||
|
@ -254,3 +254,49 @@ impl Notify<Connection> for Comment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<'a> Deletable<Connection, Delete> for Comment {
|
||||||
|
fn delete(&self, conn: &Connection) -> Delete {
|
||||||
|
let mut act = Delete::default();
|
||||||
|
act.delete_props
|
||||||
|
.set_actor_link(self.get_author(conn).into_id())
|
||||||
|
.expect("Comment::delete: actor error");
|
||||||
|
|
||||||
|
let mut tombstone = Tombstone::default();
|
||||||
|
tombstone
|
||||||
|
.object_props
|
||||||
|
.set_id_string(self.ap_url.clone().expect("Comment::delete: no ap_url"))
|
||||||
|
.expect("Comment::delete: object.id error");
|
||||||
|
act.delete_props
|
||||||
|
.set_object_object(tombstone)
|
||||||
|
.expect("Comment::delete: object error");
|
||||||
|
|
||||||
|
act.object_props
|
||||||
|
.set_id_string(format!("{}#delete", self.ap_url.clone().unwrap()))
|
||||||
|
.expect("Comment::delete: id error");
|
||||||
|
act.object_props
|
||||||
|
.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILTY)])
|
||||||
|
.expect("Comment::delete: to error");
|
||||||
|
|
||||||
|
for m in Mention::list_for_comment(&conn, self.id) {
|
||||||
|
m.delete(conn);
|
||||||
|
}
|
||||||
|
diesel::update(comments::table).filter(comments::in_response_to_id.eq(self.id))
|
||||||
|
.set(comments::in_response_to_id.eq(self.in_response_to_id))
|
||||||
|
.execute(conn)
|
||||||
|
.expect("Comment::delete: DB error could not update other comments");
|
||||||
|
diesel::delete(self)
|
||||||
|
.execute(conn)
|
||||||
|
.expect("Comment::delete: DB error");
|
||||||
|
act
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_id(id: &str, actor_id: &str, conn: &Connection) {
|
||||||
|
let actor = User::find_by_ap_url(conn, actor_id);
|
||||||
|
let comment = Comment::find_by_ap_url(conn, id);
|
||||||
|
if let Some(comment) = comment.filter(|c| c.author_id == actor.unwrap().id) {
|
||||||
|
comment.delete(conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -65,6 +65,14 @@ pub trait Inbox {
|
||||||
actor_id.as_ref(),
|
actor_id.as_ref(),
|
||||||
&(conn, searcher),
|
&(conn, searcher),
|
||||||
);
|
);
|
||||||
|
Comment::delete_id(
|
||||||
|
&act.delete_props
|
||||||
|
.object_object::<Tombstone>()?
|
||||||
|
.object_props
|
||||||
|
.id_string()?,
|
||||||
|
actor_id.as_ref(),
|
||||||
|
conn,
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
"Follow" => {
|
"Follow" => {
|
||||||
|
|
|
@ -91,6 +91,7 @@ fn main() {
|
||||||
routes::blogs::atom_feed,
|
routes::blogs::atom_feed,
|
||||||
|
|
||||||
routes::comments::create,
|
routes::comments::create,
|
||||||
|
routes::comments::delete,
|
||||||
routes::comments::activity_pub,
|
routes::comments::activity_pub,
|
||||||
|
|
||||||
routes::instance::index,
|
routes::instance::index,
|
||||||
|
|
|
@ -7,7 +7,8 @@ use rocket_i18n::I18n;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
use template_utils::Ructe;
|
use template_utils::Ructe;
|
||||||
|
|
||||||
use plume_common::{utils, activity_pub::{broadcast, ApRequest, ActivityStream}};
|
use plume_common::{utils, activity_pub::{broadcast, ApRequest,
|
||||||
|
ActivityStream, inbox::Deletable}};
|
||||||
use plume_models::{
|
use plume_models::{
|
||||||
blogs::Blog,
|
blogs::Blog,
|
||||||
comments::*,
|
comments::*,
|
||||||
|
@ -86,6 +87,18 @@ pub fn create(blog_name: String, slug: String, form: LenientForm<NewCommentForm>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/~/<blog>/<slug>/comment/<id>/delete")]
|
||||||
|
pub fn delete(blog: String, slug: String, id: i32, user: User, conn: DbConn, worker: Worker) -> Redirect {
|
||||||
|
if let Some(comment) = Comment::get(&*conn, id) {
|
||||||
|
if comment.author_id == user.id {
|
||||||
|
let dest = User::one_by_instance(&*conn);
|
||||||
|
let delete_activity = comment.delete(&*conn);
|
||||||
|
worker.execute(move || broadcast(&user, delete_activity, dest));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Redirect::to(uri!(super::posts::details: blog = blog, slug = slug, responding_to = _))
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/~/<_blog>/<_slug>/comment/<id>")]
|
#[get("/~/<_blog>/<_slug>/comment/<id>")]
|
||||||
pub fn activity_pub(_blog: String, _slug: String, id: i32, _ap: ApRequest, conn: DbConn) -> Option<ActivityStream<Note>> {
|
pub fn activity_pub(_blog: String, _slug: String, id: i32, _ap: ApRequest, conn: DbConn) -> Option<ActivityStream<Note>> {
|
||||||
Comment::get(&*conn, id).map(|c| ActivityStream::new(c.to_activity(&*conn)))
|
Comment::get(&*conn, id).map(|c| ActivityStream::new(c.to_activity(&*conn)))
|
||||||
|
|
|
@ -202,17 +202,18 @@ main .article-meta {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New comment form
|
// New comment form
|
||||||
form input[type="submit"] {
|
> form input[type="submit"] {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Response button
|
// Response/delete buttons
|
||||||
a.button {
|
a.button, form.inline, form.inline input {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: none;
|
background: none;
|
||||||
color: $black;
|
color: $black;
|
||||||
border: none;
|
border: none;
|
||||||
|
margin-right: 2em;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
color: $purple;
|
color: $purple;
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
fill: none;
|
fill: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon:before {
|
||||||
font-family: "Feather";
|
font-family: "Feather";
|
||||||
speak: none;
|
speak: none;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
|
|
@ -3,14 +3,14 @@
|
||||||
@use plume_models::users::User;
|
@use plume_models::users::User;
|
||||||
@use routes::*;
|
@use routes::*;
|
||||||
|
|
||||||
@(ctx: BaseContext, comm: &Comment, author: User, in_reply_to: Option<&str>)
|
@(ctx: BaseContext, comm: &Comment, author: User, in_reply_to: Option<&str>, blog: &str, slug: &str)
|
||||||
|
|
||||||
<div class="comment u-comment h-cite" id="comment-@comm.id">
|
<div class="comment u-comment h-cite" id="comment-@comm.id">
|
||||||
<a class="author u-author h-card" href="@uri!(user::details: name = author.get_fqn(ctx.0))">
|
<a class="author u-author h-card" href="@uri!(user::details: name = author.get_fqn(ctx.0))">
|
||||||
@avatar(ctx.0, &author, Size::Small, true, ctx.1)
|
@avatar(ctx.0, &author, Size::Small, true, ctx.1)
|
||||||
<span class="display-name p-name">@author.name(ctx.0)</span>
|
<span class="display-name p-name">@author.name(ctx.0)</span>
|
||||||
<small>@author.get_fqn(ctx.0)</small>
|
<small>@author.get_fqn(ctx.0)</small>
|
||||||
</a>
|
</a>
|
||||||
@if let Some(ref ap_url) = comm.ap_url {
|
@if let Some(ref ap_url) = comm.ap_url {
|
||||||
<a class="u-url" href="@ap_url"></a>
|
<a class="u-url" href="@ap_url"></a>
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,12 @@
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<a class="button icon icon-message-circle" href="?responding_to=@comm.id">@i18n!(ctx.1, "Respond")</a>
|
<a class="button icon icon-message-circle" href="?responding_to=@comm.id">@i18n!(ctx.1, "Respond")</a>
|
||||||
|
@if ctx.2.clone().map(|u| u.id == author.id).unwrap_or(false) {
|
||||||
|
<form class="inline icon icon-trash" method="post" action="@uri!(comments::delete: blog = blog, slug = slug, id = comm.id)">
|
||||||
|
<input onclick="return confirm('Are you sure you?')" type="submit" value="@i18n!(ctx.1, "Delete this comment")">
|
||||||
|
</form>
|
||||||
|
}
|
||||||
@for res in comm.get_responses(ctx.0) {
|
@for res in comm.get_responses(ctx.0) {
|
||||||
@:comment(ctx, &res, res.get_author(ctx.0), comm.ap_url.as_ref().map(|u| &**u))
|
@:comment(ctx, &res, res.get_author(ctx.0), comm.ap_url.as_ref().map(|u| &**u), blog, slug)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -146,7 +146,7 @@
|
||||||
@if !comments.is_empty() {
|
@if !comments.is_empty() {
|
||||||
<div class="list">
|
<div class="list">
|
||||||
@for comm in comments {
|
@for comm in comments {
|
||||||
@:comment(ctx, &comm, comm.get_author(ctx.0), Some(&article.ap_url))
|
@:comment(ctx, &comm, comm.get_author(ctx.0), Some(&article.ap_url), &blog.get_fqn(ctx.0), &article.slug)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue