Slightly improve the media experience (#452)

* Slightly improve the media experience

- Use a grid to display the list of media
- Add icons for non-image media preview
- Paginate the gallery
- Add links to the gallery in the editor and in the profile settings to make it more discoverable when you need it

Fixes #432

* Allow video and audio tags in SafeString

Otherwise we can't display their preview, nor show them in articles

Also show controls by default for these two elements

* Show fallback images for audio and unknown files, to make them more visible

* Add a new constructor to SafeString when the input is trusted and doesn't need to be escaped.

And use it to generate media previews.

* Make it possible to insert video/audio in articles
This commit is contained in:
Baptiste Gelez 2019-03-06 14:11:36 +01:00 committed by GitHub
parent a5e0486da0
commit eff2698664
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 677 additions and 58 deletions

View File

@ -7,6 +7,7 @@ trim_trailing_whitespace = true
[*.{js,rs,css,tera,html}] [*.{js,rs,css,tera,html}]
charset = utf-8 charset = utf-8
indent_size = 4
[*.{rs,tera,css,html}] [*.{rs,tera,css,html}]
indent_style = space indent_style = space

View File

@ -45,6 +45,17 @@ pub enum MediaCategory {
Unknown, Unknown,
} }
impl MediaCategory {
pub fn to_string(&self) -> &str {
match *self {
MediaCategory::Image => "image",
MediaCategory::Audio => "audio",
MediaCategory::Video => "video",
MediaCategory::Unknown => "unknown",
}
}
}
impl Media { impl Media {
insert!(medias, NewMedia); insert!(medias, NewMedia);
get!(medias); get!(medias);
@ -56,6 +67,23 @@ impl Media {
.map_err(Error::from) .map_err(Error::from)
} }
pub fn page_for_user(conn: &Connection, user: &User, (min, max): (i32, i32)) -> Result<Vec<Media>> {
medias::table
.filter(medias::owner_id.eq(user.id))
.offset(min as i64)
.limit((max - min) as i64)
.load::<Media>(conn)
.map_err(Error::from)
}
pub fn count_for_user(conn: &Connection, user: &User) -> Result<i64> {
medias::table
.filter(medias::owner_id.eq(user.id))
.count()
.get_result(conn)
.map_err(Error::from)
}
pub fn category(&self) -> MediaCategory { pub fn category(&self) -> MediaCategory {
match &*self match &*self
.file_path .file_path
@ -71,41 +99,25 @@ impl Media {
} }
} }
pub fn preview_html(&self, conn: &Connection) -> Result<SafeString> {
let url = self.url(conn)?;
Ok(match self.category() {
MediaCategory::Image => SafeString::new(&format!(
r#"<img src="{}" alt="{}" title="{}" class=\"preview\">"#,
url, escape(&self.alt_text), escape(&self.alt_text)
)),
MediaCategory::Audio => SafeString::new(&format!(
r#"<audio src="{}" title="{}" class="preview"></audio>"#,
url, escape(&self.alt_text)
)),
MediaCategory::Video => SafeString::new(&format!(
r#"<video src="{}" title="{}" class="preview"></video>"#,
url, escape(&self.alt_text)
)),
MediaCategory::Unknown => SafeString::new(""),
})
}
pub fn html(&self, conn: &Connection) -> Result<SafeString> { pub fn html(&self, conn: &Connection) -> Result<SafeString> {
let url = self.url(conn)?; let url = self.url(conn)?;
Ok(match self.category() { Ok(match self.category() {
MediaCategory::Image => SafeString::new(&format!( MediaCategory::Image => SafeString::trusted(&format!(
r#"<img src="{}" alt="{}" title="{}">"#, r#"<img src="{}" alt="{}" title="{}">"#,
url, escape(&self.alt_text), escape(&self.alt_text) url, escape(&self.alt_text), escape(&self.alt_text)
)), )),
MediaCategory::Audio => SafeString::new(&format!( MediaCategory::Audio => SafeString::trusted(&format!(
r#"<audio src="{}" title="{}"></audio>"#, r#"<div class="media-preview audio"></div><audio src="{}" title="{}" controls></audio>"#,
url, escape(&self.alt_text) url, escape(&self.alt_text)
)), )),
MediaCategory::Video => SafeString::new(&format!( MediaCategory::Video => SafeString::trusted(&format!(
r#"<video src="{}" title="{}"></video>"#, r#"<video src="{}" title="{}" controls></video>"#,
url, escape(&self.alt_text) url, escape(&self.alt_text)
)), )),
MediaCategory::Unknown => SafeString::new(""), MediaCategory::Unknown => SafeString::trusted(&format!(
r#"<a href="{}" class="media-preview unknown"></a>"#,
url,
)),
}) })
} }

View File

