parent
e729eee137
commit
5aa4c4d203
@ -0,0 +1,35 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use rocket::{http::CookieJar, State};
|
||||
use rocket_dyn_templates::{context, Template};
|
||||
|
||||
use crate::{
|
||||
cookies::LANG_COOKIE,
|
||||
i18n::DEFAULT as DEFAULT_LANG,
|
||||
models::{Settings, SessionUser, Database},
|
||||
utils::AcceptLanguage,
|
||||
};
|
||||
|
||||
#[get("/users/<user>")]
|
||||
pub async fn get_user(
|
||||
user: String,
|
||||
cookies: &CookieJar<'_>,
|
||||
settings: &State<Settings>,
|
||||
database: &State<Database>,
|
||||
accept_language: AcceptLanguage,
|
||||
) -> Template {
|
||||
Template::render(
|
||||
"user",
|
||||
context! {
|
||||
profile_user: database.get_user_by_name(&user).unwrap(),
|
||||
submissions: database.get_submissions_by_user_name(&user).unwrap(),
|
||||
settings: settings.deref(),
|
||||
lang: cookies
|
||||
.get(LANG_COOKIE)
|
||||
.map(|cookie| vec![cookie.value().to_owned()])
|
||||
.or(accept_language.0)
|
||||
.unwrap_or_else(|| vec![DEFAULT_LANG.to_owned()]),
|
||||
user: SessionUser::get(cookies).await.unwrap(),
|
||||
},
|
||||
)
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{% block title %}Tegaki Tuesday{% endblock %}</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet">
|
||||
<link href="/fonts/K-Gothic/stylesheet.css" rel="stylesheet">
|
||||
<link href="/css/style.css" rel="stylesheet">
|
||||
{% block head %}{% endblock head %}
|
||||
</head>
|
||||
<body>
|
||||
{% if user %}
|
||||
{% include "modal" %}
|
||||
{% endif %}
|
||||
<div id="content">
|
||||
<nav>{% block nav %}{% include "nav" %}{% endblock %}</nav>
|
||||
<div id="challenge">
|
||||
<div>{% block content %}{% endblock %}</div>
|
||||
<footer>
|
||||
{% block content_copyright %}
|
||||
Copyright {% include "copyright-years" %} Tegaki Tuesday. All rights reserved. 字ちゃん mascot art by <a href="https://twitter.com/bellumela" target="_blank">@bellumela</a>.
|
||||
{% endblock %}
|
||||
</footer>
|
||||
</div>
|
||||
<div id="submissions">
|
||||
<script>
|
||||
const submissionModal = image => {
|
||||
const dialog = document.createElement("dialog");
|
||||
dialog.style.padding = "0";
|
||||
const img = document.createElement("img");
|
||||
img.src = image.src;
|
||||
const defaultZoom = "75vh";
|
||||
img.style.height = defaultZoom;
|
||||
img.style.cursor = "zoom-in";
|
||||
img.style.display = "block";
|
||||
img.style.margin = "0";
|
||||
img.onclick = event => img.style.height = img.style.height ? "" : defaultZoom;
|
||||
dialog.appendChild(img);
|
||||
dialog.addEventListener("close", event => document.body.removeChild(dialog));
|
||||
document.body.appendChild(dialog);
|
||||
dialog.showModal();
|
||||
};
|
||||
</script>
|
||||
<div>
|
||||
{% for submission in submissions %}
|
||||
{% if users %}
|
||||
{% set author = users[submission.author_id] %}
|
||||
{% else %}
|
||||
{% set author = profile_user %}
|
||||
{% endif %}
|
||||
<figure>
|
||||
<img src="/{{ submission.challenge }}/{{ submission.image }}" alt="{{ author.username }}'s submission" onclick="submissionModal('{{ submission.image }}')">
|
||||
<figcaption>
|
||||
{% if profile_user %}
|
||||
<a href="/{{ submission.challenge }}">#{{ submission.challenge }}</a>
|
||||
{% else %}
|
||||
{% if not author.deleted %}<a href="/users/{{ author.name }}" target="_blank">{% endif %}{{ author.username }}{% if author.deleted %} (deleted account){% else %}</a>{% endif %}
|
||||
{% endif %}
|
||||
</figcaption>
|
||||
</figure>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<footer>
|
||||
{% block submissions_copyright %}
|
||||
Submissions are copyright {% include "copyright-years" %} their respective submitters, and are licensed under the <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)</a> license.
|
||||
{% endblock %}
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,55 @@
|
||||
{% extends "base" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Welcome to Tegaki Tuesday #{{ challenge }}!</h1>
|
||||
{% if content.japanese %}
|
||||
<div lang="ja">
|
||||
<script>
|
||||
let kyujitai = false;
|
||||
function kyujitaiToggle() {
|
||||
document.querySelectorAll("[data-kyujitai]").forEach(segment => {
|
||||
let newText = segment.getAttribute("data-kyujitai");
|
||||
segment.setAttribute("data-kyujitai", segment.firstChild.data);
|
||||
segment.firstChild.data = newText;
|
||||
})
|
||||
kyujitai = !kyujitai;
|
||||
}
|
||||
</script>
|
||||
<button onclick="kyujitaiToggle(); this.innerHTML = kyujitai ? '新字体' : '旧字体'">旧字体</button>
|
||||
{% for line in content.japanese %}
|
||||
{% for subline in line %}
|
||||
<p>
|
||||
{%- for word in subline -%}
|
||||
{%- if word.dictionary -%}
|
||||
<a href="{{ word.dictionary }}" target="_blank"{% if word.pos %} class="{{ word.pos }}" title="{{ word.pos }}"{% endif %}>
|
||||
{%- endif -%}
|
||||
{% for segment in word.text -%}
|
||||
<ruby{% if segment.kyujitai %} data-kyujitai="{{ segment.kyujitai }}"{% endif %}>{{ segment.kanji }}<rp>(</rp><rt>{{ segment.furigana | safe }}</rt><rp>)</rp></ruby>
|
||||
{%- endfor -%}
|
||||
{%- if word.dictionary -%}
|
||||
</a>
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
</p>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ content.text | safe }}
|
||||
{% if content.translation %}
|
||||
<p>
|
||||
Translation
|
||||
{% if content.translation.author %}by {{ content.translation.author }}{% endif %}
|
||||
{% if content.translation.site %} via
|
||||
{% if content.translation.site.link %}
|
||||
<a href="{{ content.translation.site.link }}" target="_blank">{{ content.translation.site.name }}</a>
|
||||
{% else %}
|
||||
{{ content.translation.site.name }}
|
||||
{% endif %}
|
||||
{%- endif -%}
|
||||
</p>
|
||||
{%- endif -%}
|
||||
{%- if content.suggester -%}
|
||||
<p><em>This challenge was suggested by <strong>{{ content.suggester }}</strong> using the <code>-h suggest</code> command.</code></em></p>
|
||||
{%- endif -%}
|
||||
{% endblock %}
|
@ -1,159 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Tegaki Tuesday</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet">
|
||||
<link href="/fonts/K-Gothic/stylesheet.css" rel="stylesheet">
|
||||
<link href="/css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
{% if user %}
|
||||
{% include "modal" %}
|
||||
{% endif %}
|
||||
<div id="content">
|
||||
<nav>
|
||||
<a class="link" href="/{{ challenge - 1 }}"><svg class="svg-inline" aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 278.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z"></path></svg></a>
|
||||
<input type="number" min="1" value="{{ challenge }}" autocomplete="false">
|
||||
<a class="link" href="/{{ challenge + 1 }}"><svg class="svg-inline" aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M342.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L274.7 256 105.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z"></path></svg></a>
|
||||
{% if content.song %}
|
||||
<div>
|
||||
<span>{% if content.song.japanese %}{{ content.song.japanese | furigana | safe }}{% else %}{{ content.song.english }}{% endif %}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if user %}
|
||||
<div class="dropdown right">
|
||||
<a href="/users/{{ user.id }}" class="link">
|
||||
<span>{{ user.username }}</span> <img src="{{ user.avatar }}">
|
||||
</a>
|
||||
<nav class="dropdown-content">
|
||||
<span class="link" onclick="showServers()">
|
||||
<span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="icon">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" />
|
||||
</svg>
|
||||
Join servers
|
||||
</span>
|
||||
</span>
|
||||
<a href="/logout" class="link">
|
||||
<span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="icon">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75" />
|
||||
</svg>
|
||||
Log out
|
||||
</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
{% else%}
|
||||
<a href="/login" class="link right">Log in</a>
|
||||
{% endif %}
|
||||
<div class="dropdown">
|
||||
<span class="link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="icon">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 21l5.25-11.25L21 21m-9-3h7.5M3 5.621a48.474 48.474 0 016-.371m0 0c1.12 0 2.233.038 3.334.114M9 5.25V3m3.334 2.364C11.176 10.658 7.69 15.08 3 17.502m9.334-12.138c.896.061 1.785.147 2.666.257m-4.589 8.495a18.023 18.023 0 01-3.827-5.802" />
|
||||
</svg>
|
||||
</span>
|
||||
<nav class="dropdown-content">
|
||||
{% set langs = 0 | langs %}
|
||||
{% for other_lang in langs %}
|
||||
{% if other_lang.code in lang %}{% continue %}{% endif %}
|
||||
<span class="link" onclick="document.cookie = 'lang={{ other_lang.code }}; expires=Tue, 19 Jan 2038 03:14:07 UTC; SameSite=Strict; path=/'; location.reload()">{{ other_lang.name }}</span>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
</a>
|
||||
</nav>
|
||||
<div id="challenge">
|
||||
<div>
|
||||
<h1>Welcome to Tegaki Tuesday #{{ challenge }}!</h1>
|
||||
{% if content.japanese %}
|
||||
<div lang="ja">
|
||||
<script>
|
||||
let kyujitai = false;
|
||||
function kyujitaiToggle() {
|
||||
document.querySelectorAll("[data-kyujitai]").forEach(segment => {
|
||||
let newText = segment.getAttribute("data-kyujitai");
|
||||
segment.setAttribute("data-kyujitai", segment.firstChild.data);
|
||||
segment.firstChild.data = newText;
|
||||
})
|
||||
kyujitai = !kyujitai;
|
||||
}
|
||||
</script>
|
||||
<button onclick="kyujitaiToggle(); this.innerHTML = kyujitai ? '新字体' : '旧字体'">旧字体</button>
|
||||
{% for line in content.japanese %}
|
||||
{% for subline in line %}
|
||||
<p>
|
||||
{%- for word in subline -%}
|
||||
{%- if word.dictionary -%}
|
||||
<a href="{{ word.dictionary }}" target="_blank"{% if word.pos %} class="{{ word.pos }}" title="{{ word.pos }}"{% endif %}>
|
||||
{%- endif -%}
|
||||
{% for segment in word.text -%}
|
||||
<ruby{% if segment.kyujitai %} data-kyujitai="{{ segment.kyujitai }}"{% endif %}>{{ segment.kanji }}<rp>(</rp><rt>{{ segment.furigana | safe }}</rt><rp>)</rp></ruby>
|
||||
{%- endfor -%}
|
||||
{%- if word.dictionary -%}
|
||||
</a>
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
</p>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ content.text | safe }}
|
||||
{% if content.translation %}
|
||||
<p>
|
||||
Translation
|
||||
{% if content.translation.author %}by {{ content.translation.author }}{% endif %}
|
||||
{% if content.translation.site %} via
|
||||
{% if content.translation.site.link %}
|
||||
<a href="{{ content.translation.site.link }}" target="_blank">{{ content.translation.site.name }}</a>
|
||||
{% else %}
|
||||
{{ content.translation.site.name }}
|
||||
{% endif %}
|
||||
{%- endif -%}
|
||||
</p>
|
||||
{%- endif -%}
|
||||
{%- if content.suggester -%}
|
||||
<p><em>This challenge was suggested by <strong>{{ content.suggester }}</strong> using the <code>-h suggest</code> command.</code></em></p>
|
||||
{%- endif -%}
|
||||
</div>
|
||||
<footer>
|
||||
Copyright {% include "copyright-years" %} Tegaki Tuesday. All rights reserved. 字ちゃん mascot art by <a href="https://twitter.com/bellumela" target="_blank">@bellumela</a>.
|
||||
</footer>
|
||||
</div>
|
||||
<div id="submissions">
|
||||
<script>
|
||||
const submissionModal = image => {
|
||||
const dialog = document.createElement("dialog");
|
||||
dialog.style.padding = "0";
|
||||
const img = document.createElement("img");
|
||||
img.src = "/{{ challenge }}/" + image;
|
||||
const defaultZoom = "75vh";
|
||||
img.style.height = defaultZoom;
|
||||
img.style.cursor = "zoom-in";
|
||||
img.style.display = "block";
|
||||
img.style.margin = "0";
|
||||
img.onclick = event => img.style.height = img.style.height ? "" : defaultZoom;
|
||||
dialog.appendChild(img);
|
||||
dialog.addEventListener("close", event => document.body.removeChild(dialog));
|
||||
document.body.appendChild(dialog);
|
||||
dialog.showModal();
|
||||
};
|
||||
</script>
|
||||
<div>
|
||||
{% for submission in submissions %}
|
||||
{% set author = users[submission.author_id] %}
|
||||
<figure>
|
||||
<img src="/{{ challenge }}/{{ submission.image }}" alt="{{ author.username }}'s submission" onclick="submissionModal('{{ submission.image }}')">
|
||||
<figcaption>{% if not author.deleted %}<a href="https://discord.com/users/{{ author.id }}" target="_blank">{% endif %}{{ author.username }}{% if author.deleted %} (deleted account){% else %}</a>{% endif %}</figcaption>
|
||||
</figure>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<footer>
|
||||
Submissions are copyright {% include "copyright-years" %} their respective submitters, and are licensed under the <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)</a> license.
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,51 @@
|
||||
{% if challenge %}
|
||||
<a class="link" href="/{{ challenge - 1 }}"><svg class="svg-inline" aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 278.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z"></path></svg></a>
|
||||
<input type="number" min="1" value="{{ challenge }}" autocomplete="false">
|
||||
<a class="link" href="/{{ challenge + 1 }}"><svg class="svg-inline" aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M342.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L274.7 256 105.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z"></path></svg></a>
|
||||
{% endif %}
|
||||
{% if content.song %}
|
||||
<div>
|
||||
<span>{% if content.song.japanese %}{{ content.song.japanese | furigana | safe }}{% else %}{{ content.song.english }}{% endif %}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if user %}
|
||||
<div class="dropdown right">
|
||||
<a href="/users/{{ user.id }}" class="link">
|
||||
<span>{{ user.username }}</span> <img src="{{ user.avatar }}">
|
||||
</a>
|
||||
<nav class="dropdown-content">
|
||||
<span class="link" onclick="showServers()">
|
||||
<span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="icon">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" />
|
||||
</svg>
|
||||
Join servers
|
||||
</span>
|
||||
</span>
|
||||
<a href="/logout" class="link">
|
||||
<span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="icon">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75" />
|
||||
</svg>
|
||||
Log out
|
||||
</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
{% else%}
|
||||
<a href="/login" class="link right">Log in</a>
|
||||
{% endif %}
|
||||
<div class="dropdown">
|
||||
<span class="link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="icon">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 21l5.25-11.25L21 21m-9-3h7.5M3 5.621a48.474 48.474 0 016-.371m0 0c1.12 0 2.233.038 3.334.114M9 5.25V3m3.334 2.364C11.176 10.658 7.69 15.08 3 17.502m9.334-12.138c.896.061 1.785.147 2.666.257m-4.589 8.495a18.023 18.023 0 01-3.827-5.802" />
|
||||
</svg>
|
||||
</span>
|
||||
<nav class="dropdown-content">
|
||||
{% set langs = 0 | langs %}
|
||||
{% for other_lang in langs %}
|
||||
{% if other_lang.code in lang %}{% continue %}{% endif %}
|
||||
<span class="link" onclick="document.cookie = 'lang={{ other_lang.code }}; expires=Tue, 19 Jan 2038 03:14:07 UTC; SameSite=Strict; path=/'; location.reload()">{{ other_lang.name }}</span>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
</a>
|
@ -0,0 +1,9 @@
|
||||
{% extends "base" %}
|
||||
|
||||
{% block content %}
|
||||
<div style="text-align: center">
|
||||
<img src="{{ profile_user.avatar }}" alt="{{ profile_user.username }}" style="border-radius: 100%">
|
||||
<h1><a href="https://discord.com/users/{{ profile_user.id }}">{{ profile_user.username }}</a>'s submissions</h1>
|
||||
{{ submissions | length }} images
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
Reference in new issue