Use Ructe (#327)
All the template are now compiled at compile-time with the `ructe` crate.
I preferred to use it instead of askama because it allows more complex Rust expressions, where askama only supports a small subset of expressions and doesn't allow them everywhere (for instance, `{{ macro!() | filter }}` would result in a parsing error).
The diff is quite huge, but there is normally no changes in functionality.
Fixes #161 and unblocks #110 and #273
This commit is contained in:
@@ -1,77 +1,75 @@
|
||||
{% import "macros" as macros %}
|
||||
@use template_utils::*;
|
||||
|
||||
@(ctx: BaseContext, title: &str, head: Content, header: Content, content: Content)
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>{% block title %}{% endblock title %} ⋅ {{ "Plume" | _ }}</title>
|
||||
<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">
|
||||
{% block head %}{% endblock head %}
|
||||
@:head()
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav id="menu">
|
||||
<a href="#" aria-label="{{ "Menu" | _ }}" title="{{ "Menu" | _ }}"><i class="icon icon-menu"></i></a>
|
||||
<a href="#" aria-label="@i18n!(ctx.1, "Menu")" title="@i18n!(ctx.1, "Menu")"><i class="icon icon-menu"></i></a>
|
||||
</nav>
|
||||
<div id="content">
|
||||
<nav>
|
||||
<a href="/" class="title">
|
||||
<img src="/static/icons/trwnh/feather/plumeFeather256.png">
|
||||
<p>{{ "Plume" | _ }}</p>
|
||||
<p>@i18n!(ctx.1, "Plume")</p>
|
||||
</a>
|
||||
<hr/>
|
||||
{% block header %}
|
||||
{% endblock header %}
|
||||
@:header()
|
||||
</nav>
|
||||
<nav>
|
||||
{% if account %}
|
||||
@if ctx.2.is_some() {
|
||||
<a href="/dashboard">
|
||||
<i class="icon icon-home" aria-label="{{ "Dashboard" | _ }}"></i>
|
||||
<span class="mobile-label">{{ "Dashboard" | _ }}</span>
|
||||
<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">
|
||||
<i class="icon icon-bell" aria-label="{{ "Notifications" | _ }}"></i>
|
||||
<span class="mobile-label">{{ "Notifications" | _ }}</span>
|
||||
<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">
|
||||
<i class="icon icon-log-out" aria-label="{{ "Log Out" | _ }}"></i>
|
||||
<span class="mobile-label">{{ "Log Out" | _ }}</span>
|
||||
<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="{{ "My account" | _ }}">
|
||||
{{ macros::avatar(user=account) }}
|
||||
<span class="mobile-label">{{ "My account" | _ }}</span>
|
||||
<a href="/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 %}
|
||||
} else {
|
||||
<a href="/login">
|
||||
<i class="icon icon-log-in"></i>
|
||||
<span class="mobile-label">{{ "Log In" | _ }}</span>
|
||||
<span class="mobile-label">@i18n!(ctx.1, "Log In")</span>
|
||||
</a>
|
||||
<a href="/users/new">
|
||||
<i class="icon icon-user-plus"></i>
|
||||
<span class="mobile-label">{{ "Register" | _ }}</span>
|
||||
<span class="mobile-label">@i18n!(ctx.1, "Register")</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
}
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
{% block content %}
|
||||
{% endblock content %}
|
||||
@:content()
|
||||
</main>
|
||||
<footer>
|
||||
<span>Plume 0.2.0</span>
|
||||
<a href="/about">{{ "About this instance" | _ }}</a>
|
||||
<a href="https://github.com/Plume-org/Plume">{{ "Source code" | _ }}</a>
|
||||
<a href="https://riot.im/app/#/room/#plume:disroot.org">{{ "Matrix room" | _ }}</a>
|
||||
{% if account %}
|
||||
{% if account.is_admin %}
|
||||
<a href="/admin">{{ "Administration" | _ }}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<span>@concat!("Plume ", env!("CARGO_PKG_VERSION"))</span>
|
||||
<a href="/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>
|
||||
}
|
||||
</footer>
|
||||
<script src="/static/js/menu.js"></script>
|
||||
</body>
|
||||
@@ -1,50 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ blog.title }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block header %}
|
||||
<a href="/~/{{ blog.fqn }}">{{ blog.title }}</a>
|
||||
{% endblock header %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ blog.title }} <small>~{{ blog.fqn }}</small></h1>
|
||||
<p>{{ blog.summary }}</p>
|
||||
<p>
|
||||
{{ "{{ count }} authors in this blog: " | _n(singular="One author in this blog: ", count = n_authors) }}
|
||||
{% for author in authors %}
|
||||
<a class="author" href="/@/{{ author.fqn }}">{{ author.name }}</a>{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
<p>
|
||||
{{ "{{ count }} articles in this blog" | _n(singular="One article in this blog", count = n_articles) }}
|
||||
</p>
|
||||
|
||||
<section>
|
||||
<h2>
|
||||
{{ "Latest articles" | _ }}
|
||||
<small><a href="/~/{{ blog.fqn }}/atom.xml" title="Atom feed">{{ macros::feather(name="rss") }}</a></small>
|
||||
</h2>
|
||||
{% if posts | length < 1 %}
|
||||
<p>{{ "No posts to see here yet." | _ }}</p>
|
||||
{% endif %}
|
||||
{% if is_author %}
|
||||
<a href="/~/{{ blog.fqn }}/new/" class="button inline-block">{{ "New article" | _ }}</a>
|
||||
{% endif %}
|
||||
<div class="cards">
|
||||
{% for article in posts %}
|
||||
{{ macros::post_card(article=article) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{{ macros::paginate(page=page, total=n_pages) }}
|
||||
</section>
|
||||
{% if is_author %}
|
||||
<h2>{{ "Danger zone" | _ }}</h2>
|
||||
<p>{{ "Be very careful, any action taken here can't be cancelled." | _ }}
|
||||
<form method="post" action="/~/{{ blog.fqn }}/delete">
|
||||
<input type="submit" class="inline-block button destructive" value="{{ "Delete this blog" | _ }}">
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,49 @@
|
||||
@use plume_models::blogs::Blog;
|
||||
@use plume_models::posts::Post;
|
||||
@use plume_models::users::User;
|
||||
@use templates::{base, partials::post_card};
|
||||
@use template_utils::*;
|
||||
|
||||
@(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>
|
||||
}, {
|
||||
<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>
|
||||
}
|
||||
</p>
|
||||
<p>
|
||||
@i18n!(ctx.1, "One article in this blog", "{0} articles in this blog", total_articles)
|
||||
</p>
|
||||
|
||||
<section>
|
||||
<h2>
|
||||
@i18n!(ctx.1, "Latest articles")
|
||||
<small><a href="/~/@fqn/atom.xml" 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>
|
||||
}
|
||||
<div class="cards">
|
||||
@for article in posts {
|
||||
@:post_card(ctx, article)
|
||||
}
|
||||
</div>
|
||||
@paginate(ctx.1, page, n_pages)
|
||||
</section>
|
||||
@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">
|
||||
<input type="submit" class="inline-block button destructive" value="@i18n!(ctx.1, "Delete this blog")">
|
||||
</form>
|
||||
}
|
||||
})
|
||||
@@ -1,15 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ "New blog" | _ }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ "Create a blog" | _ }}</h1>
|
||||
<form method="post">
|
||||
{{ macros::input(name="title", label="Title", errors=errors, form=form, props='required minlength="1"') }}
|
||||
|
||||
<input type="submit" value="{{ "Create blog" | _ }}"/>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,14 @@
|
||||
@use validator::ValidationErrors;
|
||||
@use templates::base;
|
||||
@use template_utils::*;
|
||||
@use routes::blogs::NewBlogForm;
|
||||
|
||||
@(ctx: BaseContext, form: &NewBlogForm, errors: ValidationErrors)
|
||||
|
||||
@:base(ctx, "New Blog", {}, {}, {
|
||||
<h1>@i18n!(ctx.1, "Create a blog")</h1>
|
||||
<form method="post">
|
||||
@input!(ctx.1, title (text), "Title", form, errors, "required minlength=\"1\"")
|
||||
<input type="submit" value="@i18n!(ctx.1, "Create blog")"/>
|
||||
</form>
|
||||
})
|
||||
@@ -1,5 +0,0 @@
|
||||
{% extends "errors/base" %}
|
||||
|
||||
{% block error %}
|
||||
<h1>{{ "You are not authorized." | _ }}</h1>
|
||||
{% endblock error %}
|
||||
@@ -1,6 +0,0 @@
|
||||
{% extends "errors/base" %}
|
||||
|
||||
{% block error %}
|
||||
<h1>{{ "We couldn't find this page." | _ }}</h1>
|
||||
<h2>{{ "The link that led you here may be broken." | _ }}</h2>
|
||||
{% endblock error %}
|
||||
@@ -1,6 +0,0 @@
|
||||
{% extends "errors/base" %}
|
||||
|
||||
{% block error %}
|
||||
<h1>{{ "Something broke on our side." | _ }}</h1>
|
||||
<p>{{ "Sorry about that. If you think this is a bug, please report it." | _ }}</p>
|
||||
{% endblock error %}
|
||||
@@ -1,15 +0,0 @@
|
||||
{% extends "base" %}
|
||||
|
||||
{% block title %}
|
||||
{{ error_message }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<main class="error">
|
||||
{% block error %}
|
||||
{% endblock error %}
|
||||
<p>
|
||||
{{ error_message }}
|
||||
</p>
|
||||
</main>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,9 @@
|
||||
@use templates::base as base_template;
|
||||
@use template_utils::*;
|
||||
|
||||
@(ctx: BaseContext, error_message: &str, error: Content)
|
||||
|
||||
@:base_template(ctx, error_message, {}, {}, {
|
||||
@:error()
|
||||
<p>@error_message</p>
|
||||
})
|
||||
@@ -1,6 +0,0 @@
|
||||
{% extends "errors/base" %}
|
||||
|
||||
{% block error %}
|
||||
<h1>{{ "Invalid CSRF token." | _ }}</h1>
|
||||
<p>{{ "Something is wrong with your CSRF token. Make sure cookies are enabled in you browser, and try reloading this page. If you continue to see this error message, please report it." | _ }}</p>
|
||||
{% endblock error %}
|
||||
@@ -0,0 +1,12 @@
|
||||
@use templates::errors::base;
|
||||
@use template_utils::*;
|
||||
|
||||
@(ctx: BaseContext)
|
||||
|
||||
@:base(ctx, "", {
|
||||
<h1>@i18n!(ctx.1, "Invalid CSRF token.")</h1>
|
||||
<p>@i18n!(ctx.1, r#"Something is wrong with your CSRF token.
|
||||
Make sure cookies are enabled in you browser, and try reloading this page.
|
||||
If you continue to see this error message, please report it."#)
|
||||
</p>
|
||||
})
|
||||
@@ -0,0 +1,8 @@
|
||||
@use templates::errors::base;
|
||||
@use template_utils::*;
|
||||
|
||||
@(ctx: BaseContext, error_message: &str)
|
||||
|
||||
@:base(ctx, error_message, {
|
||||
<h1>@i18n!(ctx.1, "You are not authorized.")</h1>
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
@use templates::errors::base;
|
||||
@use template_utils::*;
|
||||
|
||||
@(ctx: BaseContext)
|
||||
|
||||
@:base(ctx, "Page not found", {
|
||||
<h1>@i18n!(ctx.1, "We couldn't find this page.")</h1>
|
||||
<p>@i18n!(ctx.1, "The link that led you here may be broken.")</p>
|
||||
})
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
@use templates::errors::base;
|
||||
@use template_utils::*;
|
||||
|
||||
@(ctx: BaseContext)
|
||||
|
||||
@:base(ctx, "Internal server error", {
|
||||
<h1>@i18n!(ctx.1, "Something broke on our side.")</h1>
|
||||
<p>@i18n!(ctx.1, "Sorry about that. If you think this is a bug, please report it.")</p>
|
||||
})
|
||||
@@ -1,41 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
About {{ instance.name }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ "About {{ instance_name }}" | _(instance_name=instance.name) }}</h1>
|
||||
<section>
|
||||
{{ instance.short_description_html | safe }}
|
||||
</section>
|
||||
<div class="banner">
|
||||
<section class="stats">
|
||||
<div>
|
||||
<p>{{ "Home to" | _ }}</p>
|
||||
<em>{{ n_users }}</em>
|
||||
<p>{{ "people" | _ }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>{{ "Who wrote" | _ }}</p>
|
||||
<em>{{ n_articles }}</em>
|
||||
<p>{{ "articles" | _ }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>{{ "And connected to" | _ }}</p>
|
||||
<em>{{ n_instances }}</em>
|
||||
<p>{{ "other instances" | _ }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>{{ "Administred by" | _ }}</p>
|
||||
{{ macros::avatar(user=admin) }}
|
||||
<p><a href="/@/{{ admin.fqn }}">{{ admin.name }}</a><small>(@{{ admin.fqn }})</small></p>
|
||||
</div>
|
||||
</section>
|
||||
<p>{{ "Runs Plume {{ version }}" | _(version=version) }}
|
||||
</div>
|
||||
<section>
|
||||
{{ instance.long_description_html | safe }}
|
||||
</section>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,34 @@
|
||||
@use templates::base;
|
||||
@use template_utils::*;
|
||||
@use plume_models::{instance::Instance, users::User};
|
||||
|
||||
@(ctx: BaseContext, instance: Instance, admin: User, n_users: usize, n_articles: usize, n_instances: i64)
|
||||
|
||||
@:base(ctx, i18n!(ctx.1, "About {0}"; instance.name.clone()).as_str(), {}, {}, {
|
||||
<h1>@i18n!(ctx.1, "About {0}"; instance.name)</h1>
|
||||
<section>
|
||||
@Html(instance.short_description_html)
|
||||
</section>
|
||||
<div class="banner">
|
||||
<section class="stats">
|
||||
<div>
|
||||
<p>@Html(i18n!(ctx.1, "Home to <em>{0}</em> users"; n_users))</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>@Html(i18n!(ctx.1, "Who wrote <em>{0}</em> articles"; n_articles))</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>@Html(i18n!(ctx.1, "And connected to <em>{0}</em> other instances"; n_instances))</p>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
<p>@i18n!(ctx.1, "Runs Plume {0}"; env!("CARGO_PKG_VERSION"))</p>
|
||||
</div>
|
||||
<section>
|
||||
@Html(instance.long_description_html)
|
||||
</section>
|
||||
})
|
||||
@@ -1,35 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ "Administration of {{ instance.name }}" | _(instance=instance) }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ "Administration" | _ }}</h1>
|
||||
|
||||
{{ macros::tabs(links=['/admin', '/admin/instances', '/admin/users'], titles=['Configuration', 'Instances', 'Users'], selected=1) }}
|
||||
|
||||
<form method="post">
|
||||
{{ macros::input(name="name", label="Name", errors=errors, form=form, props='minlenght="1"', default=instance) }}
|
||||
|
||||
<label for="open_registrations">
|
||||
{% if instance.open_registrations %}
|
||||
<input type="checkbox" name="open_registrations" id="open_registrations" checked>
|
||||
{% else %}
|
||||
<input type="checkbox" name="open_registrations" id="open_registrations">
|
||||
{% endif %}
|
||||
{{ "Allow anyone to register" | _ }}
|
||||
</label>
|
||||
|
||||
<label for="short_description">{{ "Short description" | _ }}<small>{{ "Markdown is supported" | _ }}</small></label>
|
||||
<textarea id="short_description" name="short_description">{{ form.short_description | default(value=instance.short_description | safe) }}</textarea>
|
||||
|
||||
<label for="long_description">{{ "Long description" | _ }}<small>{{ "Markdown is supported" | _ }}</small></label>
|
||||
<textarea id="long_description" name="long_description">{{ form.long_description | default(value=instance.long_description | safe) }}</textarea>
|
||||
|
||||
{{ macros::input(name="default_license", label="Default license", errors=errors, form=form, props='minlenght="1"', default=instance) }}
|
||||
|
||||
<input type="submit" value="{{ "Save settings" | _ }}"/>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,39 @@
|
||||
@use templates::base;
|
||||
@use template_utils::*;
|
||||
@use plume_models::instance::Instance;
|
||||
@use routes::instance::InstanceSettingsForm;
|
||||
@use validator::ValidationErrors;
|
||||
|
||||
@(ctx: BaseContext, instance: Instance, form: InstanceSettingsForm, errors: ValidationErrors)
|
||||
|
||||
@:base(ctx, i18n!(ctx.1, "Administration of {0}"; instance.name.clone()).as_str(), {}, {}, {
|
||||
<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),
|
||||
])
|
||||
|
||||
<form method="post">
|
||||
@input!(ctx.1, name (text), "Name", form, errors.clone(), "props")
|
||||
|
||||
<label for="open_registrations">
|
||||
@if instance.open_registrations {
|
||||
<input type="checkbox" name="open_registrations" id="open_registrations" checked>
|
||||
} else {
|
||||
<input type="checkbox" name="open_registrations" id="open_registrations">
|
||||
}
|
||||
@i18n!(ctx.1, "Allow anyone to register")
|
||||
|
||||
<label for="short_description">@i18n!(ctx.1, "Short description")<small>@i18n!(ctx.1, "Markdown is supported")</small></label>
|
||||
<textarea id="short_description" name="short_description">@Html(form.short_description)</textarea>
|
||||
|
||||
<label for="long_description">@i18n!(ctx.1, "Long description")<small>@i18n!(ctx.1, "Markdown is supported")</small></label>
|
||||
<textarea id="long_description" name="long_description">@Html(form.long_description)</textarea>
|
||||
|
||||
@input!(ctx.1, default_license (text), "Default license", form, errors, "minlenght=\"1\"")
|
||||
|
||||
<input type="submit" value="@i18n!(ctx.1, "Save settings")"/>
|
||||
</form>
|
||||
})
|
||||
@@ -1,32 +0,0 @@
|
||||
<section class="spaced">
|
||||
<div class="cards">
|
||||
<div class="presentation card">
|
||||
<h2>{{ "What is Plume?" | _ }}</h2>
|
||||
<main>
|
||||
<p>{{ "Plume is a decentralized blogging engine." | _ }}</p>
|
||||
<p>{{ "Authors can manage various blogs from an unique website." | _ }}</p>
|
||||
<p>{{ "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">{{ "Create your account" | _ }}</a>
|
||||
</div>
|
||||
<div class="presentation card">
|
||||
<h2>{{ "About {{ instance_name }}" | _(instance_name=instance.name) }}</h2>
|
||||
<main>
|
||||
{{ instance.short_description_html | safe }}
|
||||
<section class="stats">
|
||||
<div>
|
||||
<p>{{ "Home to" | _ }}</p>
|
||||
<em>{{ n_users }}</em>
|
||||
<p>{{ "people" | _ }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>{{ "Who wrote" | _ }}</p>
|
||||
<em>{{ n_articles }}</em>
|
||||
<p>{{ "articles" | _ }}</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<a href="/about">{{ "Read the detailed rules" | _ }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,23 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ "All the articles of the Fediverse" | _ }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ "All the articles of the Fediverse" | _ }}</h1>
|
||||
|
||||
{% if account %}
|
||||
{{ macros::tabs(links=['/', '/feed', '/federated', '/local'], titles=['Latest articles', 'Your feed', 'Federated feed', 'Local feed'], selected=3) }}
|
||||
{% else %}
|
||||
{{ macros::tabs(links=['/', '/federated', '/local'], titles=['Latest articles', 'Federated feed', 'Local feed'], selected=2) }}
|
||||
{% endif %}
|
||||
|
||||
<div class="cards">
|
||||
{% for article in articles %}
|
||||
{{ macros::post_card(article=article) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{{ macros::paginate(page=page, total=n_pages) }}
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,31 @@
|
||||
@use templates::{base, partials::post_card};
|
||||
@use template_utils::*;
|
||||
@use plume_models::posts::Post;
|
||||
|
||||
@(ctx: BaseContext, articles: Vec<Post>, page: i32, n_pages: i32)
|
||||
|
||||
@:base(ctx, "All the articles of the Fediverse", {}, {}, {
|
||||
<h1>@i18n!(ctx.1, "All the articles of the Fediverse")</h1>
|
||||
|
||||
@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),
|
||||
])
|
||||
} else {
|
||||
@tabs(&[
|
||||
("/", i18n!(ctx.1, "Latest articles"), false),
|
||||
("/federated", i18n!(ctx.1, "Federated feed"), true),
|
||||
("/local", i18n!(ctx.1, "Local feed"), false),
|
||||
])
|
||||
}
|
||||
|
||||
<div class="cards">
|
||||
@for article in articles {
|
||||
@:post_card(ctx, article)
|
||||
}
|
||||
</div>
|
||||
@paginate(ctx.1, page, n_pages)
|
||||
})
|
||||
@@ -1,25 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ "Your feed" | _ }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>
|
||||
{{ "Your feed" | _ }}
|
||||
</h1>
|
||||
|
||||
{{ macros::tabs(links=['/', '/feed', '/federated', '/local'], titles=['Latest articles', 'Your feed', 'Federated feed', 'Local feed'], selected=2) }}
|
||||
|
||||
{% if articles | length > 0 %}
|
||||
<div class="cards">
|
||||
{% for article in articles %}
|
||||
{{ macros::post_card(article=article) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{{ macros::paginate(page=page, total=n_pages) }}
|
||||
{% else %}
|
||||
<p class="center">{{ "Nothing to see here yet. Try to follow more people." | _ }}</p>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,27 @@
|
||||
@use templates::{base, partials::post_card};
|
||||
@use template_utils::*;
|
||||
@use plume_models::posts::Post;
|
||||
|
||||
@(ctx: BaseContext, articles: Vec<Post>, page: i32, n_pages: i32)
|
||||
|
||||
@:base(ctx, "Your feed", {}, {}, {
|
||||
<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),
|
||||
])
|
||||
|
||||
@if !articles.is_empty() {
|
||||
<div class="cards">
|
||||
@for article in articles {
|
||||
@:post_card(ctx, article)
|
||||
}
|
||||
</div>
|
||||
} else {
|
||||
<p class="center">@i18n!(ctx.1, "Nothing to see here yet. Try to follow more people.")</p>
|
||||
}
|
||||
@paginate(ctx.1, page, n_pages)
|
||||
})
|
||||
@@ -1,25 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ instance.name }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ "Welcome to {{ instance_name | escape }}" | _(instance_name=instance.name) }}</h1>
|
||||
|
||||
{% if account %}
|
||||
{{ macros::tabs(links=['/', '/feed', '/federated', '/local'], titles=['Latest articles', 'Your feed', 'Federated feed', 'Local feed'], selected=1) }}
|
||||
|
||||
{{ macros::home_feed(title='Your feed', link='/feed', articles=user_feed) }}
|
||||
{{ macros::home_feed(title='Federated feed', link='/federated', articles=federated) }}
|
||||
{{ macros::home_feed(title='Local feed', link='/local', articles=local) }}
|
||||
{% include "instance/description" %}
|
||||
{% else %}
|
||||
{{ macros::tabs(links=['/', '/federated', '/local'], titles=['Latest articles', 'Federated feed', 'Local feed'], selected=1) }}
|
||||
|
||||
{{ macros::home_feed(title='Federated feed', link='/federated', articles=federated) }}
|
||||
{% include "instance/description" %}
|
||||
{{ macros::home_feed(title='Local feed', link='/local', articles=local) }}
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,34 @@
|
||||
@use templates::{base, partials::*};
|
||||
@use template_utils::*;
|
||||
@use plume_models::instance::Instance;
|
||||
@use plume_models::posts::Post;
|
||||
|
||||
@(ctx: BaseContext, instance: Instance, n_users: i32, n_articles: i32, local: Vec<Post>, federated: Vec<Post>, user_feed: Option<Vec<Post>>)
|
||||
|
||||
@:base(ctx, instance.name.clone().as_ref(), {}, {}, {
|
||||
<h1>@i18n!(ctx.1, "Welcome on {}"; instance.name.as_str())</h1>
|
||||
|
||||
@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),
|
||||
])
|
||||
|
||||
@: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")
|
||||
@: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),
|
||||
])
|
||||
|
||||
@:home_feed(ctx, federated, "/federated", "Federated feed")
|
||||
@:home_feed(ctx, local, "/local", "Local feed")
|
||||
@:instance_description(ctx, instance, n_users, n_articles)
|
||||
}
|
||||
})
|
||||
@@ -1,33 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ "Administration of {{ instance.name }}" | _(instance=instance) }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ "Instances" | _ }}</h1>
|
||||
|
||||
{{ macros::tabs(links=['/admin', '/admin/instances', '/admin/users'], titles=['Configuration', 'Instances', 'Users'], selected=2) }}
|
||||
|
||||
<div class="list">
|
||||
{% for instance in instances %}
|
||||
<div class="flex">
|
||||
<p class="grow">
|
||||
<a href="https://{{ instance.public_domain }}">{{ instance.name }}</a>
|
||||
<small>{{ instance.public_domain }}</small>
|
||||
</p>
|
||||
{% if not instance.local %}
|
||||
<form class="inline" method="post" action="/admin/instances/{{ instance.id }}/block">
|
||||
{% if instance.blocked %}
|
||||
<input type="submit" value="{{ 'Unblock' | _ }}">
|
||||
{% else %}
|
||||
<input type="submit" value="{{ 'Block' | _ }}">
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{{ macros::paginate(page=page, total=n_pages) }}
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,32 @@
|
||||
@use templates::base;
|
||||
@use template_utils::*;
|
||||
@use plume_models::instance::Instance;
|
||||
|
||||
@(ctx: BaseContext, instance: Instance, instances: Vec<Instance>, page: i32, n_pages: i32)
|
||||
|
||||
@:base(ctx, i18n!(ctx.1, "Administration of {0}"; instance.name.clone()).as_str(), {}, {}, {
|
||||
<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),
|
||||
])
|
||||
|
||||
<div class="list">
|
||||
@for instance in instances {
|
||||
<div class="flex">
|
||||
<p class="grow">
|
||||
<a href="https://@instance.public_domain">@instance.name</a>
|
||||
<small>@instance.public_domain</small>
|
||||
</p>
|
||||
@if !instance.local {
|
||||
<form class="inline" method="post" action="/admin/instances/@instance.id/block">
|
||||
<input type="submit" value="@i18n!(ctx.1, if instance.blocked { "Unblock" } else { "Block"})">
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@paginate(ctx.1, page, n_pages)
|
||||
})
|
||||
@@ -1,23 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ "Articles from {{ instance.name }}" | _(instance=instance) }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ "Articles from {{ instance.name }}" | _(instance=instance) }}</h1>
|
||||
|
||||
{% if account %}
|
||||
{{ macros::tabs(links=['/', '/feed', '/federated', '/local'], titles=['Latest articles', 'Your feed', 'Federated feed', 'Local feed'], selected=4) }}
|
||||
{% else %}
|
||||
{{ macros::tabs(links=['/', '/federated', '/local'], titles=['Latest articles', 'Federated feed', 'Local feed'], selected=3) }}
|
||||
{% endif %}
|
||||
|
||||
<div class="cards">
|
||||
{% for article in articles %}
|
||||
{{ macros::post_card(article=article) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{{ macros::paginate(page=page, total=n_pages) }}
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,32 @@
|
||||
@use templates::{base, partials::post_card};
|
||||
@use template_utils::*;
|
||||
@use plume_models::posts::Post;
|
||||
@use plume_models::instance::Instance;
|
||||
|
||||
@(ctx: BaseContext, instance: Instance, articles: Vec<Post>, page: i32, n_pages: i32)
|
||||
|
||||
@:base(ctx, i18n!(ctx.1, "Articles from {}"; instance.name.clone()).as_str(), {}, {}, {
|
||||
<h1>@i18n!(ctx.1, "Articles from {}"; instance.name)</h1>
|
||||
|
||||
@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),
|
||||
])
|
||||
} else {
|
||||
@tabs(&[
|
||||
("/", i18n!(ctx.1, "Latest articles"), false),
|
||||
("/federated", i18n!(ctx.1, "Federated feed"), false),
|
||||
("/local", i18n!(ctx.1, "Local feed"), true),
|
||||
])
|
||||
}
|
||||
|
||||
<div class="cards">
|
||||
@for article in articles {
|
||||
@:post_card(ctx, article)
|
||||
}
|
||||
</div>
|
||||
@paginate(ctx.1, page, n_pages)
|
||||
})
|
||||
@@ -1,30 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ "Users" | _ }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ "Users" | _ }}</h1>
|
||||
|
||||
{{ macros::tabs(links=['/admin', '/admin/instances', '/admin/users'], titles=['Configuration', 'Instances', 'Users'], selected=3) }}
|
||||
|
||||
<div class="list">
|
||||
{% for user in users %}
|
||||
<div class="flex">
|
||||
{{ macros::avatar(user=user) }}
|
||||
<p class="grow">
|
||||
<a href="/@/{{ user.fqn }}">{{ user.name }}</a>
|
||||
<small>@{{ user.username }}</small>
|
||||
</p>
|
||||
{% if not user.is_admin %}
|
||||
<form class="inline" method="post" href="/admin/users/{{ user.id }}/ban">
|
||||
<input type="submit" value="{{ 'Ban' | _ }}">
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{{ macros::paginate(page=page, total=n_pages) }}
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,33 @@
|
||||
@use templates::base;
|
||||
@use template_utils::*;
|
||||
@use plume_models::users::User;
|
||||
|
||||
@(ctx: BaseContext, users: Vec<User>, page: i32, n_pages: i32)
|
||||
|
||||
@:base(ctx, "Users", {}, {}, {
|
||||
<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),
|
||||
])
|
||||
|
||||
<div class="list">
|
||||
@for user in users {
|
||||
<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>
|
||||
<small>@format!("@{}", user.username)</small>
|
||||
</p>
|
||||
@if !user.is_admin {
|
||||
<form class="inline" method="post" action="/admin/users/@user.id/ban">
|
||||
<input type="submit" value="@i18n!(ctx.1, "Ban")">
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@paginate(ctx.1, page, n_pages)
|
||||
})
|
||||
@@ -1,112 +0,0 @@
|
||||
{% macro post_card(article) %}
|
||||
<div class="card">
|
||||
{% if article.cover %}
|
||||
<div class="cover" style="background-image: url('{{ article.cover.url }}')"></div>
|
||||
{% endif %}
|
||||
<h3><a href="{{ article.url }}">{{ article.post.title }}</a></h3>
|
||||
<main>
|
||||
<p>
|
||||
{% if article.post.subtitle | length > 0 %}
|
||||
{{ article.post.subtitle }}
|
||||
{% else %}
|
||||
{{ article.post.content | safe | striptags | truncate(length=200) }}
|
||||
{% endif %}
|
||||
</p>
|
||||
</main>
|
||||
<p class="author">
|
||||
{{ "By {{ link_1 }}{{ link_2 }}{{ link_3 }}{{ name | escape }}{{ link_4 }}" | _(
|
||||
link_1='<a href="/@/',
|
||||
link_2=article.author.fqn,
|
||||
link_3='/">',
|
||||
name=article.author.name,
|
||||
link_4="</a>")
|
||||
}}
|
||||
{% if article.post.published %}⋅ {{ article.date | date(format="%B %e") }}{% endif %}
|
||||
⋅ <a href="/~/{{ article.blog.fqn }}/">{{ article.blog.title }}</a>
|
||||
{% if not article.post.published %}⋅ {{ "Draft" | _ }}{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
{% endmacro post_card %}
|
||||
{% macro input(name, label, errors="", form="", type="text", props="", optional=false, default='', details=' ') %}
|
||||
<label for="{{ name }}">
|
||||
{{ label | _ }}
|
||||
{% if optional %}
|
||||
<small>{{ "Optional" | _ }}</small>
|
||||
{% endif %}
|
||||
<small>{{ details | _ }}</small>
|
||||
</label>
|
||||
{% if errors is defined and errors[name] %}
|
||||
{% for err in errors[name] %}
|
||||
<p class="error">{{ err.message | default(value="Unknown error") | _ }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% set default = default[name] | default(value="") %}
|
||||
<input type="{{ type }}" id="{{ name }}" name="{{ name }}" value="{{ form[name] | default(value=default) }}" {{ props | safe }}/>
|
||||
{% endmacro input %}
|
||||
{% macro paginate(page, total, previous="Previous page", next="Next page", query="") %}
|
||||
{% if query %}
|
||||
{% set query = query ~ "&" %}
|
||||
{% endif %}
|
||||
<div class="pagination">
|
||||
{% if page != 1 %}
|
||||
<a href="?{{ query }}page={{ page - 1 }}">{{ previous | _ }}</a>
|
||||
{% endif %}
|
||||
{% if page < total %}
|
||||
<a href="?{{ query }}page={{ page + 1 }}">{{ next | _ }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
{% macro comment(comm) %}
|
||||
<div class="comment" id="comment-{{ comm.id }}">
|
||||
<a class="author" href="/@/{{ comm.author.fqn }}/">
|
||||
{{ macros::avatar(user=comm.author, pad=true) }}
|
||||
<span class="display-name">{{ comm.author.name }}</span>
|
||||
<small>@{{ comm.author.fqn }}</small>
|
||||
</a>
|
||||
<div class="text">
|
||||
{% if comm.sensitive %}
|
||||
<details>
|
||||
<summary>{{ comm.spoiler_text }}</summary>
|
||||
{% endif %}
|
||||
{{ comm.content | safe }}
|
||||
{% if comm.sensitive %}
|
||||
</details>
|
||||
{% endif %}
|
||||
</div>
|
||||
<a class="button icon icon-message-circle" href="?responding_to={{ comm.id }}">{{ "Respond" | _ }}</a>
|
||||
{% for res in comm.responses %}
|
||||
{{ self::comment(comm=res) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
{% macro tabs(links, titles, selected) %}
|
||||
<div class="tabs">
|
||||
{% for link in links %}
|
||||
{% set idx = loop.index0 %}
|
||||
<a href="{{ link }}" {% if loop.index == selected %}class="selected"{% endif %}>{{ titles[idx] | _ }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
{% macro feather(name) %}
|
||||
<svg class="feather">
|
||||
<use xlink:href="/static/images/feather-sprite.svg#{{ name }}"/>
|
||||
</svg>
|
||||
{% endmacro %}
|
||||
{% macro home_feed(title, link, articles) %}
|
||||
{% if articles | length > 0 %}
|
||||
<h2>{{ title | _ }} — <a href="{{ link }}">{{ "View all" | _ }}</a></h2>
|
||||
<div class="cards spaced">
|
||||
{% for article in articles %}
|
||||
{{ macros::post_card(article=article) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro avatar(user, size="small", pad=true) %}
|
||||
<div
|
||||
class="avatar {{ size }} {% if pad %}padded{% endif %}"
|
||||
style="background-image: url('{{ user.avatar }}');"
|
||||
title="{{ "{{ name }}'s avatar" | _(name=user.name) }}"
|
||||
aria-label="{{ "{{ name }}'s avatar" | _(name=user.name) }}"
|
||||
></div>
|
||||
{% endmacro %}
|
||||
@@ -1,35 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ "Media details" | _ }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ "Media details" }}</h1>
|
||||
<section>
|
||||
<a href="/medias">{{ "Go back to the gallery" | _ }}</a>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<figure class="media">
|
||||
{{ media.html | safe }}
|
||||
<figcaption>{{ media.alt_text }}</figcaption>
|
||||
</figure>
|
||||
<div>
|
||||
<p>
|
||||
{{ "Markdown code" | _ }}
|
||||
<small>{{ "Copy it in your articles to insert this media." }}</small>
|
||||
</p>
|
||||
<code>{{ media.md }}</code>
|
||||
</div>
|
||||
<div>
|
||||
<form class="inline" method="post" action="/medias/{{ media.id }}/avatar">
|
||||
<input class="button" type="submit" value="{{ 'Use as avatar' | _ }}">
|
||||
</form>
|
||||
<form class="inline" method="post" action="/medias/{{ media.id }}/delete">
|
||||
<input class="button" type="submit" value="{{ 'Delete' | _ }}">
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,36 @@
|
||||
@use templates::base;
|
||||
@use template_utils::*;
|
||||
@use plume_models::medias::{Media, MediaCategory};
|
||||
|
||||
@(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>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<figure class="media">
|
||||
@Html(media.html(ctx.0))
|
||||
<figcaption>@media.alt_text</figcaption>
|
||||
</figure>
|
||||
<div>
|
||||
<p>
|
||||
@i18n!(ctx.1, "Markdown code")
|
||||
<small>@i18n!(ctx.1, "Copy it in your articles to insert this media.")</small>
|
||||
</p>
|
||||
<code>@media.markdown(ctx.0)</code>
|
||||
</div>
|
||||
<div>
|
||||
@if media.category() == MediaCategory::Image {
|
||||
<form class="inline" method="post" action="/medias/@media.id/avatar">
|
||||
<input class="button" type="submit" value="@i18n!(ctx.1, "Use as avatar")">
|
||||
</form>
|
||||
}
|
||||
<form class="inline" method="post" action="/medias/@media.id/delete">
|
||||
<input class="button" type="submit" value="@i18n!(ctx.1, "Delete")">
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
})
|
||||
@@ -1,31 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ "Your media" | _ }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ "Your media" | _ }}</h1>
|
||||
<div>
|
||||
<a href="/medias/new" class="inline-block button">Upload</a>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
{% if medias | length < 1 %}
|
||||
<p>{{ "You don't have any media yet." | _ }}</p>
|
||||
{% endif %}
|
||||
<div class="list">
|
||||
{% for media in medias %}
|
||||
<div class="card flex">
|
||||
{{ media.html_preview | safe }}
|
||||
<main class="grow">
|
||||
<p><a href="/medias/{{ media.id }}">{{ media.alt_text }}</a></p>
|
||||
</main>
|
||||
<a href="/medias/{{ media.id }}/delete">{{ "Delete" | _ }}</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{# TODO: macros::paginate(page=page, total=n_pages) #}
|
||||
</section>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,29 @@
|
||||
@use templates::base;
|
||||
@use template_utils::*;
|
||||
@use plume_models::medias::Media;
|
||||
|
||||
@(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>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
@if medias.is_empty() {
|
||||
<p>@i18n!(ctx.1, "You don't have any media yet.")</p>
|
||||
}
|
||||
<div class="list">
|
||||
@for media in medias {
|
||||
<div class="card flex">
|
||||
@Html(media.preview_html(ctx.0))
|
||||
<main class="grow">
|
||||
<p><a href="/medias/@media.id">@media.alt_text</a></p>
|
||||
</main>
|
||||
<a href="/medias/@media.id/delete">@i18n!(ctx.1, "Delete")</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
})
|
||||
@@ -1,17 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ "Media upload" | _ }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ "Media upload" | _ }}</h1>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{{ macros::input(name="alt", label="Description", errors=errors, form=form, props='required minlength="1"', details='Useful for visually impaired people and licensing') }}
|
||||
{{ macros::input(name="cw", label="Content warning", errors=errors, form=form, details='Let it empty if there is none') }}
|
||||
{{ macros::input(name="file", type='file', label="File", errors=errors, form=form, props='required') }}
|
||||
|
||||
<input type="submit" value="{{ "Send" | _ }}"/>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,28 @@
|
||||
@use templates::base;
|
||||
@use template_utils::*;
|
||||
|
||||
@(ctx: BaseContext)
|
||||
|
||||
@:base(ctx, "Media upload", {}, {}, {
|
||||
<h1>@i18n!(ctx.1, "Media upload")</h1>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<label for="alt">
|
||||
@i18n!(ctx.1, "Description")
|
||||
<small>@i18n!(ctx.1, "Useful for visually impaired people and licensing")</small>
|
||||
</label>
|
||||
<input type="text" id="alt" name="alt" required minlenght="1"/>
|
||||
|
||||
<label for="cw">
|
||||
@i18n!(ctx.1, "Content warning")
|
||||
<small>@i18n!(ctx.1, "Let it empty if there is none")</small>
|
||||
</label>
|
||||
<input type="txt" id="cw" name="cw"/>
|
||||
|
||||
<label for="file">
|
||||
@i18n!(ctx.1, "File")
|
||||
</label>
|
||||
<input type="file" id="file" name="file" required/>
|
||||
|
||||
<input type="submit" value="@i18n!(ctx.1, "Send")"/>
|
||||
</form>
|
||||
})
|
||||
@@ -1,66 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ "Notifications" | _ }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ "Notifications" | _ }}</h1>
|
||||
<div class="list">
|
||||
{% for notification in notifications %}
|
||||
<div class="card flex">
|
||||
{% if notification.kind == "COMMENT" %}
|
||||
<i class="icon icon-message-circle left-icon"></i>
|
||||
<main class="grow">
|
||||
<h3><a href="{{ notification.object.post.url }}#comment-{{ notification.object.id }}">
|
||||
{{ "{{ user }} commented your article." | _(user=notification.object.user.name | escape) }}
|
||||
</a></h3>
|
||||
<p><a href="{{ notification.object.post.url }}">{{ notification.object.post.post.title }}</a></p>
|
||||
</main>
|
||||
<p><small>{{ notification.creation_date | date(format="%B %e, %H:%M") }}</small></p>
|
||||
|
||||
{% elif notification.kind == "FOLLOW" %}
|
||||
<i class="icon icon-user-plus left-icon"></i>
|
||||
<main class="grow">
|
||||
<h3><a href="/@/{{ notification.object.follower.fqn }}/">
|
||||
{{ "{{ user }} is now following you." | _(user=notification.object.follower.name | escape) }}
|
||||
</a></h3>
|
||||
</main>
|
||||
<p><small>{{ notification.creation_date | date(format="%B %e, %H:%M") }}</small></p>
|
||||
|
||||
{% elif notification.kind == "LIKE" %}
|
||||
<i class="icon icon-heart left-icon"></i>
|
||||
<main class="grow">
|
||||
<h3>
|
||||
{{ "{{ user }} liked your article." | _(user=notification.object.user.name | escape) }}
|
||||
</h3>
|
||||
<p><a href="{{ notification.object.post.url }}">{{ notification.object.post.post.title }}</a></p>
|
||||
</main>
|
||||
<p><small>{{ notification.creation_date | date(format="%B %e, %H:%M") }}</small></p>
|
||||
|
||||
{% elif notification.kind == "MENTION" %}
|
||||
<i class="icon icon-at-sign left-icon"></i>
|
||||
<main class="grow">
|
||||
<h3><a href="{{ notification.object.url }}">
|
||||
{{ "{{ user }} mentioned you." | _(user=notification.object.user.name | escape) }}
|
||||
</a></h3>
|
||||
</main>
|
||||
<p><small>{{ notification.creation_date | date(format="%B %e, %H:%M") }}</small></p>
|
||||
|
||||
{% elif notification.kind == "RESHARE" %}
|
||||
<i class="icon icon-repeat left-icon"></i>
|
||||
<main class="grow">
|
||||
<h3>
|
||||
{{ "{{ user }} boosted your article." | _(user=notification.object.user.name | escape) }}
|
||||
</h3>
|
||||
<p><a href="{{ notification.object.post.url }}">{{ notification.object.post.post.title }}</a></p>
|
||||
</main>
|
||||
<p><small>{{ notification.creation_date | date(format="%B %e, %H:%M") }}</small></p>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{{ macros::paginate(page=page, total=n_pages) }}
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,33 @@
|
||||
@use templates::base;
|
||||
@use template_utils::*;
|
||||
@use plume_models::notifications::Notification;
|
||||
|
||||
@(ctx: BaseContext, notifications: Vec<Notification>, page: i32, n_pages: i32)
|
||||
|
||||
@:base(ctx, "Notifications", {}, {}, {
|
||||
<h1>@i18n!(ctx.1, "Notifications")</h1>
|
||||
|
||||
<div class="list">
|
||||
@for notification in notifications {
|
||||
<div class="card flex">
|
||||
<i class="icon @notification.icon_class() left-icon"></i>
|
||||
<main class="grow">
|
||||
<h3>
|
||||
@if let Some(url) = notification.get_url(ctx.0) {
|
||||
<a href="@url">
|
||||
@i18n!(ctx.1, notification.get_message(); notification.get_actor(ctx.0).name(ctx.0))
|
||||
</a>
|
||||
} else {
|
||||
@i18n!(ctx.1, notification.get_message(); notification.get_actor(ctx.0).name(ctx.0))
|
||||
}
|
||||
</h3>
|
||||
@if let Some(post) = notification.get_post(ctx.0) {
|
||||
<p><a href="@post.url(ctx.0)">@post.title</a></p>
|
||||
}
|
||||
</main>
|
||||
<p><small>@notification.creation_date.format("%B %e, %H:%M")</small></p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@paginate(ctx.1, page, n_pages)
|
||||
})
|
||||
@@ -0,0 +1,27 @@
|
||||
@use template_utils::*;
|
||||
@use plume_models::comments::Comment;
|
||||
@use plume_models::users::User;
|
||||
|
||||
@(ctx: BaseContext, comm: &Comment, author: User)
|
||||
|
||||
<div class="comment" id="comment-@comm.id">
|
||||
<a class="author" href="/@@/@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>
|
||||
</a>
|
||||
<div class="text">
|
||||
@if comm.sensitive {
|
||||
<details>
|
||||
<summary>@comm.spoiler_text</summary>
|
||||
}
|
||||
@Html(&comm.content)
|
||||
@if comm.sensitive {
|
||||
</details>
|
||||
}
|
||||
</div>
|
||||
<a class="button icon icon-message-circle" href="?responding_to=@comm.id">@i18n!(ctx.1, "Respond")</a>
|
||||
@for res in comm.get_responses(ctx.0) {
|
||||
@:comment(ctx, &res, res.get_author(ctx.0))
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,14 @@
|
||||
@use templates::partials::post_card;
|
||||
@use plume_models::posts::Post;
|
||||
@use template_utils::*;
|
||||
|
||||
@(ctx: BaseContext, articles: Vec<Post>, link: &str, title: &str)
|
||||
|
||||
@if articles.len() > 0 {
|
||||
<h2>@i18n!(ctx.1, title) — <a href="@link">@i18n!(ctx.1, "View all")</a></h2>
|
||||
<div class="cards spaced">
|
||||
@for article in articles {
|
||||
@:post_card(ctx, article)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
@use template_utils::*;
|
||||
@use plume_models::instance::Instance;
|
||||
|
||||
@(ctx: BaseContext, instance: Instance, n_users: i32, n_articles: i32)
|
||||
|
||||
<section class="spaced">
|
||||
<div class="cards">
|
||||
<div class="presentation card">
|
||||
<h2>@i18n!(ctx.1, "What is Plume?")</h2>
|
||||
<main>
|
||||
<p>@i18n!(ctx.1, "Plume is a decentralized blogging engine.")</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>
|
||||
</main>
|
||||
<a href="/users/new">@i18n!(ctx.1, "Create your account")</a>
|
||||
</div>
|
||||
<div class="presentation card">
|
||||
<h2>@i18n!(ctx.1, "About {0}"; instance.name)</h2>
|
||||
<main>
|
||||
@Html(instance.short_description_html)
|
||||
<section class="stats">
|
||||
<div>
|
||||
<p>@Html(i18n!(ctx.1, "Home to <em>{0}</em> people"; n_users))</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>@Html(i18n!(ctx.1, "Who wrote <em>{0}</em> articles"; n_articles))</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<a href="/about">@i18n!(ctx.1, "Read the detailed rules")</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,29 @@
|
||||
@use template_utils::*;
|
||||
@use plume_models::posts::Post;
|
||||
|
||||
@(ctx: BaseContext, article: Post)
|
||||
|
||||
<div class="card">
|
||||
@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>
|
||||
<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)),
|
||||
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>
|
||||
@if !article.published {
|
||||
⋅ @i18n!(ctx.1, "Draft")
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block head %}
|
||||
<meta property="og:title" content="{{ article.post.title }}"/>
|
||||
<meta property="og:type" content="article"/>
|
||||
{% if article.cover %}
|
||||
<meta property="og:image" content="{{ article.cover.url | safe }}"/>
|
||||
{% endif %}
|
||||
<meta property="og:url" content="{{ article.url | safe }}"/>
|
||||
{% if article.post.subtitle %}
|
||||
<meta property="og:description" content="{{ article.post.subtitle }}"/>
|
||||
{% endif %}
|
||||
{% endblock head %}
|
||||
|
||||
{% block title %}
|
||||
{{ article.post.title }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block header %}
|
||||
<a href="/~/{{ blog.fqn }}">{{ blog.title }}</a>
|
||||
{% endblock header %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="article">{{ article.post.title }}</h1>
|
||||
<h2 class="article">{{ article.post.subtitle }}</h2>
|
||||
<div class="article-info">
|
||||
<span class="author">{{ "Written by {{ link_1 }}{{ url }}{{ link_2 }}{{ name | escape }}{{ link_3 }}" | _(
|
||||
link_1='<a href="/@/',
|
||||
url=author.fqn,
|
||||
link_2='/">',
|
||||
name=author.name,
|
||||
link_3="</a>"
|
||||
)
|
||||
}}</span>
|
||||
—
|
||||
<span class="date">{{ date | date(format="%B %e, %Y") }}</span>
|
||||
{% if is_author %}
|
||||
—
|
||||
<a href="{{ article.url}}edit">{{ "Edit" | _ }}</a>
|
||||
—
|
||||
<form class="inline" method="post" action="{{ article.url}}delete">
|
||||
<input onclick="return confirm('Are you sure you?')" type="submit" value="{{ 'Delete this article' | _ }}">
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if not article.post.published %}
|
||||
<span class="badge">{{ "Draft" }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if article.cover %}
|
||||
<div class="cover" style="background-image: url('{{ article.cover.url }}')"></div>
|
||||
{% endif %}
|
||||
<article>
|
||||
{{ article.post.content | safe }}
|
||||
</article>
|
||||
|
||||
<div class="article-meta">
|
||||
<p>{{ "This article is under the {{ license }} license." | _(license=article.post.license) }}</p>
|
||||
<ul class="tags">
|
||||
{% for tag in article.tags %}
|
||||
{% if not tag.is_hashtag %}
|
||||
<li><a href="/tag/{{ tag.tag }}">{{ tag.tag }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="flex">
|
||||
{{ macros::avatar(user=author, pad=true, size="medium") }}
|
||||
<div class="grow">
|
||||
<h2><a href="/@/{{ author.fqn }}">{{ author.name }}</a></h2>
|
||||
<p>{{ author.summary | safe }}</h2>
|
||||
</div>
|
||||
<a href="/@/{{ author.fqn }}/follow" class="button">
|
||||
{% if is_following %}
|
||||
{{ "Unfollow" | _ }}
|
||||
{% else %}
|
||||
{{ "Follow" | _ }}
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if account %}
|
||||
<div class="actions">
|
||||
<form class="likes" action="{{ article.url }}like" method="POST">
|
||||
<p aria-label="{{ "{{ count }} likes" | _n(singular="One like", count=n_likes) }}" title="{{ "{{ count }} likes" | _n(singular="One like", count=n_likes) }}">{{ n_likes }}</p>
|
||||
|
||||
{% if has_liked %}
|
||||
<button type="submit" class="action liked">{{ macros::feather(name="heart") }}{{ "I don't like this anymore" | _ }}</button>
|
||||
{% else %}
|
||||
<button type="submit" class="action">{{ macros::feather(name="heart") }}{{ "Add yours" | _ }}</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
<form class="reshares" action="{{ article.url }}reshare" method="POST">
|
||||
<p aria-label="{{ "{{ count }} Boosts" | _n(singular="One Boost", count=n_reshares) }}" title="{{ "{{ count }} Boosts" | _n(singular="One Boost", count=n_reshares) }}">{{ n_reshares }}</p>
|
||||
|
||||
{% if has_reshared %}
|
||||
<button type="submit" class="action reshared"><i class="icon icon-repeat"></i>{{ "I don't want to boost this anymore" | _ }}</button>
|
||||
{% else %}
|
||||
<button type="submit" class="action"><i class="icon icon-repeat"></i>{{ "Boost" | _ }}</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="center">{{ "Login or use your Fediverse account to interact with this article" | _ }}</p>
|
||||
<div class="actions">
|
||||
<div class="likes">
|
||||
<p aria-label="{{ "{{ count }} likes" | _n(singular="One like", count=n_likes) }}" title="{{ "{{ count }} likes" | _n(singular="One like", count=n_likes) }}">{{ n_likes }}</p>
|
||||
<a href="/login?m=Login%20to%20like" class="action">{{ macros::feather(name="heart") }}{{ "Add yours" | _ }}</a>
|
||||
</div>
|
||||
|
||||
<div class="reshares">
|
||||
<p aria-label="{{ "{{ count }} Boosts" | _n(singular="One Boost", count=n_reshares) }}" title="{{ "{{ count }} Boosts" | _n(singular="One Boost", count=n_reshares) }}">{{ n_reshares }}</p>
|
||||
<a href="/login?m=Login%20to%20boost" class="action"><i class="icon icon-repeat"></i>{{ "Boost" | _ }}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="comments">
|
||||
<h2>{{ "Comments" | _ }}</h2>
|
||||
|
||||
{% if account %}
|
||||
<form method="post" action="{{ article.url }}comment">
|
||||
{{ macros::input(
|
||||
name="warning",
|
||||
label="Content warning",
|
||||
optional=true,
|
||||
form=comment_form,
|
||||
errors=comment_errors,
|
||||
default=default)
|
||||
}}
|
||||
|
||||
<label for="plume-editor">{{ "Your comment" | _ }}</label>
|
||||
{% if previous %}
|
||||
<input type="hidden" name="responding_to" value="{{ previous.id }}"/>
|
||||
{% endif %}
|
||||
{# Ugly, but we don't have the choice if we don't want weird paddings #}
|
||||
<textarea id="plume-editor" name="content">{% filter trim %}{% if previous %}{% if previous.author.fqn != user_fqn %}@{{ previous.author.fqn }} {% endif %}{% for mention in previous.mentions %}{% if mention != user_fqn %}@{{ mention }} {% endif %}{% endfor %}{% endif %}{% endfilter %}</textarea>
|
||||
<input type="submit" value="{{ "Submit comment" | _ }}" />
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if comments | length > 0 %}
|
||||
<div class="list">
|
||||
{% for comment in comments %}
|
||||
{{ macros::comment(comm=comment) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="center">{{ "No comments yet. Be the first to react!" | _ }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,146 @@
|
||||
@use templates::{base, partials::comment};
|
||||
@use template_utils::*;
|
||||
@use plume_models::blogs::Blog;
|
||||
@use plume_models::comments::Comment;
|
||||
@use plume_models::posts::Post;
|
||||
@use plume_models::tags::Tag;
|
||||
@use plume_models::users::User;
|
||||
@use validator::ValidationErrors;
|
||||
@use routes::comments::NewCommentForm;
|
||||
|
||||
@(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: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:description" content="@article.subtitle"/>
|
||||
}, {
|
||||
<a href="/~/@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)))))
|
||||
</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) {
|
||||
—
|
||||
<a href="@article.url(ctx.0)/edit">@i18n!(ctx.1, "Edit")</a>
|
||||
—
|
||||
<form class="inline" method="post" action="@article.url(ctx.0)/delete">
|
||||
<input onclick="return confirm('Are you sure you?')" type="submit" value="@i18n!(ctx.1, "Delete this article")">
|
||||
</form>
|
||||
}
|
||||
@if !article.published {
|
||||
<span class="badge">@i18n!(ctx.1, "Draft")</span>
|
||||
}
|
||||
</div>
|
||||
@if article.cover_id.is_some() {
|
||||
<div class="cover" style="background-image: url('@Html(article.cover_url(ctx.0).unwrap_or_default())')"></div>
|
||||
}
|
||||
<article>
|
||||
@Html(&article.content)
|
||||
</article>
|
||||
|
||||
<div class="article-meta">
|
||||
<p>@i18n!(ctx.1, "This article is under the {0} license."; &article.license)</p>
|
||||
<ul class="tags">
|
||||
@for tag in tags {
|
||||
@if !tag.is_hashtag {
|
||||
<li><a href="/tag/@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>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@if ctx.2.is_some() {
|
||||
<div class="actions">
|
||||
<form class="likes" action="@article.url(ctx.0)/like" 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 {
|
||||
<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">
|
||||
<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>
|
||||
|
||||
@if has_reshared {
|
||||
<button type="submit" class="action reshared">@icon!("repeat") @i18n!(ctx.1, "I don't want to boost this anymore")</button>
|
||||
} else {
|
||||
<button type="submit" class="action">@icon!("repeat") @i18n!(ctx.1, "Boost")</button>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
} else {
|
||||
<p class="center">@i18n!(ctx.1, "Login or use your Fediverse account to interact with this article")</p>
|
||||
<div class="actions">
|
||||
<div class="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
|
||||
</p>
|
||||
<a href="/login?m=Login%20to%20like" 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>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="comments">
|
||||
<h2>@i18n!(ctx.1, "Comments")</h2>
|
||||
|
||||
@if ctx.2.is_some() {
|
||||
<form method="post" action="@article.url(ctx.0)/comment">
|
||||
@input!(ctx.1, warning (optional text), "Content warning", comment_form, comment_errors, "")
|
||||
|
||||
<label for="plume-editor">@i18n!(ctx.1, "Your comment")</label>
|
||||
@if let Some(ref prev) = previous_comment {
|
||||
<input type="hidden" name="responding_to" value="@prev.id"/>
|
||||
}
|
||||
<textarea id="plume-editor" name="content">@comment_form.content</textarea>
|
||||
<input type="submit" value="@i18n!(ctx.1, "Submit comment")" />
|
||||
</form>
|
||||
}
|
||||
|
||||
@if !comments.is_empty() {
|
||||
<div class="list">
|
||||
@for comm in comments {
|
||||
@:comment(ctx, &comm, comm.get_author(ctx.0))
|
||||
}
|
||||
</div>
|
||||
} else {
|
||||
<p class="center">@i18n!(ctx.1, "No comments yet. Be the first to react!")</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
})
|
||||
@@ -1,68 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{% if editing %}
|
||||
{{ "Edit {{ post }}" | _(post=form.title) }}
|
||||
{% else %}
|
||||
{{ "New post" | _ }}
|
||||
{% endif %}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>
|
||||
{% if editing %}
|
||||
{{ "Edit {{ post }}" | _(post=form.title) }}
|
||||
{% else %}
|
||||
{{ "Create a post" | _ }}
|
||||
{% endif %}
|
||||
</h1>
|
||||
<form class="new-post" method="post">
|
||||
{{ macros::input(name="title", label="Title", errors=errors, form=form, props="required") }}
|
||||
{{ macros::input(name="subtitle", label="Subtitle", errors=errors, form=form, optional=true) }}
|
||||
|
||||
{% if errors is defined and errors.content %}
|
||||
{% for err in errors.content %}
|
||||
<p class="error">{{ err.message | default(value="Unknown error") | _ }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<label for="plume-editor">{{ "Content" | _ }}<small>{{ "Markdown is supported" | _ }}</small></label>
|
||||
<textarea id="plume-editor" name="content" rows="20">{{ form.content | default(value="") }}</textarea>
|
||||
|
||||
{{ macros::input(name="tags", label="Tags, separated by commas", errors=errors, form=form, optional=true) }}
|
||||
|
||||
{% set license_infos = "Default license will be {{ instance.default_license }}" | _(instance=instance) %}
|
||||
{{ macros::input(name="license", label="License", errors=errors, form=form, optional=true, details=license_infos) }}
|
||||
|
||||
<label for="cover">{{ "Illustration" | _ }}<small>{{ "Optional" | _ }}</small></label>
|
||||
<select id="cover" name="cover">
|
||||
<option value="none" {% if form is undefined or form.cover is undefined %}selected{% endif %}>{{ "None" | _ }}</option>
|
||||
{% for media in medias %}
|
||||
{% if media.category == "image" %}
|
||||
<option value="{{ media.id }}" {% if form is defined and form.cover is defined and form.cover == media.id %}selected{% endif %}>
|
||||
{{ media.alt_text | default(value=media.content_warning) }}
|
||||
</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
{% if is_draft %}
|
||||
<label for="draft">
|
||||
<input type="checkbox" name="draft" id="draft" checked>
|
||||
{{ "This is a draft, don't publish it yet." | _ }}
|
||||
</label>
|
||||
{% endif %}
|
||||
|
||||
{% if editing %}
|
||||
<input type="submit" value="{{ "Update" | _ }}" />
|
||||
{% else %}
|
||||
{% if is_draft %}
|
||||
<input type="submit" value="{{ "Update or publish" | _ }}" />
|
||||
{% else %}
|
||||
<input type="submit" value="{{ "Publish" | _ }}" />
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</form>
|
||||
<script src="/static/js/autoExpand.js"></script>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,67 @@
|
||||
@use templates::base;
|
||||
@use template_utils::*;
|
||||
@use validator::{ValidationErrors, ValidationErrorsKind};
|
||||
@use std::borrow::Cow;
|
||||
@use plume_models::medias::*;
|
||||
@use routes::posts::NewPostForm;
|
||||
|
||||
@(ctx: BaseContext, editing: bool, form: &NewPostForm, errors: ValidationErrors, default_license: String, medias: Vec<Media>, is_draft: bool)
|
||||
|
||||
@:base(ctx, &i18n!(ctx.1, if editing { "Edit {0}" } else { "New post" }; &form.title), {}, {}, {
|
||||
<h1>
|
||||
@if editing {
|
||||
@i18n!(ctx.1, "Edit {0}"; &form.title)
|
||||
} else {
|
||||
@i18n!(ctx.1, "Create a new post")
|
||||
}
|
||||
</h1>
|
||||
<form class="new-post" method="post">
|
||||
@input!(ctx.1, title (text), "Title", form, errors.clone(), "required")
|
||||
@input!(ctx.1, subtitle (optional text), "Subtitle", form, errors.clone(), "")
|
||||
|
||||
@if let Some(ValidationErrorsKind::Field(errs)) = errors.clone().errors().get("content") {
|
||||
@format!(r#"<p class="error">{}</p>"#, i18n!(ctx.1, &*errs[0].message.clone().unwrap_or(Cow::from("Unknown error"))))
|
||||
}
|
||||
|
||||
<label for="plume-editor">@i18n!(ctx.1, "Content")<small>@i18n!(ctx.1, "Markdown is supported")</small></label>
|
||||
<textarea id="plume-editor" name="content" rows="20">@form.content</textarea>
|
||||
|
||||
@input!(ctx.1, tags (optional text), "Tags, separated by commas", form, errors.clone(), "")
|
||||
|
||||
@input!(ctx.1, license (optional text), "License", &i18n!(ctx.1, "Default license will be {0}"; &default_license), form, errors, "")
|
||||
|
||||
<label for="cover">@i18n!(ctx.1, "Illustration")<small>@i18n!(ctx.1, "Optional")</small></label>
|
||||
<select id="cover" name="cover">
|
||||
<option value="none" @if form.cover.is_none() { selected }>@i18n!(ctx.1, "None")</option>
|
||||
@for media in medias {
|
||||
@if media.category() == MediaCategory::Image {
|
||||
<option value="@media.id" @if form.cover.map(|c| c == media.id).unwrap_or(false) { selected }>
|
||||
@if !media.alt_text.is_empty() {
|
||||
@media.alt_text
|
||||
} else {
|
||||
@media.content_warning.unwrap_or_default()
|
||||
}
|
||||
</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
|
||||
@if is_draft {
|
||||
<label for="draft">
|
||||
<input type="checkbox" name="draft" id="draft" checked>
|
||||
@i18n!(ctx.1, "This is a draft, don't publish it yet.")
|
||||
</label>
|
||||
}
|
||||
|
||||
@if editing {
|
||||
<input type="submit" value="@i18n!(ctx.1, "Update")" />
|
||||
} else {
|
||||
@if is_draft {
|
||||
<input type="submit" value="@i18n!(ctx.1, "Update or publish")" />
|
||||
} else {
|
||||
<input type="submit" value="@i18n!(ctx.1, "Publish")" />
|
||||
}
|
||||
}
|
||||
</form>
|
||||
<script src="/static/js/autoExpand.js"></script>
|
||||
})
|
||||
@@ -1,50 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
Search
|
||||
{% endblock title %}
|
||||
|
||||
{% block head %}
|
||||
<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>
|
||||
{% endblock head %}
|
||||
|
||||
{% block content %}
|
||||
<h1>search</h1>
|
||||
|
||||
<form method="get" id="form">
|
||||
<input id="q" name="q" placeholder="Your query" type="search">
|
||||
<details>
|
||||
<summary>Advanced search</summary>
|
||||
{{ macros::input(name="title", label="Title matching these words", props='placeholder="Title"') }}
|
||||
{{ macros::input(name="subtitle", label="Subtitle matching these words", props='placeholder="Subtitle"') }}
|
||||
{{ macros::input(name="content", label="Content matching these words", props='placeholder="Content"') }}
|
||||
{{ macros::input(name="after", label="From this date", type="date", props='max=' ~ now) }}
|
||||
{{ macros::input(name="before", label="To this date", type="date", props='max=' ~ now) }}
|
||||
|
||||
{{ macros::input(name="instance", label="Sent from one of these instance", props='placeholder="Instance domain"') }}
|
||||
{{ macros::input(name="author", label="Sent by one of these author", props='placeholder="Authors"') }}
|
||||
{{ macros::input(name="tag", label="Containing these tags", props='placeholder="Tags"') }}
|
||||
{{ macros::input(name="blog", label="In blog", props='placeholder="Blog title"') }}
|
||||
{{ macros::input(name="lang", label="Language used", props='placeholder="Language"') }}
|
||||
{{ macros::input(name="license", label="Using license", props='placeholder="License"') }}
|
||||
|
||||
|
||||
</details>
|
||||
<input type="submit" value="Search"/>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,43 @@
|
||||
@use templates::base;
|
||||
@use template_utils::*;
|
||||
|
||||
@(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>
|
||||
}, {}, {
|
||||
<h1>@i18n!(ctx.1, "Search")</h1>
|
||||
<form method="get" id="form">
|
||||
<input id="q" name="q" placeholder="Your query" type="search">
|
||||
<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, before (date), "To this date", &format!("max={}", now))
|
||||
|
||||
@input!(ctx.1, tag (text), "Containing these tags", "placeholder=\"Tags\"")
|
||||
@input!(ctx.1, instance (text), "Posted in one of these instances", "placeholder=\"Instance domain\"")
|
||||
@input!(ctx.1, author (text), "Posted by one of these authors", "placeholder=\"Authors\"")
|
||||
@input!(ctx.1, blog (text), "Posted in one of these blogs", "placeholder=\"Blog title\"")
|
||||
@input!(ctx.1, lang (text), "Wrote in this language", "placeholder=\"Language\"")
|
||||
@input!(ctx.1, license (text), "Using this license", "placeholder=\"License\"")
|
||||
</details>
|
||||
<input type="submit" value="Search"/>
|
||||
</form>
|
||||
})
|
||||
@@ -1,26 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
Search result for "{{query}}"
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Search result</h1>
|
||||
<p>{{query}}</p>
|
||||
<section>
|
||||
{% if posts | length < 1 %}
|
||||
{% if page == 1 %}
|
||||
<p>No result for your query</p>
|
||||
{% else %}
|
||||
<p>No more result for your query</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div class="cards">
|
||||
{% for article in posts %}
|
||||
{{ macros::post_card(article=article) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{{ macros::paginate(page=page, total=next_page, query= "q=" ~ query) }}
|
||||
</section>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,27 @@
|
||||
@use templates::{base, partials::post_card};
|
||||
@use template_utils::*;
|
||||
@use plume_models::posts::Post;
|
||||
|
||||
@(ctx: BaseContext, query_str: &str, articles: Vec<Post>, page: i32, n_pages: i32)
|
||||
|
||||
@:base(ctx, i18n!(ctx.1, "Search result for \"{0}\""; query_str).as_str(), {}, {}, {
|
||||
<h1>@i18n!(ctx.1, "Search result")</h1>
|
||||
<p>@query_str</p>
|
||||
|
||||
@if articles.is_empty() {
|
||||
<section>
|
||||
@if page == 1 {
|
||||
<h2>@i18n!(ctx.1, "No result for your query")</h2>
|
||||
} else {
|
||||
<h2>@i18n!(ctx.1, "No more result for your query")</h2>
|
||||
}
|
||||
</section>
|
||||
} else {
|
||||
<div class="cards">
|
||||
@for article in articles {
|
||||
@:post_card(ctx, article)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@paginate(ctx.1, page, n_pages)
|
||||
})
|
||||
@@ -1,19 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ "Login" | _ }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ "Login" | _ }}</h1>
|
||||
{% if message %}
|
||||
<p>{{ message }}</p>
|
||||
{% endif %}
|
||||
<form method="post">
|
||||
{{ macros::input(name="email_or_name", label="Username or email", errors=errors, form=form, props='minlenght="1"') }}
|
||||
{{ macros::input(name="password", label="Password", errors=errors, form=form, type="password", props='minlenght="1"') }}
|
||||
|
||||
<input type="submit" value="{{ "Login" | _ }}" />
|
||||
</form>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,18 @@
|
||||
@use template_utils::*;
|
||||
@use templates::base;
|
||||
@use validator::ValidationErrors;
|
||||
@use routes::session::LoginForm;
|
||||
|
||||
@(ctx: BaseContext, message: Option<String>, form: &LoginForm, errors: ValidationErrors)
|
||||
|
||||
@:base(ctx, "Login", {}, {}, {
|
||||
<h1>@i18n!(ctx.1, "Login")</h1>
|
||||
@if let Some(message) = message {
|
||||
<p>@message</p>
|
||||
}
|
||||
<form method="post">
|
||||
@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")" />
|
||||
</form>
|
||||
})
|
||||
@@ -1,23 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ 'Articles tagged "{{ tag }}"' | _(tag=tag) }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ 'Articles tagged "{{ tag }}"' | _(tag=tag) }}</h1>
|
||||
|
||||
{% if articles| length != 0 %}
|
||||
<div class="cards">
|
||||
{% for article in articles %}
|
||||
{{ macros::post_card(article=article) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<section>
|
||||
<h2>{{ "There is currently no article with that tag" | _ }}</h2>
|
||||
</section>
|
||||
{% endif %}
|
||||
{{ macros::paginate(page=page, total=n_pages) }}
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,22 @@
|
||||
@use templates::{base, partials::post_card};
|
||||
@use template_utils::*;
|
||||
@use plume_models::posts::Post;
|
||||
|
||||
@(ctx: BaseContext, tag: String, articles: Vec<Post>, page: i32, n_pages: i32)
|
||||
|
||||
@:base(ctx, i18n!(ctx.1, "Articles tagged \"{0}\""; &tag).as_str(), {}, {}, {
|
||||
<h1>@i18n!(ctx.1, "Articles tagged \"{0}\""; &tag)</h1>
|
||||
|
||||
@if !articles.is_empty() {
|
||||
<div class="cards">
|
||||
@for article in articles {
|
||||
@:post_card(ctx, article)
|
||||
}
|
||||
</div>
|
||||
} else {
|
||||
<section>
|
||||
<h2>@i18n!(ctx.1, "There is currently no article with that tag")</h2>
|
||||
</section>
|
||||
}
|
||||
@paginate(ctx.1, page, n_pages)
|
||||
})
|
||||
@@ -1,41 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ "Dashboard" | _ }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ "Your Dashboard" | _ }}</h1>
|
||||
|
||||
<section>
|
||||
<h2>{{ "Your Blogs" | _ }}</h2>
|
||||
{% if blogs | length < 1 %}
|
||||
<p>{{ "You don't have any blog yet. Create your own, or ask to join one." | _ }}</p>
|
||||
{% endif %}
|
||||
<div class="cards">
|
||||
{% for blog in blogs %}
|
||||
<div class="card">
|
||||
<h3><a href="/~/{{ blog.actor_id }}/">{{ blog.title }}</a></h3>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<a class="button" href="/blogs/new">{{ "Start a new blog" | _ }}</a>
|
||||
</section>
|
||||
|
||||
{% if drafts | length > 0 %}
|
||||
<section>
|
||||
<h2>{{ "Your Drafts" | _ }}</h2>
|
||||
<div class="cards">
|
||||
{% for draft in drafts %}
|
||||
{{ macros::post_card(article=draft) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<section>
|
||||
<h2>{{ "Your media" | _ }}</h2>
|
||||
<a class="button" href="/medias">{{ "Go to your gallery" | _ }}</a>
|
||||
</section>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,41 @@
|
||||
@use templates::{base, partials::post_card};
|
||||
@use template_utils::*;
|
||||
@use plume_models::blogs::Blog;
|
||||
@use plume_models::posts::Post;
|
||||
|
||||
@(ctx: BaseContext, blogs: Vec<Blog>, drafts: Vec<Post>)
|
||||
|
||||
@:base(ctx, "Your Dashboard", {}, {}, {
|
||||
<h1>@i18n!(ctx.1, "Your Dashboard")</h1>
|
||||
|
||||
<section>
|
||||
<h2>@i18n!(ctx.1, "Your Blogs")</h2>
|
||||
@if blogs.is_empty() {
|
||||
<p>@i18n!(ctx.1, "You don't have any blog yet. Create your own, or ask to join one.")</p>
|
||||
}
|
||||
<div class="cards">
|
||||
@for blog in blogs {
|
||||
<div class="card">
|
||||
<h3><a href="/~/@blog.actor_id/">@blog.title</a></h3>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<a class="button" href="/blogs/new">@i18n!(ctx.1, "Start a new blog")</a>
|
||||
</section>
|
||||
|
||||
@if !drafts.is_empty() {
|
||||
<section>
|
||||
<h2>@i18n!(ctx.1, "Your Drafts")</h2>
|
||||
<div class="cards">
|
||||
@for draft in drafts {
|
||||
@:post_card(ctx, draft)
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
<section>
|
||||
<h2>@i18n!(ctx.1, "Your media")</h2>
|
||||
<a class="button" href="/medias">@i18n!(ctx.1, "Go to your gallery")</a>
|
||||
</section>
|
||||
})
|
||||
@@ -1,41 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{% if user.display_name %}
|
||||
{% set name = user.display_name %}
|
||||
{% else %}
|
||||
{% set name = user.username %}
|
||||
{% endif %}
|
||||
|
||||
{{ name }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
{% include "users/header" %}
|
||||
|
||||
{% set user_link = ['/@', user.fqn] %}
|
||||
{% set followers_link = ['/@', user.fqn, 'followers'] %}
|
||||
{{ macros::tabs(links=[ user_link | join(sep='/'), followers_link | join(sep='/')], titles=['Articles', 'Followers'], selected=1) }}
|
||||
|
||||
{% if recents | length != 0 %}
|
||||
<h2>
|
||||
{{ "Latest articles" | _ }}
|
||||
<small><a href="/@/{{ user.fqn }}/atom.xml" title="Atom feed">{{ macros::feather(name="rss") }}</a></small>
|
||||
</h2>
|
||||
<div class="cards">
|
||||
{% for article in recents %}
|
||||
{{ macros::post_card(article=article) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if reshares | length != 0 %}
|
||||
<h2>{{ "Recently boosted" | _ }}</h2>
|
||||
<div class="cards">
|
||||
{% for article in reshares %}
|
||||
{{ macros::post_card(article=article) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,36 @@
|
||||
@use templates::{base, partials::post_card, users::header};
|
||||
@use template_utils::*;
|
||||
@use plume_models::posts::Post;
|
||||
@use plume_models::users::User;
|
||||
|
||||
@(ctx: BaseContext, user: User, follows: bool, is_remote: bool, remote_url: String, recents: Vec<Post>, reshares: Vec<Post>)
|
||||
|
||||
@:base(ctx, &user.name(ctx.0), {}, {}, {
|
||||
@: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)
|
||||
])
|
||||
|
||||
@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>
|
||||
</h2>
|
||||
<div class="cards">
|
||||
@for article in recents {
|
||||
@:post_card(ctx, article)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if !reshares.is_empty() {
|
||||
<h2>@i18n!(ctx.1, "Recently boosted")</h2>
|
||||
<div class="cards">
|
||||
@for article in reshares {
|
||||
@:post_card(ctx, article)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
})
|
||||
@@ -1,34 +0,0 @@
|
||||
{% extends "base" %}
|
||||
|
||||
{% block title %}
|
||||
{{ "Edit your account" | _ }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ "Your Profile" | _ }}</h1>
|
||||
<form method="post">
|
||||
<!-- Rocket hack to use various HTTP methods -->
|
||||
<input type=hidden name="_method" value="put">
|
||||
|
||||
<label for="display_name">{{ "Display Name" | _ }}</label>
|
||||
<input name="display_name" value="{{ account.display_name }}">
|
||||
|
||||
<label for="email">{{ "Email" | _ }}</label>
|
||||
<input name="email" value="{{ account.email }}">
|
||||
|
||||
<label for="summary">{{ "Summary" | _ }}</label>
|
||||
<input name="summary" value="{{ account.summary }}">
|
||||
|
||||
<input type="submit" value="{{ "Update account" | _ }}"/>
|
||||
</form>
|
||||
|
||||
<h2>{{ "Danger zone" | _ }}</h2>
|
||||
<p>{{ "Be very careful, any action taken here can't be cancelled." | _ }}
|
||||
{% if not account.is_admin %}
|
||||
<form method="post" action="/@/{{ account.fqn }}/delete">
|
||||
<input type="submit" class="inline-block button destructive" value="{{ 'Delete your account' | _ }}">
|
||||
</form>
|
||||
{% else %}
|
||||
<p>{{ "Sorry, but as an admin, you can't leave your instance." | _ }}</p>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,30 @@
|
||||
@use templates::base;
|
||||
@use template_utils::*;
|
||||
@use routes::user::UpdateUserForm;
|
||||
@use validator::ValidationErrors;
|
||||
|
||||
@(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">
|
||||
|
||||
@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")">
|
||||
</form>
|
||||
} else {
|
||||
<p>@i18n!(ctx.1, "Sorry, but as an admin, you can't leave your instance.")</p>
|
||||
}
|
||||
})
|
||||
@@ -1,24 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ "{{ name | escape }}'s followers" | _(name=user.name) }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
{% include "users/header" %}
|
||||
|
||||
{% set user_link = ['/@', user.fqn] %}
|
||||
{% set followers_link = ['/@', user.fqn, 'followers'] %}
|
||||
{{ macros::tabs(links=[ user_link | join(sep='/'), followers_link | join(sep='/')], titles=['Articles', 'Followers'], selected=2) }}
|
||||
|
||||
<div class="cards">
|
||||
{% for follower in followers %}
|
||||
<div class="card">
|
||||
<h3><a href="/@/{{ follower.fqn }}/">{{ follower.name }}</a> <small>@{{ follower.fqn }}</small></h3>
|
||||
<main><p>{{ follower.summary | safe }}</p></main>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{{ macros::paginate(page=page, total=n_pages) }}
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,24 @@
|
||||
@use templates::{base, users::header};
|
||||
@use template_utils::*;
|
||||
@use plume_models::users::User;
|
||||
|
||||
@(ctx: BaseContext, user: User, follows: bool, is_remote: bool, remote_url: String, followers: Vec<User>, page: i32, n_pages: i32)
|
||||
|
||||
@:base(ctx, &i18n!(ctx.1, "{0}'s followers"; user.name(ctx.0)), {}, {}, {
|
||||
@: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)
|
||||
])
|
||||
|
||||
<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>
|
||||
<main><p>@Html(follower.summary)</p></main>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@paginate(ctx.1, page, n_pages)
|
||||
})
|
||||
@@ -1,44 +0,0 @@
|
||||
<div class="user">
|
||||
<div class="flex wrap">
|
||||
{{ macros::avatar(user=user, size="medium") }}
|
||||
|
||||
<h1 class="grow flex vertical">
|
||||
{{ user.name }}
|
||||
<small>@{{ user.fqn }}</small>
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
{% if user.is_admin %}
|
||||
<span class="badge">{{ "Admin" | _ }}</span>
|
||||
{% endif %}
|
||||
|
||||
{% if is_self %}
|
||||
<span class="badge">{{ "It is you" | _ }}</span>
|
||||
{% endif %}
|
||||
|
||||
{% if is_self %}
|
||||
<a href="/@/{{ user.username }}/edit" class="button inline-block">{{ "Edit your profile" | _ }}</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% if is_remote %}
|
||||
<a class="inline-block" href="{{ user.ap_url }}" target="_blank">{{ "Open on {{ instance_url }}" | _(instance_url=instance_url) }}</a>
|
||||
{% endif %}
|
||||
|
||||
{% set not_self = not is_self %}
|
||||
{% if not_self and (account is defined) %}
|
||||
<form class="inline" method="post" action="/@/{{ user.fqn }}/follow/">
|
||||
{% if follows %}
|
||||
<input type="submit" value="{{ 'Unfollow' | _ }}">
|
||||
{% else %}
|
||||
<input type="submit" value="{{ 'Follow' | _ }}">
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="user-summary">
|
||||
{{ user.summary | safe }}
|
||||
</div>
|
||||
@@ -0,0 +1,43 @@
|
||||
@use template_utils::*;
|
||||
@use plume_models::users::User;
|
||||
|
||||
@(ctx: BaseContext, user: &User, follows: bool, is_remote: bool, instance_url: String)
|
||||
|
||||
<div class="user">
|
||||
<div class="flex wrap">
|
||||
@avatar(ctx.0, &user, Size::Medium, false, ctx.1)
|
||||
|
||||
<h1 class="grow flex vertical">
|
||||
@user.name(ctx.0)
|
||||
<small>@user.get_fqn(ctx.0)</small>
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
@if user.is_admin {
|
||||
<span class="badge">@i18n!(ctx.1, "Admin")</span>
|
||||
}
|
||||
|
||||
@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>
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@if is_remote {
|
||||
<a class="inline-block" href="@user.ap_url" target="_blank">@i18n!(ctx.1, "Open on {0}"; instance_url)</a>
|
||||
}
|
||||
|
||||
@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/">
|
||||
@if follows {
|
||||
<input type="submit" value="@i18n!(ctx.1, "Unfollow")">
|
||||
} else {
|
||||
<input type="submit" value="@i18n!(ctx.1, "Follow")">
|
||||
}
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
<div class="user-summary">
|
||||
@Html(user.summary.clone())
|
||||
</div>
|
||||
@@ -1,22 +0,0 @@
|
||||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ "New Account" | _ }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
{% if enabled %}
|
||||
<h1>{{ "Create an account" | _ }}</h1>
|
||||
<form method="post">
|
||||
{{ macros::input(name="username", label="Username", errors=errors, form=form, props='minlenght="1"') }}
|
||||
{{ macros::input(name="email", label="Email", errors=errors, form=form, type="email") }}
|
||||
{{ macros::input(name="password", label="Password", errors=errors, form=form, type="password", props='minlenght="8"') }}
|
||||
{{ macros::input(name="password_confirmation", label="Password confirmation", errors=errors, form=form, type="password", props='minlenght="8"') }}
|
||||
|
||||
<input type="submit" value="{{ "Create account" | _ }}" />
|
||||
</form>
|
||||
{% else %}
|
||||
<p class="center">{{ "Sorry, but registrations are closed on this instance. Try to find another one" | _ }}</p>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,22 @@
|
||||
@use templates::base;
|
||||
@use template_utils::*;
|
||||
@use routes::user::NewUserForm;
|
||||
@use validator::ValidationErrors;
|
||||
|
||||
@(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">
|
||||
@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\"")
|
||||
@input!(ctx.1, password_confirmation (password), "Password confirmation", form, errors, "minlenght=\"8\"")
|
||||
|
||||
<input type="submit" value="@i18n!(ctx.1, "Create account")" />
|
||||
</form>
|
||||
} else {
|
||||
<p class="center">@i18n!(ctx.1, "Sorry, but registrations are closed on this instance. Try to find another one")</p>
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user