From e5cdb2869f98f1e7aef16460d7fa2459a5f678c8 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Mon, 3 Sep 2018 21:45:17 +0200 Subject: [PATCH 01/77] Sample compose file and Dockerfile for deployment --- .dockerignore | 5 +++++ Dockerfile | 20 ++++++++++++++++++++ docs/INSTALL.md | 31 +++++++++++++++++++++++++++++++ docs/docker-compose.sample.yml | 18 ++++++++++++++++++ docs/docker.sample.env | 12 ++++++++++++ 5 files changed, 86 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docs/docker-compose.sample.yml create mode 100644 docs/docker.sample.env diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..98547cd9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +docs +data +Dockerfile +docker-compose.yml +.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..a13a2f7a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM rust:1-stretch + +RUN apt-get update && apt-get install -y --no-install-recommends \ + gettext \ + postgresql-client \ + libpq-dev \ + git \ + curl \ + gcc \ + make \ + openssl \ + libssl-dev +WORKDIR /app +COPY Cargo.toml Cargo.lock ./ +RUN cargo install diesel_cli --no-default-features --features postgres --version '=1.2.0' +COPY . . +RUN cargo build +RUN rm -rf target/debug/incremental +CMD ["cargo", "run"] +EXPOSE 7878 diff --git a/docs/INSTALL.md b/docs/INSTALL.md index fba592d3..e3a36516 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -17,6 +17,9 @@ All the following instructions will need a terminal. Here are the commands to install PostgreSQL and GetText on various operating systems. Some of them may need root permissions. +You can also install the project using Docker and docker-compose, please refer +to the `Docker install` section. + On **Debian**: ```bash @@ -142,7 +145,35 @@ mkdir media # Actually start Plume cargo run + +## Docker install + +You can use Docker and docker-compose in order to manage your Plume instance and +have it isolated from your host: + ``` +git clone git@github.com:Plume-org/Plume.git +cd Plume +cp docs/docker-compose.sample.yml docker-compose.yml +cp docs/docker.sample.env .env +# build the containers +docker-compose build +# launch the database +docker-compose up -d postgres +# run the migrations +docker-compose run --rm plume diesel migration run +# run interactive setup +docker-compose run --rm plume bash +cargo run +# copy the env file and paste it in your host .env file +cat .env +# leave the container +exit +# launch your instance for good +docker-compose up -d +``` + +Then, you can configure your reverse proxy. ## Configuring Nginx diff --git a/docs/docker-compose.sample.yml b/docs/docker-compose.sample.yml new file mode 100644 index 00000000..65150e5a --- /dev/null +++ b/docs/docker-compose.sample.yml @@ -0,0 +1,18 @@ +version: '3' + +services: + postgres: + image: postgres:10.5 + env_file: .env + restart: unless-stopped + volumes: + - "./data/postgres:/var/lib/postgresql/data" + plume: + build: . + env_file: .env + restart: unless-stopped + volumes: + - "./data/plume/static/media:/app/media" + - "./.env:/app/.env" + ports: + - "127.0.0.1:7878:7878" diff --git a/docs/docker.sample.env b/docs/docker.sample.env new file mode 100644 index 00000000..97085734 --- /dev/null +++ b/docs/docker.sample.env @@ -0,0 +1,12 @@ +BASE_URL=yourdomain.com +# generate one with openssl rand -base64 45 +ROCKET_SECRET_KEY=randomstringhere + +# you can safely leave those defaults +POSTGRES_USER=plume +POSTGRES_PASSWORD=plume +DB_URL=postgres://plume:plume@postgres:5432/plume +DATABASE_URL=postgres://plume:plume@postgres:5432/plume +USE_HTTPS=1 +ROCKET_ADDRESS=0.0.0.0 +ROCKET_PORT=7878 From dcebc4653a4d6ebee20408baf342d6aa469f0038 Mon Sep 17 00:00:00 2001 From: Bat Date: Tue, 4 Sep 2018 11:37:58 +0100 Subject: [PATCH 02/77] Make it possible to unfollow someone I was sure it was already implemented, but it looks like it wasn't --- .../down.sql | 2 + .../up.sql | 2 + plume-models/src/follows.rs | 54 +++++++++++++++++-- plume-models/src/schema.rs | 1 + src/routes/user.rs | 26 ++++----- 5 files changed, 67 insertions(+), 18 deletions(-) create mode 100644 migrations/2018-09-04-103017_follows_add_ap_url/down.sql create mode 100644 migrations/2018-09-04-103017_follows_add_ap_url/up.sql 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/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/schema.rs b/plume-models/src/schema.rs index 6a625bb8..1671bf72 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, } } diff --git a/src/routes/user.rs b/src/routes/user.rs index ae3eb6d1..cd50ffc5 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -116,20 +116,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)) } From e508eada2686d8555a20a64bd4229af7f4f06cc0 Mon Sep 17 00:00:00 2001 From: Bat Date: Tue, 4 Sep 2018 11:39:24 +0100 Subject: [PATCH 03/77] Federate follow deletion --- src/inbox.rs | 4 ++++ 1 file changed, 4 insertions(+) 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)? } From 7653551d570d13932676735020b737e0b3db9ccb Mon Sep 17 00:00:00 2001 From: Bat Date: Tue, 4 Sep 2018 11:45:41 +0100 Subject: [PATCH 04/77] Fix some compilation errors --- src/routes/user.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/routes/user.rs b/src/routes/user.rs index cd50ffc5..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), }); } })); From 94a386ea2cd166f9ff1539cac89154462ee31af1 Mon Sep 17 00:00:00 2001 From: Bat Date: Tue, 4 Sep 2018 12:26:13 +0100 Subject: [PATCH 05/77] Add subtitles to articles Fix #152 --- .../2018-09-04-104828_posts_add_subtitle/down.sql | 2 ++ .../2018-09-04-104828_posts_add_subtitle/up.sql | 2 ++ plume-models/src/posts.rs | 12 ++++++++---- plume-models/src/schema.rs | 1 + po/de.po | 4 ++++ po/en.po | 3 +++ po/fr.po | 8 +++++++- po/gl.po | 7 ++++++- po/nb.po | 4 ++++ po/pl.po | 4 ++++ po/plume.pot | 3 +++ src/routes/posts.rs | 4 +++- static/main.css | 7 +++++++ templates/macros.html.tera | 8 +++++++- templates/posts/details.html.tera | 1 + templates/posts/new.html.tera | 1 + 16 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 migrations/2018-09-04-104828_posts_add_subtitle/down.sql create mode 100644 migrations/2018-09-04-104828_posts_add_subtitle/up.sql 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/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 1671bf72..7214871e 100644 --- a/plume-models/src/schema.rs +++ b/plume-models/src/schema.rs @@ -125,6 +125,7 @@ table! { license -> Varchar, creation_date -> Timestamp, ap_url -> Varchar, + subtitle -> Text, } } diff --git a/po/de.po b/po/de.po index e177a94a..cf8bd3d9 100644 --- a/po/de.po +++ b/po/de.po @@ -508,5 +508,9 @@ msgid "" "Sorry, but registrations are closed on this instance. Try to find another one" msgstr "" +#, fuzzy +msgid "Subtitle" +msgstr "Titel" + #~ 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..08d76670 100644 --- a/po/en.po +++ b/po/en.po @@ -497,3 +497,6 @@ msgstr "" msgid "" "Sorry, but registrations are closed on this instance. Try to find another one" msgstr "" + +msgid "Subtitle" +msgstr "" diff --git a/po/fr.po b/po/fr.po index e1f3d757..b9aeecd7 100644 --- a/po/fr.po +++ b/po/fr.po @@ -503,4 +503,10 @@ 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" diff --git a/po/gl.po b/po/gl.po index 7f68eef0..5c1eafbb 100644 --- a/po/gl.po +++ b/po/gl.po @@ -496,4 +496,9 @@ 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" diff --git a/po/nb.po b/po/nb.po index 275ac691..07257571 100644 --- a/po/nb.po +++ b/po/nb.po @@ -512,6 +512,10 @@ msgid "" "Sorry, but registrations are closed on this instance. Try to find another one" msgstr "" +#, fuzzy +msgid "Subtitle" +msgstr "Tittel" + #~ msgid "One reshare" #~ msgid_plural "{{ count }} reshares" #~ msgstr[0] "Én deling" diff --git a/po/pl.po b/po/pl.po index 0b879eca..be9d2790 100644 --- a/po/pl.po +++ b/po/pl.po @@ -523,6 +523,10 @@ msgid "" "Sorry, but registrations are closed on this instance. Try to find another one" msgstr "" +#, fuzzy +msgid "Subtitle" +msgstr "Tytuł" + #~ msgid "One reshare" #~ msgid_plural "{{ count }} reshares" #~ msgstr[0] "Jedno udostępnienie" diff --git a/po/plume.pot b/po/plume.pot index ca6c8851..28d6ef68 100644 --- a/po/plume.pot +++ b/po/plume.pot @@ -486,3 +486,6 @@ msgstr "" msgid "Sorry, but registrations are closed on this instance. Try to find another one" msgstr "" + +msgid "Subtitle" +msgstr "" diff --git a/src/routes/posts.rs b/src/routes/posts.rs index 44376b89..678b2623 100644 --- a/src/routes/posts.rs +++ b/src/routes/posts.rs @@ -98,6 +98,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 +151,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/static/main.css b/static/main.css index 58521bd5..5aa1c0f4 100644 --- a/static/main.css +++ b/static/main.css @@ -131,6 +131,13 @@ main h2 { font-weight: 300; } +main h2.article { + max-width: 40rem; + margin: 0.5em auto 1em; + color: rgba(36, 36, 36, 0.6); + font-size: 1.25em; +} + /* * == Article == */ diff --git a/templates/macros.html.tera b/templates/macros.html.tera index defe0137..e1a7e989 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 e7f3dd08..39cfdca5 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 }}

