add: rewrite root html
This commit is contained in:
parent
78d0766345
commit
350e47f4b7
7 changed files with 405 additions and 442 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -339,9 +339,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bberry"
|
name = "bberry"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9413ba3ccb3dfc908093b3e2cdb7b39acc2127fe0b29268df4708cfa06c6ce30"
|
checksum = "d73b8674872fdd2eb09463fc9e3d15c4510a3abd8a1f8ef0a9e918065913aed5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit_field"
|
name = "bit_field"
|
||||||
|
|
|
@ -48,4 +48,4 @@ async-stripe = { version = "0.41.0", features = [
|
||||||
] }
|
] }
|
||||||
emojis = "0.6.4"
|
emojis = "0.6.4"
|
||||||
webp = "0.3.0"
|
webp = "0.3.0"
|
||||||
bberry = "0.1.1"
|
bberry = "0.1.2"
|
||||||
|
|
|
@ -36,7 +36,7 @@ pub const ME_JS: &str = include_str!("./public/js/me.js");
|
||||||
pub const STREAMS_JS: &str = include_str!("./public/js/streams.js");
|
pub const STREAMS_JS: &str = include_str!("./public/js/streams.js");
|
||||||
|
|
||||||
// html
|
// html
|
||||||
pub const ROOT: &str = include_str!("./public/html/root.html");
|
pub const ROOT: &str = include_str!("./public/html/root.lisp");
|
||||||
pub const MACROS: &str = include_str!("./public/html/macros.html");
|
pub const MACROS: &str = include_str!("./public/html/macros.html");
|
||||||
pub const COMPONENTS: &str = include_str!("./public/html/components.html");
|
pub const COMPONENTS: &str = include_str!("./public/html/components.html");
|
||||||
|
|
||||||
|
@ -140,7 +140,13 @@ pub(crate) async fn pull_icon(icon: &str, icons_dir: &str) {
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("download icon: {icon}");
|
println!("download icon: {icon}");
|
||||||
let svg = reqwest::get(icon_url).await.unwrap().text().await.unwrap();
|
let svg = reqwest::get(icon_url)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.text()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.replace("\n", "");
|
||||||
|
|
||||||
write(&file_path, &svg).unwrap();
|
write(&file_path, &svg).unwrap();
|
||||||
writer.insert(icon.to_string(), svg);
|
writer.insert(icon.to_string(), svg);
|
||||||
|
@ -245,7 +251,7 @@ pub(crate) async fn write_assets(config: &Config) -> PathBufD {
|
||||||
// ...
|
// ...
|
||||||
let html_path = PathBufD::current().join(&config.dirs.templates);
|
let html_path = PathBufD::current().join(&config.dirs.templates);
|
||||||
|
|
||||||
write_template!(html_path->"root.html"(crate::assets::ROOT) --config=config);
|
write_template!(html_path->"root.html"(crate::assets::ROOT) --config=config --lisp);
|
||||||
write_template!(html_path->"macros.html"(crate::assets::MACROS) --config=config);
|
write_template!(html_path->"macros.html"(crate::assets::MACROS) --config=config);
|
||||||
write_template!(html_path->"components.html"(crate::assets::COMPONENTS) --config=config);
|
write_template!(html_path->"components.html"(crate::assets::COMPONENTS) --config=config);
|
||||||
|
|
||||||
|
|
|
@ -3,24 +3,24 @@
|
||||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||||
|
|
||||||
(main
|
(main
|
||||||
(: "class" "flex flex-col gap-2")
|
("class" "flex flex-col gap-2")
|
||||||
(div
|
(div
|
||||||
(: "class" "card-nest")
|
("class" "card-nest")
|
||||||
(div
|
(div
|
||||||
(: "class" "card")
|
("class" "card")
|
||||||
(b (text "Error 😦")))
|
(b (text "Error 😦")))
|
||||||
|
|
||||||
(div
|
(div
|
||||||
(: "class" "card flex flex-col gap-4")
|
("class" "card flex flex-col gap-4")
|
||||||
(span (text "{{ error_text }}"))
|
(span (text "{{ error_text }}"))
|
||||||
|
|
||||||
(div
|
(div
|
||||||
(: "class" "w-full flex gap-2")
|
("class" "w-full flex gap-2")
|
||||||
(a
|
(a
|
||||||
(: "class" "button primary") (: "href" "/")
|
("class" "button primary") ("href" "/")
|
||||||
(text "Home"))
|
(text "Home"))
|
||||||
(a
|
(a
|
||||||
(: "class" "button secondary") (: "href" "javascript:history.back()")
|
("class" "button secondary") ("href" "javascript:history.back()")
|
||||||
(text "Back"))))))
|
(text "Back"))))))
|
||||||
|
|
||||||
(text "{% endblock %}")
|
(text "{% endblock %}")
|
||||||
|
|
|
@ -3,18 +3,18 @@
|
||||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||||
|
|
||||||
(main
|
(main
|
||||||
(: "class" "flex flex-col gap-2")
|
("class" "flex flex-col gap-2")
|
||||||
(div
|
(div
|
||||||
(: "class" "card-nest")
|
("class" "card-nest")
|
||||||
(div
|
(div
|
||||||
(: "class" "card small flex items-center justify-between gap-2")
|
("class" "card small flex items-center justify-between gap-2")
|
||||||
(span
|
(span
|
||||||
(: "class" "flex items-center gap-2")
|
("class" "flex items-center gap-2")
|
||||||
(text "{{ icon scroll-text }}")
|
(text "{{ icon scroll-text }}")
|
||||||
(span (text "{{ file_name }}"))))
|
(span (text "{{ file_name }}"))))
|
||||||
|
|
||||||
(div
|
(div
|
||||||
(: "class" "card")
|
("class" "card")
|
||||||
(span (text "{{ file|markdown|safe }}")))))
|
(span (text "{{ file|markdown|safe }}")))))
|
||||||
|
|
||||||
(text "{% endblock %}")
|
(text "{% endblock %}")
|
||||||
|
|
|
@ -1,424 +0,0 @@
|
||||||
{%- import "components.html" as components -%} {%- import "macros.html" as
|
|
||||||
macros -%}
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
|
||||||
|
|
||||||
<meta
|
|
||||||
http-equiv="content-security-policy"
|
|
||||||
content="default-src 'self' blob: *.spotify.com musicbrainz.org; img-src * data:; media-src *; font-src *; style-src 'unsafe-inline' 'self' blob: *; script-src 'self' 'unsafe-inline' blob: *; object-src 'self' blob: *; upgrade-insecure-requests; connect-src * localhost; frame-src 'self' blob: data: *"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<link rel="icon" href="/public/favicon.svg" />
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="/css/style.css" />
|
|
||||||
|
|
||||||
{% if user -%}
|
|
||||||
<script>
|
|
||||||
window.localStorage.setItem(
|
|
||||||
"tetratto:theme",
|
|
||||||
"{{ user.settings.theme_preference }}",
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
<script src="/js/loader.js"></script>
|
|
||||||
<script defer async src="/js/atto.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
globalThis.ns_verbose = false;
|
|
||||||
globalThis.ns_config = {
|
|
||||||
root: "/js/",
|
|
||||||
verbose: globalThis.ns_verbose,
|
|
||||||
version: "cache-breaker-{{ random_cache_breaker }}",
|
|
||||||
};
|
|
||||||
|
|
||||||
globalThis._app_base = {
|
|
||||||
name: "tetratto",
|
|
||||||
ns_store: {},
|
|
||||||
classes: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
globalThis.no_policy = false;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<meta name="theme-color" content="{{ config.color }}" />
|
|
||||||
<meta name="description" content="{{ config.description }}" />
|
|
||||||
<meta property="og:type" content="website" />
|
|
||||||
<meta property="og:site_name" content="{{ config.name }}" />
|
|
||||||
|
|
||||||
<meta name="turbo-prefetch" content="false" />
|
|
||||||
<meta name="turbo-refresh-method" content="morph" />
|
|
||||||
<meta name="turbo-refresh-scroll" content="preserve" />
|
|
||||||
|
|
||||||
<script
|
|
||||||
src="https://unpkg.com/@hotwired/turbo@8.0.5/dist/turbo.es2017-esm.js"
|
|
||||||
type="module"
|
|
||||||
async
|
|
||||||
defer
|
|
||||||
></script>
|
|
||||||
|
|
||||||
{% block head %}{% endblock %}
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="toast_zone"></div>
|
|
||||||
|
|
||||||
<div id="page">
|
|
||||||
<!-- prettier-ignore -->
|
|
||||||
{% if user and user.id == 0 -%}
|
|
||||||
<article>
|
|
||||||
<main>
|
|
||||||
<div class="card-nest">
|
|
||||||
<div class="card small flex items-center gap-2 red">
|
|
||||||
{{ icon "frown" }}
|
|
||||||
<span
|
|
||||||
>{{ text "general:label.account_banned" }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<span
|
|
||||||
>{{ text "general:label.account_banned_body"
|
|
||||||
}}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</article>
|
|
||||||
{% else %} {% block body %}{% endblock %} {%- endif %}
|
|
||||||
<!-- html_footer_goes_here -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script data-turbo-permanent="true" id="init-script">
|
|
||||||
document.documentElement.addEventListener("turbo:load", () => {
|
|
||||||
const atto = ns("atto");
|
|
||||||
|
|
||||||
atto.disconnect_observers();
|
|
||||||
atto.remove_false_options();
|
|
||||||
atto.clean_date_codes();
|
|
||||||
atto.link_filter();
|
|
||||||
|
|
||||||
atto["hooks::scroll"](document.body, document.documentElement);
|
|
||||||
atto["hooks::dropdown.init"](window);
|
|
||||||
atto["hooks::character_counter.init"]();
|
|
||||||
atto["hooks::long_text.init"]();
|
|
||||||
atto["hooks::alt"]();
|
|
||||||
atto["hooks::online_indicator"]();
|
|
||||||
atto["hooks::ips"]();
|
|
||||||
atto["hooks::check_reactions"]();
|
|
||||||
atto["hooks::tabs"]();
|
|
||||||
atto["hooks::spotify_time_text"](); // spotify durations
|
|
||||||
atto["hooks::verify_emoji"]();
|
|
||||||
|
|
||||||
if (document.getElementById("tokens")) {
|
|
||||||
trigger("me::render_token_picker", [
|
|
||||||
document.getElementById("tokens"),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
trigger("me::notifications_stream");
|
|
||||||
}, 250);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% if user -%}
|
|
||||||
<script data-turbo-permanent="true" id="update-seen-script">
|
|
||||||
document.documentElement.addEventListener("turbo:load", () => {
|
|
||||||
trigger("me::seen");
|
|
||||||
trigger("streams::user", ["{{ user.id }}"]);
|
|
||||||
|
|
||||||
if (!window.location.pathname.startsWith("/chats/")) {
|
|
||||||
if (window.socket) {
|
|
||||||
window.socket.send("Close");
|
|
||||||
window.socket = undefined;
|
|
||||||
console.log("socket disconnect");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
<!-- dialogs -->
|
|
||||||
<dialog id="link_filter">
|
|
||||||
<div class="inner flex flex-col gap-2">
|
|
||||||
<p>Pressing continue will bring you to the following URL:</p>
|
|
||||||
<pre><code id="link_filter_url"></code></pre>
|
|
||||||
<p>Are sure you want to go there?</p>
|
|
||||||
|
|
||||||
<hr class="margin" />
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<a
|
|
||||||
class="button primary"
|
|
||||||
id="link_filter_continue"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
onclick="document.getElementById('link_filter').close()"
|
|
||||||
>
|
|
||||||
{{ icon "external-link" }}
|
|
||||||
<span>{{ text "dialog:action.continue" }}</span>
|
|
||||||
</a>
|
|
||||||
<button
|
|
||||||
class="secondary"
|
|
||||||
type="button"
|
|
||||||
onclick="document.getElementById('link_filter').close()"
|
|
||||||
>
|
|
||||||
{{ icon "x" }}
|
|
||||||
<span>{{ text "dialog:action.cancel" }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</dialog>
|
|
||||||
|
|
||||||
<dialog id="web_api_prompt">
|
|
||||||
<div class="inner flex flex-col gap-2">
|
|
||||||
<form
|
|
||||||
class="flex gap-2 flex-col"
|
|
||||||
onsubmit="event.preventDefault()"
|
|
||||||
>
|
|
||||||
<label for="prompt" id="web_api_prompt:msg"></label>
|
|
||||||
<input id="prompt" name="prompt" />
|
|
||||||
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<div></div>
|
|
||||||
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<button
|
|
||||||
class="primary bold circle"
|
|
||||||
onclick="globalThis.web_api_prompt_submit(document.getElementById('prompt').value); document.getElementById('prompt').value = ''"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
{{ icon "check" }} {{ text "dialog:action.okay"
|
|
||||||
}}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="bold red camo"
|
|
||||||
onclick="globalThis.web_api_prompt_submit('')"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
{{ icon "x" }} {{ text "dialog:action.cancel" }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</dialog>
|
|
||||||
|
|
||||||
<dialog id="web_api_prompt_long">
|
|
||||||
<div class="inner flex flex-col gap-2">
|
|
||||||
<form
|
|
||||||
class="flex gap-2 flex-col"
|
|
||||||
onsubmit="event.preventDefault()"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
for="prompt_long"
|
|
||||||
id="web_api_prompt_long:msg"
|
|
||||||
></label>
|
|
||||||
<textarea id="prompt_long" name="prompt_long"></textarea>
|
|
||||||
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<div></div>
|
|
||||||
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<button
|
|
||||||
class="primary bold circle"
|
|
||||||
onclick="globalThis.web_api_prompt_long_submit(document.getElementById('prompt_long').value); document.getElementById('prompt_long').value = ''"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
{{ icon "check" }} {{ text "dialog:action.okay"
|
|
||||||
}}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="bold red camo"
|
|
||||||
onclick="globalThis.web_api_prompt_long_submit('')"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
{{ icon "x" }} {{ text "dialog:action.cancel" }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</dialog>
|
|
||||||
|
|
||||||
<dialog id="web_api_confirm">
|
|
||||||
<div class="inner flex flex-col gap-2">
|
|
||||||
<form
|
|
||||||
class="flex gap-2 flex-col"
|
|
||||||
onsubmit="event.preventDefault()"
|
|
||||||
>
|
|
||||||
<label id="web_api_confirm:msg"></label>
|
|
||||||
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<div></div>
|
|
||||||
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<button
|
|
||||||
class="primary bold circle"
|
|
||||||
onclick="globalThis.web_api_confirm_submit(true)"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
{{ icon "check" }} {{ text "dialog:action.yes"
|
|
||||||
}}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="bold red camo"
|
|
||||||
onclick="globalThis.web_api_confirm_submit(false)"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
{{ icon "x" }} {{ text "dialog:action.no" }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</dialog>
|
|
||||||
|
|
||||||
<div class="lightbox hidden" id="lightbox">
|
|
||||||
<button
|
|
||||||
class="lightbox_exit small square quaternary red"
|
|
||||||
onclick="trigger('ui::lightbox_close')"
|
|
||||||
>
|
|
||||||
{{ icon "x" }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<a href="" id="lightbox_img_a" target="_blank">
|
|
||||||
<img src="" alt="Image" id="lightbox_img" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if user -%}
|
|
||||||
<dialog id="tokens_dialog">
|
|
||||||
<div class="inner flex flex-col gap-2">
|
|
||||||
<form
|
|
||||||
class="flex gap-2 flex-col"
|
|
||||||
onsubmit="event.preventDefault()"
|
|
||||||
>
|
|
||||||
<div id="tokens" style="display: contents"></div>
|
|
||||||
|
|
||||||
<a href="/auth/login" class="button" data-turbo="false">
|
|
||||||
{{ icon "plus" }}
|
|
||||||
<span>{{ text "general:action.add_account" }}</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<div></div>
|
|
||||||
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<button
|
|
||||||
class="quaternary"
|
|
||||||
onclick="document.getElementById('tokens_dialog').close()"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
{{ icon "check" }}
|
|
||||||
<span>{{ text "dialog:action.okay" }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</dialog>
|
|
||||||
{%- endif %} {% if user and use_user_theme -%} {{
|
|
||||||
components::theme(user=user,
|
|
||||||
theme_preference=user.settings.theme_preference) }}
|
|
||||||
<script>
|
|
||||||
setTimeout(() => {
|
|
||||||
trigger("atto::use_theme_preference");
|
|
||||||
}, 150);
|
|
||||||
</script>
|
|
||||||
{%- endif %} {% if user and user.connections.Spotify and
|
|
||||||
config.connections.spotify_client_id and
|
|
||||||
user.connections.Spotify[0].data.token and
|
|
||||||
user.connections.Spotify[0].data.refresh_token %}
|
|
||||||
<script>
|
|
||||||
setTimeout(async () => {
|
|
||||||
if (window.spotify_init) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.spotify_init = true;
|
|
||||||
const client_id = "{{ config.connections.spotify_client_id }}";
|
|
||||||
let token = "{{ user.connections.Spotify[0].data.token }}";
|
|
||||||
let refresh_token =
|
|
||||||
"{{ user.connections.Spotify[0].data.refresh_token }}";
|
|
||||||
|
|
||||||
if (token) {
|
|
||||||
// we already have a token
|
|
||||||
const pull_playing = async () => {
|
|
||||||
const playing = await trigger("spotify::get_playing", [
|
|
||||||
token,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (playing.error) {
|
|
||||||
// refresh token
|
|
||||||
const [new_token, new_refresh_token, expires_in] =
|
|
||||||
await trigger("spotify::refresh_token", [
|
|
||||||
client_id,
|
|
||||||
refresh_token,
|
|
||||||
]);
|
|
||||||
|
|
||||||
await trigger("connections::push_con_data", [
|
|
||||||
"Spotify",
|
|
||||||
{
|
|
||||||
token: new_token,
|
|
||||||
expires_in: expires_in.toString(),
|
|
||||||
name: profile.display_name,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
token = new_token;
|
|
||||||
refresh_token = new_refresh_token;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await trigger("spotify::publish_playing", [playing]);
|
|
||||||
};
|
|
||||||
|
|
||||||
await pull_playing();
|
|
||||||
setInterval(pull_playing, 30_000);
|
|
||||||
} else {
|
|
||||||
window.spotify_needs_token = true;
|
|
||||||
}
|
|
||||||
}, 150);
|
|
||||||
</script>
|
|
||||||
{% elif user and user.connections.LastFm and
|
|
||||||
config.connections.last_fm_key and
|
|
||||||
user.connections.LastFm[0].data.session_token %}
|
|
||||||
<script>
|
|
||||||
setTimeout(async () => {
|
|
||||||
if (window.last_fm_init) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.last_fm_init = true;
|
|
||||||
const user = "{{ user.connections.LastFm[0].data.name }}";
|
|
||||||
const session_token =
|
|
||||||
"{{ user.connections.LastFm[0].data.session_token }}";
|
|
||||||
|
|
||||||
if (session_token) {
|
|
||||||
// we already have a token
|
|
||||||
const pull_playing = async () => {
|
|
||||||
const playing = await trigger("last_fm::get_playing", [
|
|
||||||
user,
|
|
||||||
session_token,
|
|
||||||
]);
|
|
||||||
await trigger("last_fm::publish_playing", [playing]);
|
|
||||||
};
|
|
||||||
|
|
||||||
await pull_playing();
|
|
||||||
setInterval(pull_playing, 30_000);
|
|
||||||
} else {
|
|
||||||
window.last_fm_needs_token = true;
|
|
||||||
}
|
|
||||||
}, 150);
|
|
||||||
</script>
|
|
||||||
{%- endif %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
381
crates/app/src/public/html/root.lisp
Normal file
381
crates/app/src/public/html/root.lisp
Normal file
|
@ -0,0 +1,381 @@
|
||||||
|
(text "{%- import \"components.html\" as components -%} {%- import \"macros.html\" as macros -%}")
|
||||||
|
(text "<!doctype html>")
|
||||||
|
|
||||||
|
(html
|
||||||
|
("lang" "en")
|
||||||
|
(head
|
||||||
|
(meta ("charset" "UTF-8"))
|
||||||
|
(meta ("name" "viewport") ("content" "width=device-width, initial-scale=1.0"))
|
||||||
|
(meta ("http-equiv" "X-UA-Compatible") ("content" "ie=edge"))
|
||||||
|
(meta ("http-equiv" "content-security-policy") ("content" "default-src 'self' blob: *.spotify.com musicbrainz.org; img-src * data:; media-src *; font-src *; style-src 'unsafe-inline' 'self' blob: *; script-src 'self' 'unsafe-inline' blob: *; object-src 'self' blob: *; upgrade-insecure-requests; connect-src * localhost; frame-src 'self' blob: data: *"))
|
||||||
|
|
||||||
|
(link ("rel" "icon") ("href" "/public/favicon.svg"))
|
||||||
|
(link ("rel" "stylesheet") ("href" "/css/style.css"))
|
||||||
|
|
||||||
|
(text "{% if user -%}
|
||||||
|
<script>
|
||||||
|
window.localStorage.setItem(
|
||||||
|
\"tetratto:theme\",
|
||||||
|
\"{{ user.settings.theme_preference }}\",
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
{%- endif %}")
|
||||||
|
|
||||||
|
(text "<script>
|
||||||
|
globalThis.ns_verbose = false;
|
||||||
|
globalThis.ns_config = {
|
||||||
|
root: \"/js/\",
|
||||||
|
verbose: globalThis.ns_verbose,
|
||||||
|
version: \"cache-breaker-{{ random_cache_breaker }}\",
|
||||||
|
};
|
||||||
|
|
||||||
|
globalThis._app_base = {
|
||||||
|
name: \"tetratto\",
|
||||||
|
ns_store: {},
|
||||||
|
classes: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
globalThis.no_policy = false;
|
||||||
|
</script>")
|
||||||
|
|
||||||
|
(script ("src" "/js/loader.js" ))
|
||||||
|
(script ("src" "/js/atto.js" ))
|
||||||
|
|
||||||
|
(meta ("name" "theme-color") ("content" "{{ config.color }}"))
|
||||||
|
(meta ("name" "description") ("content" "{{ config.description }}"))
|
||||||
|
(meta ("property" "og:type") ("content" "website"))
|
||||||
|
(meta ("property" "og:site_name") ("content" "{{ config.color }}"))
|
||||||
|
|
||||||
|
(meta ("name" "turbo-prefetch") ("content" "false"))
|
||||||
|
(meta ("name" "turbo-refresh-method") ("content" "morph"))
|
||||||
|
(meta ("name" "turbo-refresh-scroll") ("content" "preserve"))
|
||||||
|
|
||||||
|
(script ("src" "https://unpkg.com/@hotwired/turbo@8.0.5/dist/turbo.es2017-esm.js") ("type" "module") ("async" "") ("defer" ""))
|
||||||
|
|
||||||
|
(text "{% block head %}{% endblock %}"))
|
||||||
|
|
||||||
|
(body
|
||||||
|
(div ("id" "toast_zone"))
|
||||||
|
|
||||||
|
(div
|
||||||
|
("id" "page")
|
||||||
|
(text "{% if user and user.id == 0 -%}")
|
||||||
|
; account banned message
|
||||||
|
(article
|
||||||
|
(main
|
||||||
|
(div
|
||||||
|
("class" "card-nest")
|
||||||
|
(div
|
||||||
|
("class" "card small flex items-center gap-2 red")
|
||||||
|
(text "{{ icon \"frown\" }}")
|
||||||
|
(span (text "{{ text \"general:label.account_banned\" }}")))
|
||||||
|
|
||||||
|
(div
|
||||||
|
("class" "card")
|
||||||
|
(span (text "{{ text \"general:label.account_banned_body\" }}"))))))
|
||||||
|
|
||||||
|
; if we aren't banned, just show the page body
|
||||||
|
(text "{% else %} {% block body %}{% endblock %} {%- endif %}")
|
||||||
|
(text "<!-- html_footer_goes_here -->"))
|
||||||
|
|
||||||
|
; random js
|
||||||
|
(text "<script data-turbo-permanent=\"true\" id=\"init-script\">
|
||||||
|
document.documentElement.addEventListener(\"turbo:load\", () => {
|
||||||
|
const atto = ns(\"atto\");
|
||||||
|
|
||||||
|
atto.disconnect_observers();
|
||||||
|
atto.remove_false_options();
|
||||||
|
atto.clean_date_codes();
|
||||||
|
atto.link_filter();
|
||||||
|
|
||||||
|
atto[\"hooks::scroll\"](document.body, document.documentElement);
|
||||||
|
atto[\"hooks::dropdown.init\"](window);
|
||||||
|
atto[\"hooks::character_counter.init\"]();
|
||||||
|
atto[\"hooks::long_text.init\"]();
|
||||||
|
atto[\"hooks::alt\"]();
|
||||||
|
atto[\"hooks::online_indicator\"]();
|
||||||
|
atto[\"hooks::ips\"]();
|
||||||
|
atto[\"hooks::check_reactions\"]();
|
||||||
|
atto[\"hooks::tabs\"]();
|
||||||
|
atto[\"hooks::spotify_time_text\"](); // spotify durations
|
||||||
|
atto[\"hooks::verify_emoji\"]();
|
||||||
|
|
||||||
|
if (document.getElementById(\"tokens\")) {
|
||||||
|
trigger(\"me::render_token_picker\", [
|
||||||
|
document.getElementById(\"tokens\"),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
trigger(\"me::notifications_stream\");
|
||||||
|
}, 250);
|
||||||
|
});
|
||||||
|
</script>")
|
||||||
|
|
||||||
|
(text "{% if user -%}
|
||||||
|
<script data-turbo-permanent=\"true\" id=\"update-seen-script\">
|
||||||
|
document.documentElement.addEventListener(\"turbo:load\", () => {
|
||||||
|
trigger(\"me::seen\");
|
||||||
|
trigger(\"streams::user\", [\"{{ user.id }}\"]);
|
||||||
|
|
||||||
|
if (!window.location.pathname.startsWith(\"/chats/\")) {
|
||||||
|
if (window.socket) {
|
||||||
|
window.socket.send(\"Close\");
|
||||||
|
window.socket = undefined;
|
||||||
|
console.log(\"socket disconnect\");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{%- endif %}")
|
||||||
|
|
||||||
|
; dialogs
|
||||||
|
(dialog
|
||||||
|
("id" "link_filter")
|
||||||
|
(div
|
||||||
|
("class" "inner flex flex-col gap-2")
|
||||||
|
|
||||||
|
; warning stuff
|
||||||
|
(p (text "Pressing continue will bring you to the following URL:"))
|
||||||
|
(pre (code ("id" "link_filter_url")))
|
||||||
|
(p (text "Are sure you want to go there?"))
|
||||||
|
|
||||||
|
(hr ("class" "margin"))
|
||||||
|
(div
|
||||||
|
("class" "flex gap-2")
|
||||||
|
|
||||||
|
(a
|
||||||
|
("class" "button primary")
|
||||||
|
("id" "link_filter_continue")
|
||||||
|
("rel" "noopener noreferrer")
|
||||||
|
("target" "_blank")
|
||||||
|
("onclick", "document.getElementById('link_filter').close()")
|
||||||
|
(text "{{ icon \"external-link\" }}")
|
||||||
|
(span (text "{{ text \"dialog:action.continue\" }}")))
|
||||||
|
|
||||||
|
(button
|
||||||
|
("class" "secondary")
|
||||||
|
("type" "button")
|
||||||
|
("onclick", "document.getElementById('link_filter').close()")
|
||||||
|
(text "{{ icon \"x\" }}" )
|
||||||
|
(span (text "{{ text \"dialog:action.cancel\" }}"))))))
|
||||||
|
|
||||||
|
(dialog
|
||||||
|
("id" "web_api_prompt")
|
||||||
|
(div
|
||||||
|
("class" "inner flex flex-col gap-2")
|
||||||
|
(form
|
||||||
|
("class" "flex gap-2 flex-col")
|
||||||
|
("onsubmit" "event.preventDefault()")
|
||||||
|
(label ("for" "prompt") ("id" "web_api_prompt:msg"))
|
||||||
|
(input ("id" "prompt") ("name" "prompt"))
|
||||||
|
|
||||||
|
(div
|
||||||
|
("class" "flex justify-between")
|
||||||
|
(div null?)
|
||||||
|
(div
|
||||||
|
("class" "flex gap-2")
|
||||||
|
(button
|
||||||
|
("class" "primary bold circle")
|
||||||
|
("onclick", "globalThis.web_api_prompt_submit(document.getElementById('prompt').value); document.getElementById('prompt').value = ''")
|
||||||
|
("type" "button")
|
||||||
|
(text "{{ icon \"check\" }}")
|
||||||
|
(text "{{ text \"dialog:action.okay\" }}"))
|
||||||
|
|
||||||
|
(button
|
||||||
|
("class" "bold red camo")
|
||||||
|
("onclick", "globalThis.web_api_prompt_submit('')")
|
||||||
|
("type" "button")
|
||||||
|
(text "{{ icon \"x\" }}")
|
||||||
|
(text "{{ text \"dialog:action.cancel\" }}")))))))
|
||||||
|
|
||||||
|
(dialog
|
||||||
|
("id" "web_api_prompt_long")
|
||||||
|
(div
|
||||||
|
("class" "inner flex flex-col gap-2")
|
||||||
|
(form
|
||||||
|
("class" "flex gap-2 flex-col")
|
||||||
|
("onsubmit" "event.preventDefault()")
|
||||||
|
(label ("for" "prompt_long") ("id" "web_api_prompt_long:msg"))
|
||||||
|
(input ("id" "prompt_long") ("name" "prompt_long"))
|
||||||
|
|
||||||
|
(div
|
||||||
|
("class" "flex justify-between")
|
||||||
|
(div null?)
|
||||||
|
(div
|
||||||
|
("class" "flex gap-2")
|
||||||
|
(button
|
||||||
|
("class" "primary bold circle")
|
||||||
|
("onclick", "globalThis.web_api_prompt_long_submit(document.getElementById('prompt_long').value); document.getElementById('prompt_long').value = ''")
|
||||||
|
("type" "button")
|
||||||
|
(text "{{ icon \"check\" }}")
|
||||||
|
(text "{{ text \"dialog:action.okay\" }}"))
|
||||||
|
|
||||||
|
(button
|
||||||
|
("class" "bold red camo")
|
||||||
|
("onclick", "globalThis.web_api_prompt_long_submit('')")
|
||||||
|
("type" "button")
|
||||||
|
(text "{{ icon \"x\" }}")
|
||||||
|
(text "{{ text \"dialog:action.cancel\" }}")))))))
|
||||||
|
|
||||||
|
(dialog
|
||||||
|
("id" "web_api_confirm")
|
||||||
|
(div
|
||||||
|
("class" "inner flex flex-col gap-2")
|
||||||
|
(form
|
||||||
|
("class" "flex gap-2 flex-col")
|
||||||
|
("onsubmit" "event.preventDefault()")
|
||||||
|
(span ("id" "web_api_confirm:msg"))
|
||||||
|
|
||||||
|
(div
|
||||||
|
("class" "flex justify-between")
|
||||||
|
(div null?)
|
||||||
|
(div
|
||||||
|
("class" "flex gap-2")
|
||||||
|
(button
|
||||||
|
("class" "primary bold circle")
|
||||||
|
("onclick", "globalThis.web_api_confirm_submit(true)")
|
||||||
|
("type" "button")
|
||||||
|
(text "{{ icon \"check\" }}")
|
||||||
|
(text "{{ text \"dialog:action.yes\" }}"))
|
||||||
|
|
||||||
|
(button
|
||||||
|
("class" "bold red camo")
|
||||||
|
("onclick", "globalThis.web_api_confirm_submit(false)")
|
||||||
|
("type" "button")
|
||||||
|
(text "{{ icon \"x\" }}")
|
||||||
|
(text "{{ text \"dialog:action.no\" }}")))))))
|
||||||
|
|
||||||
|
(div
|
||||||
|
("class" "lightbox hidden")
|
||||||
|
("id" "lightbox")
|
||||||
|
(button
|
||||||
|
("class" "lightbox_exit small square quaternary red")
|
||||||
|
("onclick" "trigger('ui::lightbox_close')")
|
||||||
|
(text "{{ icon \"x\" }}"))
|
||||||
|
|
||||||
|
(a
|
||||||
|
("href" "")
|
||||||
|
("id" "lightbox_img_a")
|
||||||
|
("target" "_blank")))
|
||||||
|
|
||||||
|
; tokens dialog
|
||||||
|
(text "{% if user -%}")
|
||||||
|
(dialog
|
||||||
|
("id" "tokens_dialog")
|
||||||
|
(div
|
||||||
|
("class" "inner flex flex-col gap-2")
|
||||||
|
(form
|
||||||
|
("class" "flex gap-2 flex-col")
|
||||||
|
("onsubmit" "event.preventDefault()")
|
||||||
|
(div ("id" "tokens") ("style" "display: contents"))
|
||||||
|
|
||||||
|
(a
|
||||||
|
("href" "/auth/login")
|
||||||
|
("class" "button")
|
||||||
|
("data-turbo", "false")
|
||||||
|
(text "{{ icon \"plus\" }}")
|
||||||
|
(span (text "{{ text \"general:action.add_account\" }}")))
|
||||||
|
|
||||||
|
(div
|
||||||
|
("class" "flex justify-between")
|
||||||
|
(div null?)
|
||||||
|
|
||||||
|
(div
|
||||||
|
("class" "flex gap-2")
|
||||||
|
(button
|
||||||
|
("class" "quaternary")
|
||||||
|
("onclick" "document.getElementById('tokens_dialog').close()")
|
||||||
|
("type" "button")
|
||||||
|
(text "{{ icon \"check \" }}")
|
||||||
|
(span "{{ text \"dialog:action.okay\" }}")))))))
|
||||||
|
|
||||||
|
; user scripts
|
||||||
|
(text "{%- endif %} {% if user and use_user_theme -%} {{ components::theme(user=user, theme_preference=user.settings.theme_preference) }}
|
||||||
|
<script>
|
||||||
|
setTimeout(() => {
|
||||||
|
trigger(\"atto::use_theme_preference\");
|
||||||
|
}, 150);
|
||||||
|
</script>
|
||||||
|
{%- endif %} {% if user and user.connections.Spotify and config.connections.spotify_client_id and user.connections.Spotify[0].data.token and user.connections.Spotify[0].data.refresh_token %}
|
||||||
|
<script>
|
||||||
|
setTimeout(async () => {
|
||||||
|
if (window.spotify_init) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.spotify_init = true;
|
||||||
|
const client_id = \"{{ config.connections.spotify_client_id }}\";
|
||||||
|
let token = \"{{ user.connections.Spotify[0].data.token }}\";
|
||||||
|
let refresh_token =
|
||||||
|
\"{{ user.connections.Spotify[0].data.refresh_token }}\";
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
// we already have a token
|
||||||
|
const pull_playing = async () => {
|
||||||
|
const playing = await trigger(\"spotify::get_playing\", [
|
||||||
|
token,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (playing.error) {
|
||||||
|
// refresh token
|
||||||
|
const [new_token, new_refresh_token, expires_in] =
|
||||||
|
await trigger(\"spotify::refresh_token\", [
|
||||||
|
client_id,
|
||||||
|
refresh_token,
|
||||||
|
]);
|
||||||
|
|
||||||
|
await trigger(\"connections::push_con_data\", [
|
||||||
|
\"Spotify\",
|
||||||
|
{
|
||||||
|
token: new_token,
|
||||||
|
expires_in: expires_in.toString(),
|
||||||
|
name: profile.display_name,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
token = new_token;
|
||||||
|
refresh_token = new_refresh_token;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await trigger(\"spotify::publish_playing\", [playing]);
|
||||||
|
};
|
||||||
|
|
||||||
|
await pull_playing();
|
||||||
|
setInterval(pull_playing, 30_000);
|
||||||
|
} else {
|
||||||
|
window.spotify_needs_token = true;
|
||||||
|
}
|
||||||
|
}, 150);
|
||||||
|
</script>
|
||||||
|
{% elif user and user.connections.LastFm and config.connections.last_fm_key and user.connections.LastFm[0].data.session_token %}
|
||||||
|
<script>
|
||||||
|
setTimeout(async () => {
|
||||||
|
if (window.last_fm_init) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.last_fm_init = true;
|
||||||
|
const user = \"{{ user.connections.LastFm[0].data.name }}\";
|
||||||
|
const session_token =
|
||||||
|
\"{{ user.connections.LastFm[0].data.session_token }}\";
|
||||||
|
|
||||||
|
if (session_token) {
|
||||||
|
// we already have a token
|
||||||
|
const pull_playing = async () => {
|
||||||
|
const playing = await trigger(\"last_fm::get_playing\", [
|
||||||
|
user,
|
||||||
|
session_token,
|
||||||
|
]);
|
||||||
|
await trigger(\"last_fm::publish_playing\", [playing]);
|
||||||
|
};
|
||||||
|
|
||||||
|
await pull_playing();
|
||||||
|
setInterval(pull_playing, 30_000);
|
||||||
|
} else {
|
||||||
|
window.last_fm_needs_token = true;
|
||||||
|
}
|
||||||
|
}, 150);
|
||||||
|
</script>
|
||||||
|
{%- endif %}")))
|
Loading…
Add table
Add a link
Reference in a new issue