@ -19,12 +19,20 @@ lazy_static! {
static ref CLEAN: Builder<'static> = { static ref CLEAN: Builder<'static> = {
let mut b = Builder::new(); let mut b = Builder::new();
b.add_generic_attributes(iter::once("id")) b.add_generic_attributes(iter::once("id"))
.add_tags(iter::once("iframe")) .add_tags(&[ "iframe", "video", "audio" ])
.id_prefix(Some("postcontent-")) .id_prefix(Some("postcontent-"))
.url_relative(UrlRelative::Custom(Box::new(url_add_prefix))) .url_relative(UrlRelative::Custom(Box::new(url_add_prefix)))
.add_tag_attributes( .add_tag_attributes(
"iframe", "iframe",
["width", "height", "src", "frameborder"].iter().map(|&v| v), [ "width", "height", "src", "frameborder" ].iter().map(|&v| v),
)
.add_tag_attributes(
"video",
[ "src", "title", "controls" ].iter(),
)
.add_tag_attributes(
"audio",
[ "src", "title", "controls" ].iter(),
); );
b b
}; };
@ -53,6 +61,18 @@ impl SafeString {
value: CLEAN.clean(&value).to_string(), value: CLEAN.clean(&value).to_string(),
} }
} }
/// Creates a new `SafeString`, but without escaping the given value.
///
/// Only use when you are sure you can trust the input (when the HTML
/// is entirely generated by Plume, not depending on user-inputed data).
/// Prefer `SafeString::new` as much as possible.
pub fn trusted(value: impl AsRef<str>) -> Self {
SafeString {
value: value.as_ref().to_string()
}
}
pub fn set(&mut self, value: &str) { pub fn set(&mut self, value: &str) {
self.value = CLEAN.clean(value).to_string(); self.value = CLEAN.clean(value).to_string();
} }

View File

@ -236,6 +236,13 @@ msgstr "تعديل حسابك"
msgid "Your Profile" msgid "Your Profile"
msgstr "ملفك الشخصي" msgstr "ملفك الشخصي"
msgid "To change your avatar, upload it in your gallery and select from there."
msgstr ""
#, fuzzy
msgid "Upload an avatar"
msgstr "استخدمها كصورة رمزية"
#, fuzzy #, fuzzy
msgid "Display name" msgid "Display name"
msgstr "الاسم العلني" msgstr "الاسم العلني"
@ -526,6 +533,15 @@ msgstr "العنوان الثانوي"
msgid "Content" msgid "Content"
msgstr "المحتوى" msgstr "المحتوى"
msgid ""
"You can upload medias to your gallery, and copy their Markdown code in your "
"articles to insert them."
msgstr ""
#, fuzzy
msgid "Upload media"
msgstr "إرسال"
# src/template_utils.rs:144 # src/template_utils.rs:144
msgid "Tags, separated by commas" msgid "Tags, separated by commas"
msgstr "" msgstr ""
@ -719,9 +735,17 @@ msgstr "إرسال"
msgid "You don't have any media yet." msgid "You don't have any media yet."
msgstr "ليس لديك أية وسائط بعد." msgstr "ليس لديك أية وسائط بعد."
#, fuzzy
msgid "Content warning: {0}"
msgstr "تحذير عن المحتوى"
msgid "Delete" msgid "Delete"
msgstr "حذف" msgstr "حذف"
#, fuzzy
msgid "Details"
msgstr "تفاصيل الصورة"
msgid "Media upload" msgid "Media upload"
msgstr "إرسال الوسائط" msgstr "إرسال الوسائط"

View File

@ -248,6 +248,13 @@ msgstr "Ändere deinen Account"
msgid "Your Profile" msgid "Your Profile"
msgstr "Dein Profil" msgstr "Dein Profil"
msgid "To change your avatar, upload it in your gallery and select from there."
msgstr ""
#, fuzzy
msgid "Upload an avatar"
msgstr "Als Avatar verwenden"
#, fuzzy #, fuzzy
msgid "Display name" msgid "Display name"
msgstr "Anzeigename" msgstr "Anzeigename"
@ -541,6 +548,15 @@ msgstr "Untertitel"
msgid "Content" msgid "Content"
msgstr "Inhalt" msgstr "Inhalt"
msgid ""
"You can upload medias to your gallery, and copy their Markdown code in your "
"articles to insert them."
msgstr ""
#, fuzzy
msgid "Upload media"
msgstr "Hochladen"
# src/template_utils.rs:143 # src/template_utils.rs:143
msgid "Tags, separated by commas" msgid "Tags, separated by commas"
msgstr "" msgstr ""
@ -736,9 +752,16 @@ msgstr "Hochladen"
msgid "You don't have any media yet." msgid "You don't have any media yet."
msgstr "Derzeit sind noch keine Mediendateien hochgeladen." msgstr "Derzeit sind noch keine Mediendateien hochgeladen."
#, fuzzy
msgid "Content warning: {0}"
msgstr "Warnhinweis zum Inhalt"
msgid "Delete" msgid "Delete"
msgstr "Löschen" msgstr "Löschen"
msgid "Details"
msgstr ""
msgid "Media upload" msgid "Media upload"
msgstr "Hochladen von Mediendateien" msgstr "Hochladen von Mediendateien"

