add: expand infinite scrolling to stacks and profiles

This commit is contained in:
trisua 2025-06-17 14:28:18 -04:00
parent 2b253c811c
commit 3027b679db
16 changed files with 226 additions and 288 deletions

View file

@ -213,6 +213,14 @@ ol {
margin-left: var(--pad-4);
}
pre {
padding: var(--pad-4);
}
code {
padding: var(--pad-1);
}
pre,
code {
font-family: "Jetbrains Mono", "Fire Code", monospace;
@ -221,18 +229,12 @@ code {
overflow: auto;
background: var(--color-lowered);
border-radius: var(--radius);
padding: var(--pad-1);
font-size: 0.8rem;
}
pre {
padding: var(--pad-4);
}
svg.icon {
stroke: currentColor;
width: 18px;
width: 1em;
height: 1em;
}
@ -263,7 +265,6 @@ code {
overflow-wrap: normal;
text-wrap: pretty;
word-wrap: break-word;
overflow-wrap: anywhere;
}
h1,
@ -275,7 +276,6 @@ h6 {
margin: 0;
font-weight: 700;
width: -moz-max-content;
width: max-content;
position: relative;
max-width: 100%;
}
@ -350,3 +350,42 @@ blockquote {
border-left: solid 5px var(--color-super-lowered);
font-style: italic;
}
.skel {
display: block;
border-radius: var(--radius);
background: var(--color-raised);
animation: skel ease-in-out infinite 2s forwards running;
transition: opacity 0.15s;
}
@keyframes skel {
from {
background: var(--color-raised);
}
50% {
background: var(--color-lowered);
}
to {
background: var(--color-raised);
}
}
.loader {
animation: spin linear infinite 2s forwards running;
display: flex;
justify-content: center;
align-items: center;
}
@keyframes spin {
from {
transform: rotateZ(0deg);
}
to {
transform: rotateZ(360deg);
}
}

View file

@ -565,11 +565,9 @@ select:focus {
nav {
background: var(--color-primary);
color: var(--color-text-primary) !important;
color: inherit;
width: 100%;
display: flex;
justify-content: space-between;
color: var(--color-text);
position: sticky;
top: 0;
z-index: 6374;
@ -722,13 +720,12 @@ dialog {
position: fixed;
bottom: 0;
top: 0;
display: flex;
display: none;
background: var(--color-surface);
border: solid 1px var(--color-super-lowered) !important;
border-radius: var(--radius);
max-width: 100%;
border-style: none;
display: none;
margin: auto;
color: var(--color-text);
animation: popin ease-in-out 1 0.1s forwards running;

View file

@ -1,5 +1,23 @@
(div ("id" "toast_zone"))
; templates
(template
("id" "loading_skeleton")
(div
("class" "flex flex-col gap-2")
("ui_ident" "loading_skel")
(div
("class" "card lowered green flex items-center gap-2")
(div ("class" "loader") (icon (text "loader-circle")))
(span (str (text "general:label.loading"))))
(div
("class" "card secondary flex gap-2")
(div ("class" "skel avatar"))
(div
("class" "flex flex-col gap-2 w-full")
(div ("class" "skel") ("style" "width: 25%; height: 25px;"))
(div ("class" "skel") ("style" "width: 100%; height: 150px"))))))
; random js
(text "<script data-turbo-permanent=\"true\" id=\"init-script\">
document.documentElement.addEventListener(\"turbo:load\", () => {

View file

@ -240,7 +240,7 @@
("class" "card lowered red flex items-center gap-2")
(text "{{ icon \"frown\" }}")
(span
(text "Could not find original post...")))
(str (text "general:label.could_not_find_post"))))
(text "{%- endif %} {%- endif %}"))
(text "{{ self::post_media(upload_ids=post.uploads) }} {% else %}")
(details

View file

@ -27,7 +27,7 @@
(text "{{ text \"auth:label.recent_posts\" }}"))
(text "{% else %} {{ icon \"tag\" }}")
(span
(text "{{ text \"auth:label.recent_with_tag\" }}:")
(text "{{ text \"auth:label.recent_with_tag\" }}: ")
(b
(text "{{ tag }}")))
(text "{%- endif %}"))
@ -40,7 +40,13 @@
(text "{{ text \"general:link.search\" }}")))
(text "{%- endif %}"))
(div
("class" "card flex flex-col gap-4")
(text "{% for post in posts %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true, can_manage_post=is_self) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], can_manage_post=is_self, poll=post[5]) }} {%- endif %} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=posts|length, key=\"&tag=\", value=tag) }}")))
("class" "card w-full flex flex-col gap-2")
("ui_ident" "io_data_load")
(div ("ui_ident" "io_data_marker"))))
(script
(text "setTimeout(() => {
trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?user_id={{ profile.id }}&tag={{ tag }}&page=\", Number.parseInt(\"{{ page }}\") - 1]);
});"))
(text "{% endblock %}")

