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

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 { pub fn new_message(user: Option<User>, m: String, conn: DbConn, intl: I18n) -> Ructe {
render!(session::login( render!(session::login(
&(&*conn, &intl.catalog, user), &(&*conn, &intl.catalog, user),
Some(i18n!(intl.catalog, &m).to_string()), Some(m),
&LoginForm::default(), &LoginForm::default(),
ValidationErrors::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 template_utils::*;
@use routes::*;
@(ctx: BaseContext, title: &str, head: Content, header: Content, content: Content) @(ctx: BaseContext, title: &str, head: Content, header: Content, content: Content)
<!DOCTYPE html> <!DOCTYPE html>
@ -8,10 +8,10 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>@i18n!(ctx.1, title) ⋅ @i18n!(ctx.1, "Plume")</title> <title>@i18n!(ctx.1, title) ⋅ @i18n!(ctx.1, "Plume")</title>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/static/css/main.css" /> <link rel="stylesheet" href="@uri!(static_files: file = "css/main.css")" />
<link rel="stylesheet" href="/static/css/feather.css" /> <link rel="stylesheet" href="@uri!(static_files: file = "css/feather.css")" />
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="@uri!(instance::web_manifest)" />
<link rel="icon" type="image/png" href="/static/icons/trwnh/feather-filled/plumeFeatherFilled64.png"> <link rel="icon" type="image/png" href="@uri!(static_files: file = "icons/trwnh/feather-filled/plumeFeatherFilled64.png")">
@:head() @:head()
</head> </head>
<body> <body>
@ -21,8 +21,8 @@
</nav> </nav>
<div id="content"> <div id="content">
<nav> <nav>
<a href="/" class="title"> <a href="@uri!(instance::index)" class="title">
<img src="/static/icons/trwnh/feather/plumeFeather256.png"> <img src="@uri!(static_files: file = "icons/trwnh/feather/plumeFeather256.png")">
<p>@i18n!(ctx.1, "Plume")</p> <p>@i18n!(ctx.1, "Plume")</p>
</a> </a>
<hr/> <hr/>
@ -30,28 +30,28 @@
</nav> </nav>
<nav> <nav>
@if ctx.2.is_some() { @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> <i class="icon icon-home" aria-label="@i18n!(ctx.1, "Dashboard")"></i>
<span class="mobile-label">@i18n!(ctx.1, "Dashboard")</span> <span class="mobile-label">@i18n!(ctx.1, "Dashboard")</span>
</a> </a>
<a href="/notifications"> <a href="@uri!(notifications::notifications)">
<i class="icon icon-bell" aria-label="@i18n!(ctx.1, "Notifications")"></i> <i class="icon icon-bell" aria-label="@i18n!(ctx.1, "Notifications")"></i>
<span class="mobile-label">@i18n!(ctx.1, "Notifications")</span> <span class="mobile-label">@i18n!(ctx.1, "Notifications")</span>
</a> </a>
<a href="/logout"> <a href="@uri!(session::delete)">
<i class="icon icon-log-out" aria-label="@i18n!(ctx.1, "Log Out")"></i> <i class="icon icon-log-out" aria-label="@i18n!(ctx.1, "Log Out")"></i>
<span class="mobile-label">@i18n!(ctx.1, "Log Out")</span> <span class="mobile-label">@i18n!(ctx.1, "Log Out")</span>
</a> </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) @avatar(ctx.0, &ctx.2.clone().unwrap(), Size::Small, false, &ctx.1)
<span class="mobile-label">@i18n!(ctx.1, "My account")</span> <span class="mobile-label">@i18n!(ctx.1, "My account")</span>
</a> </a>
} else { } else {
<a href="/login"> <a href="@uri!(session::new)">
<i class="icon icon-log-in"></i> <i class="icon icon-log-in"></i>
<span class="mobile-label">@i18n!(ctx.1, "Log In")</span> <span class="mobile-label">@i18n!(ctx.1, "Log In")</span>
</a> </a>
<a href="/users/new"> <a href="@uri!(user::new)">
<i class="icon icon-user-plus"></i> <i class="icon icon-user-plus"></i>
<span class="mobile-label">@i18n!(ctx.1, "Register")</span> <span class="mobile-label">@i18n!(ctx.1, "Register")</span>
</a> </a>
@ -64,13 +64,13 @@
</main> </main>
<footer> <footer>
<span>@concat!("Plume ", env!("CARGO_PKG_VERSION"))</span> <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://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> <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) { @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> </footer>
<script src="/static/js/menu.js"></script> <script src="@uri!(static_files: file = "js/menu.js")"></script>
</body> </body>
</html> </html>

View File

@ -3,18 +3,19 @@
@use plume_models::users::User; @use plume_models::users::User;
@use templates::{base, partials::post_card}; @use templates::{base, partials::post_card};
@use template_utils::*; @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>) @(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(), {}, { @: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> <h1>@blog.title <small>~@fqn</small></h1>
<p>@blog.summary</p> <p>@blog.summary</p>
<p> <p>
@i18n!(ctx.1, "One author in this blog: ", "{0} authors in this blog: ", authors.len()) @i18n!(ctx.1, "One author in this blog: ", "{0} authors in this blog: ", authors.len())
@for author in authors { @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>
<p> <p>
@ -24,13 +25,13 @@
<section> <section>
<h2> <h2>
@i18n!(ctx.1, "Latest articles") @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> </h2>
@if posts.len() < 1 { @if posts.len() < 1 {
<p>@i18n!(ctx.1, "No posts to see here yet.")</p> <p>@i18n!(ctx.1, "No posts to see here yet.")</p>
} }
@if is_author { @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"> <div class="cards">
@for article in posts { @for article in posts {
@ -42,7 +43,7 @@
@if is_author { @if is_author {
<h2>@i18n!(ctx.1, "Danger zone")</h2> <h2>@i18n!(ctx.1, "Danger zone")</h2>
<p>@i18n!(ctx.1, "Be very careful, any action taken here can't be cancelled.")</p> <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")"> <input type="submit" class="inline-block button destructive" value="@i18n!(ctx.1, "Delete this blog")">
</form> </form>
} }

View File

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

View File

@ -1,6 +1,7 @@
@use plume_models::{instance::Instance, users::User};
@use templates::base; @use templates::base;
@use template_utils::*; @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) @(ctx: BaseContext, instance: Instance, admin: User, n_users: usize, n_articles: usize, n_instances: i64)
@ -23,7 +24,7 @@
<div> <div>
<p>@i18n!(ctx.1, "Administred by")</p> <p>@i18n!(ctx.1, "Administred by")</p>
@avatar(ctx.0, &admin, Size::Small, false, ctx.1) @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> </div>
</section> </section>
<p>@i18n!(ctx.1, "Runs Plume {0}"; env!("CARGO_PKG_VERSION"))</p> <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 templates::base;
@use template_utils::*; @use template_utils::*;
@use plume_models::instance::Instance;
@use routes::instance::InstanceSettingsForm; @use routes::instance::InstanceSettingsForm;
@use validator::ValidationErrors; @use routes::*;
@(ctx: BaseContext, instance: Instance, form: InstanceSettingsForm, errors: ValidationErrors) @(ctx: BaseContext, instance: Instance, form: InstanceSettingsForm, errors: ValidationErrors)
@ -10,12 +11,12 @@
<h1>@i18n!(ctx.1, "Administration")</h1> <h1>@i18n!(ctx.1, "Administration")</h1>
@tabs(&[ @tabs(&[
("/admin", i18n!(ctx.1, "Configuration"), true), (&uri!(instance::admin).to_string(), i18n!(ctx.1, "Configuration"), true),
("/admin/instances", i18n!(ctx.1, "Instances"), false), (&uri!(instance::admin_instances).to_string(), i18n!(ctx.1, "Instances"), false),
("/admin/users", i18n!(ctx.1, "Users"), 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") @input!(ctx.1, name (text), "Name", form, errors.clone(), "props")
<label for="open_registrations"> <label for="open_registrations">

View File

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

View File

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

View File

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

View File

@ -1,7 +1,8 @@
@use templates::{base, partials::post_card};
@use template_utils::*;
@use plume_models::posts::Post; @use plume_models::posts::Post;
@use plume_models::instance::Instance; @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) @(ctx: BaseContext, instance: Instance, articles: Vec<Post>, page: i32, n_pages: i32)
@ -10,16 +11,16 @@
@if let Some(_) = ctx.2 { @if let Some(_) = ctx.2 {
@tabs(&[ @tabs(&[
("/", i18n!(ctx.1, "Latest articles"), false), (&uri!(instance::index).to_string(), i18n!(ctx.1, "Latest articles"), false),
("/feed", i18n!(ctx.1, "Your feed"), false), (&uri!(instance::feed).to_string(), i18n!(ctx.1, "Your feed"), false),
("/federated", i18n!(ctx.1, "Federated feed"), false), (&uri!(instance::federated).to_string(), i18n!(ctx.1, "Federated feed"), false),
("/local", i18n!(ctx.1, "Local feed"), true), (&uri!(instance::local).to_string(), i18n!(ctx.1, "Local feed"), true),
]) ])
} else { } else {
@tabs(&[ @tabs(&[
("/", i18n!(ctx.1, "Latest articles"), false), (&uri!(instance::index).to_string(), i18n!(ctx.1, "Latest articles"), false),
("/federated", i18n!(ctx.1, "Federated feed"), false), (&uri!(instance::federated).to_string(), i18n!(ctx.1, "Federated feed"), false),
("/local", i18n!(ctx.1, "Local feed"), true), (&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 templates::base;
@use template_utils::*; @use template_utils::*;
@use plume_models::users::User; @use routes::*;
@(ctx: BaseContext, users: Vec<User>, page: i32, n_pages: i32) @(ctx: BaseContext, users: Vec<User>, page: i32, n_pages: i32)
@ -8,9 +9,9 @@
<h1>@i18n!(ctx.1, "Users")</h1> <h1>@i18n!(ctx.1, "Users")</h1>
@tabs(&[ @tabs(&[
("/admin", i18n!(ctx.1, "Configuration"), false), (&uri!(instance::admin).to_string(), i18n!(ctx.1, "Configuration"), false),
("/admin/instances", i18n!(ctx.1, "Instances"), false), (&uri!(instance::admin_instances).to_string(), i18n!(ctx.1, "Instances"), false),
("/admin/users", i18n!(ctx.1, "Users"), true), (&uri!(instance::admin_users).to_string(), i18n!(ctx.1, "Users"), true),
]) ])
<div class="list"> <div class="list">
@ -18,11 +19,11 @@
<div class="flex"> <div class="flex">
@avatar(ctx.0, &user, Size::Small, false, ctx.1) @avatar(ctx.0, &user, Size::Small, false, ctx.1)
<p class="grow"> <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> <small>@format!("@{}", user.username)</small>
</p> </p>
@if !user.is_admin { @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")"> <input type="submit" value="@i18n!(ctx.1, "Ban")">
</form> </form>
} }

View File

@ -1,13 +1,14 @@
@use plume_models::medias::{Media, MediaCategory};
@use templates::base; @use templates::base;
@use template_utils::*; @use template_utils::*;
@use plume_models::medias::{Media, MediaCategory}; @use routes::*;
@(ctx: BaseContext, media: Media) @(ctx: BaseContext, media: Media)
@:base(ctx, "Media details", {}, {}, { @:base(ctx, "Media details", {}, {}, {
<h1>@i18n!(ctx.1, "Media details")</h1> <h1>@i18n!(ctx.1, "Media details")</h1>
<section> <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>
<section> <section>
@ -24,11 +25,11 @@
</div> </div>
<div> <div>
@if media.category() == MediaCategory::Image { @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")"> <input class="button" type="submit" value="@i18n!(ctx.1, "Use as avatar")">
</form> </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")"> <input class="button" type="submit" value="@i18n!(ctx.1, "Delete")">
</form> </form>
</div> </div>

View File

@ -1,13 +1,14 @@
@use plume_models::medias::Media;
@use templates::base; @use templates::base;
@use template_utils::*; @use template_utils::*;
@use plume_models::medias::Media; @use routes::*;
@(ctx: BaseContext, medias: Vec<Media>) @(ctx: BaseContext, medias: Vec<Media>)
@:base(ctx, "Your media", {}, {}, { @:base(ctx, "Your media", {}, {}, {
<h1>@i18n!(ctx.1, "Your media")</h1> <h1>@i18n!(ctx.1, "Your media")</h1>
<div> <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> </div>
<section> <section>
@ -19,9 +20,11 @@
<div class="card flex"> <div class="card flex">
@Html(media.preview_html(ctx.0)) @Html(media.preview_html(ctx.0))
<main class="grow"> <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> </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>
} }
</div> </div>

View File

@ -1,11 +1,12 @@
@use templates::base; @use templates::base;
@use template_utils::*; @use template_utils::*;
@use routes::*;
@(ctx: BaseContext) @(ctx: BaseContext)
@:base(ctx, "Media upload", {}, {}, { @:base(ctx, "Media upload", {}, {}, {
<h1>@i18n!(ctx.1, "Media upload")</h1> <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"> <label for="alt">
@i18n!(ctx.1, "Description") @i18n!(ctx.1, "Description")
<small>@i18n!(ctx.1, "Useful for visually impaired people and licensing")</small> <small>@i18n!(ctx.1, "Useful for visually impaired people and licensing")</small>

View File

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

View File

@ -1,5 +1,6 @@
@use template_utils::*; @use template_utils::*;
@use plume_models::instance::Instance; @use plume_models::instance::Instance;
@use routes::*;
@(ctx: BaseContext, instance: Instance, n_users: i32, n_articles: i32) @(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, "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> <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> </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>
<div class="presentation card"> <div class="presentation card">
<h2>@i18n!(ctx.1, "About {0}"; instance.name)</h2> <h2>@i18n!(ctx.1, "About {0}"; instance.name)</h2>
@ -27,7 +28,7 @@
</div> </div>
</section> </section>
</main> </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>
</div> </div>
</section> </section>

View File

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

View File

@ -7,33 +7,36 @@
@use plume_models::users::User; @use plume_models::users::User;
@use validator::ValidationErrors; @use validator::ValidationErrors;
@use routes::comments::NewCommentForm; @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) @(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(), { @: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"/> <meta property="og:type" content="article"/>
@if article.cover_id.is_some() { @if article.cover_id.is_some() {
<meta property="og:image" content="@Html(article.cover_url(ctx.0).unwrap_or_default())"/> <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"/> <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> <h1 class="article">@&article.title</h1>
<h2 class="article">@&article.subtitle</h2> <h2 class="article">@&article.subtitle</h2>
<div class="article-info"> <div class="article-info">
<span class="author"> <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> </span>
&mdash; &mdash;
<span class="date">@article.creation_date.format("%B %e, %Y")</span> <span class="date">@article.creation_date.format("%B %e, %Y")</span>
@if ctx.2.clone().map(|u| u.id == author.id).unwrap_or(false) { @if ctx.2.clone().map(|u| u.id == author.id).unwrap_or(false) {
&mdash; &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; &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")"> <input onclick="return confirm('Are you sure you?')" type="submit" value="@i18n!(ctx.1, "Delete this article")">
</form> </form>
} }
@ -53,39 +56,37 @@
<ul class="tags"> <ul class="tags">
@for tag in tags { @for tag in tags {
@if !tag.is_hashtag { @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> </ul>
<div class="flex"> <div class="flex">
@avatar(ctx.0, &author, Size::Medium, true, ctx.1) @avatar(ctx.0, &author, Size::Medium, true, ctx.1)
<div class="grow"> <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> <p>@Html(&author.summary)</h2>
</div> </div>
<a href="/@@/@author.get_fqn(ctx.0)/follow" class="button"> @if !ctx.2.as_ref().map(|u| u.id == author.id).unwrap_or(false) {
@if is_following { <form action="@uri!(user::follow: name = author.get_fqn(ctx.0))" method="POST">
@i18n!(ctx.1, "Unfollow") <input type="submit" class="button" value="@if is_following {@i18n!(ctx.1, "Unfollow")} else {@i18n!(ctx.1, "Follow")}">
} else { </form>
@i18n!(ctx.1, "Follow") }
}
</a>
</div> </div>
@if ctx.2.is_some() { @if ctx.2.is_some() {
<div class="actions"> <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)"> <p aria-label="@i18n!(ctx.1, "One like", "{0} likes", &n_likes)" title="@i18n!(ctx.1, "One like", "{0} likes", n_likes)">
@n_likes @n_likes
</p> </p>
@if has_liked { @if has_liked {
<button type="submit" class="action liked">@icon!("heart") @i18n!(ctx.1, "I don't like this anymore")</button> <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> <button type="submit" class="action">@icon!("heart") @i18n!(ctx.1, "Add yours")</button>
} }
</form> </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)"> <p aria-label="@i18n!(ctx.1, "One boost", "{0} boost", &n_reshares)" title="@i18n!(ctx.1, "One boost", "{0} boosts", n_reshares)">
@n_reshares @n_reshares
</p> </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)"> <p aria-label="@i18n!(ctx.1, "One like", "{0} likes", &n_likes)" title="@i18n!(ctx.1, "One like", "{0} likes", n_likes)">
@n_likes @n_likes
</p> </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>
<div class="reshares"> <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)"> <p aria-label="@i18n!(ctx.1, "One boost", "{0} boost", &n_reshares)" title="@i18n!(ctx.1, "One boost", "{0} boosts", n_reshares)">
@n_reshares @n_reshares
</p> </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>
</div> </div>
} }
@ -120,7 +121,7 @@
<h2>@i18n!(ctx.1, "Comments")</h2> <h2>@i18n!(ctx.1, "Comments")</h2>
@if ctx.2.is_some() { @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, "") @input!(ctx.1, warning (optional text), "Content warning", comment_form, comment_errors, "")
<label for="plume-editor">@i18n!(ctx.1, "Your comment")</label> <label for="plume-editor">@i18n!(ctx.1, "Your comment")</label>

View File

@ -3,9 +3,12 @@
@use validator::{ValidationErrors, ValidationErrorsKind}; @use validator::{ValidationErrors, ValidationErrorsKind};
@use std::borrow::Cow; @use std::borrow::Cow;
@use plume_models::medias::*; @use plume_models::medias::*;
@use plume_models::blogs::Blog;
@use plume_models::posts::Post;
@use routes::posts::NewPostForm; @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), {}, {}, { @:base(ctx, &i18n!(ctx.1, if editing { "Edit {0}" } else { "New post" }; &form.title), {}, {}, {
<h1> <h1>
@ -15,7 +18,11 @@
@i18n!(ctx.1, "Create a new post") @i18n!(ctx.1, "Create a new post")
} }
</h1> </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, title (text), "Title", form, errors.clone(), "required")
@input!(ctx.1, subtitle (optional text), "Subtitle", form, errors.clone(), "") @input!(ctx.1, subtitle (optional text), "Subtitle", form, errors.clone(), "")
@ -63,5 +70,5 @@
} }
} }
</form> </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 templates::base;
@use template_utils::*; @use template_utils::*;
@use routes::*;
@(ctx: BaseContext, now: &str) @(ctx: BaseContext, now: &str)
@:base(ctx, "Search", { @: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>
}, {}, {
<h1>@i18n!(ctx.1, "Search")</h1> <h1>@i18n!(ctx.1, "Search")</h1>
<form method="get" id="form"> <form method="get" id="form">
<input id="q" name="q" placeholder="Your query" type="search"> <input id="q" name="q" placeholder="Your query" type="search">
<br/>
<details> <details>
<summary>Advanced search</summary> <summary>Advanced search</summary>
@input!(ctx.1, title (text), "Title matching these words", "placeholder=\"Title\"") @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, subtitle (text), "Subtitle matching these words", "placeholder=\"Subtitle\"")
@input!(ctx.1, content (text), "Content matching these words", "placeholder=\"Content\"") @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, before (date), "To this date", &format!("max={}", now))
@input!(ctx.1, tag (text), "Containing these tags", "placeholder=\"Tags\"") @input!(ctx.1, tag (text), "Containing these tags", "placeholder=\"Tags\"")
@ -40,4 +26,5 @@
</details> </details>
<input type="submit" value="Search"/> <input type="submit" value="Search"/>
</form> </form>
<script src="@uri!(static_files: file = "js/search.js")"></script>
}) })

View File

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

View File

@ -2,6 +2,7 @@
@use template_utils::*; @use template_utils::*;
@use plume_models::blogs::Blog; @use plume_models::blogs::Blog;
@use plume_models::posts::Post; @use plume_models::posts::Post;
@use routes::*;
@(ctx: BaseContext, blogs: Vec<Blog>, drafts: Vec<Post>) @(ctx: BaseContext, blogs: Vec<Blog>, drafts: Vec<Post>)
@ -16,11 +17,11 @@
<div class="cards"> <div class="cards">
@for blog in blogs { @for blog in blogs {
<div class="card"> <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>
} }
</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> </section>
@if !drafts.is_empty() { @if !drafts.is_empty() {
@ -36,6 +37,6 @@
<section> <section>
<h2>@i18n!(ctx.1, "Your media")</h2> <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> </section>
}) })

View File

@ -2,6 +2,7 @@
@use template_utils::*; @use template_utils::*;
@use plume_models::posts::Post; @use plume_models::posts::Post;
@use plume_models::users::User; @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>) @(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) @:header(ctx, &user, follows, is_remote, remote_url)
@tabs(&[ @tabs(&[
(&format!("/@/{}", user.get_fqn(ctx.0)), i18n!(ctx.1, "Articles"), true), (&uri!(user::details: name= user.get_fqn(ctx.0)).to_string(), i18n!(ctx.1, "Articles"), true),
(&format!("/@/{}/followers", user.get_fqn(ctx.0)), i18n!(ctx.1, "Followers"), false) (&uri!(user::followers: name = user.get_fqn(ctx.0)).to_string(), i18n!(ctx.1, "Followers"), false)
]) ])
@if !recents.is_empty() { @if !recents.is_empty() {
<h2> <h2>
@i18n!(ctx.1, "Latest articles") @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> </h2>
<div class="cards"> <div class="cards">
@for article in recents { @for article in recents {

View File

@ -2,29 +2,32 @@
@use template_utils::*; @use template_utils::*;
@use routes::user::UpdateUserForm; @use routes::user::UpdateUserForm;
@use validator::ValidationErrors; @use validator::ValidationErrors;
@use routes::*;
@(ctx: BaseContext, form: UpdateUserForm, errors: ValidationErrors) @(ctx: BaseContext, form: UpdateUserForm, errors: ValidationErrors)
@:base(ctx, "Edit your account", {}, {}, { @:base(ctx, "Edit your account", {}, {}, {
<h1>@i18n!(ctx.1, "Your Profile")</h1> @if let Some(u) = ctx.2.clone() {
<form method="post"> <h1>@i18n!(ctx.1, "Your Profile")</h1>
<!-- Rocket hack to use various HTTP methods --> <form method="post" action="@uri!(user::update: _name = u.username.clone())">
<input type=hidden name="_method" value="put"> <!-- 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, display_name (text), "Display name", form, errors.clone())
@input!(ctx.1, email (text), "Email", form, errors.clone()) @input!(ctx.1, email (text), "Email", form, errors.clone())
@input!(ctx.1, summary (text), "Summary", form, errors) @input!(ctx.1, summary (text), "Summary", form, errors)
<input type="submit" value="@i18n!(ctx.1, "Update account")"/> <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")">
</form> </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 templates::{base, users::header};
@use template_utils::*; @use template_utils::*;
@use plume_models::users::User; @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) @(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) @:header(ctx, &user, follows, is_remote, remote_url)
@tabs(&[ @tabs(&[
(&format!("/@/{}", user.get_fqn(ctx.0)), i18n!(ctx.1, "Articles"), false), (&uri!(user::details: name= user.get_fqn(ctx.0)).to_string(), i18n!(ctx.1, "Articles"), false),
(&format!("/@/{}/followers", user.get_fqn(ctx.0)), i18n!(ctx.1, "Followers"), true) (&uri!(user::followers: name = user.get_fqn(ctx.0)).to_string(), i18n!(ctx.1, "Followers"), true)
]) ])
<div class="cards"> <div class="cards">
@for follower in followers { @for follower in followers {
<div class="card"> <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> <main><p>@Html(follower.summary)</p></main>
</div> </div>
} }

View File

@ -1,5 +1,6 @@
@use template_utils::*; @use template_utils::*;
@use plume_models::users::User; @use plume_models::users::User;
@use routes::*;
@(ctx: BaseContext, user: &User, follows: bool, is_remote: bool, instance_url: String) @(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) { @if ctx.2.clone().map(|u| u.id == user.id).unwrap_or(false) {
<span class="badge">@i18n!(ctx.1, "It is you")</span> <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> </p>
</div> </div>
@ -29,7 +30,7 @@
} }
@if ctx.2.clone().map(|u| u.id != user.id).unwrap_or(false) { @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 { @if follows {
<input type="submit" value="@i18n!(ctx.1, "Unfollow")"> <input type="submit" value="@i18n!(ctx.1, "Unfollow")">
} else { } else {

View File

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