View File

@ -241,6 +241,12 @@ msgstr ""
msgid "Your Profile" msgid "Your Profile"
msgstr "" msgstr ""
msgid "To change your avatar, upload it in your gallery and select from there."
msgstr ""
msgid "Upload an avatar"
msgstr ""
# src/template_utils.rs:144 # src/template_utils.rs:144
msgid "Display name" msgid "Display name"
msgstr "" msgstr ""
@ -518,6 +524,14 @@ msgstr ""
msgid "Content" msgid "Content"
msgstr "" msgstr ""
msgid ""
"You can upload medias to your gallery, and copy their Markdown code in your "
"articles to insert them."
msgstr ""
msgid "Upload media"
msgstr ""
# src/template_utils.rs:144 # src/template_utils.rs:144
msgid "Tags, separated by commas" msgid "Tags, separated by commas"
msgstr "" msgstr ""
@ -683,9 +697,15 @@ msgstr ""
msgid "You don't have any media yet." msgid "You don't have any media yet."
msgstr "" msgstr ""
msgid "Content warning: {0}"
msgstr ""
msgid "Delete" msgid "Delete"
msgstr "" msgstr ""
msgid "Details"
msgstr ""
msgid "Media upload" msgid "Media upload"
msgstr "" msgstr ""

View File

@ -229,6 +229,12 @@ msgstr "Edita tu cuenta"
msgid "Your Profile" msgid "Your Profile"
msgstr "Tu perfil" msgstr "Tu perfil"
msgid "To change your avatar, upload it in your gallery and select from there."
msgstr ""
msgid "Upload an avatar"
msgstr ""
# src/template_utils.rs:144 # src/template_utils.rs:144
msgid "Display name" msgid "Display name"
msgstr "" msgstr ""
@ -503,6 +509,14 @@ msgstr ""
msgid "Content" msgid "Content"
msgstr "Contenido" msgstr "Contenido"
msgid ""
"You can upload medias to your gallery, and copy their Markdown code in your "
"articles to insert them."
msgstr ""
msgid "Upload media"
msgstr ""
# src/template_utils.rs:144 # src/template_utils.rs:144
msgid "Tags, separated by commas" msgid "Tags, separated by commas"
msgstr "" msgstr ""
@ -672,9 +686,15 @@ msgstr ""
msgid "You don't have any media yet." msgid "You don't have any media yet."
msgstr "" msgstr ""
msgid "Content warning: {0}"
msgstr ""
msgid "Delete" msgid "Delete"
msgstr "" msgstr ""
msgid "Details"
msgstr ""
msgid "Media upload" msgid "Media upload"
msgstr "" msgstr ""

View File

@ -247,6 +247,13 @@ msgstr "Modifier votre compte"
msgid "Your Profile" msgid "Your Profile"
msgstr "Votre profil" msgstr "Votre profil"
msgid "To change your avatar, upload it in your gallery and select from there."
msgstr ""
#, fuzzy
msgid "Upload an avatar"
msgstr "Utiliser comme avatar"
#, fuzzy #, fuzzy
msgid "Display name" msgid "Display name"
msgstr "Nom affiché" msgstr "Nom affiché"
@ -536,6 +543,15 @@ msgstr "Sous-titre"
msgid "Content" msgid "Content"
msgstr "Contenu" msgstr "Contenu"
msgid ""
"You can upload medias to your gallery, and copy their Markdown code in your "
"articles to insert them."
msgstr ""
#, fuzzy
msgid "Upload media"
msgstr "Téléverser"
# src/template_utils.rs:143 # src/template_utils.rs:143
msgid "Tags, separated by commas" msgid "Tags, separated by commas"
msgstr "" msgstr ""
@ -730,9 +746,16 @@ msgstr "Téléverser"
msgid "You don't have any media yet." msgid "You don't have any media yet."
msgstr "Vous navez pas encore de média." msgstr "Vous navez pas encore de média."
#, fuzzy
msgid "Content warning: {0}"
msgstr "Avertissement"
msgid "Delete" msgid "Delete"
msgstr "Supprimer" msgstr "Supprimer"
msgid "Details"
msgstr ""
msgid "Media upload" msgid "Media upload"
msgstr "Téléversement de média" msgstr "Téléversement de média"

View File