View file

@ -40,9 +40,9 @@
(text "{%- endif %}")))
(div
("class" "card w-full flex flex-col gap-2")
(text "{% if list|length == 0 -%}")
(text "{% if stack.users|length == 0 -%}")
(p
(text "No items yet! Maybe ")
(text "No users included yet! Maybe ")
(a
("href" "/stacks/{{ stack.id }}/manage#/users")
(text "add a user to this stack"))
@ -63,6 +63,7 @@
(div
("class" "flex gap-2 flex-wrap w-full")
(text "{% for user in list %} {{ components::user_plate(user=user, secondary=true) }} {% endfor %}"))
(text "{{ components::pagination(page=page, items=list|length) }}")
(text "{% else %}")
; user icons for circle stack
(text "{% if stack.mode == 'Circle' -%}")
@ -77,14 +78,16 @@
(text "{%- endif %}")
; posts for all stacks except blocklist
(text "{% for post in list %}
{% if post[2].read_access == \"Everybody\" -%}
{% if post[0].context.repost and post[0].context.repost.reposting -%}
{{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true) }}
{% else %}
{{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], poll=post[5]) }}
{%- endif %} {%- endif %} {% endfor %}")
(text "{%- endif %} {{ components::pagination(page=page, items=list|length) }}"))))
(div
("class" "w-full flex flex-col gap-2")
("ui_ident" "io_data_load")
(div ("ui_ident" "io_data_marker")))
(script
(text "setTimeout(() => {
trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?stack_id={{ stack.id }}&page=\", Number.parseInt(\"{{ page }}\") - 1]);
});"))
(text "{%- endif %}"))))
(script
(text "async function block_all(block = true) {

View file

@ -31,12 +31,11 @@
(div
("class" "card w-full flex flex-col gap-2")
("ui_ident" "io_data_load")
(text "{% for post in list %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], poll=post[5]) }} {%- endif %} {%- endif %} {% endfor %}")
(div ("ui_ident" "io_data_marker"))))
(script
(text "setTimeout(() => {
trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=AllPosts&page=\", Number.parseInt(\"{{ page }}\")]);
trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=AllPosts&page=\", Number.parseInt(\"{{ page }}\") - 1]);
});"))
(text "{% endblock %}")

View file

@ -9,12 +9,11 @@
(div
("class" "card w-full flex flex-col gap-2")
("ui_ident" "io_data_load")
(text "{% for post in list %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], poll=post[5]) }} {%- endif %} {%- endif %} {% endfor %}")
(div ("ui_ident" "io_data_marker"))))
(script
(text "setTimeout(() => {
trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=FollowingPosts&page=\", Number.parseInt(\"{{ page }}\")]);
trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=FollowingPosts&page=\", Number.parseInt(\"{{ page }}\") - 1]);
});"))
(text "{% endblock %}")

View file

@ -28,13 +28,12 @@
(div
("class" "card w-full flex flex-col gap-2")
("ui_ident" "io_data_load")
(text "{% for post in list %} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], poll=post[5]) }} {%- endif %} {% endfor %}")
(div ("ui_ident" "io_data_marker")))
(text "{%- endif %}"))
(script
(text "setTimeout(() => {
trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=MyCommunities&page=\", Number.parseInt(\"{{ page }}\")]);
trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=MyCommunities&page=\", Number.parseInt(\"{{ page }}\") - 1]);
});"))
(text "{% endblock %}")

View file

@ -9,12 +9,11 @@
(div
("class" "card w-full flex flex-col gap-2")
("ui_ident" "io_data_load")
(text "{% for post in list %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], poll=post[5]) }} {%- endif %} {%- endif %} {% endfor %}")
(div ("ui_ident" "io_data_marker"))))
(script
(text "setTimeout(() => {
trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=PopularPosts&page=\", Number.parseInt(\"{{ page }}\")]);
trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=PopularPosts&page=\", Number.parseInt(\"{{ page }}\") - 1]);
});"))
(text "{% endblock %}")

View file

@ -20,10 +20,13 @@
("class" "flex items-center gap-2")
(text "{{ icon \"shell\" }}")
(span
(text "That's a wrap!<!-- observer_disconnect_{{ random_cache_breaker }} -->")))
(str (text "general:label.timeline_end"))
(text "<!-- observer_disconnect_{{ random_cache_breaker }} -->")))
(text "{% if page > 0 -%}")
(a
("class" "button")
("href" "?page=0")
(icon (text "arrow-up"))
(str (text "chats:label.go_back"))))
(str (text "chats:label.go_back")))
(text "{%- endif %}"))
(text "{%- endif %}")

