diff --git a/public/api/topics/:topic_id/events/index.ts b/public/api/topics/:topic_id/events/index.ts index 58d9fea..887427e 100644 --- a/public/api/topics/:topic_id/events/index.ts +++ b/public/api/topics/:topic_id/events/index.ts @@ -53,7 +53,8 @@ export async function GET(request: Request, meta: Record): Promise< const options: FSDB_SEARCH_OPTIONS = { ...(meta.query ?? {}), - limit: Math.min(parseInt(meta.query?.limit ?? '10'), 1_000), + limit: Math.min(parseInt(meta.query?.limit ?? '10', 10), 1_000), + offset: Math.max(parseInt(meta.query?.offset ?? '0', 10), 0), sort, filter: (entry: WALK_ENTRY) => { const { @@ -63,13 +64,11 @@ export async function GET(request: Request, meta: Record): Promise< } } = /^.*\/events\/(?.*?)\/.*\/(?[A-Za-z-]+)\.json$/.exec(entry.path) ?? { groups: {} }; - const id = `${event_type}:${event_id}`; - - if (meta.query.after_id && id <= meta.query.after_id) { + if (meta.query.after_id && event_id <= meta.query.after_id) { return false; } - if (meta.query.before_id && id >= meta.query.before_id) { + if (meta.query.before_id && event_id >= meta.query.before_id) { return false; } @@ -93,7 +92,7 @@ export async function GET(request: Request, meta: Record): Promise< if (results.length === 0 && meta.query.wait) { return new Promise((resolve, reject) => { function on_create(create_event: any) { - if (meta.query.type && create_event.item.type !== meta.query.type) { + if (meta.query.type && !meta.query.type.split(',').includes(create_event.item.type)) { return; } diff --git a/public/index.html b/public/index.html index c3f9a24..bda0418 100644 --- a/public/index.html +++ b/public/index.html @@ -11,8 +11,8 @@ + - @@ -29,6 +29,7 @@ + diff --git a/public/js/_utils.js b/public/js/_utils.js new file mode 100644 index 0000000..216165a --- /dev/null +++ b/public/js/_utils.js @@ -0,0 +1,40 @@ +function get_best_coords_for_popup(_options) { + const viewport_width = document.body.getBoundingClientRect().width; + const viewport_height = document.body.getBoundingClientRect().height; + + const options = { + offset: { + x: 0, + y: 0, + }, + popup: { + width: 200, + height: 200, + }, + ..._options, + }; + + const target = options.target ?? { + x: + options.target_element?.getBoundingClientRect().left ?? + viewport_width / 2 - options.popup.width / 2, + y: + options.target_element?.getBoundingClientRect().top ?? + viewport_height / 2 - options.popup.height / 2, + }; + + const best_coords = { + x: target.x + options.offset.x, + y: target.y + options.offset.y, + }; + + if (target.x + options.offset.x + options.popup.width + options.offset.x > viewport_width) { + best_coords.x = Math.max(0, target.x - options.popup.width + options.offset.x); + } + + if (target.y + options.offset.y + options.popup.height + options.offset.y > viewport_height) { + best_coords.y = Math.max(0, target.y - options.popup.height + options.offset.y); + } + + return best_coords; +} diff --git a/public/js/eventactions.js b/public/js/eventactions.js new file mode 100644 index 0000000..e957342 --- /dev/null +++ b/public/js/eventactions.js @@ -0,0 +1,134 @@ +const event_actions_popup_width = 100; +const event_actions_popup_height = 260; + +const event_actions_popup_styling = ` +#eventactionspopup { + position: fixed; + max-width: 90%; + overflow-x: auto; + z-index: 100; + background: inherit; + overflow: hidden; + border: 1px solid var(--border-normal); + padding: 0.5rem; +} + +#eventactionspopup .icon.close { + float: right; + margin: 0.1rem; +} + +#eventactionspopup button.event-action { + display: block; + cursor: pointer; + width: 3rem; + height: 3rem; + vertical-align: top; + margin: 0.5rem; +} + +#eventactionspopup button .action-name { + display: block; + margin-top: 0.33rem; + text-transform: uppercase; + font-size: xx-small; +} +`; + +let event_actions_popup; +let event_actions_popup_form; +let event_actions_popup_search_input; +let event_actions_popup_parent_id_input; +let event_actions_popup_emojis_list; +let event_actions_popup_reaction_input; + +function open_event_actions_popup(event) { + const event_id = event.target?.closest("[data-event_id]")?.dataset?.event_id; + + const position = get_best_coords_for_popup({ + target_element: event.target, + popup: { + width: event_actions_popup_width, + height: event_actions_popup_height, + }, + offset: { + x: 4, + y: 4, + }, + }); + + event_actions_popup.dataset.current_event_id = event_id; + event_actions_popup.style.left = position.x + "px"; + event_actions_popup.style.top = position.y + "px"; + + event_actions_popup.style.visibility = "visible"; + event_actions_popup.style.opacity = "1"; + event_actions_popup.style.display = "block"; +} + +function clear_event_actions_popup() { + if (!event_actions_popup) { + return; + } + + event_actions_popup.style.visibility = "hidden"; + event_actions_popup.style.opacity = "0"; + event_actions_popup.style.display = "none"; +} + +document.addEventListener("DOMContentLoaded", () => { + if (!document.getElementById("event-actions-styling")) { + const style = document.createElement("style"); + style.id = "event-actions-styling"; + style.innerHTML = event_actions_popup_styling; + document.head.appendChild(style); + } + + event_actions_popup = document.createElement("div"); + event_actions_popup.id = "eventactionspopup"; + event_actions_popup.innerHTML = ` +
+ + + + +
+`; + + document.body.appendChild(event_actions_popup); + + document.querySelector("body").addEventListener("click", (event) => { + const is_in_the_event_actions_popup = event?.target?.closest("#eventactionspopup"); + if (is_in_the_event_actions_popup) { + return; + } + + const is_an_event_actions_button = + event?.target?.matches("button[commandfor]") || + event?.target?.closest("button[commandfor]"); + if (!is_an_event_actions_button) { + clear_event_actions_popup(); + return; + } + + event.preventDefault(); + open_event_actions_popup(event); + }); +}); diff --git a/public/js/reactions.js b/public/js/reactions.js index 51e9080..e46d664 100644 --- a/public/js/reactions.js +++ b/public/js/reactions.js @@ -54,44 +54,50 @@ const reactions_popup_styling = ` background: inherit; } -`; - -function get_best_coords_for_popup(target, offset = { x: 10, y: 10 }) { - const target_x = target?.getBoundingClientRect().left ?? 0; - const target_y = target?.getBoundingClientRect().top ?? 0; - - const viewport_width = document.body.getBoundingClientRect().width; - const viewport_height = document.body.getBoundingClientRect().height; - - const best_coords = { - x: target_x + offset.x, - y: target_y + offset.y, - }; - - if (target_x + offset.x + reactions_popup_width + offset.x > viewport_width) { - best_coords.x = Math.max(0, target_x - reactions_popup_width); - } - - if (target_y + offset.y + reactions_popup_height + offset.y > viewport_height) { - best_coords.y = Math.max(0, target_y - reactions_popup_height); - } - - return best_coords; +.reaction-container { + display: inline-block; + border: 1px solid var(--border-subtle); + border-radius: var(--border-radius); + margin-right: 0.5rem; + padding: 0.25rem; + font-size: large; } +@media screen and (max-width: 480px) { + #reactionspopup { + left: 0 !important; + right: 0 !important; + bottom: 0 !important; + top: auto !important; + width: 100% !important; + } +} + + +`; + let reactions_popup; let reactions_popup_form; +let reactions_popup_search_input; let reactions_popup_parent_id_input; let reactions_popup_emojis_list; let reactions_popup_reaction_input; function open_reactions_popup(event) { - const parent_event_id = event.target?.closest("[data-event_id]")?.dataset?.event_id; + const parent_event_id = + event.target?.closest("[data-current_event_id]")?.dataset?.current_event_id; reactions_popup_parent_id_input.value = parent_event_id ?? ""; - const position = get_best_coords_for_popup(event.target.closest("[data-reactions]"), { - x: 25, - y: 25, + const position = get_best_coords_for_popup({ + target_element: event.target.closest("[data-reactions]"), + popup: { + width: reactions_popup_width, + height: reactions_popup_height, + }, + offset: { + x: 25, + y: 25, + }, }); reactions_popup.style.left = position.x + "px"; @@ -100,6 +106,8 @@ function open_reactions_popup(event) { reactions_popup.style.visibility = "visible"; reactions_popup.style.opacity = "1"; reactions_popup.style.display = "block"; + + reactions_popup_search_input.focus(); } function clear_reactions_popup() { @@ -129,8 +137,8 @@ document.addEventListener("DOMContentLoaded", () => { id="reactions-selection-form" data-smart="true" method="POST" - on_reply="async (event) => { await document.querySelectorAll( '[data-feed]' ).forEach((feed) => feed.__render(event)); }" - on_parsed="async (event) => { await document.querySelectorAll( '[data-feed]' ).forEach((feed) => feed.__render(event)); }" + on_reply="async (event) => { clear_reactions_popup(); await document.querySelectorAll( '[data-feed]' ).forEach((feed) => feed.__render(event)); }" + on_parsed="async (event) => { clear_reactions_popup(); await document.querySelectorAll( '[data-feed]' ).forEach((feed) => feed.__render(event)); }" > @@ -193,6 +201,7 @@ document.addEventListener("DOMContentLoaded", () => { : ""; }); + reactions_popup_search_input = document.getElementById("reactions-search-input"); reactions_popup_parent_id_input = reactions_popup_form.querySelector('[name="parent_id"]'); reactions_popup_emojis_list = document.getElementById("reactions-emojis-list"); reactions_popup_reaction_input = reactions_popup_form.querySelector('[name="data.reaction"]'); @@ -216,14 +225,14 @@ document.addEventListener("DOMContentLoaded", () => { : ""; }); - const reactions_popup_search = debounce((event) => { + const debounced_search = debounce((event) => { const prompt = event.target?.value; const filtered = EMOJIS.autocomplete(prompt); - delete emojis_list.dataset.filtered; + delete reactions_popup_emojis_list.dataset.filtered; if (filtered.length) { - emojis_list.dataset.filtered = true; - emojis_list.querySelectorAll("li").forEach((li) => { + reactions_popup_emojis_list.dataset.filtered = true; + reactions_popup_emojis_list.querySelectorAll("li").forEach((li) => { if (filtered.some((entry) => entry[0] === li.dataset.emoji)) { li.dataset.filtered = true; } else { @@ -233,17 +242,16 @@ document.addEventListener("DOMContentLoaded", () => { } }, 200); - document - .getElementById("reactions-search-input") - .addEventListener("input", reactions_popup_search); - document - .getElementById("reactions-search-input") - .addEventListener("paste", reactions_popup_search); - document - .getElementById("reactions-search-input") - .addEventListener("change", reactions_popup_search); + reactions_popup_search_input.addEventListener("input", debounced_search); + reactions_popup_search_input.addEventListener("paste", debounced_search); + reactions_popup_search_input.addEventListener("change", debounced_search); document.querySelector("body").addEventListener("click", (event) => { + const is_in_the_reactions_form = event?.target?.closest("#reactionspopup"); + if (is_in_the_reactions_form) { + return; + } + const is_a_data_reactions_child = event?.target?.closest("[data-reactions]"); if (!is_a_data_reactions_child) { clear_reactions_popup(); diff --git a/public/js/smartfeeds.js b/public/js/smartfeeds.js index 387053a..8dea899 100644 --- a/public/js/smartfeeds.js +++ b/public/js/smartfeeds.js @@ -14,6 +14,10 @@ function smarten_feeds() { continue; } + feed.__started = false; + feed.__newest_id = undefined; + feed.__oldest_id = undefined; + feed.__templates = feed .querySelectorAll("template[data-for_type]") .values() @@ -76,6 +80,20 @@ function smarten_feeds() { feed.__autoscroll_debounce_timeout = undefined; feed.__render = async (item) => { + const [item_type, item_id] = item.id?.split(":", 2) ?? []; + + feed.__newest_id = + typeof item_id === "string" && item_id > (feed.__newest_id ?? "") + ? item_id + : feed.__newest_id; + feed.__oldest_id = + typeof item_id === "string" && + item_id < + (feed.__oldest_id ?? + "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz") + ? item_id + : feed.__oldest_id; + const template = feed.__templates[item.type]; if (!template) { return; @@ -96,18 +114,6 @@ function smarten_feeds() { `[data-temp_id='${item.temp_id ?? item.meta?.temp_id ?? ""}']`, ); - feed.__newest_id = - typeof item.id === "string" && item.id > (feed.__newest_id ?? "") - ? item.id - : feed.__newest_id; - feed.__oldest_id = - typeof item.id === "string" && - item.id < - (feed.__oldest_id ?? - "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz") - ? item.id - : feed.__oldest_id; - if (existing_element) { if ( existing_element.id !== item.id || diff --git a/public/tabs/blurbs/blurbs.html b/public/tabs/blurbs/blurbs.html index ecb4098..cba386b 100644 --- a/public/tabs/blurbs/blurbs.html +++ b/public/tabs/blurbs/blurbs.html @@ -106,6 +106,13 @@ margin: 1rem 0; } + .blurb-container button[commandfor="eventactionspopover"] { + position: absolute; + bottom: 0.25rem; + right: 0.25rem; + border: none; + } + .blurb-container .new-blurb-container { grid-area: newblurb; display: none; @@ -142,7 +149,7 @@ 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&limit=100&sort=newest&wait=true&after_id=${ feed.__newest_id ?? 'blurb:able-able-able-able-able-able-able-able-able-able' }" + 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-longpolling="true" data-reverse="true" data-insert="prepend" @@ -162,11 +169,31 @@ ) ?? feed ); }; + feed.__target_element = (item) => { + let target = feed; + switch (item.type) { + case "reaction": + target = document.querySelector( + `.blurb-container[data-event_id='${item.parent_id}'] > .reactions-container`, + ); + break; + case "blurb": + default: + target = + document.querySelector( + `.blurb-container[data-event_id='${item.parent_id}'] > .replies-container`, + ) ?? feed; + break; + } + + return target; + }; feed.__context = async (item) => { const blurb_datetime = datetime_to_local(item.timestamps.created); return { + event: item, blurb: item, creator: await USERS.get(item.creator_id), blurb_datetime @@ -199,10 +226,23 @@
${htmlify(md_to_html(context.blurb.data.blurb))}
+
+
+ diff --git a/public/tabs/chat/chat.css b/public/tabs/chat/chat.css index 1ceeec4..3c553cb 100644 --- a/public/tabs/chat/chat.css +++ b/public/tabs/chat/chat.css @@ -30,14 +30,8 @@ } #chat #chat-entry-container form { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; display: flex; flex-direction: row; - padding: 0.75rem; } #chat #chat-entry-container form input[type="file"] { @@ -144,16 +138,12 @@ opacity: 0.75; } -#chat .message-container .message-actions-container { +#chat .message-container button[commandfor] { position: absolute; top: 0.1rem; right: 0.1rem; - display: flex; - flex-direction: row-reverse; - border: 1px solid; - border-color: rgba(0, 0, 0, 0); - border-radius: var(--border-radius); color: rgb(from var(--text) r g b / 0.7); + border: none; z-index: 10; } @@ -237,6 +227,7 @@ #chat .message-container .message-media-container, #chat .message-container .reactions-container { padding-left: 8rem; + overflow-x: auto; } #chat .message-container .reactions-container:has(> .reaction-container) { @@ -244,15 +235,6 @@ margin-bottom: 0.25rem; } -#chat .message-container .reactions-container .reaction-container { - display: inline-block; - border: 1px solid var(--border-subtle); - border-radius: var(--border-radius); - margin-right: 0.5rem; - padding: 0.25rem; - font-size: large; -} - #chat .embed-container { position: relative; width: 100%; diff --git a/public/tabs/chat/chat.html b/public/tabs/chat/chat.html index 215d70d..84427b8 100644 --- a/public/tabs/chat/chat.html +++ b/public/tabs/chat/chat.html @@ -22,7 +22,7 @@ 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 ?? 'chat:able-able-able-able-able-able-able-able-able-able' }" + 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-longpolling="true" data-reverse="true" data-insert="append" @@ -107,42 +107,7 @@ data-creator_id="${context.creator.id}" data-temp_id="${context.event.meta?.temp_id ?? ''}" > -
- - - - - - - -
+
{ - return ( - document.querySelector( - `.essay-container[data-essay_id='${item.parent_id}'] > .replies-container`, - ) ?? feed - ); + let target = feed; + switch (item.type) { + case "reaction": + target = document.querySelector( + `.essay-container[data-event_id='${item.parent_id}'] > .reactions-container`, + ); + break; + case "essay": + default: + target = feed; + break; + } + + return target; }; feed.__context = async (item) => { const essay_datetime = datetime_to_local(item.timestamps.created); return { + event: item, essay: item, creator: await USERS.get(item.creator_id), essay_datetime, @@ -180,6 +197,19 @@
${htmlify(md_to_html(context.essay.data.essay))}
+
+ +
+ +
diff --git a/public/tabs/forum/forum.html b/public/tabs/forum/forum.html index b64ca81..0f393ea 100644 --- a/public/tabs/forum/forum.html +++ b/public/tabs/forum/forum.html @@ -14,7 +14,7 @@ ". . content" ". . newpost" ". . replies"; - max-height: 6rem; + max-height: 8rem; padding: 1rem; border: 1px solid var(--border-subtle); overflow-y: hidden; @@ -111,6 +111,13 @@ margin-top: 2rem; } + .post-container button[commandfor="eventactionspopover"] { + position: absolute; + bottom: 0.25rem; + right: 0.25rem; + border: none; + } + .post-container .new-post-container { grid-area: newpost; } @@ -139,7 +146,7 @@ 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&limit=100&sort=newest&wait=true&after_id=${ feed.__newest_id ?? 'post:able-able-able-able-able-able-able-able-able-able' }" + 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-longpolling="true" data-reverse="true" data-insert="prepend" @@ -157,17 +164,30 @@ }); feed.__target_element = (item) => { - return ( - document.querySelector( - `.post-container[data-post_id='${item.parent_id}'] > .replies-container`, - ) ?? feed - ); + let target = feed; + switch (item.type) { + case "reaction": + target = document.querySelector( + `.post-container[data-event_id='${item.parent_id}'] > .reactions-container`, + ); + break; + case "chat": + default: + target = + document.querySelector( + `.post-container[data-event_id='${item.parent_id}'] > .replies-container`, + ) ?? feed; + break; + } + + return target; }; feed.__context = async (item) => { const post_datetime = datetime_to_local(item.timestamps.created); return { + event: item, post: item, creator: await USERS.get(item.creator_id), post_datetime, @@ -215,10 +235,23 @@
${htmlify(md_to_html(context.post.data.content))}
+
+
+