@ -247,6 +247,13 @@ msgstr "Edite a súa conta"
msgid "Your Profile" msgid "Your Profile"
msgstr "O seu perfil" msgstr "O seu perfil"
msgid "To change your avatar, upload it in your gallery and select from there."
msgstr ""
#, fuzzy
msgid "Upload an avatar"
msgstr "Utilizar como avatar"
#, fuzzy #, fuzzy
msgid "Display name" msgid "Display name"
msgstr "Nome mostrado" msgstr "Nome mostrado"
@ -536,6 +543,15 @@ msgstr "Subtítulo"
msgid "Content" msgid "Content"
msgstr "Contido" msgstr "Contido"
msgid ""
"You can upload medias to your gallery, and copy their Markdown code in your "
"articles to insert them."
msgstr ""
#, fuzzy
msgid "Upload media"
msgstr "Subir"
# src/template_utils.rs:143 # src/template_utils.rs:143
msgid "Tags, separated by commas" msgid "Tags, separated by commas"
msgstr "" msgstr ""
@ -726,9 +742,16 @@ msgstr "Subir"
msgid "You don't have any media yet." msgid "You don't have any media yet."
msgstr "Aínda non ten medios" msgstr "Aínda non ten medios"
#, fuzzy
msgid "Content warning: {0}"
msgstr "Aviso sobre o contido"
msgid "Delete" msgid "Delete"
msgstr "Eliminar" msgstr "Eliminar"
msgid "Details"
msgstr ""
msgid "Media upload" msgid "Media upload"
msgstr "Subir medios" msgstr "Subir medios"

View File

@ -247,6 +247,13 @@ msgstr "Modifica il tuo account"
msgid "Your Profile" msgid "Your Profile"
msgstr "Il tuo Profilo" msgstr "Il tuo Profilo"
msgid "To change your avatar, upload it in your gallery and select from there."
msgstr ""
#, fuzzy
msgid "Upload an avatar"
msgstr "Usa come avatar"
#, fuzzy #, fuzzy
msgid "Display name" msgid "Display name"
msgstr "Nome Visualizzato" msgstr "Nome Visualizzato"
@ -539,6 +546,15 @@ msgstr "Sottotitolo"
msgid "Content" msgid "Content"
msgstr "Contenuto" msgstr "Contenuto"
msgid ""
"You can upload medias to your gallery, and copy their Markdown code in your "
"articles to insert them."
msgstr ""
#, fuzzy
msgid "Upload media"
msgstr "Carica"
# src/template_utils.rs:143 # src/template_utils.rs:143
msgid "Tags, separated by commas" msgid "Tags, separated by commas"
msgstr "" msgstr ""
@ -733,9 +749,16 @@ msgstr "Carica"
msgid "You don't have any media yet." msgid "You don't have any media yet."
msgstr "Non hai ancora nessun media." msgstr "Non hai ancora nessun media."
#, fuzzy
msgid "Content warning: {0}"
msgstr "Avviso di contenuto sensibile"
msgid "Delete" msgid "Delete"
msgstr "Elimina" msgstr "Elimina"
msgid "Details"
msgstr ""
msgid "Media upload" msgid "Media upload"
msgstr "Caricamento di un media" msgstr "Caricamento di un media"

View File

@ -240,6 +240,13 @@ msgstr "自分のアカウントを編集"
msgid "Your Profile" msgid "Your Profile"
msgstr "自分のプロフィール" msgstr "自分のプロフィール"
msgid "To change your avatar, upload it in your gallery and select from there."
msgstr ""
#, fuzzy
msgid "Upload an avatar"
msgstr "アバターとして使う"
#, fuzzy #, fuzzy
msgid "Display name" msgid "Display name"
msgstr "表示名" msgstr "表示名"
@ -531,6 +538,15 @@ msgstr "サブタイトル"
msgid "Content" msgid "Content"
msgstr "コメント" msgstr "コメント"
msgid ""
"You can upload medias to your gallery, and copy their Markdown code in your "
"articles to insert them."
msgstr ""
#, fuzzy
msgid "Upload media"
msgstr "アップロード"
# src/template_utils.rs:144 # src/template_utils.rs:144
msgid "Tags, separated by commas" msgid "Tags, separated by commas"
msgstr "" msgstr ""
@ -709,9 +725,17 @@ msgstr "アップロード"
msgid "You don't have any media yet." msgid "You don't have any media yet."
msgstr "メディアがまだありません。" msgstr "メディアがまだありません。"
#, fuzzy
msgid "Content warning: {0}"
msgstr "コンテンツの警告"
msgid "Delete" msgid "Delete"
msgstr "削除" msgstr "削除"
#, fuzzy
msgid "Details"
msgstr "メディアの詳細"
msgid "Media upload" msgid "Media upload"
msgstr "メディアのアップロード" msgstr "メディアのアップロード"

View File

