diff --git a/docs/ENV-VARS.md b/docs/ENV-VARS.md new file mode 100644 index 00000000..e1037212 --- /dev/null +++ b/docs/ENV-VARS.md @@ -0,0 +1,22 @@ +# Useful Environment Variables + +Plume relies on some environment variables for some configuration options. You can either set them before +starting the app with `cargo run` or write them in a `.env` file to have automatically loaded. + +Here are the variables that Plume uses: + +- `BASE_URL`: the domain name, or IP and port on which Plume is listening. It is used in all federation-related code. +- `DB_URL`: the URL of the PostgreSQL database, used by Plume (`postgres://plume:plume@localhost/plume` by default). +- `POSTGRES_USER`: if you just want to use a different PostgreSQL user name, and keep the rest of the default URL. +- `POSTGRES_PASSWORD`: same as `POSTGRES_USER`, but for the password. +- `USE_HTTPS`: if it is `0`, federation and medias will be using HTTP by default (`1` by default). +- `ROCKET_ADDRESS`: the adress on which Plume should listen (`0.0.0.0` by default). +- `ROCKET_PORT`: the port on which Plume should listen ([`7878` by default](https://twitter.com/ag_dubs/status/852559264510070784)) +- `ROCKET_SECRET_KEY`: key used to sign private cookies and for CSRF protection. If it is not set, it will be regenerated everytime you restart Plume, +meaning that all your users will get disconnected. You can generate one with `openssl rand -base64 32`. + +## Diesel + +Diesel, the tool we use to run migrations may be configured with the `DATABASE_URL` which should contain the URL of the +PostgreSQL database. Otherwise, you can specify `--database-url YOUR-URL` everytime you run a `diesel` command. + diff --git a/docs/README.md b/docs/README.md index 2133ad71..b7497597 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,5 +2,6 @@ - [Installing Plume (for development or production)](INSTALL.md) - [Updating your instance](UPDATE.md) +- [Useful Environment Variables](ENV-VARS.md) - [Development Guide](DEVELOPMENT.md) - [Making Plume available in your language](INTERNATIONALIZATION.md) diff --git a/migrations/2018-09-04-103017_follows_add_ap_url/down.sql b/migrations/2018-09-04-103017_follows_add_ap_url/down.sql new file mode 100644 index 00000000..a1d36e37 --- /dev/null +++ b/migrations/2018-09-04-103017_follows_add_ap_url/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE follows DROP COLUMN ap_url; diff --git a/migrations/2018-09-04-103017_follows_add_ap_url/up.sql b/migrations/2018-09-04-103017_follows_add_ap_url/up.sql new file mode 100644 index 00000000..e4c7d949 --- /dev/null +++ b/migrations/2018-09-04-103017_follows_add_ap_url/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE follows ADD COLUMN ap_url TEXT NOT NULL DEFAULT ''; diff --git a/migrations/2018-09-04-104828_posts_add_subtitle/down.sql b/migrations/2018-09-04-104828_posts_add_subtitle/down.sql new file mode 100644 index 00000000..0c39127b --- /dev/null +++ b/migrations/2018-09-04-104828_posts_add_subtitle/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE posts DROP COLUMN subtitle; diff --git a/migrations/2018-09-04-104828_posts_add_subtitle/up.sql b/migrations/2018-09-04-104828_posts_add_subtitle/up.sql new file mode 100644 index 00000000..17ab4754 --- /dev/null +++ b/migrations/2018-09-04-104828_posts_add_subtitle/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE posts ADD COLUMN subtitle TEXT NOT NULL DEFAULT ''; diff --git a/plume-models/src/follows.rs b/plume-models/src/follows.rs index 57e26fa4..eca39d3d 100644 --- a/plume-models/src/follows.rs +++ b/plume-models/src/follows.rs @@ -1,7 +1,7 @@ -use activitypub::{Actor, activity::{Accept, Follow as FollowAct}, actor::Person}; +use activitypub::{Actor, activity::{Accept, Follow as FollowAct, Undo}, actor::Person}; use diesel::{self, PgConnection, ExpressionMethods, QueryDsl, RunQueryDsl}; -use plume_common::activity_pub::{broadcast, Id, IntoId, inbox::{FromActivity, Notify, WithInbox}, sign::Signer}; +use plume_common::activity_pub::{broadcast, Id, IntoId, inbox::{FromActivity, Notify, WithInbox, Deletable}, sign::Signer}; use blogs::Blog; use notifications::*; use users::User; @@ -12,19 +12,42 @@ use schema::follows; pub struct Follow { pub id: i32, pub follower_id: i32, - pub following_id: i32 + pub following_id: i32, + pub ap_url: String, } #[derive(Insertable)] #[table_name = "follows"] pub struct NewFollow { pub follower_id: i32, - pub following_id: i32 + pub following_id: i32, + pub ap_url: String, } impl Follow { insert!(follows, NewFollow); get!(follows); + find_by!(follows, find_by_ap_url, ap_url as String); + + pub fn find(conn: &PgConnection, from: i32, to: i32) -> Option { + follows::table.filter(follows::follower_id.eq(from)) + .filter(follows::following_id.eq(to)) + .get_result(conn) + .ok() + } + + pub fn into_activity(&self, conn: &PgConnection) -> FollowAct { + let user = User::get(conn, self.follower_id).unwrap(); + let target = User::get(conn, self.following_id).unwrap(); + + let mut act = FollowAct::default(); + act.follow_props.set_actor_link::(user.clone().into_id()).expect("Follow::into_activity: actor error"); + act.follow_props.set_object_object(user.into_activity(&*conn)).unwrap(); + act.object_props.set_id_string(self.ap_url.clone()).unwrap(); + act.object_props.set_to_link(target.clone().into_id()).expect("New Follow error while setting 'to'"); + act.object_props.set_cc_link_vec::(vec![]).expect("New Follow error while setting 'cc'"); + act + } /// from -> The one sending the follow request /// target -> The target of the request, responding with Accept @@ -36,9 +59,12 @@ impl Follow { from_id: i32, target_id: i32 ) -> Follow { + let from_url: String = from.clone().into_id().into(); + let target_url: String = target.clone().into_id().into(); let res = Follow::insert(conn, NewFollow { follower_id: from_id, - following_id: target_id + following_id: target_id, + ap_url: format!("{}/follow/{}", from_url, target_url), }); let mut accept = Accept::default(); @@ -77,3 +103,21 @@ impl Notify for Follow { }); } } + +impl Deletable for Follow { + fn delete(&self, conn: &PgConnection) -> Undo { + diesel::delete(self).execute(conn).expect("Coudn't delete follow"); + + let mut undo = Undo::default(); + undo.undo_props.set_actor_link(User::get(conn, self.follower_id).unwrap().into_id()).expect("Follow::delete: actor error"); + undo.object_props.set_id_string(format!("{}/undo", self.ap_url)).expect("Follow::delete: id error"); + undo.undo_props.set_object_object(self.into_activity(conn)).expect("Follow::delete: object error"); + undo + } + + fn delete_id(id: String, conn: &PgConnection) { + if let Some(follow) = Follow::find_by_ap_url(conn, id) { + follow.delete(conn); + } + } +} diff --git a/plume-models/src/posts.rs b/plume-models/src/posts.rs index 5b67a66d..73c8c985 100644 --- a/plume-models/src/posts.rs +++ b/plume-models/src/posts.rs @@ -33,20 +33,22 @@ pub struct Post { pub published: bool, pub license: String, pub creation_date: NaiveDateTime, - pub ap_url: String + pub ap_url: String, + pub subtitle: String, } #[derive(Insertable)] #[table_name = "posts"] pub struct NewPost { - pub blog_id: i32, + pub blog_id: i32, pub slug: String, pub title: String, pub content: SafeString, pub published: bool, pub license: String, pub creation_date: Option, - pub ap_url: String + pub ap_url: String, + pub subtitle: String, } impl Post { @@ -185,6 +187,7 @@ impl Post { article.object_props.set_attributed_to_link_vec::(authors).expect("Article::into_activity: attributedTo error"); 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.set_tag_link_vec(mentions).expect("Article::into_activity: tag error"); 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"); @@ -250,7 +253,8 @@ impl FromActivity for Post { license: String::from("CC-0"), // TODO // FIXME: This is wrong: with this logic, we may use the display URL as the AP ID. We need two different fields ap_url: article.object_props.url_string().unwrap_or(article.object_props.id_string().expect("Post::from_activity: url + id error")), - creation_date: Some(article.object_props.published_utctime().expect("Post::from_activity: published error").naive_utc()) + creation_date: Some(article.object_props.published_utctime().expect("Post::from_activity: published error").naive_utc()), + subtitle: article.object_props.summary_string().expect("Post::from_activity: summary error") }); for author in authors.into_iter() { diff --git a/plume-models/src/schema.rs b/plume-models/src/schema.rs index 6a625bb8..7214871e 100644 --- a/plume-models/src/schema.rs +++ b/plume-models/src/schema.rs @@ -42,6 +42,7 @@ table! { id -> Int4, follower_id -> Int4, following_id -> Int4, + ap_url -> Text, } } @@ -124,6 +125,7 @@ table! { license -> Varchar, creation_date -> Timestamp, ap_url -> Varchar, + subtitle -> Text, } } diff --git a/plume-models/src/users.rs b/plume-models/src/users.rs index 418a9888..76b1127c 100644 --- a/plume-models/src/users.rs +++ b/plume-models/src/users.rs @@ -567,7 +567,7 @@ impl WithInbox for User { } fn is_local(&self) -> bool { - self.instance_id == 0 + self.instance_id == 1 } } diff --git a/po/de.po b/po/de.po index e177a94a..0fa6aa0d 100644 --- a/po/de.po +++ b/po/de.po @@ -508,5 +508,15 @@ msgid "" "Sorry, but registrations are closed on this instance. Try to find another one" msgstr "" +#, fuzzy +msgid "Subtitle" +msgstr "Titel" + +msgid "Login to like" +msgstr "" + +msgid "Login to boost" +msgstr "" + #~ 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 deccf96e..5a2718e8 100644 --- a/po/en.po +++ b/po/en.po @@ -497,3 +497,12 @@ msgstr "" msgid "" "Sorry, but registrations are closed on this instance. Try to find another one" msgstr "" + +msgid "Subtitle" +msgstr "" + +msgid "Login to like" +msgstr "" + +msgid "Login to boost" +msgstr "" diff --git a/po/fr.po b/po/fr.po index e1f3d757..686c24d4 100644 --- a/po/fr.po +++ b/po/fr.po @@ -503,4 +503,16 @@ msgstr "Envoyer" msgid "" "Sorry, but registrations are closed on this instance. Try to find another one" -msgstr "Désolé, mais les inscriptions sont fermées sur cette instance. Essayez d'en trouver une autre." +msgstr "" +"Désolé, mais les inscriptions sont fermées sur cette instance. Essayez d'en " +"trouver une autre." + +#, fuzzy +msgid "Subtitle" +msgstr "Titre" + +msgid "Login to like" +msgstr "" + +msgid "Login to boost" +msgstr "" diff --git a/po/gl.po b/po/gl.po index 7f68eef0..2d4597fd 100644 --- a/po/gl.po +++ b/po/gl.po @@ -496,4 +496,15 @@ msgstr "Enviar" msgid "" "Sorry, but registrations are closed on this instance. Try to find another one" msgstr "" -"Lamentámolo, pero o rexistro en esta instancia está pechado. Inténteo en outra instancia" +"Lamentámolo, pero o rexistro en esta instancia está pechado. Inténteo en " +"outra instancia" + +#, fuzzy +msgid "Subtitle" +msgstr "Título" + +msgid "Login to like" +msgstr "" + +msgid "Login to boost" +msgstr "" diff --git a/po/nb.po b/po/nb.po index 275ac691..3e0019d2 100644 --- a/po/nb.po +++ b/po/nb.po @@ -512,6 +512,16 @@ msgid "" "Sorry, but registrations are closed on this instance. Try to find another one" msgstr "" +#, fuzzy +msgid "Subtitle" +msgstr "Tittel" + +msgid "Login to like" +msgstr "" + +msgid "Login to boost" +msgstr "" + #~ msgid "One reshare" #~ msgid_plural "{{ count }} reshares" #~ msgstr[0] "Én deling" diff --git a/po/pl.po b/po/pl.po index 0b879eca..2f2e5038 100644 --- a/po/pl.po +++ b/po/pl.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: plume\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-06-15 16:33-0700\n" -"PO-Revision-Date: 2018-07-28 14:56+0200\n" +"PO-Revision-Date: 2018-09-04 17:35+0200\n" "Last-Translator: Marcin Mikołajczak \n" "Language-Team: none\n" "Language: pl\n" @@ -12,7 +12,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2);\n" -"X-Generator: Poedit 2.0.9\n" +"X-Generator: Poedit 2.1.1\n" msgid "Latest articles" msgstr "Najnowsze artykuły" @@ -92,16 +92,15 @@ msgstr "Dodaj swoje" msgid "One Boost" msgid_plural "{{ count }} Boosts" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Jedno podbicie" +msgstr[1] "{{ count }} podbicia" +msgstr[2] "{{ count }} podbić" -#, fuzzy msgid "I don't want to boost this anymore" -msgstr "Cofnij udostępnienie" +msgstr "Cofnij podbicie" msgid "Boost" -msgstr "" +msgstr "Podbij" msgid "Comments" msgstr "Komentarze" @@ -165,9 +164,8 @@ msgstr "Obserwuj" msgid "Unfollow" msgstr "Przestań obserwować" -#, fuzzy msgid "Recently boosted" -msgstr "Ostatnio udostępniono" +msgstr "Ostatnio podbite" msgid "One follower" msgid_plural "{{ count }} followers" @@ -247,9 +245,8 @@ msgstr "Musisz się zalogować, aby zobaczyć swoje powiadomienia" msgid "You need to be logged in order to write a new post" msgstr "Musisz się zalogować, aby utworzyć wpis" -#, fuzzy msgid "You need to be logged in order to boost a post" -msgstr "Musisz się zalogować, aby polubić wpis" +msgstr "Musisz się zalogować, aby podbić wpis" msgid "Invalid username or password" msgstr "Nieprawidłowa nazwa użytkownika lub hasło" @@ -268,18 +265,17 @@ msgstr "" "Napisano przez {{ link_1 }}{{ url }}{{ link_2 }}{{ name | escape }}" "{{ link_3 }}" -#, fuzzy msgid "{{ data }} boosted your article" -msgstr "{{ data }} skomentował Twój artykuł" +msgstr "{{ data }} podbił(a) Twój artykuł" msgid "{{ data }} started following you" -msgstr "{{ data }} zaczął Cię obserwować" +msgstr "{{ data }} zaczął(-ęła) Cię obserwować" msgid "{{ data }} liked your article" -msgstr "{{ data }} polubił Twój artykuł" +msgstr "{{ data }} polubił(a) Twój artykuł" msgid "{{ data }} commented your article" -msgstr "{{ data }} skomentował Twój artykuł" +msgstr "{{ data }} skomentował(a) Twój artykuł" msgid "We couldn't find this page." msgstr "Nie udało się odnaleźć tej strony." @@ -294,7 +290,7 @@ msgid "You are not author in this blog." msgstr "Nie jesteś autorem tego bloga." msgid "{{ data }} mentioned you." -msgstr "{{ data }} wspomniał o Tobie." +msgstr "{{ data }} wspomniał(a) o Tobie." msgid "Your comment" msgstr "Twój komentarz" @@ -318,9 +314,8 @@ msgid "We need an email or a username to identify you" msgstr "" "Potrzebujemy nazwy użytkownika lub adresu e-mail, aby Cię zidentyfikować" -#, fuzzy msgid "Your password can't be empty" -msgstr "Twój komentarz nie może być pusty" +msgstr "Twoje hasło nie może być puste" msgid "Passwords are not matching" msgstr "Hasła nie pasują do siebie" @@ -361,20 +356,19 @@ msgid "Next page" msgstr "Następna strona" msgid "{{ user }} mentioned you." -msgstr "{{ user }} wspomniał o Tobie." +msgstr "{{ user }} wspomniał(a) o Tobie." msgid "{{ user }} commented your article." -msgstr "{{ user }} skomentował Twój artykuł." +msgstr "{{ user }} skomentował(a) Twój artykuł." msgid "{{ user }} is now following you." -msgstr "{{ user }} zaczął Cię obserwować." +msgstr "{{ user }} zaczął(-ęła) Cię obserwować." msgid "{{ user }} liked your article." -msgstr "{{ user }} polubił Twój artykuł." +msgstr "{{ user }} polubił(a) Twój artykuł." -#, fuzzy msgid "{{ user }} boosted your article." -msgstr "{{ user }} skomentował Twój artykuł." +msgstr "{{ user }} podbił(a) Twój artykuł." msgid "Source code" msgstr "Kod źródłowy" @@ -441,7 +435,7 @@ msgid "people" msgstr "osób" msgid "Who wrote" -msgstr "Którzy napisali" +msgstr "Które napisały" msgid "articles" msgstr "artykuły" @@ -449,79 +443,83 @@ msgstr "artykuły" msgid "Read the detailed rules" msgstr "Przeczytaj szczegółowe zasady" -#, fuzzy msgid "Delete this article" -msgstr "Najnowsze artykuły" +msgstr "Usuń ten artykuł" msgid "And connected to" -msgstr "" +msgstr "Połączony z" -#, fuzzy msgid "other instances" -msgstr "O tej instancji" +msgstr "innych instancji" -#, fuzzy msgid "Administred by" -msgstr "Administracja" +msgstr "Administrowany przez" msgid "Runs Plume {{ version }}" -msgstr "" +msgstr "Działa na Plume {{ version }}" -#, fuzzy msgid "Your media" -msgstr "Twój komentarz" +msgstr "Twoja zawartość multimedialna" msgid "Go to your gallery" -msgstr "" +msgstr "Przejdź do swojej galerii" msgid "{{ name}}'s avatar'" -msgstr "" +msgstr "Awatar {{name}}" msgid "Media details" -msgstr "" +msgstr "Szczegóły zawartości multimedialnej" msgid "Go back to the gallery" -msgstr "" +msgstr "Powróć do galerii" -#, fuzzy msgid "Markdown code" -msgstr "Markdown jest obsługiwany" +msgstr "Kod Markdown" msgid "Copy it in your articles to insert this media." -msgstr "" +msgstr "Skopiuj do swoich artykułów, aby wstawić tę zawartość multimedialną." msgid "Use as avatar" -msgstr "" +msgstr "Użyj jako awataru" msgid "Delete" -msgstr "" +msgstr "Usuń" msgid "Upload" -msgstr "" +msgstr "Wyślij" msgid "You don't have any media yet." -msgstr "" +msgstr "Nie masz żadnej zawartości multimedialnej." msgid "Media upload" -msgstr "" +msgstr "Wysyłanie zawartości multimedialnej" -#, fuzzy msgid "Description" -msgstr "Szczegółowy opis" +msgstr "Opis" -#, fuzzy msgid "Content warning" -msgstr "Zawartość" +msgstr "Ostrzeżenie o zawartości" msgid "File" -msgstr "" +msgstr "Plik" msgid "Send" -msgstr "" +msgstr "Wyślij" msgid "" "Sorry, but registrations are closed on this instance. Try to find another one" msgstr "" +"Przepraszamy, rejestracja jest zamknięta na tej instancji. Spróbuj znaleźć " +"inną" + +msgid "Subtitle" +msgstr "Podtytuł" + +msgid "Login to like" +msgstr "Zaloguj się aby polubić" + +msgid "Login to boost" +msgstr "Zaloguj się aby podbić" #~ msgid "One reshare" #~ msgid_plural "{{ count }} reshares" diff --git a/po/plume.pot b/po/plume.pot index ca6c8851..9c0954b1 100644 --- a/po/plume.pot +++ b/po/plume.pot @@ -486,3 +486,12 @@ msgstr "" msgid "Sorry, but registrations are closed on this instance. Try to find another one" msgstr "" + +msgid "Subtitle" +msgstr "" + +msgid "Login to like" +msgstr "" + +msgid "Login to boost" +msgstr "" diff --git a/src/inbox.rs b/src/inbox.rs index 5a4d0bbc..414e9601 100644 --- a/src/inbox.rs +++ b/src/inbox.rs @@ -55,6 +55,10 @@ pub trait Inbox { "Announce" => { Reshare::delete_id(act.undo_props.object_object::()?.object_props.id_string()?, conn); Ok(()) + }, + "Follow" => { + Follow::delete_id(act.undo_props.object_object::()?.object_props.id_string()?, conn); + Ok(()) } _ => Err(InboxError::CantUndo)? } diff --git a/src/routes/posts.rs b/src/routes/posts.rs index 44376b89..7ecf9f4b 100644 --- a/src/routes/posts.rs +++ b/src/routes/posts.rs @@ -57,7 +57,8 @@ fn details_response(blog: String, slug: String, conn: DbConn, user: Option "date": &post.creation_date.timestamp(), "previous": query.and_then(|q| q.responding_to.map(|r| Comment::get(&*conn, r).expect("Error retrieving previous comment").to_json(&*conn, &vec![]))), "user_fqn": user.clone().map(|u| u.get_fqn(&*conn)).unwrap_or(String::new()), - "is_author": user.map(|u| post.get_authors(&*conn).into_iter().any(|a| u.id == a.id)).unwrap_or(false) + "is_author": user.clone().map(|u| post.get_authors(&*conn).into_iter().any(|a| u.id == a.id)).unwrap_or(false), + "is_following": user.map(|u| u.is_following(&*conn, post.get_authors(&*conn)[0].id)).unwrap_or(false) })) }) }) @@ -98,6 +99,7 @@ fn new(blog: String, user: User, conn: DbConn) -> Template { struct NewPostForm { #[validate(custom(function = "valid_slug", message = "Invalid title"))] pub title: String, + pub subtitle: String, pub content: String, pub license: String } @@ -150,7 +152,8 @@ fn create(blog_name: String, data: LenientForm, user: User, conn: D Instance::get_local(&*conn).map(|i| i.default_license).unwrap_or(String::from("CC-0")) }, ap_url: "".to_string(), - creation_date: None + creation_date: None, + subtitle: form.subtitle.clone() }); let post = post.update_ap_url(&*conn); PostAuthor::insert(&*conn, NewPostAuthor { diff --git a/src/routes/user.rs b/src/routes/user.rs index ae3eb6d1..c4ee8f85 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -1,5 +1,5 @@ use activitypub::{ - activity::{Create, Follow}, + activity::Create, collection::OrderedCollection, object::Article }; @@ -16,7 +16,7 @@ use workerpool::thunk::*; use plume_common::activity_pub::{ ActivityStream, broadcast, Id, IntoId, ApRequest, - inbox::{FromActivity, Notify} + inbox::{FromActivity, Notify, Deletable} }; use plume_common::utils; use plume_models::{ @@ -71,7 +71,8 @@ fn details(name: String, conn: DbConn, account: Option, worker: Worker, fe .unwrap_or_else(|| User::fetch_from_url(&*fecth_followers_conn, user_id).expect("Couldn't fetch follower")); follows::Follow::insert(&*fecth_followers_conn, follows::NewFollow { follower_id: follower.id, - following_id: user_clone.id + following_id: user_clone.id, + ap_url: format!("{}/follow/{}", follower.ap_url, user_clone.ap_url), }); } })); @@ -116,20 +117,20 @@ fn dashboard_auth() -> Flash { #[get("/@//follow")] fn follow(name: String, conn: DbConn, user: User, worker: Worker) -> Redirect { let target = User::find_by_fqn(&*conn, name.clone()).unwrap(); - let f = follows::Follow::insert(&*conn, follows::NewFollow { - follower_id: user.id, - following_id: target.id - }); - f.notify(&*conn); + if let Some(follow) = follows::Follow::find(&*conn, user.id, target.id) { + let delete_act = follow.delete(&*conn); + worker.execute(Thunk::of(move || broadcast(&user, delete_act, vec![target]))); + } else { + let f = follows::Follow::insert(&*conn, follows::NewFollow { + follower_id: user.id, + following_id: target.id, + ap_url: format!("{}/follow/{}", user.ap_url, target.ap_url), + }); + f.notify(&*conn); - let mut act = Follow::default(); - act.follow_props.set_actor_link::(user.clone().into_id()).unwrap(); - act.follow_props.set_object_object(user.into_activity(&*conn)).unwrap(); - act.object_props.set_id_string(format!("{}/follow/{}", user.ap_url, target.ap_url)).unwrap(); - act.object_props.set_to_link(target.clone().into_id()).expect("New Follow error while setting 'to'"); - act.object_props.set_cc_link_vec::(vec![]).expect("New Follow error while setting 'cc'"); - - worker.execute(Thunk::of(move || broadcast(&user, act, vec![target]))); + let act = f.into_activity(&*conn); + worker.execute(Thunk::of(move || broadcast(&user, act, vec![target]))); + } Redirect::to(uri!(details: name = name)) } diff --git a/static/css/main.css b/static/css/main.css index 1eb14d86..7f1fa1db 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -192,9 +192,18 @@ } article img { + display: block; + margin: 3em auto; max-width: 100%; } + article pre { + padding: 1em; + background: #DADADA; + overflow: auto; + border-radius: 5px; + } + /* Article.Meta */ main .article-meta, main .article-meta button { @@ -235,8 +244,8 @@ font-size: 1.5em; } - main .article-meta .likes button, - main .article-meta .reshares button { + main .article-meta .likes .action, + main .article-meta .reshares .action { display: flex; flex-direction: column; align-items: center; @@ -246,15 +255,16 @@ background: none; color: #242424; border: none; + font-size: 1.1em; } main .article-meta .likes > p, - main .article-meta .likes button:hover { color: #E92F2F; } + main .article-meta .likes .action:hover { color: #E92F2F; } main .article-meta .reshares > p, - main .article-meta .reshares button:hover { color: #7765E3; } + main .article-meta .reshares .action:hover { color: #7765E3; } - main .article-meta .likes button svg.feather, - main .article-meta .reshares button i { + main .article-meta .likes .action svg.feather, + main .article-meta .reshares .action i { transition: background 0.1s ease-in; display: flex; align-items: center; @@ -267,39 +277,39 @@ border-radius: 50%; } - main .article-meta .likes button svg.feather { + main .article-meta .likes .action svg.feather { padding: 0.7em; box-sizing: border-box; color: #E92F2F; fill: none; border: solid #E92F2F thin; } - main .article-meta .likes button:hover svg.feather { + main .article-meta .likes .action:hover svg.feather { background: rgba(233, 47, 47, 0.15); } - main .article-meta .reshares button i { + main .article-meta .reshares .action i { color: #7765E3; border: solid #7765E3 thin; font-weight: 600; } - main .article-meta .reshares button:hover i { + main .article-meta .reshares .action:hover i { background: rgba(119, 101, 227, 0.15); } - main .article-meta .likes button.liked svg.feather { background: #E92F2F; fill: currentColor; } - main .article-meta .likes button.liked:hover svg.feather { + main .article-meta .likes .action.liked svg.feather { background: #E92F2F; fill: currentColor; } + main .article-meta .likes .action.liked:hover svg.feather { background: rgba(233, 47, 47, 0.25); color: #E92F2F; } - main .article-meta .reshares button.reshared i { background: #7765E3; } - main .article-meta .reshares button.reshared:hover i { + main .article-meta .reshares .action.reshared i { background: #7765E3; } + main .article-meta .reshares .action.reshared:hover i { background: rgba(119, 101, 227, 0.25); color: #7765E3; } - main .article-meta .likes button.liked svg.feather, - main .article-meta .reshares button.reshared i { + main .article-meta .likes .action.liked svg.feather, + main .article-meta .reshares .action.reshared i { color: #F4F4F4; font-weight: 900; } @@ -796,5 +806,5 @@ } .avatar.padded { - margin-right: 1em; + margin-right: 2rem; } diff --git a/templates/base.html.tera b/templates/base.html.tera index 2a6104a5..6825e736 100644 --- a/templates/base.html.tera +++ b/templates/base.html.tera @@ -30,7 +30,7 @@ {{ "Notifications" | _ }} - + {{ "Log Out" | _ }} diff --git a/templates/macros.html.tera b/templates/macros.html.tera index e03025fd..653439ea 100644 --- a/templates/macros.html.tera +++ b/templates/macros.html.tera @@ -2,7 +2,13 @@

