{ 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." } } }