@ -253,6 +253,12 @@ msgstr "Rediger kontoen din"
msgid "Your Profile" msgid "Your Profile"
msgstr "Din profil" msgstr "Din profil"
msgid "To change your avatar, upload it in your gallery and select from there."
msgstr ""
msgid "Upload an avatar"
msgstr ""
#, fuzzy #, fuzzy
msgid "Display name" msgid "Display name"
msgstr "Visningsnavn" msgstr "Visningsnavn"
@ -565,6 +571,15 @@ msgstr "Tittel"
msgid "Content" msgid "Content"
msgstr "Innhold" msgstr "Innhold"
msgid ""
"You can upload medias to your gallery, and copy their Markdown code in your "
"articles to insert them."
msgstr ""
#, fuzzy
msgid "Upload media"
msgstr "Din kommentar"
# src/template_utils.rs:143 # src/template_utils.rs:143
msgid "Tags, separated by commas" msgid "Tags, separated by commas"
msgstr "" msgstr ""
@ -748,9 +763,16 @@ msgstr ""
msgid "You don't have any media yet." msgid "You don't have any media yet."
msgstr "" msgstr ""
#, fuzzy
msgid "Content warning: {0}"
msgstr "Innhold"
msgid "Delete" msgid "Delete"
msgstr "" msgstr ""
msgid "Details"
msgstr ""
msgid "Media upload" msgid "Media upload"
msgstr "" msgstr ""

View File

@ -222,6 +222,13 @@ msgstr "Edytuj swoje konto"
msgid "Your Profile" msgid "Your Profile"
msgstr "Twój profil" msgstr "Twój profil"
msgid "To change your avatar, upload it in your gallery and select from there."
msgstr ""
#, fuzzy
msgid "Upload an avatar"
msgstr "Użyj jako awataru"
msgid "Display name" msgid "Display name"
msgstr "Nazwa wyświetlana" msgstr "Nazwa wyświetlana"
@ -505,6 +512,15 @@ msgstr "Podtytuł"
msgid "Content" msgid "Content"
msgstr "Zawartość" msgstr "Zawartość"
msgid ""
"You can upload medias to your gallery, and copy their Markdown code in your "
"articles to insert them."
msgstr ""
#, fuzzy
msgid "Upload media"
msgstr "Wyślij"
# src/template_utils.rs:143 # src/template_utils.rs:143
msgid "Tags, separated by commas" msgid "Tags, separated by commas"
msgstr "Tagi, oddzielone przecinkami" msgstr "Tagi, oddzielone przecinkami"
@ -678,9 +694,17 @@ msgstr "Wyślij"
msgid "You don't have any media yet." msgid "You don't have any media yet."
msgstr "Nie masz żadnej zawartości multimedialnej." msgstr "Nie masz żadnej zawartości multimedialnej."
#, fuzzy
msgid "Content warning: {0}"
msgstr "Ostrzeżenie o zawartości"
msgid "Delete" msgid "Delete"
msgstr "Usuń" msgstr "Usuń"
#, fuzzy
msgid "Details"
msgstr "Szczegóły zawartości multimedialnej"
msgid "Media upload" msgid "Media upload"
msgstr "Wysyłanie zawartości multimedialnej" msgstr "Wysyłanie zawartości multimedialnej"

View File

@ -239,6 +239,12 @@ msgstr ""
msgid "Your Profile" msgid "Your Profile"
msgstr "" msgstr ""
msgid "To change your avatar, upload it in your gallery and select from there."
msgstr ""
msgid "Upload an avatar"
msgstr ""
# src/template_utils.rs:144 # src/template_utils.rs:144
msgid "Display name" msgid "Display name"
msgstr "" msgstr ""
@ -510,6 +516,12 @@ msgstr ""
msgid "Content" msgid "Content"
msgstr "" msgstr ""
msgid "You can upload medias to your gallery, and copy their Markdown code in your articles to insert them."
msgstr ""
msgid "Upload media"
msgstr ""
# src/template_utils.rs:144 # src/template_utils.rs:144
msgid "Tags, separated by commas" msgid "Tags, separated by commas"
msgstr "" msgstr ""
@ -668,9 +680,15 @@ msgstr ""
msgid "You don't have any media yet." msgid "You don't have any media yet."
msgstr "" msgstr ""
msgid "Content warning: {0}"
msgstr ""
msgid "Delete" msgid "Delete"
msgstr "" msgstr ""
msgid "Details"
msgstr ""
msgid "Media upload" msgid "Media upload"
msgstr "" msgstr ""

View File

