370 lines
12 KiB
JavaScript
370 lines
12 KiB
JavaScript
{
|
|
const ta = document.getElementById("babycode-content");
|
|
|
|
for (let button of document.querySelectorAll(".reply-button")) {
|
|
button.addEventListener("click", (e) => {
|
|
ta.value += button.value;
|
|
ta.scrollIntoView()
|
|
ta.focus();
|
|
})
|
|
}
|
|
|
|
function supportsPopover() {
|
|
return Object.hasOwn(HTMLElement.prototype, "popover");
|
|
}
|
|
|
|
if (supportsPopover()){
|
|
let quotedPostContainer = null;
|
|
function isQuoteSelectionValid() {
|
|
const selection = document.getSelection();
|
|
|
|
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
|
|
return false;
|
|
}
|
|
|
|
const range = selection.getRangeAt(0);
|
|
const commonAncestor = range.commonAncestorContainer;
|
|
|
|
const ancestorElement = commonAncestor.nodeType === Node.TEXT_NODE
|
|
? commonAncestor.parentNode
|
|
: commonAncestor;
|
|
|
|
const container = ancestorElement.closest(".post-inner");
|
|
if (!container) {
|
|
return false;
|
|
}
|
|
const success = container.contains(ancestorElement);
|
|
if (success) {
|
|
quotedPostContainer = container;
|
|
}
|
|
return success;
|
|
}
|
|
|
|
let quotePopover = null;
|
|
let isSelecting = false;
|
|
|
|
document.addEventListener("mousedown", () => {
|
|
isSelecting = true;
|
|
})
|
|
|
|
document.addEventListener("mouseup", () => {
|
|
isSelecting = false;
|
|
handlePossibleSelection();
|
|
})
|
|
|
|
document.addEventListener("keyup", (e) => {
|
|
if (e.shiftKey && (e.key.startsWith('Arrow') || e.key === 'Home' || e.key === 'End')) {
|
|
handlePossibleSelection();
|
|
}
|
|
})
|
|
|
|
function handlePossibleSelection() {
|
|
setTimeout(() => {
|
|
const valid = isQuoteSelectionValid();
|
|
if (isSelecting || !valid) {
|
|
removeQuotePopover();
|
|
return;
|
|
}
|
|
|
|
const selection = document.getSelection();
|
|
const selectionStr = selection.toString().trim();
|
|
if (selection.isCollapsed || selectionStr === "") {
|
|
removeQuotePopover();
|
|
return;
|
|
}
|
|
|
|
showQuotePopover();
|
|
}, 50)
|
|
}
|
|
|
|
function removeQuotePopover() {
|
|
quotePopover?.hidePopover();
|
|
}
|
|
|
|
function createQuotePopover() {
|
|
quotePopover = document.createElement("div");
|
|
quotePopover.popover = "auto";
|
|
quotePopover.className = "quote-popover";
|
|
|
|
const quoteButton = document.createElement("button");
|
|
quoteButton.textContent = "Quote fragment"
|
|
quoteButton.className = "reduced"
|
|
quotePopover.appendChild(quoteButton);
|
|
|
|
document.body.appendChild(quotePopover);
|
|
return quoteButton;
|
|
}
|
|
|
|
function showQuotePopover() {
|
|
if (!quotePopover) {
|
|
const quoteButton = createQuotePopover();
|
|
quoteButton.addEventListener("click", () => {
|
|
console.log("Quoting:", document.getSelection().toString());
|
|
const postPermalink = quotedPostContainer.dataset.postPermalink;
|
|
const authorUsername = quotedPostContainer.dataset.authorUsername;
|
|
console.log(postPermalink, authorUsername);
|
|
if (ta.value.trim() !== "") {
|
|
ta.value += "\n"
|
|
}
|
|
ta.value += `[url=${postPermalink}]${authorUsername} said:[/url]\n[quote]< :scissors: > ${document.getSelection().toString()} < :scissors: >[/quote]\n`;
|
|
ta.scrollIntoView()
|
|
ta.focus();
|
|
|
|
document.getSelection().empty();
|
|
removeQuotePopover();
|
|
})
|
|
}
|
|
|
|
const range = document.getSelection().getRangeAt(0);
|
|
const rect = range.getBoundingClientRect();
|
|
const scrollY = window.scrollY || window.pageYOffset;
|
|
|
|
quotePopover.style.setProperty("top", `${rect.top + scrollY - 55}px`)
|
|
quotePopover.style.setProperty("left", `${rect.left + rect.width/2}px`)
|
|
|
|
if (!quotePopover.matches(':popover-open')) {
|
|
quotePopover.showPopover();
|
|
}
|
|
}
|
|
}
|
|
|
|
const deleteDialog = document.getElementById("delete-dialog");
|
|
const deleteDialogCloseButton = document.getElementById("post-delete-dialog-close");
|
|
let deletionTargetPostContainer;
|
|
|
|
function closeDeleteDialog() {
|
|
deletionTargetPostContainer.style.removeProperty("background-color");
|
|
deleteDialog.close();
|
|
}
|
|
|
|
deleteDialogCloseButton.addEventListener("click", (e) => {
|
|
closeDeleteDialog();
|
|
})
|
|
deleteDialog.addEventListener("click", (e) => {
|
|
if (e.target === deleteDialog) {
|
|
closeDeleteDialog();
|
|
}
|
|
})
|
|
for (let button of document.querySelectorAll(".post-delete-button")) {
|
|
button.addEventListener("click", (e) => {
|
|
deleteDialog.showModal();
|
|
const postId = button.value;
|
|
deletionTargetPostContainer = document.getElementById("post-" + postId).querySelector(".post-content-container");
|
|
deletionTargetPostContainer.style.setProperty("background-color", "#fff");
|
|
const form = document.getElementById("post-delete-form");
|
|
form.action = `/post/${postId}/delete`
|
|
})
|
|
}
|
|
|
|
const threadEndpoint = document.getElementById("thread-subscribe-endpoint").value;
|
|
let now = Math.floor(new Date() / 1000);
|
|
function hideNotification() {
|
|
const notification = document.getElementById('new-post-notification');
|
|
notification.classList.add('hidden');
|
|
}
|
|
|
|
function showNewPostNotification(url) {
|
|
const notification = document.getElementById("new-post-notification");
|
|
|
|
notification.classList.remove("hidden");
|
|
|
|
document.getElementById("dismiss-new-post-button").onclick = () => {
|
|
now = Math.floor(new Date() / 1000);
|
|
hideNotification();
|
|
tryFetchUpdate();
|
|
}
|
|
|
|
document.getElementById("go-to-new-post-button").href = url;
|
|
|
|
document.getElementById("unsub-new-post-button").onclick = () => {
|
|
hideNotification();
|
|
}
|
|
}
|
|
|
|
function tryFetchUpdate() {
|
|
if (!threadEndpoint) return;
|
|
const body = JSON.stringify({'since': now});
|
|
fetch(threadEndpoint, {method: "POST", headers: {"Content-Type": "application/json"}, body: body})
|
|
.then(res => res.json())
|
|
.then(json => {
|
|
if (json.status === "none") {
|
|
setTimeout(tryFetchUpdate, 5000);
|
|
} else if (json.status === "new_post") {
|
|
showNewPostNotification(json.url);
|
|
}
|
|
})
|
|
.catch(error => console.log(error))
|
|
}
|
|
tryFetchUpdate();
|
|
|
|
if (supportsPopover()){
|
|
const reactionEmoji = document.getElementById("allowed-reaction-emoji").value.split(" ");
|
|
let reactionPopover = null;
|
|
let reactionTargetPostId = null;
|
|
|
|
function tryAddReaction(emoji, postId = reactionTargetPostId) {
|
|
const body = JSON.stringify({
|
|
"emoji": emoji,
|
|
});
|
|
fetch(`/api/add-reaction/${postId}`, {method: "POST", headers: {"Content-Type": "application/json"}, body: body})
|
|
.then(res => res.json())
|
|
.then(json => {
|
|
if (json.status === "added") {
|
|
const post = document.getElementById(`post-${postId}`);
|
|
const spans = Array.from(post.querySelectorAll(".reaction-count")).filter((span) => {
|
|
return span.dataset.emoji === emoji
|
|
});
|
|
if (spans.length > 0) {
|
|
const currentValue = spans[0].textContent;
|
|
spans[0].textContent = `${parseInt(currentValue) + 1}`;
|
|
const button = spans[0].closest(".reaction-button");
|
|
button.classList.add("active");
|
|
} else {
|
|
const span = document.createElement("span");
|
|
span.classList = "reaction-container";
|
|
span.dataset.emoji = emoji;
|
|
|
|
const button = document.createElement("button");
|
|
button.type = "button";
|
|
button.className = "reduced reaction-button active";
|
|
|
|
button.addEventListener("click", () => {
|
|
tryAddReaction(emoji, postId);
|
|
})
|
|
|
|
const img = document.createElement("img");
|
|
img.src = `/static/emoji/${emoji}.png`;
|
|
|
|
button.textContent = " x";
|
|
|
|
const reactionCountSpan = document.createElement("span")
|
|
reactionCountSpan.className = "reaction-count"
|
|
reactionCountSpan.textContent = "1"
|
|
|
|
button.insertAdjacentElement("afterbegin", img);
|
|
button.appendChild(reactionCountSpan);
|
|
|
|
span.appendChild(button);
|
|
|
|
const post = document.getElementById(`post-${postId}`);
|
|
post.querySelector(".post-reactions").insertBefore(span, post.querySelector(".add-reaction-button"));
|
|
}
|
|
} else if (json.error_code === 409) {
|
|
console.log("reaction exists, gonna try and remove");
|
|
tryRemoveReaction(emoji, postId);
|
|
} else {
|
|
console.warn(json)
|
|
}
|
|
})
|
|
.catch(error => console.error(error));
|
|
}
|
|
|
|
function tryRemoveReaction(emoji, postId = reactionTargetPostId) {
|
|
const body = JSON.stringify({
|
|
"emoji": emoji,
|
|
});
|
|
|
|
fetch(`/api/remove-reaction/${postId}`, {method: "POST", headers: {"Content-Type": "application/json"}, body: body})
|
|
.then(res => res.json())
|
|
.then(json => {
|
|
if (json.status === "removed") {
|
|
const post = document.getElementById(`post-${postId}`);
|
|
const spans = Array.from(post.querySelectorAll(".reaction-container")).filter((span) => {
|
|
return span.dataset.emoji === emoji
|
|
});
|
|
if (spans.length > 0) {
|
|
const reactionCountSpan = spans[0].querySelector(".reaction-count");
|
|
const currentValue = parseInt(reactionCountSpan.textContent);
|
|
if (currentValue - 1 === 0) {
|
|
spans[0].remove();
|
|
} else {
|
|
reactionCountSpan.textContent = `${parseInt(currentValue) - 1}`;
|
|
const button = reactionCountSpan.closest(".reaction-button");
|
|
button.classList.remove("active");
|
|
}
|
|
}
|
|
} else {
|
|
console.warn(json)
|
|
}
|
|
})
|
|
.catch(error => console.error(error));
|
|
}
|
|
|
|
function createReactionPopover() {
|
|
reactionPopover = document.createElement("div");
|
|
reactionPopover.className = "reaction-popover";
|
|
reactionPopover.popover = "auto";
|
|
|
|
const inner = document.createElement("div");
|
|
inner.className = "reaction-popover-inner";
|
|
|
|
reactionPopover.appendChild(inner);
|
|
|
|
for (let emoji of reactionEmoji) {
|
|
const img = document.createElement("img");
|
|
img.src = `/static/emoji/${emoji}.png`;
|
|
|
|
const button = document.createElement("button");
|
|
button.type = "button";
|
|
button.className = "reduced";
|
|
button.appendChild(img);
|
|
button.addEventListener("click", () => {
|
|
tryAddReaction(emoji);
|
|
})
|
|
|
|
button.dataset.emojiName = emoji;
|
|
|
|
inner.appendChild(button);
|
|
}
|
|
|
|
reactionPopover.addEventListener("beforetoggle", (e) => {
|
|
if (e.newState === "closed") {
|
|
reactionTargetPostId = null;
|
|
}
|
|
})
|
|
|
|
document.body.appendChild(reactionPopover);
|
|
}
|
|
|
|
function showReactionPopover() {
|
|
if (!reactionPopover) {
|
|
createReactionPopover();
|
|
}
|
|
|
|
if (!reactionPopover.matches(':popover-open')) {
|
|
reactionPopover.showPopover();
|
|
}
|
|
}
|
|
|
|
for (let button of document.querySelectorAll(".add-reaction-button")) {
|
|
button.addEventListener("click", (e) => {
|
|
showReactionPopover();
|
|
reactionTargetPostId = e.target.dataset.postId;
|
|
|
|
const rect = e.target.getBoundingClientRect();
|
|
const popoverRect = reactionPopover.getBoundingClientRect();
|
|
const scrollY = window.scrollY || window.pageYOffset;
|
|
|
|
reactionPopover.style.setProperty("top", `${rect.top + scrollY + rect.height}px`)
|
|
reactionPopover.style.setProperty("left", `${rect.left + rect.width/2 - popoverRect.width/2}px`)
|
|
})
|
|
}
|
|
|
|
for (let button of document.querySelectorAll(".reaction-button")) {
|
|
button.addEventListener("click", () => {
|
|
const reactionContainer = button.closest(".reaction-container")
|
|
const emoji = reactionContainer.dataset.emoji;
|
|
const postId = reactionContainer.dataset.postId;
|
|
console.log(reactionContainer);
|
|
tryAddReaction(emoji, postId);
|
|
})
|
|
}
|
|
|
|
} else {
|
|
for (let button of document.querySelectorAll(".add-reaction-button")) {
|
|
button.disabled = true;
|
|
button.title = "Enable JS to add reactions."
|
|
}
|
|
}
|
|
}
|