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
pull/339/head
Trinity Pointard 6 years ago
parent 70af57c6e1
commit b4e4b497ee

@ -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
))))
}
}

@ -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()
))

@ -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 = '';
}
}
});
}

@ -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>

@ -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>
}

@ -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>

@ -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>

@ -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">

@ -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),
])
}

@ -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() {

@ -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)
}
})

@ -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>
}

@ -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),
])
}

@ -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>
}

@ -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>

@ -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>

@ -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>

@ -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>

@ -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>

@ -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")
}

@ -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>

@ -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>
})

@ -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>
})

@ -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")" />

@ -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>
})

@ -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 {

@ -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>
}
}
})

@ -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>
}

@ -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 {

@ -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\"")

Loading…
Cancel
Save