@ -235,6 +235,13 @@ msgstr "Editar sua conta"
msgid "Your Profile" msgid "Your Profile"
msgstr "Seu Perfil" msgstr "Seu Perfil"
msgid "To change your avatar, upload it in your gallery and select from there."
msgstr ""
#, fuzzy
msgid "Upload an avatar"
msgstr "Utilizar como avatar"
#, fuzzy #, fuzzy
msgid "Display name" msgid "Display name"
msgstr "Nome exibido" msgstr "Nome exibido"
@ -526,6 +533,15 @@ msgstr "Subtítulo"
msgid "Content" msgid "Content"
msgstr "Conteúdo" msgstr "Conteúdo"
msgid ""
"You can upload medias to your gallery, and copy their Markdown code in your "
"articles to insert them."
msgstr ""
#, fuzzy
msgid "Upload media"
msgstr "Carregar"
# src/template_utils.rs:144 # src/template_utils.rs:144
msgid "Tags, separated by commas" msgid "Tags, separated by commas"
msgstr "" msgstr ""
@ -701,9 +717,17 @@ msgstr "Carregar"
msgid "You don't have any media yet." msgid "You don't have any media yet."
msgstr "Você ainda não tem nenhuma mídia." msgstr "Você ainda não tem nenhuma mídia."
#, fuzzy
msgid "Content warning: {0}"
msgstr "Alerta de conteúdo"
msgid "Delete" msgid "Delete"
msgstr "Suprimir" msgstr "Suprimir"
#, fuzzy
msgid "Details"
msgstr "Detalhes da mídia"
msgid "Media upload" msgid "Media upload"
msgstr "Carregamento de mídia" msgstr "Carregamento de mídia"

View File

@ -252,6 +252,13 @@ msgstr "Редактировать ваш аккаунт"
msgid "Your Profile" msgid "Your Profile"
msgstr "Ваш профиль" msgstr "Ваш профиль"
msgid "To change your avatar, upload it in your gallery and select from there."
msgstr ""
#, fuzzy
msgid "Upload an avatar"
msgstr "Использовать как аватар"
#, fuzzy #, fuzzy
msgid "Display name" msgid "Display name"
msgstr "Имя для отображения" msgstr "Имя для отображения"
@ -543,6 +550,15 @@ msgstr "Подзаголовок"
msgid "Content" msgid "Content"
msgstr "Содержимое" msgstr "Содержимое"
msgid ""
"You can upload medias to your gallery, and copy their Markdown code in your "
"articles to insert them."
msgstr ""
#, fuzzy
msgid "Upload media"
msgstr "Загрузить"
# src/template_utils.rs:143 # src/template_utils.rs:143
msgid "Tags, separated by commas" msgid "Tags, separated by commas"
msgstr "" msgstr ""
@ -740,9 +756,17 @@ msgstr "Загрузить"
msgid "You don't have any media yet." msgid "You don't have any media yet."
msgstr "Пока что вы не можете загружать медиафайлы." msgstr "Пока что вы не можете загружать медиафайлы."
#, fuzzy
msgid "Content warning: {0}"
msgstr "Предупреждение о контенте"
msgid "Delete" msgid "Delete"
msgstr "Удалить" msgstr "Удалить"
#, fuzzy
msgid "Details"
msgstr "Детали медиафайла"
msgid "Media upload" msgid "Media upload"
msgstr "Загрузка медиафайлов" msgstr "Загрузка медиафайлов"

View File

@ -5,14 +5,17 @@ use rocket_i18n::I18n;
use std::fs; use std::fs;
use plume_models::{Error, db_conn::DbConn, medias::*, users::User}; use plume_models::{Error, db_conn::DbConn, medias::*, users::User};
use template_utils::Ructe; use template_utils::Ructe;
use routes::errors::ErrorPage; use routes::{Page, errors::ErrorPage};
#[get("/medias")] #[get("/medias?<page>")]
pub fn list(user: User, conn: DbConn, intl: I18n) -> Result<Ructe, ErrorPage> { pub fn list(user: User, conn: DbConn, intl: I18n, page: Option<Page>) -> Result<Ructe, ErrorPage> {
let medias = Media::for_user(&*conn, user.id)?; let page = page.unwrap_or_default();
let medias = Media::page_for_user(&*conn, &user, page.limits())?;
Ok(render!(medias::index( Ok(render!(medias::index(
&(&*conn, &intl.catalog, Some(user)), &(&*conn, &intl.catalog, Some(user.clone())),
medias medias,
page.0,
Page::total(Media::count_for_user(&*conn, &user)? as i32)
))) )))
} }
@ -109,7 +112,7 @@ pub fn delete(id: i32, user: User, conn: DbConn) -> Result<Redirect, ErrorPage>
if media.owner_id == user.id { if media.owner_id == user.id {
media.delete(&*conn)?; media.delete(&*conn)?;
} }
Ok(Redirect::to(uri!(list))) Ok(Redirect::to(uri!(list: page = _)))
} }
#[post("/medias/<id>/avatar")] #[post("/medias/<id>/avatar")]

View File

