Compare commits

...

3 commits

Author SHA1 Message Date
Izalia Mae beb9d9c3e5 add web manifest to frontend 2024-03-28 07:12:27 -04:00
Izalia Mae ec7e254740 merge menu and toast resources into api.js and style.css 2024-03-28 06:04:17 -04:00
Izalia Mae 014c6896b1 some minor changes
* version bump to 0.3.1
* remove debug print in frontend endpoints
* remove unused json import
* fix toast messages for accepting/denying follow requests
2024-03-28 05:57:28 -04:00
12 changed files with 155 additions and 128 deletions

View file

@ -1 +1 @@
__version__ = '0.3.0' __version__ = '0.3.1'

View file

@ -44,7 +44,8 @@ def get_csp(request: web.Request) -> str:
"connect-src 'self'", "connect-src 'self'",
"img-src 'self'", "img-src 'self'",
"object-src 'none'", "object-src 'none'",
"frame-ancestors 'none'" "frame-ancestors 'none'",
f"manifest-src 'self' https://{request.app.config.domain}"
] ]
return '; '.join(data) + ';' return '; '.join(data) + ';'

View file

@ -13,9 +13,7 @@
%meta(name="viewport" content="width=device-width, initial-scale=1") %meta(name="viewport" content="width=device-width, initial-scale=1")
%link(rel="stylesheet" type="text/css" href="/theme/{{config.theme}}.css" nonce="{{view.request['hash']}}" class="theme") %link(rel="stylesheet" type="text/css" href="/theme/{{config.theme}}.css" nonce="{{view.request['hash']}}" class="theme")
%link(rel="stylesheet" type="text/css" href="/static/style.css" nonce="{{view.request['hash']}}") %link(rel="stylesheet" type="text/css" href="/static/style.css" nonce="{{view.request['hash']}}")
%link(rel="stylesheet" type="text/css" href="/static/toast.css" nonce="{{view.request['hash']}}") %link(rel="manifest" href="/manifest.json")
%script(type="application/javascript" src="/static/menu.js" nonce="{{view.request['hash']}}", defer)
%script(type="application/javascript" src="/static/toast.js" nonce="{{view.request['hash']}}", defer)
%script(type="application/javascript" src="/static/api.js" nonce="{{view.request['hash']}}", defer) %script(type="application/javascript" src="/static/api.js" nonce="{{view.request['hash']}}", defer)
-block head -block head

View file

@ -1,3 +1,61 @@
// toast notifications
const notifications = document.querySelector("#notifications")
function remove_toast(toast) {
toast.classList.add("hide");
if (toast.timeoutId) {
clearTimeout(toast.timeoutId);
}
setTimeout(() => toast.remove(), 300);
}
function toast(text, type="error", timeout=5) {
const toast = document.createElement("li");
toast.className = `section ${type}`
toast.innerHTML = `<span class=".text">${text}</span><a href="#">&#10006;</span>`
toast.querySelector("a").addEventListener("click", async (event) => {
event.preventDefault();
await remove_toast(toast);
});
notifications.appendChild(toast);
toast.timeoutId = setTimeout(() => remove_toast(toast), timeout * 1000);
}
// menu
const body = document.getElementById("container")
const menu = document.getElementById("menu");
const menu_open = document.getElementById("menu-open");
const menu_close = document.getElementById("menu-close");
menu_open.addEventListener("click", (event) => {
var new_value = menu.attributes.visible.nodeValue === "true" ? "false" : "true";
menu.attributes.visible.nodeValue = new_value;
});
menu_close.addEventListener("click", (event) => {
menu.attributes.visible.nodeValue = "false"
});
body.addEventListener("click", (event) => {
if (event.target === menu_open) {
return;
}
menu.attributes.visible.nodeValue = "false";
});
// misc
function get_date_string(date) { function get_date_string(date) {
var year = date.getFullYear().toString(); var year = date.getFullYear().toString();
var month = date.getMonth().toString(); var month = date.getMonth().toString();

View file

@ -100,6 +100,7 @@ async function req_response(domain, accept) {
} }
if (!accept) { if (!accept) {
toast("Denied instance request", "message");
return; return;
} }
@ -117,7 +118,7 @@ async function req_response(domain, accept) {
} }
}); });
toast("Removed instance", "message"); toast("Accepted instance request", "message");
} }

View file

@ -1,21 +0,0 @@
const body = document.getElementById("container")
const menu = document.getElementById("menu");
const menu_open = document.getElementById("menu-open");
const menu_close = document.getElementById("menu-close");
menu_open.addEventListener("click", (event) => {
var new_value = menu.attributes.visible.nodeValue === "true" ? "false" : "true";
menu.attributes.visible.nodeValue = new_value;
});
menu_close.addEventListener("click", (event) => {
menu.attributes.visible.nodeValue = "false"
});
body.addEventListener("click", (event) => {
if (event.target === menu_open) {
return;
}
menu.attributes.visible.nodeValue = "false";
});

View file

