autonomous.contact/public/js/reactions.js
2025-10-14 22:20:54 -07:00

256 lines
6.9 KiB
JavaScript

const reactions_popup_width = 280;
const reactions_popup_height = 280;
const reactions_popup_styling = `
#reactionspopup {
position: fixed;
width: ${reactions_popup_width}px;
height: ${reactions_popup_height}px;
z-index: 100;
background: inherit;
overflow: hidden;
border: 1px solid var(--border-normal);
padding: 0.5rem;
text-align: center;
}
#reactionspopup .icon.close {
float: right;
margin: 0.5rem;
}
#reactionspopup input[name="search"] {
width: 80%;
}
#reactionspopup ul {
margin-top: 0.5rem;
padding: 0.5rem;
text-align: left;
overflow: scroll;
}
#reactionspopup ul li {
display: inline-block;
cursor: pointer;
}
#reactionspopup ul[data-filtered] li {
display: none;
visibility: hidden;
}
#reactionspopup ul[data-filtered] li[data-filtered] {
display: inline-block;
visibility: visible;
}
#reactionspopup #reactions-names-display {
position: absolute;
left: 0;
bottom: 0;
right: 0;
height: 2rem;
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;
}
let reactions_popup;
let reactions_popup_form;
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;
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,
});
reactions_popup.style.left = position.x + "px";
reactions_popup.style.top = position.y + "px";
reactions_popup.style.visibility = "visible";
reactions_popup.style.opacity = "1";
reactions_popup.style.display = "block";
}
function clear_reactions_popup() {
if (!reactions_popup) {
return;
}
reactions_popup.style.visibility = "hidden";
reactions_popup.style.opacity = "0";
reactions_popup.style.display = "none";
}
document.addEventListener("DOMContentLoaded", () => {
if (!document.getElementById("reactions-styling")) {
const style = document.createElement("style");
style.id = "reactions-styling";
style.innerHTML = reactions_popup_styling;
document.head.appendChild(style);
}
reactions_popup = document.createElement("div");
reactions_popup.id = "reactionspopup";
reactions_popup.innerHTML = `
<div class="icon close" onclick="clear_reactions_popup()"></div>
<form
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)); }"
>
<input id="reactions-search-input" name="search" type="text" placeholder="Search..." data-skip="true" />
<input type="hidden" name="type" value="reaction" />
<input
type="hidden"
name="id"
generator="(_input, form) => 'TEMP-' + form.__submitted_at.toISOString()"
reset-on-submit
/>
<input
type="hidden"
name="meta.temp_id"
generator="(_input, form) => 'TEMP-' + form.__submitted_at.toISOString()"
reset-on-submit
/>
<input
type="hidden"
name="creator_id"
generator="() => { return JSON.parse( document.body.dataset.user ?? '{}' ).id; }"
/>
<input
type="hidden"
name="timestamps.created"
generator="(_input, form) => form.__submitted_at.toISOString()"
reset-on-submit
/>
<input
type="hidden"
name="timestamps.updated"
generator="(_input, form) => form.__submitted_at.toISOString()"
reset-on-submit
/>
<input type="hidden" name="parent_id" reset-on-submit />
<input type="hidden" name="data.reaction" reset-on-submit />
<ul id="reactions-emojis-list">
${Object.keys(EMOJIS.MAP)
.map(
(emoji) =>
`<li data-emoji="${emoji}" data-names="${EMOJIS.MAP[emoji].map((name) => `:${name}:`).join(" ")}">${emoji}</li>`,
)
.join("\n")}
</ul>
</form>
<div id="reactions-names-display"></div>
`;
document.body.appendChild(reactions_popup);
reactions_popup_form = document.getElementById("reactions-selection-form");
document.addEventListener("topic_changed", ({ detail: { topic_id } }) => {
const reaction_topic_id = topic_id ?? document.body.dataset.topic;
reactions_popup_form.action = reaction_topic_id
? `/api/topics/${reaction_topic_id}/events`
: "";
});
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"]');
reactions_popup_names_display = document.getElementById("reactions-names-display");
reactions_popup_emojis_list.querySelectorAll("li[data-emoji]").forEach((emoji_selector) => {
emoji_selector.addEventListener("click", (event) => {
event.preventDefault();
const selector = event.target;
const emoji = selector.dataset.emoji;
reactions_popup_reaction_input.value = emoji;
reactions_popup_form.requestSubmit();
});
});
reactions_popup_form.addEventListener("mouseover", (event) => {
reactions_popup_names_display.textContent = event.target.matches("li[data-names]")
? event.target.dataset.names
: "";
});
const reactions_popup_search = debounce((event) => {
const prompt = event.target?.value;
const filtered = EMOJIS.autocomplete(prompt);
delete emojis_list.dataset.filtered;
if (filtered.length) {
emojis_list.dataset.filtered = true;
emojis_list.querySelectorAll("li").forEach((li) => {
if (filtered.some((entry) => entry[0] === li.dataset.emoji)) {
li.dataset.filtered = true;
} else {
delete li.dataset.filtered;
}
});
}
}, 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);
document.querySelector("body").addEventListener("click", (event) => {
const is_a_data_reactions_child = event?.target?.closest("[data-reactions]");
if (!is_a_data_reactions_child) {
clear_reactions_popup();
return;
}
event.preventDefault();
open_reactions_popup(event);
});
});