add: infinitely scrolling timelines

This commit is contained in:
trisua 2025-06-17 01:52:17 -04:00
parent 822aaed0c8
commit 2b253c811c
12 changed files with 316 additions and 9 deletions

View file

@ -117,7 +117,7 @@
(text "{{ self::post(post=post, owner=owner, secondary=secondary, community=community, show_community=show_community, can_manage_post=can_manage_post, repost=repost, expect_repost=true) }}"))
(text "{%- endmacro %} {% macro post(post, owner, question=false, secondary=false, community=false, show_community=true, can_manage_post=false, repost=false, expect_repost=false, poll=false, dont_show_title=false) -%} {% if community and show_community and community.id != config.town_square or question %}")
(div
("class" "card-nest")
("class" "card-nest post_outer:{{ post.id }}")
(text "{% if question -%} {{ self::question(question=question[0], owner=question[1], profile=owner) }} {% else %}")
(div
("class" "card small")
@ -130,8 +130,7 @@
(text "{% if post.context.is_pinned or post.context.is_profile_pinned -%} {{ icon \"pin\" }} {%- endif %}")))
(text "{%- endif %} {%- endif %}")
(div
("class" "card flex flex-col gap-2 {% if secondary -%}secondary{%- endif %}")
("id" "post:{{ post.id }}")
("class" "card flex flex-col gap-2 post:{{ post.id }} {% if secondary -%}secondary{%- endif %}")
("data-community" "{{ post.community }}")
("data-ownsup" "{{ owner.permissions|has_supporter }}")
("hook" "verify_emojis")

View file

@ -35,6 +35,7 @@
};
globalThis.no_policy = false;
globalThis.BUILD_CODE = \"{{ random_cache_breaker }}\";
</script>")
(script ("src" "/js/loader.js" ))

View file

@ -30,6 +30,13 @@
(text "{%- endif %}")
(div
("class" "card w-full flex flex-col gap-2")
(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 %} {{ components::pagination(page=page, items=list|length) }}")))
("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 }}\")]);
});"))
(text "{% endblock %}")

View file

@ -8,6 +8,13 @@
(text "{{ macros::timelines_nav(selected=\"following\", posts=\"/following\", questions=\"/following/questions\") }}")
(div
("class" "card w-full flex flex-col gap-2")
(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 %} {{ components::pagination(page=page, items=list|length) }}")))
("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 }}\")]);
});"))
(text "{% endblock %}")

View file

@ -27,7 +27,14 @@
(text "{% else %}")
(div
("class" "card w-full flex flex-col gap-2")
(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 %} {{ components::pagination(page=page, items=list|length) }}"))
("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 }}\")]);
});"))
(text "{% endblock %}")

View file

@ -8,6 +8,13 @@
(text "{{ macros::timelines_nav(selected=\"popular\", posts=\"/popular\", questions=\"/popular/questions\") }}")
(div
("class" "card w-full flex flex-col gap-2")
(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 %} {{ components::pagination(page=page, items=list|length) }}")))
("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 }}\")]);
});"))
(text "{% endblock %}")

View file

@ -0,0 +1,29 @@
(text "{%- import \"components.html\" as components -%} {%- import \"macros.html\" as macros -%}")
(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 %}")
(datalist
("ui_ident" "list_posts_{{ page }}")
(text "{% for post in list -%}")
(option ("value" "{{ post[0].id }}"))
(text "{%- endfor %}"))
(text "{% if list|length == 0 -%}")
(div
("class" "card lowered green flex justify-between items-center gap-2")
(div
("class" "flex items-center gap-2")
(text "{{ icon \"shell\" }}")
(span
(text "That's a wrap!<!-- observer_disconnect_{{ random_cache_breaker }} -->")))
(a
("class" "button")
("href" "?page=0")
(icon (text "arrow-up"))
(str (text "chats:label.go_back"))))
(text "{%- endif %}")

View file

@ -1119,6 +1119,127 @@ ${option.input_element_type === "textarea" ? `${option.value}</textarea>` : ""}
document.getElementById("lightbox").classList.add("hidden");
}, 250);
});
// intersection observer infinite scrolling
self.IO_DATA_OBSERVER = new IntersectionObserver(
async (entries) => {
for (const entry of entries) {
if (!entry.isIntersecting) {
continue;
}
await self.io_load_data();
break;
}
},
{
root: document.body,
rootMargin: "0px",
threshold: 1,
},
);
self.define("io_data_load", (_, tmpl, page) => {
self.IO_DATA_MARKER = document.querySelector(
"[ui_ident=io_data_marker]",
);
self.IO_DATA_ELEMENT = document.querySelector(
"[ui_ident=io_data_load]",
);
if (!self.IO_DATA_ELEMENT || !self.IO_DATA_MARKER) {
console.warn(
"ui::io_data_load called, but required elements don't exist",
);
return;
}
self.IO_DATA_TMPL = tmpl;
self.IO_DATA_PAGE = page;
self.IO_DATA_SEEN_IDS = [];
self.IO_DATA_OBSERVER.observe(self.IO_DATA_MARKER);
});
self.define("io_load_data", async () => {
self.IO_DATA_PAGE += 1;
console.log("load page", self.IO_DATA_PAGE);
const text = await (
await fetch(`${self.IO_DATA_TMPL}${self.IO_DATA_PAGE}`)
).text();
if (
text.includes(
`That's a wrap!<!-- observer_disconnect_${window.BUILD_CODE} -->`,
)
) {
console.log("io_data_end; disconnect");
self.IO_DATA_OBSERVER.disconnect();
self.IO_DATA_ELEMENT.innerHTML += text;
return;
}
self.IO_DATA_ELEMENT.innerHTML += text;
setTimeout(() => {
// move marker to bottom of dom hierarchy
self.IO_DATA_ELEMENT.children[
self.IO_DATA_ELEMENT.children.length - 1
].after(self.IO_DATA_MARKER);
// remove posts we've already seen
function remove_elements(id, outer = false) {
let idx = 0;
for (const element of Array.from(
document.querySelectorAll(
`.post${outer ? "_outer" : ""}\\:${id}`,
),
)) {
if (idx === 0) {
idx += 1;
continue;
}
// everything that isn't the first element should be removed
element.remove();
console.log("removed duplicate post");
}
}
for (const id of self.IO_DATA_SEEN_IDS) {
remove_elements(id, false);
remove_elements(id, true); // scoop up questions
}
// push ids
for (const opt of Array.from(
document.querySelectorAll(
`[ui_ident=list_posts_${self.IO_DATA_PAGE}] option`,
),
)) {
const v = opt.getAttribute("value");
if (!self.IO_DATA_SEEN_IDS[v]) {
self.IO_DATA_SEEN_IDS.push(v);
}
}
}, 150);
// run hooks
const atto = ns("atto");
atto.clean_date_codes();
atto.clean_poll_date_codes();
atto.link_filter();
atto["hooks::long_text.init"]();
atto["hooks::alt"]();
atto["hooks::online_indicator"]();
atto["hooks::verify_emoji"]();
});
})();
(() => {