@ -204,6 +204,37 @@ textarea {
text-align: center; text-align: center;
} }
#notifications {
position: fixed;
top: 40px;
left: 50%;
transform: translateX(-50%);
}
#notifications li {
position: relative;
overflow: hidden;
list-style: none;
border-radius: 5px;
padding: 5px;;
margin-bottom: var(--spacing);
animation: show_toast 0.3s ease forwards;
display: grid;
grid-template-columns: auto max-content;
grid-gap: 5px;
align-items: center;
}
#notifications a {
font-size: 1.5em;
line-height: 1em;
text-decoration: none;
}
#notifications li.hide {
animation: hide_toast 0.3s ease forwards;
}
#footer { #footer {
display: grid; display: grid;
grid-template-columns: auto auto; grid-template-columns: auto auto;
@ -296,6 +327,44 @@ textarea {
} }
@keyframes show_toast {
0% {
transform: translateX(100%);
}
40% {
transform: translateX(-5%);
}
80% {
transform: translateX(0%);
}
100% {
transform: translateX(-10px);
}
}
@keyframes hide_toast {
0% {
transform: translateX(-10px);
}
40% {
transform: translateX(0%);
}
80% {
transform: translateX(-5%);
}
100% {
transform: translateX(calc(100% + 20px));
}
}
@media (max-width: 1026px) { @media (max-width: 1026px) {
body { body {
margin: 0px; margin: 0px;

View file

@ -1,68 +0,0 @@
#notifications {
position: fixed;
top: 40px;
left: 50%;
transform: translateX(-50%);
}
#notifications li {
position: relative;
overflow: hidden;
list-style: none;
border-radius: 5px;
padding: 5px;;
margin-bottom: var(--spacing);
animation: show_toast 0.3s ease forwards;
display: grid;
grid-template-columns: auto max-content;
grid-gap: 5px;
align-items: center;
}
#notifications a {
font-size: 1.5em;
line-height: 1em;
text-decoration: none;
}
#notifications li.hide {
animation: hide_toast 0.3s ease forwards;
}
@keyframes show_toast {
0% {
transform: translateX(100%);
}
40% {
transform: translateX(-5%);
}
80% {
transform: translateX(0%);
}
100% {
transform: translateX(-10px);
}
}
@keyframes hide_toast {
0% {
transform: translateX(-10px);
}
40% {
transform: translateX(0%);
}
80% {
transform: translateX(-5%);
}
100% {
transform: translateX(calc(100% + 20px));
}
}

View file

@ -1,26 +0,0 @@
const notifications = document.querySelector("#notifications")
function remove_toast(toast) {
toast.classList.add("hide");
if (toast.timeoutId) {
clearTimeout(toast.timeoutId);
}
setTimeout(() => toast.remove(), 300);
}
function toast(text, type="error", timeout=5) {
const toast = document.createElement("li");
toast.className = `section ${type}`
toast.innerHTML = `<span class=".text">${text}</span><a href="#">&#10006;</span>`
toast.querySelector("a").addEventListener("click", async (event) => {
event.preventDefault();
await remove_toast(toast);
});
notifications.appendChild(toast);
toast.timeoutId = setTimeout(() => remove_toast(toast), timeout * 1000);
}

View file

@ -38,7 +38,8 @@ MIMETYPES = {
'css': 'text/css', 'css': 'text/css',
'html': 'text/html', 'html': 'text/html',
'json': 'application/json', 'json': 'application/json',
'text': 'text/plain' 'text': 'text/plain',
'webmanifest': 'application/manifest+json'
} }
NODEINFO_NS = { NODEINFO_NS = {

View file

@ -3,7 +3,6 @@ from __future__ import annotations
import aputils import aputils
import traceback import traceback
import typing import typing
import json
from .base import View, register_route from .base import View, register_route

View file

@ -45,7 +45,6 @@ async def handle_frontend_path(request: web.Request, handler: Callable) -> Respo
response = await handler(request) response = await handler(request)
if not request['user'] and request['token']: if not request['user'] and request['token']:
print("del token")
response.del_cookie('user-token') response.del_cookie('user-token')
return response return response
@ -211,11 +210,27 @@ class AdminConfig(View):
return Response.new(data, ctype = 'html') return Response.new(data, ctype = 'html')
@register_route('/style.css') @register_route('/manifest.json')
class StyleCss(View): class ManifestJson(View):
async def get(self, request: Request) -> Response: async def get(self, request: Request) -> Response:
data = self.template.render('style.css', self) with self.database.session(False) as conn:
return Response.new(data, ctype = 'css') config = conn.get_config_all()
theme = THEMES[config.theme]
data = {
'background_color': theme['background'],
'categories': ['activitypub'],
'description': 'Message relay for the ActivityPub network',
'display': 'standalone',
'name': config['name'],
'orientation': 'portrait',
'scope': f"https://{self.config.domain}/",
'short_name': 'ActivityRelay',
'start_url': f"https://{self.config.domain}/",
'theme_color': theme['primary']
}
return Response.new(data, ctype = 'webmanifest')
@register_route('/theme/{theme}.css') @register_route('/theme/{theme}.css')