refactor: first pass on getting the client back into working order

(still broken, but loading as a baseline)
This commit is contained in:
Andy Burke 2025-11-08 17:15:26 -08:00
parent a5707e2f81
commit afeb6f75e8
23 changed files with 358 additions and 322 deletions

View file

@ -148,8 +148,8 @@
<div
id="blurbs-list"
data-feed
data-precheck="!!document.body.dataset.user && document.body.dataset.user.indexOf( 'topics.blurbs.read' ) !== -1"
data-source="/api/topics/${ document.body.dataset.topic }/events?type=blurb,reaction&limit=100&sort=newest&wait=true&after_id=${ feed.__newest_id ?? 'able-able-able-able-able-able-able-able-able-able' }"
data-precheck="!!document.body.dataset.user && document.body.dataset.user.indexOf( 'events.read.blurb' ) !== -1"
data-source="/api/events?type=blurb,reaction&limit=100&sort=newest&wait=true&after_id=${ feed.__newest_id ?? 'able-able-able-able-able-able-able-able-able-able' }"
data-longpolling="true"
data-reverse="true"
data-insert="prepend"
@ -159,7 +159,6 @@
{
const feed = document.currentScript.closest("[data-feed]");
APP.on("topic_changed", () => { feed.__reset && feed.__reset(); });
APP.on("user_logged_in", () => { feed.__reset && feed.__reset(); });
feed.__target_element = (item) => {

View file

@ -25,7 +25,7 @@
display: inline-block;
}
</style>
<div class="new-blurb-container" data-requires-permission="topics.blurbs.create">
<div class="new-blurb-container" data-requires-permission="events.create.blurb">
<label>
<input type="checkbox" collapse-toggle />
<i class="icon plus" style="display: inline-block; margin-right: 0.5rem"></i>
@ -33,6 +33,7 @@
</label>
<form
data-smart="true"
action="/api/events"
method="POST"
class="blurb-creation-form collapsible"
style="
@ -40,7 +41,6 @@
width: 100%;
transition: all 0.5s;
"
url="/api/topics/${ document.body.dataset.topic }/events"
on_reply="async (event) => { await document.getElementById( 'blurbs-list' ).__render(event); document.getElementById(event.id)?.classList.remove('sending'); }"
on_parsed="async (event) => { await document.getElementById( 'blurbs-list' ).__render(event); document.getElementById(event.id)?.classList.add('sending'); }"
>

View file

@ -1,3 +1,3 @@
# Calendar
The calendar should help people coordinate events around a topic.
The calendar should help people coordinate events.

View file

@ -0,0 +1,202 @@
<script>
APP.on("channels_updated", ({ channels }) => {
const channel_list = document.getElementById("channel-list");
channel_list.innerHTML = "";
for (const channel of channels.sort((lhs, rhs) => lhs.name.localeCompare(rhs.name))) {
channel_list.insertAdjacentHTML(
"beforeend",
`<li id="channel-selector-${channel.id}" class="channel" data-channel-selector-for="${channel.id}"><a href="#/channel/${channel.id}/chat">${channel.name}</a></li>`,
);
}
});
function update_channel_indicators(event) {
document
.querySelectorAll("[data-channel-selector-for]")
.forEach((element) => element.classList.remove("active"));
const new_channel_id = event?.detail?.channel_id ?? document.body.dataset.channel;
if (new_channel_id) {
document
.querySelectorAll(`[data-channel-selector-for="${new_channel_id}"]`)
.forEach((element) => element.classList.add("active"));
}
for ( const watch of APP.user_watches ) {
// find the channel indicator for this watch
// if there is new stuff - TODO implement a HEAD for getting latest event id?
// add a class of 'new-content'
}
}
APP.on("channels_updated", update_channel_indicators);
APP.on("channel_changed", update_channel_indicators);
APP.on("user_logged_in", update_channel_indicators);
document.addEventListener(
"contextmenu",
(event) => {
if (!event.target?.closest("#sidebar")) {
return;
}
const channel_selector = event.target.closest("li.channel");
if (!channel_selector) {
return;
}
event.preventDefault();
const context_menu = document.getElementById("sidebar-context-menu");
context_menu.dataset.prepare = true;
const position = get_best_coords_for_popup({
target_element: channel_selector,
popup: {
width: context_menu.getBoundingClientRect().width,
height: context_menu.getBoundingClientRect().height,
},
offset: {
x: 4,
y: 4,
},
});
context_menu.style.left = position.x + "px";
context_menu.style.top = position.y + "px";
context_menu.dataset.show = true;
},
false,
);
document.addEventListener("click", (event) => {
if (!event.target?.closest("#sidebar-context-menu")) {
const context_menu = document.getElementById("sidebar-context-menu");
delete context_menu.dataset.show;
delete context_menu.dataset.prepare;
}
});
APP.on( 'view_changed', ( {view} ) => {
if ( !view === 'chat' ) {
return;
}
const sidebar_dynamic_container = document.getElementById( 'sidebar-dynamic-container');
if ( !sidebar_dynamic_container ) {
console.error( 'could not get #sidebar-dynamic-container' );
return;
}
const template = document.getElementById( 'channel-list-template');
sidebar_dynamic_container.innerHTML = template.innerHTML.trim();
APP.CHANNELS.update();
});
</script>
<style>
#channel-list-container #channel-creation-container {
margin-top: 0.5rem;
}
#channel-list-container #channel-creation-container #toggle-channel-creation-form-button {
transform: scale(0.8);
}
#channel-list-container .channel-list {
list-style-type: none;
margin-left: 1rem;
}
#channel-list-container .channel-list > li.channel a:before {
position: absolute;
left: -1.75rem;
top: 0;
font-weight: bold;
font-size: x-large;
content: "#";
color: var(--text);
}
#channel-list-container .channel-list > li.channel a {
position: relative;
display: block;
width: 100%;
min-height: 1.5rem;
line-height: 1.5rem;
font-weight: bold;
font-size: large;
margin-left: 1.75rem;
text-decoration: none;
margin-bottom: 0.75rem;
}
#channel-list-container .channel-list > li.channel.active a {
color: var(--accent);
}
</style>
<template id="channel-list-template">
<div id="channel-list-container">
<div style="margin-bottom: 1rem">
<span class="title">channels</span>
</div>
<ul id="channel-list" class="channel-list"></ul>
<div id="channel-creation-container" data-requires-permission="channels.create">
<button
id="toggle-channel-creation-form-button"
onclick="((event) => {
event.preventDefault();
const channel_create_form = document.getElementById( 'channel-create' );
channel_create_form.style[ 'height' ] = channel_create_form.style[ 'height' ] === '5rem' ? '0' : '5rem';
})(event)"
>
<div class="icon plus"></div>
</button>
<form
id="channel-create"
data-smart="true"
action="/api/channels"
method="POST"
style="
margin-top: 1rem;
width: 100%;
overflow: hidden;
height: 0;
overflow: hidden;
transition: all 0.5s;
"
>
<input
id="new-channel-name-input"
type="text"
name="name"
value=""
placeholder="new channel"
/>
<input type="submit" hidden />
<script>
{
const form = document.currentScript.closest("form");
const channel_create_form = document.getElementById("channel-create");
const new_channel_name_input =
document.getElementById("new-channel-name-input");
form.on_reply = (new_channel) => {
const channel_list = document.getElementById("channel-list");
channel_list.querySelectorAll( 'li' ).forEach( (li) => li.classList.remove( 'active' ) );
channel_list.insertAdjacentHTML(
"beforeend",
`<li id="channel-selector-${new_channel.id}" class="channel active"><a href="#/chat/channel/${new_channel.id}">${new_channel.name}</a></li>`,
);
new_channel_name_input.value = "";
window.location.hash = `/chat/channel/${new_channel.id}`;
channel_create_form.style["height"] = "0";
};
}
</script>
</form>
</div>
</div>
</template>