{{ 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 %} From c0174a310c511fb312315de0d283e90d24f82e02 Mon Sep 17 00:00:00 2001 From: Bat Date: Tue, 4 Sep 2018 12:36:26 +0100 Subject: [PATCH 06/77] Add a forgotten " in template --- templates/base.html.tera | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/base.html.tera b/templates/base.html.tera index d65f9366..398857b0 100644 --- a/templates/base.html.tera +++ b/templates/base.html.tera @@ -30,7 +30,7 @@ {{ "Notifications" | _ }} - + {{ "Log Out" | _ }} From dbdc9f317e7f222e2d301ffc9baad3b2e33fa816 Mon Sep 17 00:00:00 2001 From: Bat Date: Tue, 4 Sep 2018 13:46:08 +0100 Subject: [PATCH 07/77] Show like/boost counters even when not logged in --- po/de.po | 6 ++++++ po/en.po | 6 ++++++ po/fr.po | 6 ++++++ po/gl.po | 6 ++++++ po/nb.po | 6 ++++++ po/pl.po | 6 ++++++ po/plume.pot | 6 ++++++ static/main.css | 32 +++++++++++++++---------------- templates/posts/details.html.tera | 19 ++++++++++++++---- 9 files changed, 73 insertions(+), 20 deletions(-) diff --git a/po/de.po b/po/de.po index cf8bd3d9..0fa6aa0d 100644 --- a/po/de.po +++ b/po/de.po @@ -512,5 +512,11 @@ msgstr "" 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 08d76670..5a2718e8 100644 --- a/po/en.po +++ b/po/en.po @@ -500,3 +500,9 @@ 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 b9aeecd7..686c24d4 100644 --- a/po/fr.po +++ b/po/fr.po @@ -510,3 +510,9 @@ msgstr "" #, 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 5c1eafbb..2d4597fd 100644 --- a/po/gl.po +++ b/po/gl.po @@ -502,3 +502,9 @@ msgstr "" #, 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 07257571..3e0019d2 100644 --- a/po/nb.po +++ b/po/nb.po @@ -516,6 +516,12 @@ msgstr "" 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 be9d2790..7955c549 100644 --- a/po/pl.po +++ b/po/pl.po @@ -527,6 +527,12 @@ msgstr "" msgid "Subtitle" msgstr "Tytuł" +msgid "Login to like" +msgstr "" + +msgid "Login to boost" +msgstr "" + #~ msgid "One reshare" #~ msgid_plural "{{ count }} reshares" #~ msgstr[0] "Jedno udostępnienie" diff --git a/po/plume.pot b/po/plume.pot index 28d6ef68..9c0954b1 100644 --- a/po/plume.pot +++ b/po/plume.pot @@ -489,3 +489,9 @@ msgstr "" msgid "Subtitle" msgstr "" + +msgid "Login to like" +msgstr "" + +msgid "Login to boost" +msgstr "" diff --git a/static/main.css b/static/main.css index 5aa1c0f4..0e65fa79 100644 --- a/static/main.css +++ b/static/main.css @@ -207,8 +207,8 @@ main .article-meta .reshares > p { 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; @@ -221,12 +221,12 @@ main .article-meta .reshares button { } 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 i, -main .article-meta .reshares button i { +main .article-meta .likes .action i, +main .article-meta .reshares .action i { transition: background 0.1s ease-in; display: flex; align-items: center; @@ -239,37 +239,37 @@ main .article-meta .reshares button i { border-radius: 50%; } -main .article-meta .likes button i { +main .article-meta .likes .action i { color: #E92F2F; border: solid #E92F2F thin; font-weight: 400; } -main .article-meta .likes button:hover i { +main .article-meta .likes .action:hover i { 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 i { background: #E92F2F; } -main .article-meta .likes button.liked:hover i { +main .article-meta .likes .action.liked i { background: #E92F2F; } +main .article-meta .likes .action.liked:hover i { 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 i, -main .article-meta .reshares button.reshared i { +main .article-meta .likes .action.liked i, +main .article-meta .reshares .action.reshared i { color: #F4F4F4; font-weight: 900; } diff --git a/templates/posts/details.html.tera b/templates/posts/details.html.tera index 39cfdca5..cae86e50 100644 --- a/templates/posts/details.html.tera +++ b/templates/posts/details.html.tera @@ -41,23 +41,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" | _ }}

