diff --git a/src/main.rs b/src/main.rs index 21210e5b..793376a4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -#![feature(plugin, custom_derive, decl_macro, iterator_find_map)] +#![feature(plugin, custom_derive, decl_macro, iterator_find_map, iterator_flatten)] #![plugin(rocket_codegen)] extern crate activitypub; diff --git a/src/models/mentions.rs b/src/models/mentions.rs index f40f9f37..aebdc7ae 100644 --- a/src/models/mentions.rs +++ b/src/models/mentions.rs @@ -33,6 +33,7 @@ impl Mention { get!(mentions); find_by!(mentions, find_by_ap_url, ap_url as String); list_by!(mentions, list_for_user, mentioned_id as i32); + list_by!(mentions, list_for_post, post_id as i32); pub fn get_mentioned(&self, conn: &PgConnection) -> Option { User::get(conn, self.mentioned_id) @@ -46,6 +47,14 @@ impl Mention { self.post_id.and_then(|id| Comment::get(conn, id)) } + pub fn build_activity(conn: &PgConnection, ment: String) -> link::Mention { + let user = User::find_by_fqn(conn, ment.clone()); + let mut mention = link::Mention::default(); + mention.link_props.set_href_string(user.clone().map(|u| u.ap_url).unwrap_or(String::new())).expect("Error setting mention's href"); + mention.link_props.set_name_string(format!("@{}", ment)).expect("Error setting mention's name"); + mention + } + pub fn to_activity(&self, conn: &PgConnection) -> link::Mention { let user = self.get_mentioned(conn); let mut mention = link::Mention::default(); diff --git a/src/models/posts.rs b/src/models/posts.rs index f4a6408f..368d2834 100644 --- a/src/models/posts.rs +++ b/src/models/posts.rs @@ -145,6 +145,8 @@ impl Post { let mut to = self.get_receivers_urls(conn); to.push(PUBLIC_VISIBILTY.to_string()); + let mentions = Mention::list_for_post(conn, self.id).into_iter().map(|m| m.to_activity(conn)).collect::>(); + let mut article = Article::default(); article.object_props = ObjectProperties { name: Some(serde_json::to_value(self.title.clone()).unwrap()), @@ -152,11 +154,11 @@ impl Post { attributed_to: Some(serde_json::to_value(self.get_authors(conn).into_iter().map(|x| x.ap_url).collect::>()).unwrap()), content: Some(serde_json::to_value(self.content.clone()).unwrap()), published: Some(serde_json::to_value(self.creation_date).unwrap()), - tag: Some(serde_json::to_value(Vec::::new()).unwrap()), + tag: Some(serde_json::to_value(mentions).unwrap()), url: Some(serde_json::to_value(self.compute_id(conn)).unwrap()), to: Some(serde_json::to_value(to).unwrap()), cc: Some(serde_json::to_value(Vec::::new()).unwrap()), - ..ObjectProperties::default() + ..ObjectProperties::default() }; article } diff --git a/src/routes/posts.rs b/src/routes/posts.rs index 0755d57b..fe56629d 100644 --- a/src/routes/posts.rs +++ b/src/routes/posts.rs @@ -4,11 +4,12 @@ use rocket::response::{Redirect, Flash}; use rocket_contrib::Template; use serde_json; -use activity_pub::{broadcast, context, activity_pub, ActivityPub}; +use activity_pub::{broadcast, context, activity_pub, ActivityPub, Id}; use db_conn::DbConn; use models::{ blogs::*, comments::Comment, + mentions::Mention, post_authors::*, posts::*, users::User @@ -87,7 +88,7 @@ fn create(blog_name: String, data: Form, user: User, conn: DbConn) if slug == "new" || Post::find_by_slug(&*conn, slug.clone(), blog.id).is_some() { Redirect::to(uri!(new: blog = blog_name)) } else { - let content = utils::md_to_html(form.content.to_string().as_ref()); + let (content, mentions) = utils::md_to_html(form.content.to_string().as_ref()); let post = Post::insert(&*conn, NewPost { blog_id: blog.id, @@ -104,6 +105,10 @@ fn create(blog_name: String, data: Form, user: User, conn: DbConn) author_id: user.id }); + for m in mentions.into_iter() { + Mention::from_activity(&*conn, Mention::build_activity(&*conn, m), Id::new(post.compute_id(&*conn))); + } + let act = post.create_activity(&*conn); broadcast(&*conn, &user, act, user.get_followers(&*conn)); diff --git a/src/utils.rs b/src/utils.rs index 77745286..f65ff1c9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -20,45 +20,51 @@ pub fn requires_login(message: &str, url: Uri) -> Flash { Flash::new(Redirect::to(Uri::new(format!("/login?m={}", gettext(message.to_string())))), "callback", url.as_str()) } - -pub fn md_to_html(md: &str) -> String { +/// Returns (HTML, mentions) +pub fn md_to_html(md: &str) -> (String, Vec) { let parser = Parser::new_ext(md, Options::all()); - let parser = parser.flat_map(|evt| match evt { - Event::Text(txt) => txt.chars().fold((vec![], false, String::new(), 0), |(mut events, in_mention, text_acc, n), c| { - if in_mention { - if (c.is_alphanumeric() || c == '@' || c == '.' || c == '-' || c == '_') && (n < (txt.chars().count() - 1)) { - (events, in_mention, text_acc + c.to_string().as_ref(), n + 1) - } else { - let mention = text_acc + c.to_string().as_ref(); - let short_mention = mention.clone(); - let short_mention = short_mention.splitn(1, '@').nth(0).unwrap_or(""); - let link = Tag::Link(format!("/@/{}/", mention).into(), short_mention.to_string().into()); + let (parser, mentions): (Vec>, Vec>) = parser.map(|evt| match evt { + Event::Text(txt) => { + let (evts, _, _, _, new_mentions) = txt.chars().fold((vec![], false, String::new(), 0, vec![]), |(mut events, in_mention, text_acc, n, mut mentions), c| { + if in_mention { + if (c.is_alphanumeric() || c == '@' || c == '.' || c == '-' || c == '_') && (n < (txt.chars().count() - 1)) { + (events, in_mention, text_acc + c.to_string().as_ref(), n + 1, mentions) + } else { + let mention = text_acc + c.to_string().as_ref(); + let short_mention = mention.clone(); + let short_mention = short_mention.splitn(1, '@').nth(0).unwrap_or(""); + let link = Tag::Link(format!("/@/{}/", mention).into(), short_mention.to_string().into()); - events.push(Event::Start(link.clone())); - events.push(Event::Text(format!("@{}", short_mention).into())); - events.push(Event::End(link)); + mentions.push(mention); + events.push(Event::Start(link.clone())); + events.push(Event::Text(format!("@{}", short_mention).into())); + events.push(Event::End(link)); - (events, false, c.to_string(), n + 1) - } - } else { - if c == '@' { - events.push(Event::Text(text_acc.into())); - (events, true, String::new(), n + 1) - } else { - if n >= (txt.chars().count() - 1) { // Add the text after at the end, even if it is not followed by a mention. - events.push(Event::Text((text_acc.clone() + c.to_string().as_ref()).into())) + (events, false, c.to_string(), n + 1, mentions) + } + } else { + if c == '@' { + events.push(Event::Text(text_acc.into())); + (events, true, String::new(), n + 1, mentions) + } else { + if n >= (txt.chars().count() - 1) { // Add the text after at the end, even if it is not followed by a mention. + events.push(Event::Text((text_acc.clone() + c.to_string().as_ref()).into())) + } + (events, in_mention, text_acc + c.to_string().as_ref(), n + 1, mentions) } - (events, in_mention, text_acc + c.to_string().as_ref(), n + 1) } - } - }).0, - _ => vec![evt] - }); + }); + (evts, new_mentions) + }, + _ => (vec![evt], vec![]) + }).unzip(); + let parser = parser.into_iter().flatten(); + let mentions = mentions.into_iter().flatten(); // TODO: fetch mentionned profiles in background, if needed let mut buf = String::new(); html::push_html(&mut buf, parser); - buf + (buf, mentions.collect()) }