View file

@ -21,8 +21,8 @@
<div
id="chat-content"
data-feed
data-precheck="!!document.body.dataset.user && document.body.dataset.user.indexOf( 'topics.chat.read' ) !== -1"
data-source="/api/topics/${ document.body.dataset.topic }/events?type=chat,reaction&limit=100&sort=newest&wait=true&after_id=${ feed.__newest_id ?? 'able-able-able-able-able-able-able-able-able-able' }"
data-precheck="!!document.body.dataset.user && document.body.dataset.user.indexOf( 'events.read.chat' ) !== -1"
data-source="/api/events?type=chat,reaction&channel=${ document.body.dataset.channel }&limit=100&sort=newest&wait=true&after_id=${ feed.__newest_id ?? 'able-able-able-able-able-able-able-able-able-able' }"
data-longpolling="true"
data-reverse="true"
data-insert="append"
@ -32,7 +32,7 @@
{
const feed = document.currentScript.closest("[data-feed]");
APP.on("topic_changed", () => { feed.__reset && feed.__reset(); });
APP.on("channel_changed", () => { feed.__reset && feed.__reset(); });
APP.on("user_logged_in", () => { feed.__reset && feed.__reset(); });
const time_tick_tock_timeout = 60_000;
@ -147,7 +147,8 @@
<form
id="chat-entry"
data-smart="true"
data-requires-permission="topics.chat.write"
action="/api/events"
data-requires-permission="events.write.chat"
method="POST"
class="post-creation-form collapsible"
style="
@ -158,15 +159,6 @@
on_reply="async (event) => { await document.getElementById( 'chat-content' ).__render(event); document.getElementById(event.id)?.classList.remove('sending'); }"
on_parsed="async (event) => { await document.getElementById( 'chat-content' ).__render(event); document.getElementById(event.id)?.classList.add('sending'); }"
>
<script>
{
const form = document.currentScript.closest("form");
APP.on( "topic_changed", ({ topic_id }) => {
form.action = topic_id ? `/api/topics/${topic_id}/events` : "";
});
}
</script>
<input type="hidden" name="type" value="chat" />
<input
@ -188,6 +180,12 @@
generator="() => { return APP.user?.id; }"
/>
<input
type="hidden"
name="channel"
generator="() => { return document.body.dataset.channel; }"
/>
<input
type="hidden"
name="timestamps.created"
@ -233,3 +231,4 @@
</div>
</div>
</div>
<!-- #include file="./channel_sidebar.html" -->

View file

@ -115,8 +115,8 @@
<div
id="essays-list"
data-feed
data-precheck="!!document.body.dataset.user && document.body.dataset.user.indexOf( 'topics.essays.read' ) !== -1"
data-source="/api/topics/${ document.body.dataset.topic }/events?type=essay,reaction&limit=100&sort=newest&wait=true&after_id=${ feed.__newest_id ?? 'able-able-able-able-able-able-able-able-able-able' }"
data-precheck="!!document.body.dataset.user && document.body.dataset.user.indexOf( 'events.read.essay' ) !== -1"
data-source="/api/events?type=essay,reaction&limit=100&sort=newest&wait=true&after_id=${ feed.__newest_id ?? 'able-able-able-able-able-able-able-able-able-able' }"
data-longpolling="true"
data-reverse="true"
data-insert="prepend"
@ -126,7 +126,6 @@
{
const feed = document.currentScript.closest("[data-feed]");
APP.on("topic_changed", () => { feed.__reset && feed.__reset(); });
APP.on("user_logged_in", () => { feed.__reset && feed.__reset(); });
feed.__target_element = (item) => {

View file

@ -22,7 +22,7 @@
display: inline-block;
}
</style>
<div class="new-essay-container" data-requires-permission="topics.essays.create">
<div class="new-essay-container" data-requires-permission="events.create.essay">
<label>
<input type="checkbox" collapse-toggle />
<i class="icon plus" style="display: inline-block; margin-right: 0.5rem"></i>
@ -31,8 +31,8 @@
<form
data-smart="true"
method="POST"
action="/api/events"
class="essay-creation-form collapsible"
url="/api/topics/${ document.body.dataset.topic }/events"
style="
margin-top: 1rem
width: 100%;

View file

@ -145,8 +145,8 @@
<div
id="posts-list"
data-feed
data-precheck="!!document.body.dataset.user && document.body.dataset.user.indexOf( 'topics.essays.read' ) !== -1"
data-source="/api/topics/${ document.body.dataset.topic }/events?type=post,reaction&limit=100&sort=newest&wait=true&after_id=${ feed.__newest_id ?? 'able-able-able-able-able-able-able-able-able-able' }"
data-precheck="!!document.body.dataset.user && document.body.dataset.user.indexOf( 'events.read.post' ) !== -1"
data-source="/api/events?type=post,reaction&limit=100&sort=newest&wait=true&after_id=${ feed.__newest_id ?? 'able-able-able-able-able-able-able-able-able-able' }"
data-longpolling="true"
data-reverse="true"
data-insert="prepend"
@ -156,7 +156,6 @@
{
const feed = document.currentScript.closest("[data-feed]");
APP.on("topic_changed", () => { feed.__reset && feed.__reset(); });
APP.on("user_logged_in", () => { feed.__reset && feed.__reset(); });
feed.__target_element = (item) => {

View file

@ -6,15 +6,15 @@
</label>
<form
data-smart="true"
data-requires-permission="topics.posts.create"
data-requires-permission="events.create.post"
method="POST"
action="/api/events"
class="post-creation-form collapsible"
style="
margin-top: 1rem
width: 100%;
transition: all 0.5s;
"
url="/api/topics/${ document.body.dataset.topic }/events"
on_reply="async (event) => { await document.getElementById( 'posts-list' ).__render(event); document.getElementById(event.id)?.classList.remove('sending'); }"
on_parsed="async (event) => { await document.getElementById( 'posts-list' ).__render(event); document.getElementById(event.id)?.classList.add('sending'); }"
>

View file

@ -1,3 +1,3 @@
# Resources
Resources should be a wiki for organizing community knowledge on a topic.
Resources should be a wiki for organizing community knowledge.

View file

@ -14,7 +14,7 @@
const tab_selector = event.target;
const view = tab_selector.dataset.view;
if (view) {
window.location.hash = `/topic/${document.body.dataset.topic}/${view}`;
window.location.hash = `/${view}${ document.body.dataset.channel ? `/channel/${ document.body.dataset.channel }` : '' }`;
}
});
}