mirror of
https://git.joinplu.me/Plume/Plume.git
synced 2024-11-22 19:41:03 +00:00
Merge branch 'master' of https://github.com/Plume-org/Plume into icons
This commit is contained in:
commit
a30b99f93e
25 changed files with 299 additions and 108 deletions
22
docs/ENV-VARS.md
Normal file
22
docs/ENV-VARS.md
Normal file
|
@ -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.
|
||||
|
|
@ -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)
|
||||
|
|
2
migrations/2018-09-04-103017_follows_add_ap_url/down.sql
Normal file
2
migrations/2018-09-04-103017_follows_add_ap_url/down.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
ALTER TABLE follows DROP COLUMN ap_url;
|
2
migrations/2018-09-04-103017_follows_add_ap_url/up.sql
Normal file
2
migrations/2018-09-04-103017_follows_add_ap_url/up.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
-- Your SQL goes here
|
||||
ALTER TABLE follows ADD COLUMN ap_url TEXT NOT NULL DEFAULT '';
|
2
migrations/2018-09-04-104828_posts_add_subtitle/down.sql
Normal file
2
migrations/2018-09-04-104828_posts_add_subtitle/down.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
ALTER TABLE posts DROP COLUMN subtitle;
|
2
migrations/2018-09-04-104828_posts_add_subtitle/up.sql
Normal file
2
migrations/2018-09-04-104828_posts_add_subtitle/up.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
-- Your SQL goes here
|
||||
ALTER TABLE posts ADD COLUMN subtitle TEXT NOT NULL DEFAULT '';
|
|
@ -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<Follow> {
|
||||
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::<Id>(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::<Id>(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<PgConnection> for Follow {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Deletable<PgConnection, Undo> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<NaiveDateTime>,
|
||||
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::<Id>(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::<Id>(to.into_iter().map(Id::new).collect()).expect("Article::into_activity: to error");
|
||||
|
@ -250,7 +253,8 @@ impl FromActivity<Article, PgConnection> 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() {
|
||||
|
|
|
@ -42,6 +42,7 @@ table! {
|
|||
id -> Int4,
|
||||
follower_id -> Int4,
|
||||
following_id -> Int4,
|
||||
ap_url -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,6 +125,7 @@ table! {
|
|||
license -> Varchar,
|
||||
creation_date -> Timestamp,
|
||||
ap_url -> Varchar,
|
||||
subtitle -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -567,7 +567,7 @@ impl WithInbox for User {
|
|||
}
|
||||
|
||||
fn is_local(&self) -> bool {
|
||||
self.instance_id == 0
|
||||
self.instance_id == 1
|
||||
}
|
||||
}
|
||||
|
||||
|
|
10
po/de.po
10
po/de.po
|
@ -508,5 +508,15 @@ msgid ""
|
|||
"Sorry, but registrations are closed on this instance. Try to find another one"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Subtitle"
|
||||
msgstr "Titel"
|
||||
|
||||
msgid "Login to like"
|
||||
msgstr ""
|
||||
|
||||
msgid "Login to boost"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Your password should be at least 8 characters long"
|
||||
#~ msgstr "Das Passwort sollte mindestens 8 Zeichen lang sein"
|
||||
|
|
9
po/en.po
9
po/en.po
|
@ -497,3 +497,12 @@ msgstr ""
|
|||
msgid ""
|
||||
"Sorry, but registrations are closed on this instance. Try to find another one"
|
||||
msgstr ""
|
||||
|
||||
msgid "Subtitle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Login to like"
|
||||
msgstr ""
|
||||
|
||||
msgid "Login to boost"
|
||||
msgstr ""
|
||||
|
|
14
po/fr.po
14
po/fr.po
|
@ -503,4 +503,16 @@ msgstr "Envoyer"
|
|||
|
||||
msgid ""
|
||||
"Sorry, but registrations are closed on this instance. Try to find another one"
|
||||
msgstr "Désolé, mais les inscriptions sont fermées sur cette instance. Essayez d'en trouver une autre."
|
||||
msgstr ""
|
||||
"Désolé, mais les inscriptions sont fermées sur cette instance. Essayez d'en "
|
||||
"trouver une autre."
|
||||
|
||||
#, fuzzy
|
||||
msgid "Subtitle"
|
||||
msgstr "Titre"
|
||||
|
||||
msgid "Login to like"
|
||||
msgstr ""
|
||||
|
||||
msgid "Login to boost"
|
||||
msgstr ""
|
||||
|
|
13
po/gl.po
13
po/gl.po
|
@ -496,4 +496,15 @@ msgstr "Enviar"
|
|||
msgid ""
|
||||
"Sorry, but registrations are closed on this instance. Try to find another one"
|
||||
msgstr ""
|
||||
"Lamentámolo, pero o rexistro en esta instancia está pechado. Inténteo en outra instancia"
|
||||
"Lamentámolo, pero o rexistro en esta instancia está pechado. Inténteo en "
|
||||
"outra instancia"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Subtitle"
|
||||
msgstr "Título"
|
||||
|
||||
msgid "Login to like"
|
||||
msgstr ""
|
||||
|
||||
msgid "Login to boost"
|
||||
msgstr ""
|
||||
|
|
10
po/nb.po
10
po/nb.po
|
@ -512,6 +512,16 @@ msgid ""
|
|||
"Sorry, but registrations are closed on this instance. Try to find another one"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Subtitle"
|
||||
msgstr "Tittel"
|
||||
|
||||
msgid "Login to like"
|
||||
msgstr ""
|
||||
|
||||
msgid "Login to boost"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "One reshare"
|
||||
#~ msgid_plural "{{ count }} reshares"
|
||||
#~ msgstr[0] "Én deling"
|
||||
|
|
108
po/pl.po
108
po/pl.po
|
@ -3,7 +3,7 @@ msgstr ""
|
|||
"Project-Id-Version: plume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||
"PO-Revision-Date: 2018-07-28 14:56+0200\n"
|
||||
"PO-Revision-Date: 2018-09-04 17:35+0200\n"
|
||||
"Last-Translator: Marcin Mikołajczak <me@m4sk.in>\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: pl\n"
|
||||
|
@ -12,7 +12,7 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
|
||||
"|| n%100>=20) ? 1 : 2);\n"
|
||||
"X-Generator: Poedit 2.0.9\n"
|
||||
"X-Generator: Poedit 2.1.1\n"
|
||||
|
||||
msgid "Latest articles"
|
||||
msgstr "Najnowsze artykuły"
|
||||
|
@ -92,16 +92,15 @@ msgstr "Dodaj swoje"
|
|||
|
||||
msgid "One Boost"
|
||||
msgid_plural "{{ count }} Boosts"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[2] ""
|
||||
msgstr[0] "Jedno podbicie"
|
||||
msgstr[1] "{{ count }} podbicia"
|
||||
msgstr[2] "{{ count }} podbić"
|
||||
|
||||
#, fuzzy
|
||||
msgid "I don't want to boost this anymore"
|
||||
msgstr "Cofnij udostępnienie"
|
||||
msgstr "Cofnij podbicie"
|
||||
|
||||
msgid "Boost"
|
||||
msgstr ""
|
||||
msgstr "Podbij"
|
||||
|
||||
msgid "Comments"
|
||||
msgstr "Komentarze"
|
||||
|
@ -165,9 +164,8 @@ msgstr "Obserwuj"
|
|||
msgid "Unfollow"
|
||||
msgstr "Przestań obserwować"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Recently boosted"
|
||||
msgstr "Ostatnio udostępniono"
|
||||
msgstr "Ostatnio podbite"
|
||||
|
||||
msgid "One follower"
|
||||
msgid_plural "{{ count }} followers"
|
||||
|
@ -247,9 +245,8 @@ msgstr "Musisz się zalogować, aby zobaczyć swoje powiadomienia"
|
|||
msgid "You need to be logged in order to write a new post"
|
||||
msgstr "Musisz się zalogować, aby utworzyć wpis"
|
||||
|
||||
#, fuzzy
|
||||
msgid "You need to be logged in order to boost a post"
|
||||
msgstr "Musisz się zalogować, aby polubić wpis"
|
||||
msgstr "Musisz się zalogować, aby podbić wpis"
|
||||
|
||||
msgid "Invalid username or password"
|
||||
msgstr "Nieprawidłowa nazwa użytkownika lub hasło"
|
||||
|
@ -268,18 +265,17 @@ msgstr ""
|
|||
"Napisano przez {{ link_1 }}{{ url }}{{ link_2 }}{{ name | escape }}"
|
||||
"{{ link_3 }}"
|
||||
|
||||
#, fuzzy
|
||||
msgid "{{ data }} boosted your article"
|
||||
msgstr "{{ data }} skomentował Twój artykuł"
|
||||
msgstr "{{ data }} podbił(a) Twój artykuł"
|
||||
|
||||
msgid "{{ data }} started following you"
|
||||
msgstr "{{ data }} zaczął Cię obserwować"
|
||||
msgstr "{{ data }} zaczął(-ęła) Cię obserwować"
|
||||
|
||||
msgid "{{ data }} liked your article"
|
||||
msgstr "{{ data }} polubił Twój artykuł"
|
||||
msgstr "{{ data }} polubił(a) Twój artykuł"
|
||||
|
||||
msgid "{{ data }} commented your article"
|
||||
msgstr "{{ data }} skomentował Twój artykuł"
|
||||
msgstr "{{ data }} skomentował(a) Twój artykuł"
|
||||
|
||||
msgid "We couldn't find this page."
|
||||
msgstr "Nie udało się odnaleźć tej strony."
|
||||
|
@ -294,7 +290,7 @@ msgid "You are not author in this blog."
|
|||
msgstr "Nie jesteś autorem tego bloga."
|
||||
|
||||
msgid "{{ data }} mentioned you."
|
||||
msgstr "{{ data }} wspomniał o Tobie."
|
||||
msgstr "{{ data }} wspomniał(a) o Tobie."
|
||||
|
||||
msgid "Your comment"
|
||||
msgstr "Twój komentarz"
|
||||
|
@ -318,9 +314,8 @@ msgid "We need an email or a username to identify you"
|
|||
msgstr ""
|
||||
"Potrzebujemy nazwy użytkownika lub adresu e-mail, aby Cię zidentyfikować"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Your password can't be empty"
|
||||
msgstr "Twój komentarz nie może być pusty"
|
||||
msgstr "Twoje hasło nie może być puste"
|
||||
|
||||
msgid "Passwords are not matching"
|
||||
msgstr "Hasła nie pasują do siebie"
|
||||
|
@ -361,20 +356,19 @@ msgid "Next page"
|
|||
msgstr "Następna strona"
|
||||
|
||||
msgid "{{ user }} mentioned you."
|
||||
msgstr "{{ user }} wspomniał o Tobie."
|
||||
msgstr "{{ user }} wspomniał(a) o Tobie."
|
||||
|
||||
msgid "{{ user }} commented your article."
|
||||
msgstr "{{ user }} skomentował Twój artykuł."
|
||||
msgstr "{{ user }} skomentował(a) Twój artykuł."
|
||||
|
||||
msgid "{{ user }} is now following you."
|
||||
msgstr "{{ user }} zaczął Cię obserwować."
|
||||
msgstr "{{ user }} zaczął(-ęła) Cię obserwować."
|
||||
|
||||
msgid "{{ user }} liked your article."
|
||||
msgstr "{{ user }} polubił Twój artykuł."
|
||||
msgstr "{{ user }} polubił(a) Twój artykuł."
|
||||
|
||||
#, fuzzy
|
||||
msgid "{{ user }} boosted your article."
|
||||
msgstr "{{ user }} skomentował Twój artykuł."
|
||||
msgstr "{{ user }} podbił(a) Twój artykuł."
|
||||
|
||||
msgid "Source code"
|
||||
msgstr "Kod źródłowy"
|
||||
|
@ -441,7 +435,7 @@ msgid "people"
|
|||
msgstr "osób"
|
||||
|
||||
msgid "Who wrote"
|
||||
msgstr "Którzy napisali"
|
||||
msgstr "Które napisały"
|
||||
|
||||
msgid "articles"
|
||||
msgstr "artykuły"
|
||||
|
@ -449,79 +443,83 @@ msgstr "artykuły"
|
|||
msgid "Read the detailed rules"
|
||||
msgstr "Przeczytaj szczegółowe zasady"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Delete this article"
|
||||
msgstr "Najnowsze artykuły"
|
||||
msgstr "Usuń ten artykuł"
|
||||
|
||||
msgid "And connected to"
|
||||
msgstr ""
|
||||
msgstr "Połączony z"
|
||||
|
||||
#, fuzzy
|
||||
msgid "other instances"
|
||||
msgstr "O tej instancji"
|
||||
msgstr "innych instancji"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Administred by"
|
||||
msgstr "Administracja"
|
||||
msgstr "Administrowany przez"
|
||||
|
||||
msgid "Runs Plume {{ version }}"
|
||||
msgstr ""
|
||||
msgstr "Działa na Plume {{ version }}"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Your media"
|
||||
msgstr "Twój komentarz"
|
||||
msgstr "Twoja zawartość multimedialna"
|
||||
|
||||
msgid "Go to your gallery"
|
||||
msgstr ""
|
||||
msgstr "Przejdź do swojej galerii"
|
||||
|
||||
msgid "{{ name}}'s avatar'"
|
||||
msgstr ""
|
||||
msgstr "Awatar {{name}}"
|
||||
|
||||
msgid "Media details"
|
||||
msgstr ""
|
||||
msgstr "Szczegóły zawartości multimedialnej"
|
||||
|
||||
msgid "Go back to the gallery"
|
||||
msgstr ""
|
||||
msgstr "Powróć do galerii"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Markdown code"
|
||||
msgstr "Markdown jest obsługiwany"
|
||||
msgstr "Kod Markdown"
|
||||
|
||||
msgid "Copy it in your articles to insert this media."
|
||||
msgstr ""
|
||||
msgstr "Skopiuj do swoich artykułów, aby wstawić tę zawartość multimedialną."
|
||||
|
||||
msgid "Use as avatar"
|
||||
msgstr ""
|
||||
msgstr "Użyj jako awataru"
|
||||
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
msgstr "Usuń"
|
||||
|
||||
msgid "Upload"
|
||||
msgstr ""
|
||||
msgstr "Wyślij"
|
||||
|
||||
msgid "You don't have any media yet."
|
||||
msgstr ""
|
||||
msgstr "Nie masz żadnej zawartości multimedialnej."
|
||||
|
||||
msgid "Media upload"
|
||||
msgstr ""
|
||||
msgstr "Wysyłanie zawartości multimedialnej"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Description"
|
||||
msgstr "Szczegółowy opis"
|
||||
msgstr "Opis"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Content warning"
|
||||
msgstr "Zawartość"
|
||||
msgstr "Ostrzeżenie o zawartości"
|
||||
|
||||
msgid "File"
|
||||
msgstr ""
|
||||
msgstr "Plik"
|
||||
|
||||
msgid "Send"
|
||||
msgstr ""
|
||||
msgstr "Wyślij"
|
||||
|
||||
msgid ""
|
||||
"Sorry, but registrations are closed on this instance. Try to find another one"
|
||||
msgstr ""
|
||||
"Przepraszamy, rejestracja jest zamknięta na tej instancji. Spróbuj znaleźć "
|
||||
"inną"
|
||||
|
||||
msgid "Subtitle"
|
||||
msgstr "Podtytuł"
|
||||
|
||||
msgid "Login to like"
|
||||
msgstr "Zaloguj się aby polubić"
|
||||
|
||||
msgid "Login to boost"
|
||||
msgstr "Zaloguj się aby podbić"
|
||||
|
||||
#~ msgid "One reshare"
|
||||
#~ msgid_plural "{{ count }} reshares"
|
||||
|
|
|
@ -486,3 +486,12 @@ msgstr ""
|
|||
|
||||
msgid "Sorry, but registrations are closed on this instance. Try to find another one"
|
||||
msgstr ""
|
||||
|
||||
msgid "Subtitle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Login to like"
|
||||
msgstr ""
|
||||
|
||||
msgid "Login to boost"
|
||||
msgstr ""
|
||||
|
|
|
@ -55,6 +55,10 @@ pub trait Inbox {
|
|||
"Announce" => {
|
||||
Reshare::delete_id(act.undo_props.object_object::<Announce>()?.object_props.id_string()?, conn);
|
||||
Ok(())
|
||||
},
|
||||
"Follow" => {
|
||||
Follow::delete_id(act.undo_props.object_object::<Like>()?.object_props.id_string()?, conn);
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(InboxError::CantUndo)?
|
||||
}
|
||||
|
|
|
@ -57,7 +57,8 @@ fn details_response(blog: String, slug: String, conn: DbConn, user: Option<User>
|
|||
"date": &post.creation_date.timestamp(),
|
||||
"previous": query.and_then(|q| q.responding_to.map(|r| Comment::get(&*conn, r).expect("Error retrieving previous comment").to_json(&*conn, &vec![]))),
|
||||
"user_fqn": user.clone().map(|u| u.get_fqn(&*conn)).unwrap_or(String::new()),
|
||||
"is_author": user.map(|u| post.get_authors(&*conn).into_iter().any(|a| u.id == a.id)).unwrap_or(false)
|
||||
"is_author": user.clone().map(|u| post.get_authors(&*conn).into_iter().any(|a| u.id == a.id)).unwrap_or(false),
|
||||
"is_following": user.map(|u| u.is_following(&*conn, post.get_authors(&*conn)[0].id)).unwrap_or(false)
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
@ -98,6 +99,7 @@ fn new(blog: String, user: User, conn: DbConn) -> Template {
|
|||
struct NewPostForm {
|
||||
#[validate(custom(function = "valid_slug", message = "Invalid title"))]
|
||||
pub title: String,
|
||||
pub subtitle: String,
|
||||
pub content: String,
|
||||
pub license: String
|
||||
}
|
||||
|
@ -150,7 +152,8 @@ fn create(blog_name: String, data: LenientForm<NewPostForm>, 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 {
|
||||
|
|
|
@ -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<User>, worker: Worker, fe
|
|||
.unwrap_or_else(|| User::fetch_from_url(&*fecth_followers_conn, user_id).expect("Couldn't fetch follower"));
|
||||
follows::Follow::insert(&*fecth_followers_conn, follows::NewFollow {
|
||||
follower_id: follower.id,
|
||||
following_id: user_clone.id
|
||||
following_id: user_clone.id,
|
||||
ap_url: format!("{}/follow/{}", follower.ap_url, user_clone.ap_url),
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
@ -116,20 +117,20 @@ fn dashboard_auth() -> Flash<Redirect> {
|
|||
#[get("/@/<name>/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::<Id>(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::<Id>(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))
|
||||
}
|
||||
|
||||
|
|
|
@ -192,9 +192,18 @@
|
|||
}
|
||||
|
||||
article img {
|
||||
display: block;
|
||||
margin: 3em auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
article pre {
|
||||
padding: 1em;
|
||||
background: #DADADA;
|
||||
overflow: auto;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Article.Meta */
|
||||
|
||||
main .article-meta, main .article-meta button {
|
||||
|
@ -235,8 +244,8 @@
|
|||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
main .article-meta .likes button,
|
||||
main .article-meta .reshares button {
|
||||
main .article-meta .likes .action,
|
||||
main .article-meta .reshares .action {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
@ -246,15 +255,16 @@
|
|||
background: none;
|
||||
color: #242424;
|
||||
border: none;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
main .article-meta .likes > p,
|
||||
main .article-meta .likes button:hover { color: #E92F2F; }
|
||||
main .article-meta .likes .action:hover { color: #E92F2F; }
|
||||
main .article-meta .reshares > p,
|
||||
main .article-meta .reshares button:hover { color: #7765E3; }
|
||||
main .article-meta .reshares .action:hover { color: #7765E3; }
|
||||
|
||||
main .article-meta .likes button svg.feather,
|
||||
main .article-meta .reshares button i {
|
||||
main .article-meta .likes .action svg.feather,
|
||||
main .article-meta .reshares .action i {
|
||||
transition: background 0.1s ease-in;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -267,39 +277,39 @@
|
|||
border-radius: 50%;
|
||||
}
|
||||
|
||||
main .article-meta .likes button svg.feather {
|
||||
main .article-meta .likes .action svg.feather {
|
||||
padding: 0.7em;
|
||||
box-sizing: border-box;
|
||||
color: #E92F2F;
|
||||
fill: none;
|
||||
border: solid #E92F2F thin;
|
||||
}
|
||||
main .article-meta .likes button:hover svg.feather {
|
||||
main .article-meta .likes .action:hover svg.feather {
|
||||
background: rgba(233, 47, 47, 0.15);
|
||||
}
|
||||
|
||||
main .article-meta .reshares button i {
|
||||
main .article-meta .reshares .action i {
|
||||
color: #7765E3;
|
||||
border: solid #7765E3 thin;
|
||||
font-weight: 600;
|
||||
}
|
||||
main .article-meta .reshares button:hover i {
|
||||
main .article-meta .reshares .action:hover i {
|
||||
background: rgba(119, 101, 227, 0.15);
|
||||
}
|
||||
|
||||
main .article-meta .likes button.liked svg.feather { background: #E92F2F; fill: currentColor; }
|
||||
main .article-meta .likes button.liked:hover svg.feather {
|
||||
main .article-meta .likes .action.liked svg.feather { background: #E92F2F; fill: currentColor; }
|
||||
main .article-meta .likes .action.liked:hover svg.feather {
|
||||
background: rgba(233, 47, 47, 0.25);
|
||||
color: #E92F2F;
|
||||
}
|
||||
main .article-meta .reshares button.reshared i { background: #7765E3; }
|
||||
main .article-meta .reshares button.reshared:hover i {
|
||||
main .article-meta .reshares .action.reshared i { background: #7765E3; }
|
||||
main .article-meta .reshares .action.reshared:hover i {
|
||||
background: rgba(119, 101, 227, 0.25);
|
||||
color: #7765E3;
|
||||
}
|
||||
|
||||
main .article-meta .likes button.liked svg.feather,
|
||||
main .article-meta .reshares button.reshared i {
|
||||
main .article-meta .likes .action.liked svg.feather,
|
||||
main .article-meta .reshares .action.reshared i {
|
||||
color: #F4F4F4;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
@ -796,5 +806,5 @@
|
|||
}
|
||||
|
||||
.avatar.padded {
|
||||
margin-right: 1em;
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<span class="mobile-label">{{ "Notifications" | _ }}</span>
|
||||
</a>
|
||||
<a href="/logout">
|
||||
<i class="icon icon-log-out aria-label="{{ "Log Out" | _ }}"></i>
|
||||
<i class="icon icon-log-out" aria-label="{{ "Log Out" | _ }}"></i>
|
||||
<span class="mobile-label">{{ "Log Out" | _ }}</span>
|
||||
</a>
|
||||
<a href="/me">
|
||||
|
|
|
@ -2,7 +2,13 @@
|
|||
<div class="card">
|
||||
<h3><a href="{{ article.url }}">{{ article.post.title }}</a></h3>
|
||||
<main
|
||||
<p>{{ article.post.content | safe | striptags | truncate(length=200) }}</p>
|
||||
<p>
|
||||
{% if article.post.subtitle | length > 0 %}
|
||||
{{ article.post.subtitle }}
|
||||
{% else %}
|
||||
{{ article.post.content | safe | striptags | truncate(length=200) }}
|
||||
{% endif %}
|
||||
</p>
|
||||
</main>
|
||||
<p class="author">
|
||||
{{ "By {{ link_1 }}{{ link_2 }}{{ link_3 }}{{ name | escape }}{{ link_4 }}" | _(
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
{% block content %}
|
||||
<h1 class="article">{{ article.post.title }}</h1>
|
||||
<h2 class="article">{{ article.post.subtitle }}</h2>
|
||||
<p class="article-info">
|
||||
<span class="author">{{ "Written by {{ link_1 }}{{ url }}{{ link_2 }}{{ name | escape }}{{ link_3 }}" | _(
|
||||
link_1='<a href="/@/',
|
||||
|
@ -33,6 +34,20 @@
|
|||
|
||||
<div class="article-meta">
|
||||
<p>{{ "This article is under the {{ license }} license." | _(license=article.post.license) }}</p>
|
||||
<div class="flex">
|
||||
<img src="{{ author.avatar }}" alt="{{ author.name }}" class="avatar medium padded">
|
||||
<div class="grow">
|
||||
<h2><a href="/@/{{ author.fqn }}">{{ author.name }}</a></h2>
|
||||
<p>{{ author.summary | safe }}</h2>
|
||||
</div>
|
||||
<a href="/@/{{ author.fqn }}/follow" class="button">
|
||||
{% if is_following %}
|
||||
{{ "Unfollow" | _ }}
|
||||
{% else %}
|
||||
{{ "Follow" | _ }}
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if account %}
|
||||
<div class="actions">
|
||||
|
@ -40,23 +55,34 @@
|
|||
<p aria-label="{{ "{{ count }} likes" | _n(singular="One like", count=n_likes) }}" title="{{ "{{ count }} likes" | _n(singular="One like", count=n_likes) }}">{{ n_likes }}</p>
|
||||
|
||||
{% if has_liked %}
|
||||
<button type="submit" class="liked">{{ macros::feather(name="heart") }}{{ "I don't like this anymore" | _ }}</button>
|
||||
<button type="submit" class="action liked">{{ macros::feather(name="heart") }}{{ "I don't like this anymore" | _ }}</button>
|
||||
{% else %}
|
||||
<button type="submit">{{ macros::feather(name="heart") }}{{ "Add yours" | _ }}</button>
|
||||
<button type="submit" class="action">{{ macros::feather(name="heart") }}{{ "Add yours" | _ }}</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
<form class="reshares" action="{{ article.url }}reshare" method="POST">
|
||||
<p aria-label="{{ "{{ count }} Boosts" | _n(singular="One Boost", count=n_reshares) }}" title="{{ "{{ count }} Boosts" | _n(singular="One Boost", count=n_reshares) }}">{{ n_reshares }}</p>
|
||||
|
||||
{% if has_reshared %}
|
||||
<button type="submit" class="reshared"><i class="icon icon-repeat"></i>{{ "I don't want to boost this anymore" | _ }}</button>
|
||||
<button type="submit" class="action reshared"><i class="icon icon-repeat"></i>{{ "I don't want to boost this anymore" | _ }}</button>
|
||||
{% else %}
|
||||
<button type="submit"><i class="icon icon-repeat"></i>{{ "Boost" | _ }}</button>
|
||||
<button type="submit" class="action"><i class="icon icon-repeat"></i>{{ "Boost" | _ }}</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="center">{{ "Login or use your Fediverse account to interact with this article" | _ }}</p>
|
||||
<div class="actions">
|
||||
<div class="likes">
|
||||
<p aria-label="{{ "{{ count }} likes" | _n(singular="One like", count=n_likes) }}" title="{{ "{{ count }} likes" | _n(singular="One like", count=n_likes) }}">{{ n_likes }}</p>
|
||||
<a href="/login?m=Login%20to%20like" class="action">{{ macros::feather(name="heart") }}{{ "Add yours" | _ }}</a>
|
||||
</div>
|
||||
|
||||
<div class="reshares">
|
||||
<p aria-label="{{ "{{ count }} Boosts" | _n(singular="One Boost", count=n_reshares) }}" title="{{ "{{ count }} Boosts" | _n(singular="One Boost", count=n_reshares) }}">{{ n_reshares }}</p>
|
||||
<a href="/login?m=Login%20to%20boost" class="action"><i class="icon icon-repeat"></i>{{ "Boost" | _ }}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="comments">
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<h1>{{ "Create a post" | _ }}</h1>
|
||||
<form class="new-post" method="post">
|
||||
{{ macros::input(name="title", label="Title", errors=errors, form=form, props="required") }}
|
||||
{{ macros::input(name="subtitle", label="Subtitle", errors=errors, form=form, optional=true) }}
|
||||
|
||||
{% if errors is defined and errors.content %}
|
||||
{% for err in errors.content %}
|
||||
|
|
Loading…
Reference in a new issue