@ -307,6 +307,10 @@ figure {
figcaption { figcaption {
padding: 1em; padding: 1em;
} }
audio, video {
width: 100%;
}
} }
.preview { .preview {
@ -318,6 +322,30 @@ figure {
margin-right: 20px; margin-right: 20px;
} }
.media-preview {
min-height: 8em;
&:not(.image) {
background-color: #7765E3;
background-repeat: no-repeat;
background-position: center;
background-size: 4em;
}
&.unknown {
background-image: url('/static/images/unknown-file.svg');
display: block;
}
&.audio {
background-image: url('/static/images/audio-file.svg');
}
&.video {
background-image: url('/static/images/video-file.svg');
}
}
/// Avatars /// Avatars
.avatar { .avatar {
background-position: center; background-position: center;

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-headphones"
version="1.1"
id="svg6"
sodipodi:docname="audio-file.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview8"
showgrid="false"
inkscape:zoom="9.8333333"
inkscape:cx="12"
inkscape:cy="12"
inkscape:window-x="0"
inkscape:window-y="30"
inkscape:window-maximized="1"
inkscape:current-layer="svg6" />
<path
d="M3 18v-6a9 9 0 0 1 18 0v6"
id="path2"
style="stroke:#ffffff;stroke-opacity:1" />
<path
d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z"
id="path4"
style="stroke:#ffffff;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-file"
version="1.1"
id="svg6"
sodipodi:docname="unknown-file.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview8"
showgrid="false"
inkscape:zoom="9.8333333"
inkscape:cx="12"
inkscape:cy="12"
inkscape:window-x="0"
inkscape:window-y="30"
inkscape:window-maximized="1"
inkscape:current-layer="svg6" />
<path
d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"
id="path2"
style="stroke:#ffffff;stroke-opacity:1" />
<polyline
points="13 2 13 9 20 9"
id="polyline4"
style="stroke:#ffffff;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-film"
version="1.1"
id="svg18"
sodipodi:docname="video-file.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)">
<metadata
id="metadata24">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs22" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview20"
showgrid="false"
inkscape:zoom="9.8333333"
inkscape:cx="12"
inkscape:cy="12"
inkscape:window-x="0"
inkscape:window-y="30"
inkscape:window-maximized="1"
inkscape:current-layer="svg18" />
<rect
x="2"
y="2"
width="20"
height="20"
rx="2.18"
ry="2.18"
id="rect2"
style="stroke:#ffffff;stroke-opacity:1" />
<line
x1="7"
y1="2"
x2="7"
y2="22"
id="line4"
style="stroke:#ffffff;stroke-opacity:1" />
<line
x1="17"
y1="2"
x2="17"
y2="22"
id="line6"
style="stroke:#ffffff;stroke-opacity:1" />
<line
x1="2"
y1="12"
x2="22"
y2="12"
id="line8"
style="stroke:#ffffff;stroke-opacity:1" />
<line
x1="2"
y1="7"
x2="7"
y2="7"
id="line10"
style="stroke:#ffffff;stroke-opacity:1" />
<line
x1="2"
y1="17"
x2="7"
y2="17"
id="line12"
style="stroke:#ffffff;stroke-opacity:1" />
<line
x1="17"
y1="17"
x2="22"
y2="17"
id="line14"
style="stroke:#ffffff;stroke-opacity:1" />
<line
x1="17"
y1="7"
x2="22"
y2="7"
id="line16"
style="stroke:#ffffff;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -9,7 +9,7 @@
@:base(ctx, i18n!(ctx.1, "Media details"), {}, {}, { @:base(ctx, i18n!(ctx.1, "Media details"), {}, {}, {
<h1>@i18n!(ctx.1, "Media details")</h1> <h1>@i18n!(ctx.1, "Media details")</h1>
<section> <section>
<a href="@uri!(medias::list)">@i18n!(ctx.1, "Go back to the gallery")</a> <a href="@uri!(medias::list: page = _)">@i18n!(ctx.1, "Go back to the gallery")</a>
</section> </section>
<section> <section>

View File

@ -1,10 +1,9 @@
@use plume_models::medias::Media; @use plume_models::medias::*;
@use plume_models::safe_string::SafeString;
@use templates::base; @use templates::base;
@use template_utils::*; @use template_utils::*;
@use routes::*; @use routes::*;
@(ctx: BaseContext, medias: Vec<Media>) @(ctx: BaseContext, medias: Vec<Media>, page: i32, n_pages: i32)
@:base(ctx, i18n!(ctx.1, "Your media"), {}, {}, { @:base(ctx, i18n!(ctx.1, "Your media"), {}, {}, {
<h1>@i18n!(ctx.1, "Your media")</h1> <h1>@i18n!(ctx.1, "Your media")</h1>
@ -12,22 +11,33 @@
<a href="@uri!(medias::new)" class="inline-block button">@i18n!(ctx.1, "Upload")</a> <a href="@uri!(medias::new)" class="inline-block button">@i18n!(ctx.1, "Upload")</a>
</div> </div>
<section> @if medias.is_empty() {
@if medias.is_empty() { <p>@i18n!(ctx.1, "You don't have any media yet.")</p>
<p>@i18n!(ctx.1, "You don't have any media yet.")</p> }
<div class="cards spaced">
@for media in medias {
<div class="card">
<div class="cover media-preview @media.category().to_string()"
@if media.category() == MediaCategory::Image {
style="background-image: url('@media.url(ctx.0).unwrap_or_default()')"
}
></div>
<main>
<p class="p-summary">@media.alt_text</p>
@if let Some(cw) = media.content_warning {
<p>@i18n!(ctx.1, "Content warning: {0}"; cw)</p>
}
</main>
<footer>
<form action="@uri!(medias::delete: id = media.id)" class="inline" method="POST">
<input type="submit" value="@i18n!(ctx.1, "Delete")"/>
</form>
&mdash;
<a href="@uri!(medias::details: id = media.id)">@i18n!(ctx.1, "Details")</a>
</footer>
</div>
} }
<div class="list"> </div>
@for media in medias { @paginate(ctx.1, page, n_pages)
<div class="card flex">
@Html(media.preview_html(ctx.0).unwrap_or(SafeString::new("")))
<main class="grow">
<p><a href="@uri!(medias::details: id = media.id)">@media.alt_text</a></p>
</main>
<form action="@uri!(medias::delete: id = media.id)" class="inline" method="POST">
<input type="submit" value="@i18n!(ctx.1, "Delete")"/>
</form>
</div>
}
</div>
</section>
}) })

View File

@ -16,7 +16,7 @@
<main> <main>
<p class="p-summary">@article.subtitle</p> <p class="p-summary">@article.subtitle</p>
</main> </main>
<p class="author"> <footer class="authors">
@Html(i18n!(ctx.1, "By {0}"; format!( @Html(i18n!(ctx.1, "By {0}"; format!(
"<a class=\"p-author h-card\" href=\"{}\">{}</a>", "<a class=\"p-author h-card\" href=\"{}\">{}</a>",
uri!(user::details: name = article.get_authors(ctx.0).unwrap_or_default()[0].get_fqn(ctx.0)), uri!(user::details: name = article.get_authors(ctx.0).unwrap_or_default()[0].get_fqn(ctx.0)),
@ -29,6 +29,6 @@
@if !article.published { @if !article.published {
⋅ @i18n!(ctx.1, "Draft") ⋅ @i18n!(ctx.1, "Draft")
} }
</p> </footer>
</div> </div>

View File

@ -27,6 +27,10 @@
<label for="plume-editor">@i18n!(ctx.1, "Content")<small>@i18n!(ctx.1, "Markdown syntax is supported")</small></label> <label for="plume-editor">@i18n!(ctx.1, "Content")<small>@i18n!(ctx.1, "Markdown syntax is supported")</small></label>
<textarea id="plume-editor" name="content" rows="20">@form.content</textarea> <textarea id="plume-editor" name="content" rows="20">@form.content</textarea>
<small id="editor-left">@content_len</small> <small id="editor-left">@content_len</small>
<p>
@i18n!(ctx.1, "You can upload medias to your gallery, and copy their Markdown code in your articles to insert them.")
<a href="@uri!(medias::new)">@i18n!(ctx.1, "Upload media")</a>
</p>
@input!(ctx.1, tags (optional text), "Tags, separated by commas", form, errors.clone(), "") @input!(ctx.1, tags (optional text), "Tags, separated by commas", form, errors.clone(), "")

View File

@ -37,6 +37,6 @@
<section> <section>
<h2>@i18n!(ctx.1, "Your media")</h2> <h2>@i18n!(ctx.1, "Your media")</h2>
<a class="button" href="@uri!(medias::list)">@i18n!(ctx.1, "Go to your gallery")</a> <a class="button" href="@uri!(medias::list: page = _)">@i18n!(ctx.1, "Go to your gallery")</a>
</section> </section>
}) })

View File

@ -9,6 +9,10 @@
@:base(ctx, i18n!(ctx.1, "Edit your account"), {}, {}, { @:base(ctx, i18n!(ctx.1, "Edit your account"), {}, {}, {
@if let Some(u) = ctx.2.clone() { @if let Some(u) = ctx.2.clone() {
<h1>@i18n!(ctx.1, "Your Profile")</h1> <h1>@i18n!(ctx.1, "Your Profile")</h1>
<p>
@i18n!(ctx.1, "To change your avatar, upload it in your gallery and select from there.")
<a href="@uri!(medias::new)">@i18n!(ctx.1, "Upload an avatar")</a>
</p>
<form method="post" action="@uri!(user::update: _name = u.username.clone())"> <form method="post" action="@uri!(user::update: _name = u.username.clone())">
<!-- Rocket hack to use various HTTP methods --> <!-- Rocket hack to use various HTTP methods -->
<input type=hidden name="_method" value="put"> <input type=hidden name="_method" value="put">