diff --git a/plume-models/src/posts.rs b/plume-models/src/posts.rs index af37f4dc..def6def7 100644 --- a/plume-models/src/posts.rs +++ b/plume-models/src/posts.rs @@ -59,6 +59,27 @@ impl Post { find_by!(posts, find_by_slug, slug as String, blog_id as i32); find_by!(posts, find_by_ap_url, ap_url as String); + pub fn list_by_tag(conn: &PgConnection, tag: String, (min, max): (i32, i32)) -> Vec { + use schema::tags; + + let ids = tags::table.filter(tags::tag.eq(tag)).select(tags::post_id); + posts::table.filter(posts::id.eq(any(ids))) + .order(posts::creation_date.desc()) + .offset(min.into()) + .limit((max - min).into()) + .get_results::(conn) + .expect("Error loading posts by tag") + } + + pub fn count_for_tag(conn: &PgConnection, tag: String) -> i64 { + use schema::tags; + let ids = tags::table.filter(tags::tag.eq(tag)).select(tags::post_id); + posts::table.filter(posts::id.eq(any(ids))) + .count() + .get_result(conn) + .expect("Error counting posts by tag") + } + pub fn count_local(conn: &PgConnection) -> usize { use schema::post_authors; use schema::users; @@ -220,7 +241,7 @@ impl Post { article.object_props.set_content_string(self.content.get().clone()).expect("Article::into_activity: content error"); article.object_props.set_published_utctime(Utc.from_utc_datetime(&self.creation_date)).expect("Article::into_activity: published error"); article.object_props.set_summary_string(self.subtitle.clone()).expect("Article::into_activity: summary error"); - article.object_props.tag = json!(mentions_json.append(&mut tags_json)); + article.object_props.tag = Some(json!(mentions_json.append(&mut tags_json))); article.object_props.set_url_string(self.ap_url.clone()).expect("Article::into_activity: url error"); article.object_props.set_to_link_vec::(to.into_iter().map(Id::new).collect()).expect("Article::into_activity: to error"); article.object_props.set_cc_link_vec::(vec![]).expect("Article::into_activity: cc error"); @@ -300,11 +321,11 @@ impl FromActivity for Post { // save mentions and tags if let Some(serde_json::Value::Array(tags)) = article.object_props.tag.clone() { for tag in tags.into_iter() { - serde_json::from_value::(tag) + serde_json::from_value::(tag.clone()) .map(|m| Mention::from_activity(conn, m, post.id, true)) .ok(); - serde_json::from_value::(tag) + serde_json::from_value::(tag.clone()) .map(|t| Tag::from_activity(conn, t, post.id)) .ok(); } diff --git a/plume-models/src/tags.rs b/plume-models/src/tags.rs index 23a0d787..b8ac6147 100644 --- a/plume-models/src/tags.rs +++ b/plume-models/src/tags.rs @@ -5,7 +5,7 @@ use ap_url; use instance::Instance; use schema::tags; -#[derive(Serialize, Queryable)] +#[derive(Serialize, Queryable, Clone)] pub struct Tag { pub id: i32, pub tag: String, @@ -24,12 +24,13 @@ pub struct NewTag { impl Tag { insert!(tags, NewTag); get!(tags); + find_by!(tags, find_by_name, tag as String); list_by!(tags, for_post, post_id as i32); pub fn into_activity(&self, conn: &PgConnection) -> Hashtag { - let ht = Hashtag::default(); + let mut ht = Hashtag::default(); ht.set_href_string(ap_url(format!("{}/tag/{}", Instance::get_local(conn).unwrap().public_domain, self.tag))).expect("Tag::into_activity: href error"); - ht.set_name_string(self.tag).expect("Tag::into_activity: name error"); + ht.set_name_string(self.tag.clone()).expect("Tag::into_activity: name error"); ht } diff --git a/po/de.po b/po/de.po index 011e3f3a..dc916e9b 100644 --- a/po/de.po +++ b/po/de.po @@ -545,5 +545,9 @@ msgstr "Über {{ instance_name }}" msgid "View all" msgstr "" +#, fuzzy +msgid "Articles tagged \"{{ tag }}\"" +msgstr "Über {{ instance_name }}" + #~ msgid "Your password should be at least 8 characters long" #~ msgstr "Das Passwort sollte mindestens 8 Zeichen lang sein" diff --git a/po/en.po b/po/en.po index 7e6333e9..fb54ba7d 100644 --- a/po/en.po +++ b/po/en.po @@ -531,3 +531,7 @@ msgstr "Welcome on {{ instance_name }}" msgid "View all" msgstr "" + +#, fuzzy +msgid "Articles tagged \"{{ tag }}\"" +msgstr "Welcome on {{ instance_name }}" diff --git a/po/fr.po b/po/fr.po index 6df847d8..f6e33c27 100644 --- a/po/fr.po +++ b/po/fr.po @@ -539,3 +539,7 @@ msgstr "Articles de {{ instance.name }}" msgid "View all" msgstr "Tout afficher" + +#, fuzzy +msgid "Articles tagged \"{{ tag }}\"" +msgstr "Articles de {{ instance.name }}" diff --git a/po/gl.po b/po/gl.po index 576e4309..1937b200 100644 --- a/po/gl.po +++ b/po/gl.po @@ -535,3 +535,7 @@ msgstr "Acerca de {{ instance_name }}" msgid "View all" msgstr "" + +#, fuzzy +msgid "Articles tagged \"{{ tag }}\"" +msgstr "Acerca de {{ instance_name }}" diff --git a/po/nb.po b/po/nb.po index ed08eeab..046a17fd 100644 --- a/po/nb.po +++ b/po/nb.po @@ -549,6 +549,10 @@ msgstr "Om {{ instance_name }}" msgid "View all" msgstr "" +#, fuzzy +msgid "Articles tagged \"{{ tag }}\"" +msgstr "Om {{ instance_name }}" + #~ msgid "One reshare" #~ msgid_plural "{{ count }} reshares" #~ msgstr[0] "Én deling" diff --git a/po/pl.po b/po/pl.po index d5f7d282..8184bbfd 100644 --- a/po/pl.po +++ b/po/pl.po @@ -548,6 +548,10 @@ msgstr "O {{ instance_name }}" msgid "View all" msgstr "" +#, fuzzy +msgid "Articles tagged \"{{ tag }}\"" +msgstr "O {{ instance_name }}" + #~ msgid "One reshare" #~ msgid_plural "{{ count }} reshares" #~ msgstr[0] "Jedno udostępnienie" diff --git a/po/plume.pot b/po/plume.pot index f4d25180..49228b02 100644 --- a/po/plume.pot +++ b/po/plume.pot @@ -519,3 +519,6 @@ msgstr "" msgid "View all" msgstr "" + +msgid "Articles tagged \"{{ tag }}\"" +msgstr "" diff --git a/src/main.rs b/src/main.rs index aff653a5..4c463b70 100644 --- a/src/main.rs +++ b/src/main.rs @@ -101,6 +101,9 @@ fn main() { routes::static_files, + routes::tags::tag, + routes::tags::paginated_tag, + routes::user::me, routes::user::details, routes::user::dashboard, diff --git a/src/routes/mod.rs b/src/routes/mod.rs index d4442732..25abb2a6 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -108,6 +108,7 @@ pub mod notifications; pub mod posts; pub mod reshares; pub mod session; +pub mod tags; pub mod user; pub mod well_known; diff --git a/src/routes/tags.rs b/src/routes/tags.rs new file mode 100644 index 00000000..491aed5c --- /dev/null +++ b/src/routes/tags.rs @@ -0,0 +1,28 @@ +use rocket_contrib::Template; +use serde_json; + +use plume_models::{ + db_conn::DbConn, + posts::Post, + tags::Tag, + users::User, +}; +use routes::Page; + +#[get("/tag/")] +fn tag(user: Option, conn: DbConn, name: String) -> Template { + paginated_tag(user, conn, name, Page::first()) +} + +#[get("/tag/?")] +fn paginated_tag(user: Option, conn: DbConn, name: String, page: Page) -> Template { + let tag = Tag::find_by_name(&*conn, name).expect("Rendering tags::tag: tag not found"); + let posts = Post::list_by_tag(&*conn, tag.tag.clone(), page.limits()); + Template::render("tags/index", json!({ + "tag": tag.clone(), + "account": user.map(|u| u.to_json(&*conn)), + "articles": posts.into_iter().map(|p| p.to_json(&*conn)).collect::>(), + "page": page.page, + "n_pages": Page::total(Post::count_for_tag(&*conn, tag.tag) as i32) + })) +} diff --git a/templates/posts/details.html.tera b/templates/posts/details.html.tera index 3a3158dc..9667ba59 100644 --- a/templates/posts/details.html.tera +++ b/templates/posts/details.html.tera @@ -36,7 +36,7 @@

{{ "This article is under the {{ license }} license." | _(license=article.post.license) }}

    {% for tag in article.tags %} -
  • {{ tag.tag }}
  • +
  • {{ tag.tag }}
  • {% endfor %}
diff --git a/templates/tags/index.html.tera b/templates/tags/index.html.tera new file mode 100644 index 00000000..ad225ba6 --- /dev/null +++ b/templates/tags/index.html.tera @@ -0,0 +1,17 @@ +{% extends "base" %} +{% import "macros" as macros %} + +{% block title %} +{{ 'Articles tagged "{{ tag }}"' | _(tag=tag.tag) }} +{% endblock title %} + +{% block content %} +

{{ 'Articles tagged "{{ tag }}"' | _(tag=tag.tag) }}

+ +
+ {% for article in articles %} + {{ macros::post_card(article=article) }} + {% endfor %} +
+ {{ macros::paginate(page=page, total=n_pages) }} +{% endblock content %}