+
{% endif %}
From d60289aac8797fdad013c451ede0edbe471052f0 Mon Sep 17 00:00:00 2001 From: Bat Date: Tue, 4 Sep 2018 14:02:01 +0100 Subject: [PATCH 08/77] Local instance ID is 1, not 0 This prevented local users from being removed before broadcasting an activity. Fixes #113 --- plume-models/src/users.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 } } From 3b2d69893d3b082856fd1252367efa9af20c9c61 Mon Sep 17 00:00:00 2001 From: Bat Date: Tue, 4 Sep 2018 14:32:20 +0100 Subject: [PATCH 09/77] Better style for code blocks --- static/main.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/static/main.css b/static/main.css index 0e65fa79..b1d72d6e 100644 --- a/static/main.css +++ b/static/main.css @@ -167,6 +167,13 @@ article img { max-width: 100%; } +article pre { + background: #DADADA; + padding: 1em; + overflow: auto; + border-radius: 5px; +} + /* Article.Meta */ main .article-meta, main .article-meta button { From dd0663348ee09673dd0c6d7cbffd5a5fd94b4862 Mon Sep 17 00:00:00 2001 From: Bat Date: Tue, 4 Sep 2018 14:39:46 +0100 Subject: [PATCH 10/77] Better image styling --- static/main.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/static/main.css b/static/main.css index b1d72d6e..c30b313e 100644 --- a/static/main.css +++ b/static/main.css @@ -164,7 +164,9 @@ main article { } article img { - max-width: 100%; + max-width: 100%; + margin: 3em auto; + display: block; } article pre { From bba9a563298102a85c8a5afdfc2964366fd1114e Mon Sep 17 00:00:00 2001 From: Bat Date: Tue, 4 Sep 2018 14:55:15 +0100 Subject: [PATCH 11/77] Add author biography at the bottom of the post --- src/routes/posts.rs | 3 ++- static/main.css | 2 +- templates/posts/details.html.tera | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/routes/posts.rs b/src/routes/posts.rs index 678b2623..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) })) }) }) diff --git a/static/main.css b/static/main.css index c30b313e..c91cb76f 100644 --- a/static/main.css +++ b/static/main.css @@ -778,5 +778,5 @@ figcaption { } .avatar.padded { - margin-right: 1em; + margin-right: 2rem; } diff --git a/templates/posts/details.html.tera b/templates/posts/details.html.tera index cae86e50..ea8280c9 100644 --- a/templates/posts/details.html.tera +++ b/templates/posts/details.html.tera @@ -34,6 +34,20 @@ From adcfd8847168f265c057816a9f030ecff6e81eeb Mon Sep 17 00:00:00 2001 From: Bat Date: Tue, 4 Sep 2018 18:51:08 +0100 Subject: [PATCH 14/77] Document env vars --- docs/ENV-VARS.md | 22 ++++++++++++++++++++++ docs/README.md | 1 + 2 files changed, 23 insertions(+) create mode 100644 docs/ENV-VARS.md 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) From 4330600980e2058c42e1a2318223837eeeccd921 Mon Sep 17 00:00:00 2001 From: Madeorsk Date: Tue, 4 Sep 2018 21:16:36 +0200 Subject: [PATCH 15/77] Show and animate .mobile-label on normal screens. --- static/css/main.css | 1257 ++++++++++++++++++++++--------------------- 1 file changed, 640 insertions(+), 617 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index d3c7f988..1eb14d86 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -8,770 +8,793 @@ * == Global == */ -html, body { - margin: 0; - padding: 0; - background: #F4F4F4; - color: #242424; - font-family: "Route159", serif; -} + html, body { + margin: 0; + padding: 0; + background: #F4F4F4; + color: #242424; + font-family: "Route159", serif; + } -a, a:visited { - color: #7765E3; - text-decoration: none; - outline: none; -} + a, a:visited { + color: #7765E3; + text-decoration: none; + outline: none; + } -small { - margin-left: 1em; - color: rgba(36, 36, 36, 0.6); - font-size: 0.75em; - word-wrap: break-word; - word-break: break-all; -} + small { + margin-left: 1em; + color: rgba(36, 36, 36, 0.6); + font-size: 0.75em; + word-wrap: break-word; + word-break: break-all; + } -.center { - text-align: center; - font-weight: bold; - opacity: 0.6; - padding: 5em; -} + .center { + text-align: center; + font-weight: bold; + opacity: 0.6; + padding: 5em; + } -.spaced { - margin: 4rem 0; -} + .spaced { + margin: 4rem 0; + } -.banner { - background: #DADADA; - padding-top: 2em; - padding-bottom: 1em; - margin: 3em 0px; -} + .banner { + background: #DADADA; + padding-top: 2em; + padding-bottom: 1em; + margin: 3em 0px; + } /* * == Header == */ -header { - background: #ECECEC; -} -header #content { - display: flex; - align-content: center; - justify-content: space-between; -} + header { + background: #ECECEC; + } + header #content { + display: flex; + align-content: center; + justify-content: space-between; + } -header nav#menu { - position: relative; - display: none; - transform: skewX(-15deg); - left: -1em; - padding: 1em 1em 1em 2em; - background: #7765E3; - align-self: flex-start; -} -header nav#menu a { - transform: skewX(15deg); - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - width: 1.4em; - height: 1.4em; - margin: 0; - padding: 0; - color: #ECECEC; - font-size: 1.33em; -} + header nav#menu { + position: relative; + display: none; + transform: skewX(-15deg); + left: -1em; + padding: 1em 1em 1em 2em; + background: #7765E3; + align-self: flex-start; + } + header nav#menu a { + transform: skewX(15deg); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 1.4em; + height: 1.4em; + margin: 0; + padding: 0; + color: #ECECEC; + font-size: 1.33em; + } -header nav { - display: flex; - flex-direction: row; - align-items: center; -} -header nav hr { - height: 100%; - width: 0.2em; - background: #7765E3; - border: none; - transform: skewX(-15deg); -} -header nav a { - display: block; - margin: 0 2em; - font-size: 1em; -} -header nav a.title { - margin: 0; - padding: 0.5em 2em; - font-size: 1.75em; - text-align: center; -} -header nav i { - font-size: 1.2em; -} + header nav { + display: flex; + flex-direction: row; + align-items: center; + } + header nav hr { + height: 100%; + width: 0.2em; + background: #7765E3; + border: none; + transform: skewX(-15deg); + } + header nav a { + display: flex; + align-items: center; + position: relative; + align-self: stretch; + margin: 0; + padding: 0 2em; + font-size: 1em; + } + header nav a.title { + margin: 0; + padding: 0.5em 2em; + font-size: 1.75em; + text-align: center; + } + header nav a i { font-size: 1.2em; } + + /* Only enable label animations on normal screens. */ + @media screen and (min-width: 900px) { + header nav a i { + transition: all 0.2s ease; + margin: 0; + } + + header nav a:hover i { margin-bottom: 0.75em; } + + header nav a .mobile-label { + transition: all 0.2s ease; + display: block; + position: absolute; + left: 50%; + transform: translate(-50%, 0); + opacity: 0; + font-size: 0.9em; + white-space: nowrap; + } + header nav a:hover .mobile-label { + opacity: 1; + transform: translate(-50%, 80%); + } + header nav a img + .mobile-label { display: none; } + } /* * == Main == */ -body > main > * { - padding: 0 20%; -} + body > main > * { + padding: 0 20%; + } -main h1 { - font-family: "Route159"; - font-size: 2.5em; - font-weight: 300; -} -main h1.article { - max-width: 40rem; - margin: 1em auto 0.5em; - font-family: "Playfair Display", serif; - font-size: 2.5em; - font-weight: normal; -} + main h1 { + font-family: "Route159"; + font-size: 2.5em; + font-weight: 300; + } + main h1.article { + max-width: 40rem; + margin: 1em auto 0.5em; + font-family: "Playfair Display", serif; + font-size: 2.5em; + font-weight: normal; + } -main h2 { - font-family: "Route159"; - font-size: 1.75em; - font-weight: 300; -} + main h2 { + font-family: "Route159"; + font-size: 1.75em; + font-weight: 300; + } /* * == Article == */ -/* Author */ + /* Author */ -main .article-info { - max-width: 40rem; - margin: 0 auto 3em; - font-size: 0.95em; - font-weight: 400; -} -main .article-info .author { font-weight: 600; } -main .article-info .author a { font-weight: 600; } + main .article-info { + max-width: 40rem; + margin: 0 auto 3em; + font-size: 0.95em; + font-weight: 400; + } + main .article-info .author { font-weight: 600; } + main .article-info .author a { font-weight: 600; } -/* Article */ + /* Article */ -main article { - max-width: 40rem; - margin: 2.5em auto; - font-family: "Lora", serif; - font-size: 1.2em; - line-height: 1.7em; -} + main article { + max-width: 40rem; + margin: 2.5em auto; + font-family: "Lora", serif; + font-size: 1.2em; + line-height: 1.7em; + } -article img { - max-width: 100%; -} + article img { + max-width: 100%; + } -/* Article.Meta */ + /* Article.Meta */ -main .article-meta, main .article-meta button { - padding: 0; - font-size: 1.1em; - margin-top: 10%; -} -main .article-meta > * { margin: 0 20%; } + main .article-meta, main .article-meta button { + padding: 0; + font-size: 1.1em; + margin-top: 10%; + } + main .article-meta > * { margin: 0 20%; } -main .article-meta > p { - margin: 2em 20%; - font-size: 0.9em; -} + main .article-meta > p { + margin: 2em 20%; + font-size: 0.9em; + } -/* ~ Likes ~ */ + /* ~ Likes ~ */ -main .article-meta .likes p, -main .article-meta .reshares p { display: inline-block; margin: 0; } + main .article-meta .likes p, + main .article-meta .reshares p { display: inline-block; margin: 0; } -/* Like / Reshare button */ + /* Like / Reshare button */ -main .article-meta .actions { - display: flex; - flex-direction: row; - justify-content: space-around; -} + main .article-meta .actions { + display: flex; + flex-direction: row; + justify-content: space-around; + } -main .article-meta .likes, -main .article-meta .reshares { - display: flex; - flex-direction: column; - align-items: center; - padding: 0.5em 0; -} + main .article-meta .likes, + main .article-meta .reshares { + display: flex; + flex-direction: column; + align-items: center; + padding: 0.5em 0; + } -main .article-meta .likes > p, -main .article-meta .reshares > p { - font-size: 1.5em; -} + main .article-meta .likes > p, + main .article-meta .reshares > p { + font-size: 1.5em; + } -main .article-meta .likes button, -main .article-meta .reshares button { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - margin: 0; - padding: 0; - background: none; - color: #242424; - border: none; -} + main .article-meta .likes button, + main .article-meta .reshares button { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + margin: 0; + padding: 0; + background: none; + color: #242424; + border: none; + } -main .article-meta .likes > p, -main .article-meta .likes button:hover { color: #E92F2F; } -main .article-meta .reshares > p, -main .article-meta .reshares button:hover { color: #7765E3; } + main .article-meta .likes > p, + main .article-meta .likes button:hover { color: #E92F2F; } + main .article-meta .reshares > p, + main .article-meta .reshares button:hover { color: #7765E3; } -main .article-meta .likes button svg.feather, -main .article-meta .reshares button i { - transition: background 0.1s ease-in; - display: flex; - align-items: center; - justify-content: center; + main .article-meta .likes button svg.feather, + main .article-meta .reshares button i { + transition: background 0.1s ease-in; + display: flex; + align-items: center; + justify-content: center; - margin: 0.5em 0; - width: 2.5em; - height: 2.5em; + margin: 0.5em 0; + width: 2.5em; + height: 2.5em; - border-radius: 50%; -} + border-radius: 50%; + } -main .article-meta .likes button 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 { - background: rgba(233, 47, 47, 0.15); -} + main .article-meta .likes button 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 { + background: rgba(233, 47, 47, 0.15); + } -main .article-meta .reshares button i { - color: #7765E3; - border: solid #7765E3 thin; - font-weight: 600; -} -main .article-meta .reshares button:hover i { - background: rgba(119, 101, 227, 0.15); -} + main .article-meta .reshares button i { + color: #7765E3; + border: solid #7765E3 thin; + font-weight: 600; + } + main .article-meta .reshares button: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 { - 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 { - background: rgba(119, 101, 227, 0.25); - color: #7765E3; -} + main .article-meta .likes button.liked svg.feather { background: #E92F2F; fill: currentColor; } + main .article-meta .likes button.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 { + background: rgba(119, 101, 227, 0.25); + color: #7765E3; + } -main .article-meta .likes button.liked svg.feather, -main .article-meta .reshares button.reshared i { - color: #F4F4F4; - font-weight: 900; -} + main .article-meta .likes button.liked svg.feather, + main .article-meta .reshares button.reshared i { + color: #F4F4F4; + font-weight: 900; + } -/* ~ Comments ~ */ + /* ~ Comments ~ */ -main .article-meta .comments { margin: 0; } -main .article-meta .comments > * { margin-left: 20%; margin-right: 20%; } + main .article-meta .comments { margin: 0; } + main .article-meta .comments > * { margin-left: 20%; margin-right: 20%; } -.comments h2 { - color: #7765E3; - font-size: 1.5em; - font-weight: 600; -} + .comments h2 { + color: #7765E3; + font-size: 1.5em; + font-weight: 600; + } -/* New comment */ + /* New comment */ -main .article-meta .comments form input[type="submit"] -{ font-size: 1em; } + main .article-meta .comments form input[type="submit"] + { font-size: 1em; } -/* Comment / Respond button */ + /* Comment / Respond button */ -main .article-meta .comments a.button:before { - color: #7765E3; - padding: 0.5em; -} -main .article-meta .comments a.button { - display: inline-block; - padding: 0; - background: none; - color: #242424; - border: none; -} -main .article-meta .comments a.button:hover { color: #7765E3; } + main .article-meta .comments a.button:before { + color: #7765E3; + padding: 0.5em; + } + main .article-meta .comments a.button { + display: inline-block; + padding: 0; + background: none; + color: #242424; + border: none; + } + main .article-meta .comments a.button:hover { color: #7765E3; } -/* Comment button only */ -main .article-meta .comments > a.button { margin-bottom: 1em; } + /* Comment button only */ + main .article-meta .comments > a.button { margin-bottom: 1em; } -/* == List == */ + /* == List == */ -main .article-meta .comments .list { - display: grid; - margin: 0; - padding: 0 20%; - background: #ECECEC; -} + main .article-meta .comments .list { + display: grid; + margin: 0; + padding: 0 20%; + background: #ECECEC; + } -/* ~ Comment ~ */ + /* ~ Comment ~ */ -.comments .list .comment { - padding: 2em; - font-size: 1em; -} + .comments .list .comment { + padding: 2em; + font-size: 1em; + } -.comments .list > .comment { - border: none; -} + .comments .list > .comment { + border: none; + } -.comments .list .comment .author { - display: flex; - flex-direction: row; - align-items: center; - align-content: center; -} -.comments .list .comment .author * { - transition: all 0.1s ease-in; -} -.comments .list .comment .author .display-name { - color: #242424; -} -.comments .list .comment .author:hover .display-name { color: #7765E3; } -.comments .list .comment .author:hover small { opacity: 1; } + .comments .list .comment .author { + display: flex; + flex-direction: row; + align-items: center; + align-content: center; + } + .comments .list .comment .author * { + transition: all 0.1s ease-in; + } + .comments .list .comment .author .display-name { + color: #242424; + } + .comments .list .comment .author:hover .display-name { color: #7765E3; } + .comments .list .comment .author:hover small { opacity: 1; } -.comments .list .comment .text { - padding: 1.25em 0; - font-family: "Lora", serif; - font-size: 1.1em; - line-height: 1.4em; - text-align: left; -} + .comments .list .comment .text { + padding: 1.25em 0; + font-family: "Lora", serif; + font-size: 1.1em; + line-height: 1.4em; + text-align: left; + } /* * == Form == */ -label { - display: block; - margin: 2em auto 1em; - font-size: 1.2em; - max-width: 40rem; -} -input, textarea { - transition: all 0.1s ease-in; - display: block; - width: 100%; - max-width: 40rem; - margin: auto; - padding: 1em; - box-sizing: border-box; + label { + display: block; + margin: 2em auto 1em; + font-size: 1.2em; + max-width: 40rem; + } + input, textarea { + transition: all 0.1s ease-in; + display: block; + width: 100%; + max-width: 40rem; + margin: auto; + padding: 1em; + box-sizing: border-box; - background: #F4F4F4; - color: #242424; - border: none; - border: solid #DADADA thin; - border-radius: 0.5em; + background: #F4F4F4; + color: #242424; + border: none; + border: solid #DADADA thin; + border-radius: 0.5em; - font-size: 1.2em; - font-weight: 400; -} -form input[type="submit"] { margin: 2em auto; } -input:focus, textarea:focus { - background: #FAFAFA; - border-color: #7765E3; -} + font-size: 1.2em; + font-weight: 400; + } + form input[type="submit"] { margin: 2em auto; } + input:focus, textarea:focus { + background: #FAFAFA; + border-color: #7765E3; + } -textarea { + textarea { resize: vertical; font-family: "Lora", serif; font-size: 1.1em; line-height: 1.5em; -} + } -input[type="checkbox"] { - display: inline; - margin: initial; - min-width: initial; - width: initial; -} + input[type="checkbox"] { + display: inline; + margin: initial; + min-width: initial; + width: initial; + } -/* Button & Submit */ + /* Button & Submit */ -.button, input[type="submit"], button { - transition: all 0.1s ease-in; - display: inline-block; + .button, input[type="submit"], button { + transition: all 0.1s ease-in; + display: inline-block; - border-radius: 0.5em; - margin: 0.5em auto; - padding: 0.75em 1em; + border-radius: 0.5em; + margin: 0.5em auto; + padding: 0.75em 1em; - background: transparent; - color: #7765E3; - border: 1px solid #7765E3; + background: transparent; + color: #7765E3; + border: 1px solid #7765E3; - cursor: pointer; -} -input[type="submit"] { display: block; } -.button:hover, input[type="submit"]:hover { - background: #7765E399; - color: white; -} + cursor: pointer; + } + input[type="submit"] { display: block; } + .button:hover, input[type="submit"]:hover { + background: #7765E399; + color: white; + } -/* Errors */ + /* Errors */ -p.error { - color: #ef767a; - font-weight: bold; - max-width: 40rem; - margin: 1em auto; -} + p.error { + color: #ef767a; + font-weight: bold; + max-width: 40rem; + margin: 1em auto; + } /* * == New post == */ -form.new-post .title { - margin: 0 auto; - padding: 0.75em 0; + form.new-post .title { + margin: 0 auto; + padding: 0.75em 0; - background: none; - border: none; + background: none; + border: none; - font-family: "Playfair Display", serif; - font-size: 2em; - text-align: left; -} -form.new-post textarea { - min-height: 20em; - overflow-y: hidden; - resize: none; - box-sizing: content-box; -} -form.new-post input[type="submit"] { - background: #ECECEC; - color: #242424; - border: none; + font-family: "Playfair Display", serif; + font-size: 2em; + text-align: left; + } + form.new-post textarea { + min-height: 20em; + overflow-y: hidden; + resize: none; + box-sizing: content-box; + } + form.new-post input[type="submit"] { + background: #ECECEC; + color: #242424; + border: none; - font-family: "Playfair Display", serif; - font-size: 1.5em; -} -form.new-post input[type="submit"]:hover { background: #DADADA; } + font-family: "Playfair Display", serif; + font-size: 1.5em; + } + form.new-post input[type="submit"]:hover { background: #DADADA; } /* * == User == */ -.user h1 { - display: flex; - flex-direction: row; - align-items: center; - margin-bottom: 0; -} -.badge { - margin-left: 1em; - padding: 0.35em 1em; + .user h1 { + display: flex; + flex-direction: row; + align-items: center; + margin-bottom: 0; + } + .badge { + margin-left: 1em; + padding: 0.35em 1em; - background: #F4F4F4; - color: #7765E3; - border: 1px solid #7765E3; - border-radius: 1em; + background: #F4F4F4; + color: #7765E3; + border: 1px solid #7765E3; + border-radius: 1em; - font-size: 1rem; -} + font-size: 1rem; + } /* * == Blog index == */ -.cards { - display: flex; - flex-direction: row; - flex-wrap: wrap; - padding: 0 5%; -} -.card { - flex: 1; - display: flex; - flex-direction: column; + .cards { + display: flex; + flex-direction: row; + flex-wrap: wrap; + padding: 0 5%; + } + .card { + flex: 1; + display: flex; + flex-direction: column; - min-width: 17.5em; - min-height: 17.5em; - margin: 1em; - padding: 1em; - box-sizing: border-box; + min-width: 17.5em; + min-height: 17.5em; + margin: 1em; + padding: 1em; + box-sizing: border-box; - background: #E3E3E3; + background: #E3E3E3; - text-overflow: ellipsis; -} -.list .card { -/* TODO */ - background: 0; - margin: 2em 0; - padding: 0; - min-height: 0; -} + text-overflow: ellipsis; + } + .list .card { + /* TODO */ + background: 0; + margin: 2em 0; + padding: 0; + min-height: 0; + } -/* ~ Card content ~ */ + /* ~ Card content ~ */ -/* Title */ + /* Title */ -.card h3 { - margin: 0.75em 0; - font-family: "Playfair Display", serif; - font-size: 1.75em; - font-weight: normal; -} -.card h3 a { transition: color 0.1s ease-in; color: #242424; } -.card h3 a:hover { color: #7765E3; } + .card h3 { + margin: 0.75em 0; + font-family: "Playfair Display", serif; + font-size: 1.75em; + font-weight: normal; + } + .card h3 a { transition: color 0.1s ease-in; color: #242424; } + .card h3 a:hover { color: #7765E3; } -/* Content */ + /* Content */ -.card main { - flex: 1; + .card main { + flex: 1; - font-family: "Lora", serif; - font-size: 1em; - line-height: 1.25em; - text-align: left; - overflow: hidden; -} + font-family: "Lora", serif; + font-size: 1em; + line-height: 1.25em; + text-align: left; + overflow: hidden; + } -/* Presentation */ -.presentation > h2, .presentation > a { - text-align: center; -} + /* Presentation */ + .presentation > h2, .presentation > a { + text-align: center; + } -.presentation > a { - font-size: 1.2em; - margin: 1em; -} + .presentation > a { + font-size: 1.2em; + margin: 1em; + } -/* Stats */ -.stats { - display: flex; - justify-content: space-around; - margin: 2em; -} + /* Stats */ + .stats { + display: flex; + justify-content: space-around; + margin: 2em; + } -.stats > div { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} + .stats > div { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } -.stats em { - text-align: center; - font-weight: bold; -} + .stats em { + text-align: center; + font-weight: bold; + } /* ================= * * Small Screens * * ================= */ -.mobile-label { - display: none; -} + @media screen and (max-width: 900px) { + header { + flex-direction: column; + } -@media screen and (max-width: 900px) { - header { - flex-direction: column; - } + header nav#menu { + display: inline-flex; + } - header nav#menu { - display: inline-flex; - } + header #content { + display: none; + text-align: center; + } - header #content { - display: none; - text-align: center; - } + @keyframes menuOpening { + from { + transform: scaleX(0); + transform-origin: left; + opacity: 0; + } - @keyframes menuOpening { - from { - transform: scaleX(0); - transform-origin: left; - opacity: 0; - } + to { + transform: scaleX(1); + transform-origin: left; + opacity: 1; + } + } - to { - transform: scaleX(1); - transform-origin: left; - opacity: 1; - } - } + header:focus-within #content { + position: fixed; + display: flex; + flex-direction: column; + justify-content: flex-start; - header:focus-within #content { - position: fixed; - display: flex; - flex-direction: column; - justify-content: flex-start; + top: 0; + left: 0; + width: 100%; + height: 100%; + box-sizing: border-box; - top: 0; - left: 0; - width: 100%; - height: 100%; - box-sizing: border-box; + animation: 0.2s menuOpening; + } + header:focus-within #content::before { + content: ""; + position: absolute; + transform: skewX(-10deg); + top: 0; + left: -20%; + width: 100%; + height: 100%; - animation: 0.2s menuOpening; - } - header:focus-within #content::before { - content: ""; - position: absolute; - transform: skewX(-10deg); - top: 0; - left: -20%; - width: 100%; - height: 100%; + z-index: -10; - z-index: -10; + background: #7765E3; + } + header:focus-within #content > nav { + flex-direction: column; + align-items: flex-start; + } + header:focus-within #content > nav a { + display: flex; + flex-direction: row; + align-items: center; + margin: 0; + padding: 1rem 1.5rem; + color: #F4F4F4; + font-size: 1.4em; + font-weight: 300; + } + header:focus-within #content > nav a > *:first-child { width: 3rem; } + header:focus-within #content > nav a > img:first-child { height: 3rem; } + header:focus-within #content > nav a > *:last-child { margin-left: 1rem; } + header:focus-within #content > nav a.title { + font-size: 1.8em; + } + header:focus-within #content > nav hr { + display: block; + margin: 0; + width: 100%; + border: solid #F4F4F4 0.1rem; + } + header:focus-within #content > nav a .mobile-label { + display: initial; + } - background: #7765E3; - } - header:focus-within #content > nav { - flex-direction: column; - align-items: flex-start; - } - header:focus-within #content > nav a { - display: flex; - flex-direction: row; - align-items: center; - margin: 0; - padding: 1rem 1.5rem; - color: #F4F4F4; - font-size: 1.4em; - font-weight: 300; - } - header:focus-within #content > nav a > *:first-child { width: 3rem; } - header:focus-within #content > nav a > img:first-child { height: 3rem; } - header:focus-within #content > nav a > *:last-child { margin-left: 1rem; } - header:focus-within #content > nav a.title { - font-size: 1.8em; - } - header:focus-within #content > nav hr { - display: block; - margin: 0; - width: 100%; - border: solid #F4F4F4 0.1rem; - } + body > main > * { + padding: 0 5%; + } + main .article-meta > * { margin: 0 5%; } - body > main > * { - padding: 0 5%; - } - main .article-meta > * { margin: 0 5%; } + main .article-meta > p { + margin: 2em 5%; + font-size: 0.9em; + } + main .article-meta .comments > * { margin-left: 5%; margin-right: 5%; } - main .article-meta > p { - margin: 2em 5%; - font-size: 0.9em; - } - main .article-meta .comments > * { margin-left: 5%; margin-right: 5%; } + .card { + min-width: 80%; + min-height: 80%; + } + } - .card { - min-width: 80%; - min-height: 80%; - } + /*== Pagination ==*/ + .pagination { + display: flex; + justify-content: space-evenly; + } - .mobile-label { - display: initial; - } -} + .pagination > * { + padding: 2em; + } -/*== Pagination ==*/ -.pagination { - display: flex; - justify-content: space-evenly; -} + /*== Flex boxes ==*/ + .flex { + display: flex; + flex-direction: row; + align-items: center; + } -.pagination > * { - padding: 2em; -} + .flex .grow { + flex: 1; + } -/*== Flex boxes ==*/ -.flex { - display: flex; - flex-direction: row; - align-items: center; -} + .left-icon { + align-self: center; + padding: 1em; + background: #DADADA; + border-radius: 50px; + margin: 1em; + margin-right: 2em; + } -.flex .grow { - flex: 1; -} + /*== Footer ==*/ + body > footer { + display: flex; + align-content: center; + justify-content: space-between; + background: #ECECEC; + padding: 0 20%; + margin-top: 5em; + } -.left-icon { - align-self: center; - padding: 1em; - background: #DADADA; - border-radius: 50px; - margin: 1em; - margin-right: 2em; -} + body > footer * { + margin: 5em 0; + } -/*== Footer ==*/ -body > footer { - display: flex; - align-content: center; - justify-content: space-between; - background: #ECECEC; - padding: 0 20%; - margin-top: 5em; -} + /** Medias **/ -body > footer * { - margin: 5em 0; -} + figure { + text-align: center; + margin: 2em; + max-width: 100%; + width: auto; + height: auto; + } -/** Medias **/ + figure > * { + max-width: 100%; + } -figure { - text-align: center; - margin: 2em; - max-width: 100%; - width: auto; - height: auto; -} + figcaption { + padding: 1em; + } -figure > * { - max-width: 100%; -} + .preview { + display: block; + max-width: 100px; + max-height: 100px; + width: auto; + height: auto; + margin-right: 20px; + } -figcaption { - padding: 1em; -} + /** Avatars **/ -.preview { - display: block; - max-width: 100px; - max-height: 100px; - width: auto; - height: auto; - margin-right: 20px; -} + .avatar { + border-radius: 100%; + } -/** Avatars **/ + .avatar.small { + width: 50px; + height: 50px; + } -.avatar { - border-radius: 100%; -} + .avatar.medium { + width: 100px; + height: 100px; + margin: 20px; + } -.avatar.small { - width: 50px; - height: 50px; -} - -.avatar.medium { - width: 100px; - height: 100px; - margin: 20px; -} - -.avatar.padded { - margin-right: 1em; -} + .avatar.padded { + margin-right: 1em; + } From babb3a81f52c7c566683e596e10ed3b12defb301 Mon Sep 17 00:00:00 2001 From: Bat Date: Tue, 4 Sep 2018 20:56:27 +0100 Subject: [PATCH 16/77] Local timeline --- plume-models/src/posts.rs | 15 +++++++++++++++ src/main.rs | 2 ++ src/routes/instance.rs | 18 ++++++++++++++++++ templates/instance/local.html.tera | 17 +++++++++++++++++ 4 files changed, 52 insertions(+) create mode 100644 templates/instance/local.html.tera diff --git a/plume-models/src/posts.rs b/plume-models/src/posts.rs index 73c8c985..53051422 100644 --- a/plume-models/src/posts.rs +++ b/plume-models/src/posts.rs @@ -113,6 +113,7 @@ impl Post { .expect("Error loading a page of posts for blog") } + /// Give a page of all the recent posts known to this instance (= federated timeline) pub fn get_recents_page(conn: &PgConnection, (min, max): (i32, i32)) -> Vec { posts::table.order(posts::creation_date.desc()) .offset(min.into()) @@ -121,6 +122,20 @@ impl Post { .expect("Error loading recent posts page") } + /// Give a page of posts from a specific instance + pub fn get_instance_page(conn: &PgConnection, instance_id: i32, (min, max): (i32, i32)) -> Vec { + use schema::blogs; + + let blog_ids = blogs::table.filter(blogs::instance_id.eq(instance_id)).select(blogs::id); + + posts::table.order(posts::creation_date.desc()) + .filter(posts::blog_id.eq(any(blog_ids))) + .offset(min.into()) + .limit((max - min).into()) + .load::(conn) + .expect("Error loading local posts page") + } + pub fn get_authors(&self, conn: &PgConnection) -> Vec { use schema::users; use schema::post_authors; diff --git a/src/main.rs b/src/main.rs index f0f9fcef..c00c4570 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,6 +57,8 @@ fn main() { routes::instance::paginated_index, routes::instance::index, + routes::instance::paginated_local, + routes::instance::local, routes::instance::admin, routes::instance::update_settings, routes::instance::shared_inbox, diff --git a/src/routes/instance.rs b/src/routes/instance.rs index cbf710ca..159fc6cd 100644 --- a/src/routes/instance.rs +++ b/src/routes/instance.rs @@ -44,6 +44,24 @@ fn index(conn: DbConn, user: Option) -> Template { paginated_index(conn, user, Page::first()) } +#[get("/local?")] +fn paginated_local(conn: DbConn, user: Option, page: Page) -> Template { + let instance = Instance::get_local(&*conn).unwrap(); + let articles = Post::get_instance_page(&*conn, instance.id, page.limits()); + Template::render("instance/local", json!({ + "account": user.map(|u| u.to_json(&*conn)), + "instance": instance, + "page": page.page, + "n_pages": Page::total(Post::count_local(&*conn) as i32), + "articles": articles.into_iter().map(|p| p.to_json(&*conn)).collect::>() + })) +} + +#[get("/local")] +fn local(conn: DbConn, user: Option) -> Template { + paginated_local(conn, user, Page::first()) +} + #[get("/admin")] fn admin(conn: DbConn, admin: Admin) -> Template { Template::render("instance/admin", json!({ diff --git a/templates/instance/local.html.tera b/templates/instance/local.html.tera new file mode 100644 index 00000000..9c550f4e --- /dev/null +++ b/templates/instance/local.html.tera @@ -0,0 +1,17 @@ +{% extends "base" %} +{% import "macros" as macros %} + +{% block title %} +{{ "Articles from {{ instance.name }}" | _(instance=instance) }} +{% endblock title %} + +{% block content %} +

