Works on template

Use uri! to generate links instead of hardcoded urls
Fix #110
Fix invalid links needing to be POST forms
Translate login message for boost and like directly from template
Put js for search in its own file
This commit is contained in:
Trinity Pointard 2018-12-07 12:05:01 +01:00
parent 70af57c6e1
commit b4e4b497ee
30 changed files with 218 additions and 175 deletions

View file

@ -121,12 +121,14 @@ pub fn new(blog: String, user: User, conn: DbConn, intl: I18n) -> Option<Ructe>
let medias = Media::for_user(&*conn, user.id);
Some(render!(posts::new(
&(&*conn, &intl.catalog, Some(user)),
b,
false,
&NewPostForm::default(),
true,
None,
ValidationErrors::default(),
Instance::get_local(&*conn).expect("posts::new error: Local instance is null").default_license,
medias,
true
medias
)))
}
}
@ -143,7 +145,7 @@ pub fn edit(blog: String, slug: String, user: User, conn: DbConn, intl: I18n) ->
)))
} else {
let source = if !post.source.is_empty() {
post.source
post.source.clone()
} else {
post.content.get().clone() // fallback to HTML if the markdown was not stored
};
@ -151,6 +153,7 @@ pub fn edit(blog: String, slug: String, user: User, conn: DbConn, intl: I18n) ->
let medias = Media::for_user(&*conn, user.id);
Some(render!(posts::new(
&(&*conn, &intl.catalog, Some(user)),
b,
true,
&NewPostForm {
title: post.title.clone(),
@ -165,10 +168,11 @@ pub fn edit(blog: String, slug: String, user: User, conn: DbConn, intl: I18n) ->
draft: true,
cover: post.cover_id,
},
!post.published,
Some(post),
ValidationErrors::default(),
Instance::get_local(&*conn).expect("posts::new error: Local instance is null").default_license,
medias,
!post.published
medias
)))
}
}
@ -182,7 +186,7 @@ pub fn update(blog: String, slug: String, user: User, conn: DbConn, form: Lenien
let new_slug = if !post.published {
form.title.to_string().to_kebab_case()
} else {
post.slug
post.slug.clone()
};
let mut errors = match form.validate() {
@ -260,12 +264,14 @@ pub fn update(blog: String, slug: String, user: User, conn: DbConn, form: Lenien
let medias = Media::for_user(&*conn, user.id);
let temp = render!(posts::new(
&(&*conn, &intl.catalog, Some(user)),
b,
true,
&*form,
form.draft.clone(),
Some(post),
errors.clone(),
Instance::get_local(&*conn).expect("posts::new error: Local instance is null").default_license,
medias.clone(),
form.draft.clone()
medias.clone()
));
Err(Some(temp))
}
@ -378,12 +384,14 @@ pub fn create(blog_name: String, form: LenientForm<NewPostForm>, user: User, con
let medias = Media::for_user(&*conn, user.id);
Err(Some(render!(posts::new(
&(&*conn, &intl.catalog, Some(user)),
blog,
false,
&*form,
form.draft,
None,
errors.clone(),
Instance::get_local(&*conn).expect("posts::new error: Local instance is null").default_license,
medias,
form.draft
medias
))))
}
}

View file

@ -28,7 +28,7 @@ pub fn new(user: Option<User>, conn: DbConn, intl: I18n) -> Ructe {
pub fn new_message(user: Option<User>, m: String, conn: DbConn, intl: I18n) -> Ructe {
render!(session::login(
&(&*conn, &intl.catalog, user),
Some(i18n!(intl.catalog, &m).to_string()),
Some(m),
&LoginForm::default(),
ValidationErrors::default()
))

13
static/js/search.js Normal file
View file

@ -0,0 +1,13 @@
window.onload = function(evt) {
var form = document.getElementById('form');
form.addEventListener('submit', function () {
for (var input of form.getElementsByTagName('input')) {
if (input.name === '') {
input.name = input.id
}
if (input.name && !input.value) {
input.name = '';
}
}
});
}

View file

@ -1,5 +1,5 @@
@use template_utils::*;
@use routes::*;
@(ctx: BaseContext, title: &str, head: Content, header: Content, content: Content)
<!DOCTYPE html>
@ -8,10 +8,10 @@
<meta charset="utf-8" />
<title>@i18n!(ctx.1, title) ⋅ @i18n!(ctx.1, "Plume")</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/static/css/main.css" />
<link rel="stylesheet" href="/static/css/feather.css" />
<link rel="manifest" href="/manifest.json" />
<link rel="icon" type="image/png" href="/static/icons/trwnh/feather-filled/plumeFeatherFilled64.png">
<link rel="stylesheet" href="@uri!(static_files: file = "css/main.css")" />
<link rel="stylesheet" href="@uri!(static_files: file = "css/feather.css")" />
<link rel="manifest" href="@uri!(instance::web_manifest)" />
<link rel="icon" type="image/png" href="@uri!(static_files: file = "icons/trwnh/feather-filled/plumeFeatherFilled64.png")">
@:head()
</head>
<body>
@ -21,8 +21,8 @@
</nav>
<div id="content">
<nav>
<a href="/" class="title">
<img src="/static/icons/trwnh/feather/plumeFeather256.png">
<a href="@uri!(instance::index)" class="title">
<img src="@uri!(static_files: file = "icons/trwnh/feather/plumeFeather256.png")">
<p>@i18n!(ctx.1, "Plume")</p>
</a>
<hr/>
@ -30,28 +30,28 @@
</nav>
<nav>
@if ctx.2.is_some() {
<a href="/dashboard">
<a href="@uri!(user::dashboard)">
<i class="icon icon-home" aria-label="@i18n!(ctx.1, "Dashboard")"></i>
<span class="mobile-label">@i18n!(ctx.1, "Dashboard")</span>
</a>
<a href="/notifications">
<a href="@uri!(notifications::notifications)">
<i class="icon icon-bell" aria-label="@i18n!(ctx.1, "Notifications")"></i>
<span class="mobile-label">@i18n!(ctx.1, "Notifications")</span>
</a>
<a href="/logout">
<a href="@uri!(session::delete)">
<i class="icon icon-log-out" aria-label="@i18n!(ctx.1, "Log Out")"></i>
<span class="mobile-label">@i18n!(ctx.1, "Log Out")</span>
</a>
<a href="/me" title="@i18n!(ctx.1, "My account")">
<a href="@uri!(user::me)" title="@i18n!(ctx.1, "My account")">
@avatar(ctx.0, &ctx.2.clone().unwrap(), Size::Small, false, &ctx.1)
<span class="mobile-label">@i18n!(ctx.1, "My account")</span>
</a>
} else {
<a href="/login">
<a href="@uri!(session::new)">
<i class="icon icon-log-in"></i>
<span class="mobile-label">@i18n!(ctx.1, "Log In")</span>
</a>
<a href="/users/new">
<a href="@uri!(user::new)">
<i class="icon icon-user-plus"></i>
<span class="mobile-label">@i18n!(ctx.1, "Register")</span>
</a>
@ -64,13 +64,13 @@
</main>
<footer>
<span>@concat!("Plume ", env!("CARGO_PKG_VERSION"))</span>
<a href="/about">@i18n!(ctx.1, "About this instance")</a>
<a href="@uri!(instance::about)">@i18n!(ctx.1, "About this instance")</a>
<a href="https://github.com/Plume-org/Plume">@i18n!(ctx.1, "Source code")</a>
<a href="https://riot.im/app/#/room/#plume:disroot.org">@i18n!(ctx.1, "Matrix room")</a>
@if ctx.2.clone().map(|a| a.is_admin).unwrap_or(false) {
<a href="/admin">@i18n!(ctx.1, "Administration")</a>
<a href="@uri!(instance::admin)">@i18n!(ctx.1, "Administration")</a>
}
</footer>
<script src="/static/js/menu.js"></script>
<script src="@uri!(static_files: file = "js/menu.js")"></script>
</body>
</html>

View file

@ -3,18 +3,19 @@
@use plume_models::users::User;
@use templates::{base, partials::post_card};
@use template_utils::*;
@use routes::*;
@(ctx: BaseContext, blog: Blog, fqn: String, authors: &Vec<User>, total_articles: usize, page: i32, n_pages: i32, is_author: bool, posts: Vec<Post>)
@:base(ctx, blog.title.as_ref(), {}, {
<a href="/~/@fqn">@blog.title</a>
<a href="@uri!(blogs::details: name = &fqn)">@blog.title</a>
}, {
<h1>@blog.title <small>~@fqn</small></h1>
<p>@blog.summary</p>
<p>
@i18n!(ctx.1, "One author in this blog: ", "{0} authors in this blog: ", authors.len())
@for author in authors {
<a class="author" href="/@@/@author.get_fqn(ctx.0)">@author.name(ctx.0)</a>
<a class="author" href="@uri!(user::details: name = author.get_fqn(ctx.0))">@author.name(ctx.0)</a>
}
</p>
<p>
@ -24,13 +25,13 @@
<section>
<h2>
@i18n!(ctx.1, "Latest articles")
<small><a href="/~/@fqn/atom.xml" title="Atom feed">@icon!("rss")</a></small>
<small><a href="@uri!(blogs::atom_feed: name = &fqn)" title="Atom feed">@icon!("rss")</a></small>
</h2>
@if posts.len() < 1 {
<p>@i18n!(ctx.1, "No posts to see here yet.")</p>
}
@if is_author {
<a href="/~/@fqn/new/" class="button inline-block">@i18n!(ctx.1, "New article")</a>
<a href="@uri!(posts::new: blog = &fqn)" class="button inline-block">@i18n!(ctx.1, "New article")</a>
}
<div class="cards">
@for article in posts {
@ -42,7 +43,7 @@
@if is_author {
<h2>@i18n!(ctx.1, "Danger zone")</h2>
<p>@i18n!(ctx.1, "Be very careful, any action taken here can't be cancelled.")</p>
<form method="post" action="/~/@fqn/delete">
<form method="post" action="@uri!(blogs::delete: name = &fqn)">
<input type="submit" class="inline-block button destructive" value="@i18n!(ctx.1, "Delete this blog")">
</form>
}

View file

@ -2,12 +2,13 @@
@use templates::base;
@use template_utils::*;
@use routes::blogs::NewBlogForm;
@use routes::*;
@(ctx: BaseContext, form: &NewBlogForm, errors: ValidationErrors)
@:base(ctx, "New Blog", {}, {}, {
<h1>@i18n!(ctx.1, "Create a blog")</h1>
<form method="post">
<form method="post" action="@uri!(blogs::create)">
@input!(ctx.1, title (text), "Title", form, errors, "required minlength=\"1\"")
<input type="submit" value="@i18n!(ctx.1, "Create blog")"/>
</form>

View file

@ -1,6 +1,7 @@
@use plume_models::{instance::Instance, users::User};
@use templates::base;
@use template_utils::*;
@use plume_models::{instance::Instance, users::User};
@use routes::*;
@(ctx: BaseContext, instance: Instance, admin: User, n_users: usize, n_articles: usize, n_instances: i64)
@ -23,7 +24,7 @@
<div>
<p>@i18n!(ctx.1, "Administred by")</p>
@avatar(ctx.0, &admin, Size::Small, false, ctx.1)
<p><a href="/@@/@admin.get_fqn(ctx.0)">@admin.name(ctx.0)</a><small>@@@admin.get_fqn(ctx.0)</small></p>
<p><a href="@uri!(user::details: name = admin.get_fqn(ctx.0))">@admin.name(ctx.0)</a><small>@@@admin.get_fqn(ctx.0)</small></p>
</div>
</section>
<p>@i18n!(ctx.1, "Runs Plume {0}"; env!("CARGO_PKG_VERSION"))</p>

View file

@ -1,8 +1,9 @@
@use plume_models::instance::Instance;
@use validator::ValidationErrors;
@use templates::base;
@use template_utils::*;
@use plume_models::instance::Instance;
@use routes::instance::InstanceSettingsForm;
@use validator::ValidationErrors;
@use routes::*;
@(ctx: BaseContext, instance: Instance, form: InstanceSettingsForm, errors: ValidationErrors)
@ -10,12 +11,12 @@
<h1>@i18n!(ctx.1, "Administration")</h1>
@tabs(&[
("/admin", i18n!(ctx.1, "Configuration"), true),
("/admin/instances", i18n!(ctx.1, "Instances"), false),
("/admin/users", i18n!(ctx.1, "Users"), false),
(&uri!(instance::admin).to_string(), i18n!(ctx.1, "Configuration"), true),
(&uri!(instance::admin_instances).to_string(), i18n!(ctx.1, "Instances"), false),
(&uri!(instance::admin_users).to_string(), i18n!(ctx.1, "Users"), false),
])
<form method="post">
<form method="post" action="@uri!(instance::update_settings)">
@input!(ctx.1, name (text), "Name", form, errors.clone(), "props")
<label for="open_registrations">

View file

@ -1,6 +1,7 @@
@use plume_models::posts::Post;
@use templates::{base, partials::post_card};
@use template_utils::*;
@use plume_models::posts::Post;
@use routes::*;
@(ctx: BaseContext, articles: Vec<Post>, page: i32, n_pages: i32)
@ -9,16 +10,16 @@
@if let Some(_) = ctx.2 {
@tabs(&[
("/", i18n!(ctx.1, "Latest articles"), false),
("/feed", i18n!(ctx.1, "Your feed"), false),
("/federated", i18n!(ctx.1, "Federated feed"), true),
("/local", i18n!(ctx.1, "Local feed"), false),
(&uri!(instance::index).to_string(), i18n!(ctx.1, "Latest articles"), false),
(&uri!(instance::feed).to_string(), i18n!(ctx.1, "Your feed"), false),
(&uri!(instance::federated).to_string(), i18n!(ctx.1, "Federated feed"), true),
(&uri!(instance::local).to_string(), i18n!(ctx.1, "Local feed"), false),
])
} else {
@tabs(&[
("/", i18n!(ctx.1, "Latest articles"), false),
("/federated", i18n!(ctx.1, "Federated feed"), true),
("/local", i18n!(ctx.1, "Local feed"), false),
(&uri!(instance::index).to_string(), i18n!(ctx.1, "Latest articles"), false),
(&uri!(instance::federated).to_string(), i18n!(ctx.1, "Federated feed"), true),
(&uri!(instance::local).to_string(), i18n!(ctx.1, "Local feed"), false),
])
}

View file

@ -1,6 +1,7 @@
@use plume_models::posts::Post;
@use templates::{base, partials::post_card};
@use template_utils::*;
@use plume_models::posts::Post;
@use routes::*;
@(ctx: BaseContext, articles: Vec<Post>, page: i32, n_pages: i32)
@ -8,10 +9,10 @@
<h1>@i18n!(ctx.1, "Your feed")</h1>
@tabs(&[
("/", i18n!(ctx.1, "Latest articles"), false),
("/feed", i18n!(ctx.1, "Your feed"), true),
("/federated", i18n!(ctx.1, "Federated feed"), false),
("/local", i18n!(ctx.1, "Local feed"), false),
(&uri!(instance::index).to_string(), i18n!(ctx.1, "Latest articles"), false),
(&uri!(instance::feed).to_string(), i18n!(ctx.1, "Your feed"), true),
(&uri!(instance::federated).to_string(), i18n!(ctx.1, "Federated feed"), false),
(&uri!(instance::local).to_string(), i18n!(ctx.1, "Local feed"), false),
])
@if !articles.is_empty() {

View file

@ -2,6 +2,7 @@
@use template_utils::*;
@use plume_models::instance::Instance;
@use plume_models::posts::Post;
@use routes::*;
@(ctx: BaseContext, instance: Instance, n_users: i32, n_articles: i32, local: Vec<Post>, federated: Vec<Post>, user_feed: Option<Vec<Post>>)
@ -10,25 +11,25 @@
@if ctx.2.is_some() {
@tabs(&[
("/", i18n!(ctx.1, "Latest articles"), true),
("/feed", i18n!(ctx.1, "Your feed"), false),
("/federated", i18n!(ctx.1, "Federated feed"), false),
("/local", i18n!(ctx.1, "Local feed"), false),
(&uri!(instance::index).to_string(), i18n!(ctx.1, "Latest articles"), true),
(&uri!(instance::feed).to_string(), i18n!(ctx.1, "Your feed"), false),
(&uri!(instance::federated).to_string(), i18n!(ctx.1, "Federated feed"), false),
(&uri!(instance::local).to_string(), i18n!(ctx.1, "Local feed"), false),
])
@:home_feed(ctx, user_feed.unwrap_or_default(), "/feed", "Your feed")
@:home_feed(ctx, federated, "/federated", "Federated feed")
@:home_feed(ctx, local, "/local", "Local feed")
@:home_feed(ctx, user_feed.unwrap_or_default(), &uri!(instance::feed).to_string(), "Your feed")
@:home_feed(ctx, federated, &uri!(instance::federated).to_string(), "Federated feed")
@:home_feed(ctx, local, &uri!(instance::local).to_string(), "Local feed")
@:instance_description(ctx, instance, n_users, n_articles)
} else {
@tabs(&[
("/", i18n!(ctx.1, "Latest articles"), true),
("/federated", i18n!(ctx.1, "Federated feed"), false),
("/local", i18n!(ctx.1, "Local feed"), false),
(&uri!(instance::index).to_string(), i18n!(ctx.1, "Latest articles"), true),
(&uri!(instance::federated).to_string(), i18n!(ctx.1, "Federated feed"), false),
(&uri!(instance::local).to_string(), i18n!(ctx.1, "Local feed"), false),
])
@:home_feed(ctx, federated, "/federated", "Federated feed")
@:home_feed(ctx, local, "/local", "Local feed")
@:home_feed(ctx, federated, &uri!(instance::federated).to_string(), "Federated feed")
@:home_feed(ctx, local, &uri!(instance::local).to_string(), "Local feed")
@:instance_description(ctx, instance, n_users, n_articles)
}
})

View file

@ -1,6 +1,7 @@
@use plume_models::instance::Instance;
@use templates::base;
@use template_utils::*;
@use plume_models::instance::Instance;
@use routes::*;
@(ctx: BaseContext, instance: Instance, instances: Vec<Instance>, page: i32, n_pages: i32)
@ -8,9 +9,9 @@
<h1>@i18n!(ctx.1, "Instances")</h1>
@tabs(&[
("/admin", i18n!(ctx.1, "Configuration"), false),
("/admin/instances", i18n!(ctx.1, "Instances"), true),
("/admin/users", i18n!(ctx.1, "Users"), false),
(&uri!(instance::admin).to_string(), i18n!(ctx.1, "Configuration"), false),
(&uri!(instance::admin_instances).to_string(), i18n!(ctx.1, "Instances"), true),
(&uri!(instance::admin_users).to_string(), i18n!(ctx.1, "Users"), false),
])
<div class="list">
@ -21,7 +22,7 @@
<small>@instance.public_domain</small>
</p>
@if !instance.local {
<form class="inline" method="post" action="/admin/instances/@instance.id/block">
<form class="inline" method="post" action="@uri!(instance::toggle_block: id = instance.id)">
<input type="submit" value="@i18n!(ctx.1, if instance.blocked { "Unblock" } else { "Block"})">
</form>
}

View file

@ -1,7 +1,8 @@
@use templates::{base, partials::post_card};
@use template_utils::*;
@use plume_models::posts::Post;
@use plume_models::instance::Instance;
@use templates::{base, partials::post_card};
@use template_utils::*;
@use routes::*;
@(ctx: BaseContext, instance: Instance, articles: Vec<Post>, page: i32, n_pages: i32)
@ -10,16 +11,16 @@
@if let Some(_) = ctx.2 {
@tabs(&[
("/", i18n!(ctx.1, "Latest articles"), false),
("/feed", i18n!(ctx.1, "Your feed"), false),
("/federated", i18n!(ctx.1, "Federated feed"), false),
("/local", i18n!(ctx.1, "Local feed"), true),
(&uri!(instance::index).to_string(), i18n!(ctx.1, "Latest articles"), false),
(&uri!(instance::feed).to_string(), i18n!(ctx.1, "Your feed"), false),
(&uri!(instance::federated).to_string(), i18n!(ctx.1, "Federated feed"), false),
(&uri!(instance::local).to_string(), i18n!(ctx.1, "Local feed"), true),
])
} else {
@tabs(&[
("/", i18n!(ctx.1, "Latest articles"), false),
("/federated", i18n!(ctx.1, "Federated feed"), false),
("/local", i18n!(ctx.1, "Local feed"), true),
(&uri!(instance::index).to_string(), i18n!(ctx.1, "Latest articles"), false),
(&uri!(instance::federated).to_string(), i18n!(ctx.1, "Federated feed"), false),
(&uri!(instance::local).to_string(), i18n!(ctx.1, "Local feed"), true),
])
}

View file

@ -1,6 +1,7 @@
@use plume_models::users::User;
@use templates::base;
@use template_utils::*;
@use plume_models::users::User;
@use routes::*;
@(ctx: BaseContext, users: Vec<User>, page: i32, n_pages: i32)
@ -8,9 +9,9 @@
<h1>@i18n!(ctx.1, "Users")</h1>
@tabs(&[
("/admin", i18n!(ctx.1, "Configuration"), false),
("/admin/instances", i18n!(ctx.1, "Instances"), false),
("/admin/users", i18n!(ctx.1, "Users"), true),
(&uri!(instance::admin).to_string(), i18n!(ctx.1, "Configuration"), false),
(&uri!(instance::admin_instances).to_string(), i18n!(ctx.1, "Instances"), false),
(&uri!(instance::admin_users).to_string(), i18n!(ctx.1, "Users"), true),
])
<div class="list">
@ -18,11 +19,11 @@
<div class="flex">
@avatar(ctx.0, &user, Size::Small, false, ctx.1)
<p class="grow">
<a href="/@@/@user.get_fqn(ctx.0)">@user.name(ctx.0)</a>
<a href="@uri!(user::details: name = user.get_fqn(ctx.0))">@user.name(ctx.0)</a>
<small>@format!("@{}", user.username)</small>
</p>
@if !user.is_admin {
<form class="inline" method="post" action="/admin/users/@user.id/ban">
<form class="inline" method="post" action="@uri!(instance::ban: id = user.id)">
<input type="submit" value="@i18n!(ctx.1, "Ban")">
</form>
}

View file

@ -1,13 +1,14 @@
@use plume_models::medias::{Media, MediaCategory};
@use templates::base;
@use template_utils::*;
@use plume_models::medias::{Media, MediaCategory};
@use routes::*;
@(ctx: BaseContext, media: Media)
@:base(ctx, "Media details", {}, {}, {
<h1>@i18n!(ctx.1, "Media details")</h1>
<section>
<a href="/medias">@i18n!(ctx.1, "Go back to the gallery")</a>
<a href="@uri!(medias::list)">@i18n!(ctx.1, "Go back to the gallery")</a>
</section>
<section>
@ -24,11 +25,11 @@
</div>
<div>
@if media.category() == MediaCategory::Image {
<form class="inline" method="post" action="/medias/@media.id/avatar">
<form class="inline" method="post" action="@uri!(medias::set_avatar: id = media.id)">
<input class="button" type="submit" value="@i18n!(ctx.1, "Use as avatar")">
</form>
}
<form class="inline" method="post" action="/medias/@media.id/delete">
<form class="inline" method="post" action="@uri!(medias::delete: id = media.id)">
<input class="button" type="submit" value="@i18n!(ctx.1, "Delete")">
</form>
</div>

View file

@ -1,13 +1,14 @@
@use plume_models::medias::Media;
@use templates::base;
@use template_utils::*;
@use plume_models::medias::Media;
@use routes::*;
@(ctx: BaseContext, medias: Vec<Media>)
@:base(ctx, "Your media", {}, {}, {
<h1>@i18n!(ctx.1, "Your media")</h1>
<div>
<a href="/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>
<section>
@ -19,9 +20,11 @@
<div class="card flex">
@Html(media.preview_html(ctx.0))
<main class="grow">
<p><a href="/medias/@media.id">@media.alt_text</a></p>
<p><a href="@uri!(medias::details: id = media.id)">@media.alt_text</a></p>
</main>
<a href="/medias/@media.id/delete">@i18n!(ctx.1, "Delete")</a>
<form action="@uri!(medias::delete: id = media.id)" class="inline" method="POST">
<input type="submit" value="@i18n!(ctx.1, "Delete")"/>
</form>
</div>
}
</div>

View file

@ -1,11 +1,12 @@
@use templates::base;
@use template_utils::*;
@use routes::*;
@(ctx: BaseContext)
@:base(ctx, "Media upload", {}, {}, {
<h1>@i18n!(ctx.1, "Media upload")</h1>
<form method="post" enctype="multipart/form-data">
<form method="post" enctype="multipart/form-data" action="@uri!(medias::upload)">
<label for="alt">
@i18n!(ctx.1, "Description")
<small>@i18n!(ctx.1, "Useful for visually impaired people and licensing")</small>

View file

@ -1,11 +1,12 @@
@use template_utils::*;
@use plume_models::comments::Comment;
@use plume_models::users::User;
@use routes::*;
@(ctx: BaseContext, comm: &Comment, author: User)
<div class="comment" id="comment-@comm.id">
<a class="author" href="/@@/@author.get_fqn(ctx.0)/">
<a class="author" href="@uri!(user::details: name = author.get_fqn(ctx.0))">
@avatar(ctx.0, &author, Size::Small, true, ctx.1)
<span class="display-name">@author.name(ctx.0)</span>
<small>@author.get_fqn(ctx.0)</small>

View file

@ -1,5 +1,6 @@
@use template_utils::*;
@use plume_models::instance::Instance;
@use routes::*;
@(ctx: BaseContext, instance: Instance, n_users: i32, n_articles: i32)
@ -12,7 +13,7 @@
<p>@i18n!(ctx.1, "Authors can manage various blogs from an unique website.")</p>
<p>@i18n!(ctx.1, "Articles are also visible on other Plume websites, and you can interact with them directly from other platforms like Mastodon.")</p>
</main>
<a href="/users/new">@i18n!(ctx.1, "Create your account")</a>
<a href="@uri!(user::new)">@i18n!(ctx.1, "Create your account")</a>
</div>
<div class="presentation card">
<h2>@i18n!(ctx.1, "About {0}"; instance.name)</h2>
@ -27,7 +28,7 @@
</div>
</section>
</main>
<a href="/about">@i18n!(ctx.1, "Read the detailed rules")</a>
<a href="@uri!(instance::about)">@i18n!(ctx.1, "Read the detailed rules")</a>
</div>
</div>
</section>

View file

@ -1,5 +1,6 @@
@use template_utils::*;
@use plume_models::posts::Post;
@use template_utils::*;
@use routes::*;
@(ctx: BaseContext, article: Post)
@ -7,20 +8,20 @@
@if article.cover_id.is_some() {
<div class="cover" style="background-image: url('@Html(article.cover_url(ctx.0).unwrap_or_default())')"></div>
}
<h3><a href="@article.url(ctx.0)">@article.title</a></h3>
<h3><a href="@uri!(posts::details: blog = article.get_blog(ctx.0).get_fqn(ctx.0), slug = &article.slug)">@article.title</a></h3>
<main>
<p>@article.subtitle</p>
</main>
<p class="author">
@Html(i18n!(ctx.1, "By {0}"; format!(
"<a href=\"/@/{}/\">{}</a>",
escape(&article.get_authors(ctx.0)[0].get_fqn(ctx.0)),
"<a href=\"{}\">{}</a>",
uri!(user::details: name = article.get_authors(ctx.0)[0].get_fqn(ctx.0)),
escape(&article.get_authors(ctx.0)[0].name(ctx.0))
)))
@if article.published {
⋅ @article.creation_date.format("%B %e, %Y")
}
<a href="/~/@article.get_blog(ctx.0).get_fqn(ctx.0)/">@article.get_blog(ctx.0).title</a>
<a href="@uri!(blogs::details: name = article.get_blog(ctx.0).get_fqn(ctx.0))">@article.get_blog(ctx.0).title</a>
@if !article.published {
⋅ @i18n!(ctx.1, "Draft")
}

View file

@ -7,33 +7,36 @@
@use plume_models::users::User;
@use validator::ValidationErrors;
@use routes::comments::NewCommentForm;
@use routes::*;
@(ctx: BaseContext, article: Post, blog: Blog, comment_form: &NewCommentForm, comment_errors: ValidationErrors, tags: Vec<Tag>, comments: Vec<Comment>, previous_comment: Option<Comment>, n_likes: usize, n_reshares: usize, has_liked: bool, has_reshared: bool, is_following: bool, author: User)
@:base(ctx, &article.title.clone(), {
<meta property="og:title" content="article.title"/>
<meta property="og:title" content="@article.title"/>
<meta property="og:type" content="article"/>
@if article.cover_id.is_some() {
<meta property="og:image" content="@Html(article.cover_url(ctx.0).unwrap_or_default())"/>
}
<meta property="og:url" content="@Html(article.url(ctx.0))"/>
<meta property="og:url" content="@uri!(posts::details: blog = blog.get_fqn(ctx.0), slug = &article.slug)"/>
<meta property="og:description" content="@article.subtitle"/>
}, {
<a href="/~/@blog.get_fqn(ctx.0)">@blog.title</a>
<a href="@uri!(blogs::details: name = blog.get_fqn(ctx.0))">@blog.title</a>
}, {
<h1 class="article">@&article.title</h1>
<h2 class="article">@&article.subtitle</h2>
<div class="article-info">
<span class="author">
@Html(i18n!(ctx.1, "Written by {0}"; format!("<a href=\"/@/{}/\">{}</a>", escape(&author.get_fqn(ctx.0)), escape(&author.name(ctx.0)))))
@Html(i18n!(ctx.1, "Written by {0}"; format!("<a href=\"{}\">{}</a>",
uri!(user::details: name = &author.get_fqn(ctx.0)),
escape(&author.name(ctx.0)))))
</span>
&mdash;
<span class="date">@article.creation_date.format("%B %e, %Y")</span>
@if ctx.2.clone().map(|u| u.id == author.id).unwrap_or(false) {
&mdash;
<a href="@article.url(ctx.0)/edit">@i18n!(ctx.1, "Edit")</a>
<a href="@uri!(posts::edit: blog = blog.get_fqn(ctx.0), slug = &article.slug)">@i18n!(ctx.1, "Edit")</a>
&mdash;
<form class="inline" method="post" action="@article.url(ctx.0)/delete">
<form class="inline" method="post" action="@uri!(posts::delete: blog_name = blog.get_fqn(ctx.0), slug = &article.slug)">
<input onclick="return confirm('Are you sure you?')" type="submit" value="@i18n!(ctx.1, "Delete this article")">
</form>
}
@ -53,39 +56,37 @@
<ul class="tags">
@for tag in tags {
@if !tag.is_hashtag {
<li><a href="/tag/@tag.tag">@tag.tag</a></li>
<li><a href="@uri!(tags::tag: name = &tag.tag)">@tag.tag</a></li>
}
}
</ul>
<div class="flex">
@avatar(ctx.0, &author, Size::Medium, true, ctx.1)
<div class="grow">
<h2><a href="/@@/@author.get_fqn(ctx.0)">@author.name(ctx.0)</a></h2>
<h2><a href="@uri!(user::details: name = author.get_fqn(ctx.0))">@author.name(ctx.0)</a></h2>
<p>@Html(&author.summary)</h2>
</div>
<a href="/@@/@author.get_fqn(ctx.0)/follow" class="button">
@if is_following {
@i18n!(ctx.1, "Unfollow")
} else {
@i18n!(ctx.1, "Follow")
}
</a>
@if !ctx.2.as_ref().map(|u| u.id == author.id).unwrap_or(false) {
<form action="@uri!(user::follow: name = author.get_fqn(ctx.0))" method="POST">
<input type="submit" class="button" value="@if is_following {@i18n!(ctx.1, "Unfollow")} else {@i18n!(ctx.1, "Follow")}">
</form>
}
</div>
@if ctx.2.is_some() {
<div class="actions">
<form class="likes" action="@article.url(ctx.0)/like" method="POST">
<form class="likes" action="@uri!(likes::create: blog = blog.get_fqn(ctx.0), slug = &article.slug)" method="POST">
<p aria-label="@i18n!(ctx.1, "One like", "{0} likes", &n_likes)" title="@i18n!(ctx.1, "One like", "{0} likes", n_likes)">
@n_likes
</p>
@if has_liked {
<button type="submit" class="action liked">@icon!("heart") @i18n!(ctx.1, "I don't like this anymore")</button>
} else {
} else {
<button type="submit" class="action">@icon!("heart") @i18n!(ctx.1, "Add yours")</button>
}
</form>
<form class="reshares" action="@article.url(ctx.0)/reshare" method="POST">
<form class="reshares" action="@uri!(reshares::create: blog = blog.get_fqn(ctx.0), slug = &article.slug)" method="POST">
<p aria-label="@i18n!(ctx.1, "One boost", "{0} boost", &n_reshares)" title="@i18n!(ctx.1, "One boost", "{0} boosts", n_reshares)">
@n_reshares
</p>
@ -104,14 +105,14 @@
<p aria-label="@i18n!(ctx.1, "One like", "{0} likes", &n_likes)" title="@i18n!(ctx.1, "One like", "{0} likes", n_likes)">
@n_likes
</p>
<a href="/login?m=Login%20to%20like" class="action">@icon!("heart") @i18n!(ctx.1, "Add yours")</a>
<a href="@uri!(session::new_message: m = i18n!(ctx.1, "Login to like"))" class="action">@icon!("heart") @i18n!(ctx.1, "Add yours")</a>
</div>
<div class="reshares">
<p aria-label="@i18n!(ctx.1, "One boost", "{0} boost", &n_reshares)" title="@i18n!(ctx.1, "One boost", "{0} boosts", n_reshares)">
@n_reshares
</p>
<a href="/login?m=Login%20to%20boost" class="action">@icon!("repeat") @i18n!(ctx.1, "Boost")</a>
<a href="@uri!(session::new_message: m = i18n!(ctx.1, "Login to boost"))" class="action">@icon!("repeat") @i18n!(ctx.1, "Boost")</a>
</div>
</div>
}
@ -120,7 +121,7 @@
<h2>@i18n!(ctx.1, "Comments")</h2>
@if ctx.2.is_some() {
<form method="post" action="@article.url(ctx.0)/comment">
<form method="post" action="@uri!(comments::create: blog_name = blog.get_fqn(ctx.0), slug = &article.slug)">
@input!(ctx.1, warning (optional text), "Content warning", comment_form, comment_errors, "")
<label for="plume-editor">@i18n!(ctx.1, "Your comment")</label>

View file

@ -3,9 +3,12 @@
@use validator::{ValidationErrors, ValidationErrorsKind};
@use std::borrow::Cow;
@use plume_models::medias::*;
@use plume_models::blogs::Blog;
@use plume_models::posts::Post;
@use routes::posts::NewPostForm;
@use routes::*;
@(ctx: BaseContext, editing: bool, form: &NewPostForm, errors: ValidationErrors, default_license: String, medias: Vec<Media>, is_draft: bool)
@(ctx: BaseContext, blog: Blog, editing: bool, form: &NewPostForm, is_draft: bool, article: Option<Post>, errors: ValidationErrors, default_license: String, medias: Vec<Media>)
@:base(ctx, &i18n!(ctx.1, if editing { "Edit {0}" } else { "New post" }; &form.title), {}, {}, {
<h1>
@ -15,7 +18,11 @@
@i18n!(ctx.1, "Create a new post")
}
</h1>
<form class="new-post" method="post">
@if let Some(article) = article {
<form class="new-post" method="post" action="@uri!(posts::update: blog = blog.actor_id, slug = &article.slug)">
} else {
<form class="new-post" method="post" action="@uri!(posts::new: blog = blog.actor_id)">
}
@input!(ctx.1, title (text), "Title", form, errors.clone(), "required")
@input!(ctx.1, subtitle (optional text), "Subtitle", form, errors.clone(), "")
@ -63,5 +70,5 @@
}
}
</form>
<script src="/static/js/autoExpand.js"></script>
<script src="@uri!(static_files: file = "js/autoExpand.js")"></script>
})

View file

@ -1,34 +1,20 @@
@use templates::base;
@use template_utils::*;
@use routes::*;
@(ctx: BaseContext, now: &str)
@:base(ctx, "Search", {
<script>
window.onload = function(evt) @{
var form = document.getElementById('form');
form.addEventListener('submit', function () @{
for (var input of form.getElementsByTagName('input')) @{
if (input.name === '') @{
input.name = input.id
@}
if (input.name && !input.value) @{
input.name = '';
@}
@}
@});
@}
</script>
}, {}, {
@:base(ctx, "Search", {}, {}, {
<h1>@i18n!(ctx.1, "Search")</h1>
<form method="get" id="form">
<input id="q" name="q" placeholder="Your query" type="search">
<br/>
<details>
<summary>Advanced search</summary>
@input!(ctx.1, title (text), "Title matching these words", "placeholder=\"Title\"")
@input!(ctx.1, subtitle (text), "Subtitle matching these words", "placeholder=\"Subtitle\"")
@input!(ctx.1, content (text), "Content matching these words", "placeholder=\"Content\"")
@input!(ctx.1, after (date), "From this date", &format!("max={}", now)))
@input!(ctx.1, after (date), "From this date", &format!("max={}", now))
@input!(ctx.1, before (date), "To this date", &format!("max={}", now))
@input!(ctx.1, tag (text), "Containing these tags", "placeholder=\"Tags\"")
@ -40,4 +26,5 @@
</details>
<input type="submit" value="Search"/>
</form>
<script src="@uri!(static_files: file = "js/search.js")"></script>
})

View file

@ -2,6 +2,7 @@
@use templates::base;
@use validator::ValidationErrors;
@use routes::session::LoginForm;
@use routes::*;
@(ctx: BaseContext, message: Option<String>, form: &LoginForm, errors: ValidationErrors)
@ -10,7 +11,7 @@
@if let Some(message) = message {
<p>@message</p>
}
<form method="post">
<form method="post" action="@uri!(session::create)">
@input!(ctx.1, email_or_name (text), "Username or email", form, errors.clone(), "minlenght=\"1\"")
@input!(ctx.1, password (password), "Password", form, errors, "minlenght=\"1\"")
<input type="submit" value="@i18n!(ctx.1, "Login")" />

View file

@ -2,6 +2,7 @@
@use template_utils::*;
@use plume_models::blogs::Blog;
@use plume_models::posts::Post;
@use routes::*;
@(ctx: BaseContext, blogs: Vec<Blog>, drafts: Vec<Post>)
@ -16,11 +17,11 @@
<div class="cards">
@for blog in blogs {
<div class="card">
<h3><a href="/~/@blog.actor_id/">@blog.title</a></h3>
<h3><a href="@uri!(blogs::details: name = blog.actor_id)">@blog.title</a></h3>
</div>
}
</div>
<a class="button" href="/blogs/new">@i18n!(ctx.1, "Start a new blog")</a>
<a class="button" href="@uri!(blogs::new)">@i18n!(ctx.1, "Start a new blog")</a>
</section>
@if !drafts.is_empty() {
@ -36,6 +37,6 @@
<section>
<h2>@i18n!(ctx.1, "Your media")</h2>
<a class="button" href="/medias">@i18n!(ctx.1, "Go to your gallery")</a>
<a class="button" href="@uri!(medias::list)">@i18n!(ctx.1, "Go to your gallery")</a>
</section>
})

View file

@ -2,6 +2,7 @@
@use template_utils::*;
@use plume_models::posts::Post;
@use plume_models::users::User;
@use routes::*;
@(ctx: BaseContext, user: User, follows: bool, is_remote: bool, remote_url: String, recents: Vec<Post>, reshares: Vec<Post>)
@ -9,14 +10,14 @@
@:header(ctx, &user, follows, is_remote, remote_url)
@tabs(&[
(&format!("/@/{}", user.get_fqn(ctx.0)), i18n!(ctx.1, "Articles"), true),
(&format!("/@/{}/followers", user.get_fqn(ctx.0)), i18n!(ctx.1, "Followers"), false)
(&uri!(user::details: name= user.get_fqn(ctx.0)).to_string(), i18n!(ctx.1, "Articles"), true),
(&uri!(user::followers: name = user.get_fqn(ctx.0)).to_string(), i18n!(ctx.1, "Followers"), false)
])
@if !recents.is_empty() {
<h2>
@i18n!(ctx.1, "Latest articles")
<small><a href="/@@/@user.get_fqn(ctx.0)/atom.xml" title="@i18n!(ctx.1, "Atom feed")">@icon!("rss")</a></small>
<small><a href="@uri!(user::atom_feed: name = user.get_fqn(ctx.0))" title="@i18n!(ctx.1, "Atom feed")">@icon!("rss")</a></small>
</h2>
<div class="cards">
@for article in recents {

View file

@ -2,29 +2,32 @@
@use template_utils::*;
@use routes::user::UpdateUserForm;
@use validator::ValidationErrors;
@use routes::*;
@(ctx: BaseContext, form: UpdateUserForm, errors: ValidationErrors)
@:base(ctx, "Edit your account", {}, {}, {
<h1>@i18n!(ctx.1, "Your Profile")</h1>
<form method="post">
<!-- Rocket hack to use various HTTP methods -->
<input type=hidden name="_method" value="put">
@if let Some(u) = ctx.2.clone() {
<h1>@i18n!(ctx.1, "Your Profile")</h1>
<form method="post" action="@uri!(user::update: _name = u.username.clone())">
<!-- Rocket hack to use various HTTP methods -->
<input type=hidden name="_method" value="put">
@input!(ctx.1, display_name (text), "Display name", form, errors.clone())
@input!(ctx.1, email (text), "Email", form, errors.clone())
@input!(ctx.1, summary (text), "Summary", form, errors)
@input!(ctx.1, display_name (text), "Display name", form, errors.clone())
@input!(ctx.1, email (text), "Email", form, errors.clone())
@input!(ctx.1, summary (text), "Summary", form, errors)
<input type="submit" value="@i18n!(ctx.1, "Update account")"/>
</form>
<h2>@i18n!(ctx.1, "Danger zone")</h2>
<p>@i18n!(ctx.1, "Be very careful, any action taken here can't be cancelled.")
@if !ctx.2.clone().expect("Editing profile while not connected").is_admin {
<form method="post" action="/@@/@ctx.2.clone().expect("Editing profile while not connected").get_fqn(ctx.0)/delete">
<input type="submit" class="inline-block button destructive" value="@i18n!(ctx.1, "Delete your account")">
<input type="submit" value="@i18n!(ctx.1, "Update account")"/>
</form>
} else {
<p>@i18n!(ctx.1, "Sorry, but as an admin, you can't leave your instance.")</p>
<h2>@i18n!(ctx.1, "Danger zone")</h2>
<p>@i18n!(ctx.1, "Be very careful, any action taken here can't be cancelled.")
@if !u.is_admin {
<form method="post" action="@uri!(user::delete: name = u.username)">
<input type="submit" class="inline-block button destructive" value="@i18n!(ctx.1, "Delete your account")">
</form>
} else {
<p>@i18n!(ctx.1, "Sorry, but as an admin, you can't leave your instance.")</p>
}
}
})

View file

@ -1,6 +1,7 @@
@use templates::{base, users::header};
@use template_utils::*;
@use plume_models::users::User;
@use routes::*;
@(ctx: BaseContext, user: User, follows: bool, is_remote: bool, remote_url: String, followers: Vec<User>, page: i32, n_pages: i32)
@ -8,14 +9,14 @@
@:header(ctx, &user, follows, is_remote, remote_url)
@tabs(&[
(&format!("/@/{}", user.get_fqn(ctx.0)), i18n!(ctx.1, "Articles"), false),
(&format!("/@/{}/followers", user.get_fqn(ctx.0)), i18n!(ctx.1, "Followers"), true)
(&uri!(user::details: name= user.get_fqn(ctx.0)).to_string(), i18n!(ctx.1, "Articles"), false),
(&uri!(user::followers: name = user.get_fqn(ctx.0)).to_string(), i18n!(ctx.1, "Followers"), true)
])
<div class="cards">
@for follower in followers {
<div class="card">
<h3><a href="/@@/@follower.get_fqn(ctx.0)/">@follower.name(ctx.0)</a> <small>@format!("@{}", follower.get_fqn(ctx.0))</small></h3>
<h3><a href="@uri!(user::details: name = follower.get_fqn(ctx.0))">@follower.name(ctx.0)</a> <small>@format!("@{}", follower.get_fqn(ctx.0))</small></h3>
<main><p>@Html(follower.summary)</p></main>
</div>
}

View file

@ -1,5 +1,6 @@
@use template_utils::*;
@use plume_models::users::User;
@use routes::*;
@(ctx: BaseContext, user: &User, follows: bool, is_remote: bool, instance_url: String)
@ -19,7 +20,7 @@
@if ctx.2.clone().map(|u| u.id == user.id).unwrap_or(false) {
<span class="badge">@i18n!(ctx.1, "It is you")</span>
<a href="/@@/@user.username/edit" class="button inline-block">@i18n!(ctx.1, "Edit your profile")</a>
<a href="@uri!(user::edit: name = &user.username)" class="button inline-block">@i18n!(ctx.1, "Edit your profile")</a>
}
</p>
</div>
@ -29,7 +30,7 @@
}
@if ctx.2.clone().map(|u| u.id != user.id).unwrap_or(false) {
<form class="inline" method="post" action="/@@/@user.get_fqn(ctx.0)/follow/">
<form class="inline" method="post" action="@uri!(user::follow: name = user.get_fqn(ctx.0))">
@if follows {
<input type="submit" value="@i18n!(ctx.1, "Unfollow")">
} else {

View file

@ -2,13 +2,14 @@
@use template_utils::*;
@use routes::user::NewUserForm;
@use validator::ValidationErrors;
@use routes::*;
@(ctx: BaseContext, enabled: bool, form: &NewUserForm, errors: ValidationErrors)
@:base(ctx, "Edit your account", {}, {}, {
@if enabled {
<h1>@i18n!(ctx.1, "Create an account")</h1>
<form method="post">
<form method="post" action="@uri!(user::create)">
@input!(ctx.1, username (text), "Username", form, errors.clone(), "minlenght=\"1\"")
@input!(ctx.1, email (text), "Email", form, errors.clone())
@input!(ctx.1, password (password), "Password", form, errors.clone(), "minlenght=\"8\"")