View file

@ -25,10 +25,10 @@ function media_theme_pref() {
}
}
function set_theme(theme) {
window.set_theme = (theme) => {
window.localStorage.setItem("tetratto:theme", theme);
document.documentElement.className = theme;
}
};
media_theme_pref();
@ -91,7 +91,7 @@ media_theme_pref();
self.define("rel_date", (_, date) => {
// stolen and slightly modified because js dates suck
const diff = Math.abs((new Date().getTime() - date.getTime()) / 1000);
const diff = Math.abs((Date.now() - date.getTime()) / 1000);
const day_diff = Math.floor(diff / 86400);
if (Number.isNaN(day_diff) || day_diff < 0 || day_diff >= 31) {
@ -396,7 +396,7 @@ media_theme_pref();
counter.innerText = `${target.value.length}/${target.getAttribute("maxlength")}`;
});
self.define("hooks::character_counter.init", (_, event) => {
self.define("hooks::character_counter.init", (_) => {
for (const element of Array.from(
document.querySelectorAll("[hook=counter]") || [],
)) {
@ -413,7 +413,7 @@ media_theme_pref();
element.innerHTML = full_text;
});
self.define("hooks::long_text.init", (_, event) => {
self.define("hooks::long_text.init", (_) => {
for (const element of Array.from(
document.querySelectorAll("[hook=long]") || [],
)) {
@ -493,13 +493,13 @@ media_theme_pref();
});
self.define("last_seen_just_now", (_, last_seen) => {
const now = new Date().getTime();
const now = Date.now();
const maximum_time_to_be_considered_online = 60000 * 2; // 2 minutes
return now - last_seen <= maximum_time_to_be_considered_online;
});
self.define("last_seen_recently", (_, last_seen) => {
const now = new Date().getTime();
const now = Date.now();
const maximum_time_to_be_considered_idle = 60000 * 5; // 5 minutes
return now - last_seen <= maximum_time_to_be_considered_idle;
});
@ -585,8 +585,8 @@ media_theme_pref();
self.define(
"hooks::attach_to_partial",
({ $ }, partial, full, attach, wrapper, page, run_on_load) => {
return new Promise((resolve, reject) => {
({ $ }, partial, full, attach, wrapper, page) => {
return new Promise((resolve, _) => {
async function load_partial() {
const url = `${partial}${partial.includes("?") ? "&" : "?"}page=${page}`;
history.replaceState(
@ -1148,6 +1148,8 @@ ${option.input_element_type === "textarea" ? `${option.value}</textarea>` : ""}
"[ui_ident=io_data_load]",
);
self.IO_HTML_TMPL = document.getElementById("loading_skeleton");
if (!self.IO_DATA_ELEMENT || !self.IO_DATA_MARKER) {
console.warn(
"ui::io_data_load called, but required elements don't exist",
@ -1167,14 +1169,19 @@ ${option.input_element_type === "textarea" ? `${option.value}</textarea>` : ""}
self.IO_DATA_PAGE += 1;
console.log("load page", self.IO_DATA_PAGE);
// show loading component
const loading = self.IO_HTML_TMPL.content.cloneNode(true);
self.IO_DATA_ELEMENT.appendChild(loading);
// ...
const text = await (
await fetch(`${self.IO_DATA_TMPL}${self.IO_DATA_PAGE}`)
).text();
self.IO_DATA_ELEMENT.querySelector("[ui_ident=loading_skel]").remove();
if (
text.includes(
`That's a wrap!<!-- observer_disconnect_${window.BUILD_CODE} -->`,
)
text.includes(`!<!-- observer_disconnect_${window.BUILD_CODE} -->`)
) {
console.log("io_data_end; disconnect");
self.IO_DATA_OBSERVER.disconnect();
@ -1251,7 +1258,7 @@ ${option.input_element_type === "textarea" ? `${option.value}</textarea>` : ""}
self.define(
"open",
async ({ $ }, warning_id, warning_hash, warning_page = "") => {
async (_, warning_id, warning_hash, warning_page = "") => {
// check localStorage for this warning_id
if (accepted_warnings[warning_id] !== undefined) {
// check hash
@ -1272,7 +1279,7 @@ ${option.input_element_type === "textarea" ? `${option.value}</textarea>` : ""}
},
);
self.define("accept", ({ _ }, warning_id, warning_hash) => {
self.define("accept", (_, warning_id, warning_hash) => {
accepted_warnings[warning_id] = warning_hash;
window.localStorage.setItem(