{{ "Articles from {{ instance.name }}" | _(instance=instance) }}

+ +
+ {% for article in articles %} + {{ macros::post_card(article=article) }} + {% endfor %} +
+ {{ macros::paginate(page=page, total=n_pages) }} +{% endblock content %} From 1496598a45b5768b046942f652798b9593921473 Mon Sep 17 00:00:00 2001 From: Bat Date: Wed, 5 Sep 2018 15:21:50 +0100 Subject: [PATCH 17/77] User feed --- plume-models/src/posts.rs | 14 ++++++++++++++ src/main.rs | 2 ++ src/routes/instance.rs | 17 +++++++++++++++++ templates/instance/feed.html.tera | 23 +++++++++++++++++++++++ 4 files changed, 56 insertions(+) create mode 100644 templates/instance/feed.html.tera diff --git a/plume-models/src/posts.rs b/plume-models/src/posts.rs index 53051422..c826f766 100644 --- a/plume-models/src/posts.rs +++ b/plume-models/src/posts.rs @@ -136,6 +136,20 @@ impl Post { .expect("Error loading local posts page") } + /// Give a page of customized user feed, based on a list of followed users + pub fn user_feed_page(conn: &PgConnection, followed: Vec, (min, max): (i32, i32)) -> Vec { + use schema::post_authors; + let post_ids = post_authors::table.filter(post_authors::author_id.eq(any(followed))) + .select(post_authors::post_id); + + posts::table.order(posts::creation_date.desc()) + .filter(posts::id.eq(any(post_ids))) + .offset(min.into()) + .limit((max - min).into()) + .load::(conn) + .expect("Error loading user feed page") + } + pub fn get_authors(&self, conn: &PgConnection) -> Vec { use schema::users; use schema::post_authors; diff --git a/src/main.rs b/src/main.rs index c00c4570..c058e3fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,6 +59,8 @@ fn main() { routes::instance::index, routes::instance::paginated_local, routes::instance::local, + routes::instance::paginated_feed, + routes::instance::feed, routes::instance::admin, routes::instance::update_settings, routes::instance::shared_inbox, diff --git a/src/routes/instance.rs b/src/routes/instance.rs index 159fc6cd..b78e5ece 100644 --- a/src/routes/instance.rs +++ b/src/routes/instance.rs @@ -62,6 +62,23 @@ fn local(conn: DbConn, user: Option) -> Template { paginated_local(conn, user, Page::first()) } +#[get("/feed")] +fn feed(conn: DbConn, user: User) -> Template { + paginated_feed(conn, user, Page::first()) +} + +#[get("/feed?")] +fn paginated_feed(conn: DbConn, user: User, page: Page) -> Template { + let followed = user.get_following(&*conn); + let articles = Post::user_feed_page(&*conn, followed.into_iter().map(|u| u.id).collect(), page.limits()); + Template::render("instance/feed", json!({ + "account": user.to_json(&*conn), + "page": page.page, + "n_pages": Page::total(Post::count_local(&*conn) as i32), + "articles": articles.into_iter().map(|p| p.to_json(&*conn)).collect::>() + })) +} + #[get("/admin")] fn admin(conn: DbConn, admin: Admin) -> Template { Template::render("instance/admin", json!({ diff --git a/templates/instance/feed.html.tera b/templates/instance/feed.html.tera new file mode 100644 index 00000000..58263260 --- /dev/null +++ b/templates/instance/feed.html.tera @@ -0,0 +1,23 @@ +{% extends "base" %} +{% import "macros" as macros %} + +{% block title %} +{{ "Your feed" | _ }} +{% endblock title %} + +{% block content %} +