{{ article.post.title }}

{{ article.post.content | safe | striptags | truncate(length=200) }}

+

+ {% if article.post.subtitle | length > 0 %} + {{ article.post.subtitle }} + {% else %} + {{ article.post.content | safe | striptags | truncate(length=200) }} + {% endif %} +

{{ "By {{ link_1 }}{{ link_2 }}{{ link_3 }}{{ name | escape }}{{ link_4 }}" | _( diff --git a/templates/posts/details.html.tera b/templates/posts/details.html.tera index ea4fb1d3..e64efffd 100644 --- a/templates/posts/details.html.tera +++ b/templates/posts/details.html.tera @@ -11,6 +11,7 @@ {% block content %}

{{ article.post.title }}

+

{{ article.post.subtitle }}

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

+ {% if account %}
@@ -40,23 +55,34 @@

{{ n_likes }}

{% if has_liked %} - + {% else %} - + {% endif %}

{{ n_reshares }}

{% if has_reshared %} - + {% else %} - + {% endif %}
{% else %}

{{ "Login or use your Fediverse account to interact with this article" | _ }}

+
+ + +
+

{{ n_reshares }}

+ {{ "Boost" | _ }} +
+
{% endif %}
diff --git a/templates/posts/new.html.tera b/templates/posts/new.html.tera index 1892f2f9..86bd4fef 100644 --- a/templates/posts/new.html.tera +++ b/templates/posts/new.html.tera @@ -9,6 +9,7 @@

{{ "Create a post" | _ }}

{{ macros::input(name="title", label="Title", errors=errors, form=form, props="required") }} + {{ macros::input(name="subtitle", label="Subtitle", errors=errors, form=form, optional=true) }} {% if errors is defined and errors.content %} {% for err in errors.content %}