+ {{ "Your feed" | _ }} +

+ + {% if articles | length > 0 %} +
+ {% for article in articles %} + {{ macros::post_card(article=article) }} + {% endfor %} +
+ {{ macros::paginate(page=page, total=n_pages) }} + {% else %} +

{{ "Nothing to see here yet. Try to follow more people." | _ }}

+ {% endif %} +{% endblock content %} From 79348e06ffe0ac9e28fcca709a12b11699802c9c Mon Sep 17 00:00:00 2001 From: Bat Date: Wed, 5 Sep 2018 15:37:49 +0100 Subject: [PATCH 18/77] Add a dedicated page for the federated feed --- src/main.rs | 2 ++ src/routes/instance.rs | 16 ++++++++++++++++ templates/instance/federated.html.tera | 17 +++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 templates/instance/federated.html.tera diff --git a/src/main.rs b/src/main.rs index c058e3fa..f8dde60d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,6 +61,8 @@ fn main() { routes::instance::local, routes::instance::paginated_feed, routes::instance::feed, + routes::instance::paginated_federated, + routes::instance::federated, routes::instance::admin, routes::instance::update_settings, routes::instance::shared_inbox, diff --git a/src/routes/instance.rs b/src/routes/instance.rs index b78e5ece..8f2f6cd4 100644 --- a/src/routes/instance.rs +++ b/src/routes/instance.rs @@ -79,6 +79,22 @@ fn paginated_feed(conn: DbConn, user: User, page: Page) -> Template { })) } +#[get("/federated")] +fn federated(conn: DbConn, user: Option) -> Template { + paginated_federated(conn, user, Page::first()) +} + +#[get("/federated?")] +fn paginated_federated(conn: DbConn, user: Option, page: Page) -> Template { + let articles = Post::get_recents_page(&*conn, page.limits()); + Template::render("instance/federated", json!({ + "account": user.map(|u| u.to_json(&*conn)), + "page": page.page, + "n_pages": Page::total(Post::count_local(&*conn) as i32), + "articles": articles.into_iter().map(|p| p.to_json(&*conn)).collect::>() + })) +} + #[get("/admin")] fn admin(conn: DbConn, admin: Admin) -> Template { Template::render("instance/admin", json!({ diff --git a/templates/instance/federated.html.tera b/templates/instance/federated.html.tera new file mode 100644 index 00000000..8bf6c649 --- /dev/null +++ b/templates/instance/federated.html.tera @@ -0,0 +1,17 @@ +{% extends "base" %} +{% import "macros" as macros %} + +{% block title %} +{{ "All the articles of the Fediverse" | _ }} +{% endblock title %} + +{% block content %} +

{{ "All the articles of the Fediverse" | _ }}

+ +
+ {% for article in articles %} + {{ macros::post_card(article=article) }} + {% endfor %} +
+ {{ macros::paginate(page=page, total=n_pages) }} +{% endblock content %} From 30cfd96e24012c95163134a827754249ebeba2e1 Mon Sep 17 00:00:00 2001 From: Bat Date: Wed, 5 Sep 2018 16:19:57 +0100 Subject: [PATCH 19/77] Add tabs to the different feeds --- static/main.css | 19 +++++++++++++++++++ templates/instance/federated.html.tera | 6 ++++++ templates/instance/feed.html.tera | 2 ++ templates/instance/index.html.tera | 9 +++++++-- templates/instance/local.html.tera | 6 ++++++ templates/macros.html.tera | 8 ++++++++ 6 files changed, 48 insertions(+), 2 deletions(-) diff --git a/static/main.css b/static/main.css index c91cb76f..8939ec2f 100644 --- a/static/main.css +++ b/static/main.css @@ -780,3 +780,22 @@ figcaption { .avatar.padded { margin-right: 2rem; } + +/** Tabs **/ + +.tabs { + border-bottom: 1px solid #DADADA; + padding: 0px; + margin: auto 20% 2em; +} + +.tabs a { + display: inline-block; + color: #242424; + padding: 1em; +} + +.tabs a.selected { + color: #7765E3; + border-bottom: 1px solid #7765E3; +} diff --git a/templates/instance/federated.html.tera b/templates/instance/federated.html.tera index 8bf6c649..c97d1cf5 100644 --- a/templates/instance/federated.html.tera +++ b/templates/instance/federated.html.tera @@ -8,6 +8,12 @@ {% block content %}

{{ "All the articles of the Fediverse" | _ }}

+ {% if account %} + {{ macros::tabs(links=['/', '/feed', '/federated', '/local'], titles=['Latest articles', 'Your feed', 'Federated feed', 'Local feed'], selected=3) }} + {% else %} + {{ macros::tabs(links=['/', '/federated', '/local'], titles=['Latest articles', 'Federated feed', 'Local feed'], selected=2) }} + {% endif %} +
{% for article in articles %} {{ macros::post_card(article=article) }} diff --git a/templates/instance/feed.html.tera b/templates/instance/feed.html.tera index 58263260..19347dfb 100644 --- a/templates/instance/feed.html.tera +++ b/templates/instance/feed.html.tera @@ -10,6 +10,8 @@ {{ "Your feed" | _ }} + {{ macros::tabs(links=['/', '/feed', '/federated', '/local'], titles=['Latest articles', 'Your feed', 'Federated feed', 'Local feed'], selected=2) }} + {% if articles | length > 0 %}
{% for article in articles %} diff --git a/templates/instance/index.html.tera b/templates/instance/index.html.tera index dd9e1ac9..f9c69e1b 100644 --- a/templates/instance/index.html.tera +++ b/templates/instance/index.html.tera @@ -8,14 +8,19 @@ {% block content %}

{{ "Welcome on {{ instance_name | escape }}" | _(instance_name=instance.name) }}

-

{{ "Latest articles" | _ }}

+ {% if account %} + {{ macros::tabs(links=['/', '/feed', '/federated', '/local'], titles=['Latest articles', 'Your feed', 'Federated feed', 'Local feed'], selected=1) }} + {% else %} + {{ macros::tabs(links=['/', '/federated', '/local'], titles=['Latest articles', 'Federated feed', 'Local feed'], selected=1) }} + {% endif %} +
{% for article in recents %} {{ macros::post_card(article=article) }} {% endfor %}
{{ macros::paginate(page=page, total=n_pages) }} - +
diff --git a/templates/instance/local.html.tera b/templates/instance/local.html.tera index 9c550f4e..5c821380 100644 --- a/templates/instance/local.html.tera +++ b/templates/instance/local.html.tera @@ -8,6 +8,12 @@ {% block content %}

{{ "Articles from {{ instance.name }}" | _(instance=instance) }}

+ {% if account %} + {{ macros::tabs(links=['/', '/feed', '/federated', '/local'], titles=['Latest articles', 'Your feed', 'Federated feed', 'Local feed'], selected=4) }} + {% else %} + {{ macros::tabs(links=['/', '/federated', '/local'], titles=['Latest articles', 'Federated feed', 'Local feed'], selected=3) }} + {% endif %} +
{% for article in articles %} {{ macros::post_card(article=article) }} diff --git a/templates/macros.html.tera b/templates/macros.html.tera index e1a7e989..aaaee1aa 100644 --- a/templates/macros.html.tera +++ b/templates/macros.html.tera @@ -63,3 +63,11 @@ {% endfor %}
{% endmacro %} +{% macro tabs(links, titles, selected) %} +
+ {% for link in links %} + {% set idx = loop.index0 %} + {{ titles[idx] | _ }} + {% endfor %} +
+{% endmacro %} From 99f04893bb110729d4668528359b6378f9df3113 Mon Sep 17 00:00:00 2001 From: Bat Date: Wed, 5 Sep 2018 16:41:31 +0100 Subject: [PATCH 20/77] Add tabs to the user profile --- static/main.css | 4 ++++ templates/users/details.html.tera | 8 ++++---- templates/users/followers.html.tera | 5 ++++- templates/users/header.html.tera | 12 ++++++------ 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/static/main.css b/static/main.css index 8939ec2f..99396f7a 100644 --- a/static/main.css +++ b/static/main.css @@ -799,3 +799,7 @@ figcaption { color: #7765E3; border-bottom: 1px solid #7765E3; } + +.user-summary { + margin: 2em 0px; +} diff --git a/templates/users/details.html.tera b/templates/users/details.html.tera index 3939846b..c48e9f21 100644 --- a/templates/users/details.html.tera +++ b/templates/users/details.html.tera @@ -14,11 +14,11 @@ {% block content %} {% include "users/header" %} -
- {{ user.summary | safe }} -
+ {% set user_link = ['/@', user.fqn] %} + {% set followers_link = ['/@', user.fqn, 'followers'] %} + {{ macros::tabs(links=[ user_link | join(sep='/'), followers_link | join(sep='/')], titles=['Articles', 'Followers'], selected=1) }} - {% if recents | length != 0 %} + {% if recents | length != 0 %}

{{ "Latest articles" | _ }} diff --git a/templates/users/followers.html.tera b/templates/users/followers.html.tera index 6c330e20..fb2a3f91 100644 --- a/templates/users/followers.html.tera +++ b/templates/users/followers.html.tera @@ -8,7 +8,10 @@ {% block content %} {% include "users/header" %} -

{{ "Followers" | _ }}

+ {% set user_link = ['/@', user.fqn] %} + {% set followers_link = ['/@', user.fqn, 'followers'] %} + {{ macros::tabs(links=[ user_link | join(sep='/'), followers_link | join(sep='/')], titles=['Articles', 'Followers'], selected=2) }} +
{% for follower in followers %}
diff --git a/templates/users/header.html.tera b/templates/users/header.html.tera index af4daa34..437e7187 100644 --- a/templates/users/header.html.tera +++ b/templates/users/header.html.tera @@ -15,12 +15,11 @@ {{ "It is you" | _ }} {% endif %} + {% if is_self %} + {{ "Edit your profile" | _ }} + {% endif %}
- {% if is_self %} - {{ "Edit your profile" | _ }} - {% endif %} - {% if is_remote %} {{ "Open on {{ instance_url }}" | _(instance_url=instance_url) }} {% endif %} @@ -35,6 +34,7 @@ {% endif %}
-
- {{ "{{ count }} followers" | _n(singular="One follower", count=n_followers) }} + +
+ {{ user.summary | safe }}
From 09bae00b9c96a76cb91cfaae821d32a4a066ab89 Mon Sep 17 00:00:00 2001 From: Bat Date: Wed, 5 Sep 2018 16:50:54 +0100 Subject: [PATCH 21/77] Update POT + french translation --- po/de.po | 24 ++++++++++++++++++++++++ po/en.po | 22 ++++++++++++++++++++++ po/fr.po | 28 ++++++++++++++++++++++++---- po/gl.po | 24 ++++++++++++++++++++++++ po/nb.po | 24 ++++++++++++++++++++++++ po/pl.po | 24 ++++++++++++++++++++++++ po/plume.pot | 21 +++++++++++++++++++++ 7 files changed, 163 insertions(+), 4 deletions(-) diff --git a/po/de.po b/po/de.po index 0fa6aa0d..c68c290f 100644 --- a/po/de.po +++ b/po/de.po @@ -518,5 +518,29 @@ msgstr "" msgid "Login to boost" msgstr "" +#, fuzzy +msgid "Your feed" +msgstr "Dein Kommentar" + +msgid "Federated feed" +msgstr "" + +msgid "Local feed" +msgstr "" + +msgid "Nothing to see here yet. Try to follow more people." +msgstr "" + +#, fuzzy +msgid "Articles" +msgstr "Artikel geschrieben haben" + +msgid "All the articles of the Fediverse" +msgstr "" + +#, fuzzy +msgid "Articles from {{ instance.name }}" +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 5a2718e8..088a8966 100644 --- a/po/en.po +++ b/po/en.po @@ -506,3 +506,25 @@ msgstr "" msgid "Login to boost" msgstr "" + +msgid "Your feed" +msgstr "" + +msgid "Federated feed" +msgstr "" + +msgid "Local feed" +msgstr "" + +msgid "Nothing to see here yet. Try to follow more people." +msgstr "" + +msgid "Articles" +msgstr "" + +msgid "All the articles of the Fediverse" +msgstr "" + +#, fuzzy +msgid "Articles from {{ instance.name }}" +msgstr "Welcome on {{ instance_name }}" diff --git a/po/fr.po b/po/fr.po index 686c24d4..44929779 100644 --- a/po/fr.po +++ b/po/fr.po @@ -507,12 +507,32 @@ msgstr "" "Désolé, mais les inscriptions sont fermées sur cette instance. Essayez d'en " "trouver une autre." -#, fuzzy msgid "Subtitle" -msgstr "Titre" +msgstr "Sous-titre" msgid "Login to like" -msgstr "" +msgstr "Connectez-vous pour aimer." msgid "Login to boost" -msgstr "" +msgstr "Connectez-vous pour booster." + +msgid "Your feed" +msgstr "Votre flux" + +msgid "Federated feed" +msgstr "Flux fédéré" + +msgid "Local feed" +msgstr "Flux local" + +msgid "Nothing to see here yet. Try to follow more people." +msgstr "Rien par ici pour le moment. Essayez de suivre plus de monde." + +msgid "Articles" +msgstr "Articles" + +msgid "All the articles of the Fediverse" +msgstr "Tous les articles de la Fédiverse" + +msgid "Articles from {{ instance.name }}" +msgstr "Articles de {{ instance_name }}" diff --git a/po/gl.po b/po/gl.po index 2d4597fd..5e540067 100644 --- a/po/gl.po +++ b/po/gl.po @@ -508,3 +508,27 @@ msgstr "" msgid "Login to boost" msgstr "" + +#, fuzzy +msgid "Your feed" +msgstr "Os seus medios" + +msgid "Federated feed" +msgstr "" + +msgid "Local feed" +msgstr "" + +msgid "Nothing to see here yet. Try to follow more people." +msgstr "" + +#, fuzzy +msgid "Articles" +msgstr "artigos" + +msgid "All the articles of the Fediverse" +msgstr "" + +#, fuzzy +msgid "Articles from {{ instance.name }}" +msgstr "Acerca de {{ instance_name }}" diff --git a/po/nb.po b/po/nb.po index 3e0019d2..616c28c1 100644 --- a/po/nb.po +++ b/po/nb.po @@ -522,6 +522,30 @@ msgstr "" msgid "Login to boost" msgstr "" +#, fuzzy +msgid "Your feed" +msgstr "Din kommentar" + +msgid "Federated feed" +msgstr "" + +msgid "Local feed" +msgstr "" + +msgid "Nothing to see here yet. Try to follow more people." +msgstr "" + +#, fuzzy +msgid "Articles" +msgstr "artikler" + +msgid "All the articles of the Fediverse" +msgstr "" + +#, fuzzy +msgid "Articles from {{ instance.name }}" +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 2f2e5038..de3db664 100644 --- a/po/pl.po +++ b/po/pl.po @@ -521,6 +521,30 @@ msgstr "Zaloguj się aby polubić" msgid "Login to boost" msgstr "Zaloguj się aby podbić" +#, fuzzy +msgid "Your feed" +msgstr "Twoja zawartość multimedialna" + +msgid "Federated feed" +msgstr "" + +msgid "Local feed" +msgstr "" + +msgid "Nothing to see here yet. Try to follow more people." +msgstr "" + +#, fuzzy +msgid "Articles" +msgstr "artykuły" + +msgid "All the articles of the Fediverse" +msgstr "" + +#, fuzzy +msgid "Articles from {{ instance.name }}" +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 9c0954b1..f7afb413 100644 --- a/po/plume.pot +++ b/po/plume.pot @@ -495,3 +495,24 @@ msgstr "" msgid "Login to boost" msgstr "" + +msgid "Your feed" +msgstr "" + +msgid "Federated feed" +msgstr "" + +msgid "Local feed" +msgstr "" + +msgid "Nothing to see here yet. Try to follow more people." +msgstr "" + +msgid "Articles" +msgstr "" + +msgid "All the articles of the Fediverse" +msgstr "" + +msgid "Articles from {{ instance.name }}" +msgstr "" From c1d8cf6943bb06fd74b404bd705d45b301f25835 Mon Sep 17 00:00:00 2001 From: Bat Date: Wed, 5 Sep 2018 16:56:23 +0100 Subject: [PATCH 22/77] Update notification icons to use Feather --- templates/notifications/index.html.tera | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/notifications/index.html.tera b/templates/notifications/index.html.tera index ef0135a5..3c2d174e 100644 --- a/templates/notifications/index.html.tera +++ b/templates/notifications/index.html.tera @@ -11,7 +11,7 @@ {% for notification in notifications %} +{% endmacro %} {% macro feather(name) %} From 39342d2f72cb55c37d3cfefb9a68d548cd079d65 Mon Sep 17 00:00:00 2001 From: Bat Date: Wed, 5 Sep 2018 17:24:05 +0100 Subject: [PATCH 24/77] Fix a bug in french translation Wrong variable name --- po/fr.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/fr.po b/po/fr.po index 44929779..bb93cb5a 100644 --- a/po/fr.po +++ b/po/fr.po @@ -535,4 +535,4 @@ msgid "All the articles of the Fediverse" msgstr "Tous les articles de la Fédiverse" msgid "Articles from {{ instance.name }}" -msgstr "Articles de {{ instance_name }}" +msgstr "Articles de {{ instance.name }}" From b66d4f73ceaa324b59792d3f3ff483aecdd005cf Mon Sep 17 00:00:00 2001 From: Bat Date: Wed, 5 Sep 2018 18:03:02 +0100 Subject: [PATCH 25/77] Reorganize the homepage --- po/de.po | 3 ++ po/en.po | 3 ++ po/fr.po | 3 ++ po/gl.po | 3 ++ po/nb.po | 3 ++ po/pl.po | 3 ++ po/plume.pot | 3 ++ src/main.rs | 1 - src/routes/instance.rs | 22 +++++------ static/css/main.css | 4 ++ templates/instance/description.html.tera | 32 ++++++++++++++++ templates/instance/index.html.tera | 49 +++++------------------- templates/macros.html.tera | 8 ++++ 13 files changed, 85 insertions(+), 52 deletions(-) create mode 100644 templates/instance/description.html.tera diff --git a/po/de.po b/po/de.po index c68c290f..011e3f3a 100644 --- a/po/de.po +++ b/po/de.po @@ -542,5 +542,8 @@ msgstr "" msgid "Articles from {{ instance.name }}" msgstr "Über {{ instance_name }}" +msgid "View all" +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 088a8966..7e6333e9 100644 --- a/po/en.po +++ b/po/en.po @@ -528,3 +528,6 @@ msgstr "" #, fuzzy msgid "Articles from {{ instance.name }}" msgstr "Welcome on {{ instance_name }}" + +msgid "View all" +msgstr "" diff --git a/po/fr.po b/po/fr.po index bb93cb5a..6df847d8 100644 --- a/po/fr.po +++ b/po/fr.po @@ -536,3 +536,6 @@ msgstr "Tous les articles de la Fédiverse" msgid "Articles from {{ instance.name }}" msgstr "Articles de {{ instance.name }}" + +msgid "View all" +msgstr "Tout afficher" diff --git a/po/gl.po b/po/gl.po index 5e540067..576e4309 100644 --- a/po/gl.po +++ b/po/gl.po @@ -532,3 +532,6 @@ msgstr "" #, fuzzy msgid "Articles from {{ instance.name }}" msgstr "Acerca de {{ instance_name }}" + +msgid "View all" +msgstr "" diff --git a/po/nb.po b/po/nb.po index 616c28c1..ed08eeab 100644 --- a/po/nb.po +++ b/po/nb.po @@ -546,6 +546,9 @@ msgstr "" msgid "Articles from {{ instance.name }}" msgstr "Om {{ instance_name }}" +msgid "View all" +msgstr "" + #~ msgid "One reshare" #~ msgid_plural "{{ count }} reshares" #~ msgstr[0] "Én deling" diff --git a/po/pl.po b/po/pl.po index de3db664..d5f7d282 100644 --- a/po/pl.po +++ b/po/pl.po @@ -545,6 +545,9 @@ msgstr "" msgid "Articles from {{ instance.name }}" msgstr "O {{ instance_name }}" +msgid "View all" +msgstr "" + #~ msgid "One reshare" #~ msgid_plural "{{ count }} reshares" #~ msgstr[0] "Jedno udostępnienie" diff --git a/po/plume.pot b/po/plume.pot index f7afb413..f4d25180 100644 --- a/po/plume.pot +++ b/po/plume.pot @@ -516,3 +516,6 @@ msgstr "" msgid "Articles from {{ instance.name }}" msgstr "" + +msgid "View all" +msgstr "" diff --git a/src/main.rs b/src/main.rs index f8dde60d..aff653a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,7 +55,6 @@ fn main() { routes::comments::create, - routes::instance::paginated_index, routes::instance::index, routes::instance::paginated_local, routes::instance::local, diff --git a/src/routes/instance.rs b/src/routes/instance.rs index 8f2f6cd4..1125da4a 100644 --- a/src/routes/instance.rs +++ b/src/routes/instance.rs @@ -15,18 +15,23 @@ use plume_models::{ use inbox::Inbox; use routes::Page; -#[get("/?")] -fn paginated_index(conn: DbConn, user: Option, page: Page) -> Template { +#[get("/")] +fn index(conn: DbConn, user: Option) -> Template { match Instance::get_local(&*conn) { Some(inst) => { - let recents = Post::get_recents_page(&*conn, page.limits()); + let federated = Post::get_recents_page(&*conn, Page::first().limits()); + let local = Post::get_instance_page(&*conn, inst.id, Page::first().limits()); + let user_feed = user.clone().map(|user| { + let followed = user.get_following(&*conn); + Post::user_feed_page(&*conn, followed.into_iter().map(|u| u.id).collect(), Page::first().limits()) + }); Template::render("instance/index", json!({ "instance": inst, "account": user.map(|u| u.to_json(&*conn)), - "recents": recents.into_iter().map(|p| p.to_json(&*conn)).collect::>(), - "page": page.page, - "n_pages": Page::total(Post::count(&*conn) as i32), + "federated": federated.into_iter().map(|p| p.to_json(&*conn)).collect::>(), + "local": local.into_iter().map(|p| p.to_json(&*conn)).collect::>(), + "user_feed": user_feed.map(|f| f.into_iter().map(|p| p.to_json(&*conn)).collect::>()), "n_users": User::count_local(&*conn), "n_articles": Post::count_local(&*conn) })) @@ -39,11 +44,6 @@ fn paginated_index(conn: DbConn, user: Option, page: Page) -> Template { } } -#[get("/")] -fn index(conn: DbConn, user: Option) -> Template { - paginated_index(conn, user, Page::first()) -} - #[get("/local?")] fn paginated_local(conn: DbConn, user: Option, page: Page) -> Template { let instance = Instance::get_local(&*conn).unwrap(); diff --git a/static/css/main.css b/static/css/main.css index 683cc725..adc4bdce 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -41,6 +41,10 @@ margin: 4rem 0; } + .cards.spaced { + margin: 1rem 0 5rem; + } + .banner { background: #DADADA; padding-top: 2em; diff --git a/templates/instance/description.html.tera b/templates/instance/description.html.tera new file mode 100644 index 00000000..ab289506 --- /dev/null +++ b/templates/instance/description.html.tera @@ -0,0 +1,32 @@ +
+
+
+

{{ "What is Plume?" | _ }}

+
+

{{ "Plume is a decentralized blogging engine." | _ }}

+

{{ "Authors can manage various blogs from an unique website." | _ }}

+

{{ "Articles are also visible on other Plume websites, and you can interact with them directly from other platforms like Mastodon." | _ }}

+
+ {{ "Create your account" | _ }} +
+
+

{{ "About {{ instance_name }}" | _(instance_name=instance.name) }}

+
+ {{ instance.short_description_html | safe }} +
+
+

{{ "Home to" | _ }}

+ {{ n_users }} +

{{ "people" | _ }}

+
+
+

{{ "Who wrote" | _ }}

+ {{ n_articles }} +

{{ "articles" | _ }}

+
+
+
+ {{ "Read the detailed rules" | _ }} +
+
+
diff --git a/templates/instance/index.html.tera b/templates/instance/index.html.tera index f9c69e1b..339ae791 100644 --- a/templates/instance/index.html.tera +++ b/templates/instance/index.html.tera @@ -10,47 +10,16 @@ {% if account %} {{ macros::tabs(links=['/', '/feed', '/federated', '/local'], titles=['Latest articles', 'Your feed', 'Federated feed', 'Local feed'], selected=1) }} + + {{ macros::home_feed(title='Your feed', link='/feed', articles=user_feed) }} + {{ macros::home_feed(title='Federated feed', link='/federated', articles=federated) }} + {{ macros::home_feed(title='Local feed', link='/local', articles=local) }} + {% include "instance/description" %} {% else %} {{ macros::tabs(links=['/', '/federated', '/local'], titles=['Latest articles', 'Federated feed', 'Local feed'], selected=1) }} + + {{ macros::home_feed(title='Federated feed', link='/federated', articles=federated) }} + {% include "instance/description" %} + {{ macros::home_feed(title='Local feed', link='/local', articles=local) }} {% endif %} - -
- {% for article in recents %} - {{ macros::post_card(article=article) }} - {% endfor %} -
- {{ macros::paginate(page=page, total=n_pages) }} - -
-
-
-

{{ "What is Plume?" | _ }}

-
-

{{ "Plume is a decentralized blogging engine." | _ }}

-

{{ "Authors can manage various blogs from an unique website." | _ }}

-

{{ "Articles are also visible on other Plume websites, and you can interact with them directly from other platforms like Mastodon." | _ }}

-
- {{ "Create your account" | _ }} -
-
-

{{ "About {{ instance_name }}" | _(instance_name=instance.name) }}

-
- {{ instance.short_description_html | safe }} -
-
-

{{ "Home to" | _ }}

- {{ n_users }} -

{{ "people" | _ }}

-
-
-

{{ "Who wrote" | _ }}

- {{ n_articles }} -

{{ "articles" | _ }}

-
-
-
- {{ "Read the detailed rules" | _ }} -
-
-
{% endblock content %} diff --git a/templates/macros.html.tera b/templates/macros.html.tera index 15c0f8aa..f1c9343b 100644 --- a/templates/macros.html.tera +++ b/templates/macros.html.tera @@ -76,3 +76,11 @@ {% endmacro %} +{% macro home_feed(title, link, articles) %} +

{{ title | _ }} — {{ "View all" | _ }}

+
+ {% for article in articles %} + {{ macros::post_card(article=article) }} + {% endfor %} +
+{% endmacro %} From 13a04198f091a5662e8422f4ac0e578d3e91cb8d Mon Sep 17 00:00:00 2001 From: Bat Date: Wed, 5 Sep 2018 18:07:39 +0100 Subject: [PATCH 26/77] Improve tabs on small screens --- static/css/main.css | 238 +++++++++++++++++++++++--------------------- 1 file changed, 122 insertions(+), 116 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index adc4bdce..19fde670 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -615,6 +615,123 @@ font-weight: bold; } + /*== Pagination ==*/ + .pagination { + display: flex; + justify-content: space-evenly; +} + +.pagination > * { + padding: 2em; +} + +/*== Flex boxes ==*/ +.flex { + display: flex; + flex-direction: row; + align-items: center; +} + +.flex .grow { + flex: 1; +} + +.left-icon { + align-self: center; + padding: 1em; + background: #DADADA; + border-radius: 50px; + margin: 1em; + margin-right: 2em; +} + +/*== Footer ==*/ +body > footer { + display: flex; + align-content: center; + justify-content: space-between; + background: #ECECEC; + padding: 0 20%; + margin-top: 5em; +} + +body > footer * { + margin: 5em 0; +} + +/** Medias **/ + +figure { + text-align: center; + margin: 2em; + max-width: 100%; + width: auto; + height: auto; +} + +figure > * { + max-width: 100%; +} + +figcaption { + padding: 1em; +} + +.preview { + display: block; + max-width: 100px; + max-height: 100px; + width: auto; + height: auto; + margin-right: 20px; +} + +/** Avatars **/ + +.avatar { + border-radius: 100%; +} + +.avatar.small { + width: 50px; + height: 50px; +} + +.avatar.medium { + width: 100px; + height: 100px; + margin: 20px; +} + +.avatar.padded { + margin-right: 2rem; +} + +/** Tabs **/ + +.tabs { + border-bottom: 1px solid #DADADA; + padding: 0px; + margin: auto 20% 2em; + overflow: auto; + display: flex; +} + +.tabs a { + display: inline-block; + color: #242424; + padding: 1em; +} + +.tabs a.selected { + color: #7765E3; + border-bottom: 1px solid #7765E3; +} + +.user-summary { + margin: 2em 0px; +} + /* ================= * * Small Screens * * ================= */ @@ -718,120 +835,9 @@ .card { min-width: 80%; min-height: 80%; - } + } + + .tabs { + margin: auto 0px 2em; + } } - - /*== Pagination ==*/ - .pagination { - display: flex; - justify-content: space-evenly; - } - - .pagination > * { - padding: 2em; - } - - /*== Flex boxes ==*/ - .flex { - display: flex; - flex-direction: row; - align-items: center; - } - - .flex .grow { - flex: 1; - } - - .left-icon { - align-self: center; - padding: 1em; - background: #DADADA; - border-radius: 50px; - margin: 1em; - margin-right: 2em; - } - - /*== Footer ==*/ - body > footer { - display: flex; - align-content: center; - justify-content: space-between; - background: #ECECEC; - padding: 0 20%; - margin-top: 5em; - } - - body > footer * { - margin: 5em 0; - } - - /** Medias **/ - - figure { - text-align: center; - margin: 2em; - max-width: 100%; - width: auto; - height: auto; - } - - figure > * { - max-width: 100%; - } - - figcaption { - padding: 1em; - } - - .preview { - display: block; - max-width: 100px; - max-height: 100px; - width: auto; - height: auto; - margin-right: 20px; - } - - /** Avatars **/ - - .avatar { - border-radius: 100%; - } - - .avatar.small { - width: 50px; - height: 50px; - } - - .avatar.medium { - width: 100px; - height: 100px; - margin: 20px; - } - - .avatar.padded { - margin-right: 2rem; - } - -/** Tabs **/ - -.tabs { - border-bottom: 1px solid #DADADA; - padding: 0px; - margin: auto 20% 2em; -} - -.tabs a { - display: inline-block; - color: #242424; - padding: 1em; -} - -.tabs a.selected { - color: #7765E3; - border-bottom: 1px solid #7765E3; -} - -.user-summary { - margin: 2em 0px; -} From 9b119691d37f0d4884e542f50eb5ded299a601e9 Mon Sep 17 00:00:00 2001 From: Bat Date: Wed, 5 Sep 2018 18:28:23 +0100 Subject: [PATCH 27/77] Don't display empty sections on the homepage --- templates/macros.html.tera | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/templates/macros.html.tera b/templates/macros.html.tera index f1c9343b..b336f281 100644 --- a/templates/macros.html.tera +++ b/templates/macros.html.tera @@ -77,10 +77,12 @@ {% endmacro %} {% macro home_feed(title, link, articles) %} -

{{ title | _ }} — {{ "View all" | _ }}

-
- {% for article in articles %} - {{ macros::post_card(article=article) }} - {% endfor %} -
+ {% if articles | length > 0 %} +

{{ title | _ }} — {{ "View all" | _ }}

+
+ {% for article in articles %} + {{ macros::post_card(article=article) }} + {% endfor %} +
+ {% endif %} {% endmacro %} From e16acf84366d83901c4c195dfbd5c3f9536b160c Mon Sep 17 00:00:00 2001 From: Bat Date: Wed, 5 Sep 2018 18:35:23 +0100 Subject: [PATCH 28/77] Mobile style improvement Stack items when possible --- static/css/main.css | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index 19fde670..d1a9f1b5 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -835,9 +835,16 @@ figcaption { .card { min-width: 80%; min-height: 80%; - } + } - .tabs { - margin: auto 0px 2em; - } + .tabs { + margin: auto 0px 2em; + } + + .stats { flex-direction: column; } + body > footer { + flex-direction: column; + align-items: center; + } + body > footer * { margin: 1em auto; } } From 2b7a5bee93dda2b1d86bd98a4c910e873fbab307 Mon Sep 17 00:00:00 2001 From: Bat Date: Wed, 5 Sep 2018 19:05:53 +0100 Subject: [PATCH 29/77] Add a tag model --- .../2018-09-05-174106_create_tags/down.sql | 2 ++ .../2018-09-05-174106_create_tags/up.sql | 7 ++++++ plume-models/src/lib.rs | 1 + plume-models/src/schema.rs | 11 +++++++++ plume-models/src/tags.rs | 23 +++++++++++++++++++ 5 files changed, 44 insertions(+) create mode 100644 migrations/2018-09-05-174106_create_tags/down.sql create mode 100644 migrations/2018-09-05-174106_create_tags/up.sql create mode 100644 plume-models/src/tags.rs diff --git a/migrations/2018-09-05-174106_create_tags/down.sql b/migrations/2018-09-05-174106_create_tags/down.sql new file mode 100644 index 00000000..43c79a4b --- /dev/null +++ b/migrations/2018-09-05-174106_create_tags/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE tags; diff --git a/migrations/2018-09-05-174106_create_tags/up.sql b/migrations/2018-09-05-174106_create_tags/up.sql new file mode 100644 index 00000000..9ef32855 --- /dev/null +++ b/migrations/2018-09-05-174106_create_tags/up.sql @@ -0,0 +1,7 @@ +-- Your SQL goes here +CREATE TABLE tags ( + id SERIAL PRIMARY KEY, + tag TEXT NOT NULL DEFAULT '', + is_hastag BOOLEAN NOT NULL DEFAULT 'f', + post_id INTEGER REFERENCES posts(id) ON DELETE CASCADE NOT NULL +) diff --git a/plume-models/src/lib.rs b/plume-models/src/lib.rs index f2c80c62..acfc2b15 100644 --- a/plume-models/src/lib.rs +++ b/plume-models/src/lib.rs @@ -119,4 +119,5 @@ pub mod posts; pub mod reshares; pub mod safe_string; pub mod schema; +pub mod tags; pub mod users; diff --git a/plume-models/src/schema.rs b/plume-models/src/schema.rs index 7214871e..1a9cd293 100644 --- a/plume-models/src/schema.rs +++ b/plume-models/src/schema.rs @@ -139,6 +139,15 @@ table! { } } +table! { + tags (id) { + id -> Int4, + tag -> Text, + is_hastag -> Bool, + post_id -> Int4, + } +} + table! { users (id) { id -> Int4, @@ -178,6 +187,7 @@ joinable!(post_authors -> users (author_id)); joinable!(posts -> blogs (blog_id)); joinable!(reshares -> posts (post_id)); joinable!(reshares -> users (user_id)); +joinable!(tags -> posts (post_id)); joinable!(users -> instances (instance_id)); allow_tables_to_appear_in_same_query!( @@ -193,5 +203,6 @@ allow_tables_to_appear_in_same_query!( post_authors, posts, reshares, + tags, users, ); diff --git a/plume-models/src/tags.rs b/plume-models/src/tags.rs new file mode 100644 index 00000000..359f32d0 --- /dev/null +++ b/plume-models/src/tags.rs @@ -0,0 +1,23 @@ +use diesel::{self, PgConnection, ExpressionMethods, RunQueryDsl, QueryDsl}; +use schema::tags; + +#[derive(Queryable)] +pub struct Tag { + pub id: i32, + pub tag: String, + pub is_hastag: bool, + pub post_id: i32 +} + +#[derive(Insertable)] +#[table_name = "tags"] +pub struct NewTag { + pub tag: String, + pub is_hastag: bool, + pub post_id: i32 +} + +impl Tag { + insert!(tags, NewTag); + get!(tags); +} From 5b3eca63e01cbe916483416ba6787b30e616c867 Mon Sep 17 00:00:00 2001 From: Bat Date: Wed, 5 Sep 2018 21:18:27 +0100 Subject: [PATCH 30/77] Make it possible to tag articles and display them --- plume-models/src/posts.rs | 4 +++- plume-models/src/tags.rs | 3 ++- src/routes/posts.rs | 13 ++++++++++++- static/css/main.css | 23 ++++++++++++++++++++--- templates/posts/details.html.tera | 5 +++++ templates/posts/new.html.tera | 2 ++ 6 files changed, 44 insertions(+), 6 deletions(-) diff --git a/plume-models/src/posts.rs b/plume-models/src/posts.rs index c826f766..2bb978fb 100644 --- a/plume-models/src/posts.rs +++ b/plume-models/src/posts.rs @@ -19,6 +19,7 @@ use likes::Like; use mentions::Mention; use post_authors::*; use reshares::Reshare; +use tags::Tag; use users::User; use schema::posts; use safe_string::SafeString; @@ -244,7 +245,8 @@ impl Post { "author": self.get_authors(conn)[0].to_json(conn), "url": format!("/~/{}/{}/", blog.get_fqn(conn), self.slug), "date": self.creation_date.timestamp(), - "blog": blog.to_json(conn) + "blog": blog.to_json(conn), + "tags": Tag::for_post(&*conn, self.id) }) } diff --git a/plume-models/src/tags.rs b/plume-models/src/tags.rs index 359f32d0..e94a54d1 100644 --- a/plume-models/src/tags.rs +++ b/plume-models/src/tags.rs @@ -1,7 +1,7 @@ use diesel::{self, PgConnection, ExpressionMethods, RunQueryDsl, QueryDsl}; use schema::tags; -#[derive(Queryable)] +#[derive(Serialize, Queryable)] pub struct Tag { pub id: i32, pub tag: String, @@ -20,4 +20,5 @@ pub struct NewTag { impl Tag { insert!(tags, NewTag); get!(tags); + list_by!(tags, for_post, post_id as i32); } diff --git a/src/routes/posts.rs b/src/routes/posts.rs index 7ecf9f4b..3c9ca023 100644 --- a/src/routes/posts.rs +++ b/src/routes/posts.rs @@ -1,5 +1,5 @@ use activitypub::object::Article; -use heck::KebabCase; +use heck::{CamelCase, KebabCase}; use rocket::{State, request::LenientForm}; use rocket::response::{Redirect, Flash}; use rocket_contrib::Template; @@ -19,6 +19,7 @@ use plume_models::{ post_authors::*, posts::*, safe_string::SafeString, + tags::*, users::User }; @@ -101,6 +102,7 @@ struct NewPostForm { pub title: String, pub subtitle: String, pub content: String, + pub tags: String, pub license: String } @@ -165,6 +167,15 @@ fn create(blog_name: String, data: LenientForm, user: User, conn: D Mention::from_activity(&*conn, Mention::build_activity(&*conn, m), post.id, true); } + let tags = form.tags.split(",").map(|t| t.trim().to_camel_case()).filter(|t| t.len() > 0); + for tag in tags { + Tag::insert(&*conn, NewTag { + tag: tag, + is_hastag: false, + post_id: post.id + }); + } + let act = post.create_activity(&*conn); let followers = user.get_followers(&*conn); worker.execute(Thunk::of(move || broadcast(&user, act, followers))); diff --git a/static/css/main.css b/static/css/main.css index d1a9f1b5..a19e6549 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -217,10 +217,27 @@ } main .article-meta > * { margin: 0 20%; } - main .article-meta > p { - margin: 2em 20%; +main .article-meta > p { + margin: 2em 20%; font-size: 0.9em; - } +} + +/** Tags **/ + +main .article-meta .tags { + list-style: none; + display: inline-block; + padding: 0px; + margin-bottom: 2em; +} + +main .article-meta .tags li { + display: inline; + background: #DADADA; + padding: 10px 20px; + margin-right: 10px; + border-radius: 3px; +} /* ~ Likes ~ */ diff --git a/templates/posts/details.html.tera b/templates/posts/details.html.tera index e64efffd..3a3158dc 100644 --- a/templates/posts/details.html.tera +++ b/templates/posts/details.html.tera @@ -34,6 +34,11 @@