let flashActive = false; const flashTypes = [ "error", "success", "warn" ]; const flash = ({ type, msg }) => { let flashContainer; if(tmp = document.querySelector("div#flash")) flashContainer = tmp; else { flashContainer = document.createElement("div"); flashContainer.id = "flash"; document.body.insertAdjacentElement("afterbegin", flashContainer); } flashContainer.innerHTML = msg; if(flashTypes.includes(type)) { flashContainer.className = ""; flashContainer.classList.add(type); } if(flashActive) return false; flashActive = true; flashContainer.animate( [ { bottom: "-28px" }, { bottom: 0 } ], { duration: 500, fill: "both" } ).onfinish = () => setTimeout(() => { flashContainer.animate( [ { bottom: 0 }, { bottom: "-28px" } ], { duration: 500, fill: "both" } ).onfinish = () => flashActive = false; }, 4 * 1e3); return true; }; (async () => { if(_addtag = document.querySelector("a#a_addtag")) { const postid = +document.querySelector("a.id-link").innerText; const poster = document.querySelector("a#a_username").innerText; let tags = [...document.querySelectorAll("#tags > .badge")].map(t => t.innerText.slice(0, -2)); const deleteEvent = async e => { e.preventDefault(); if(!confirm("Do you really want to delete this tag?")) return; const tagname = e.target.parentElement.querySelector('a:first-child').innerText; const res = await (await fetch("/api/v2/admin/" + postid + "/tags/" + encodeURIComponent(tagname), { method: 'DELETE' })).json(); if(!res.success) return alert("uff"); tags = res.tags.map(t => t.tag); renderTags(res.tags); }; const queryapi = async (url, data, method = 'GET') => { let req; if(method == 'POST') { req = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }); } else { let s = []; for(const [ key, val ] of Object.entries(data)) s.push(encodeURIComponent(key) + "=" + encodeURIComponent(val)); req = await fetch(url + '?' + s.join('&')); } return await req.json(); }; const get = async (url, data) => queryapi(url, data, 'GET'); const post = async (url, data) => queryapi(url, data, 'POST'); const renderTags = _tags => { [...document.querySelectorAll("#tags > .badge")].forEach(tag => tag.parentElement.removeChild(tag)); _tags.reverse().forEach(tag => { const a = document.createElement("a"); a.href = `/tag/${tag.normalized}`; a.style = "color: inherit !important"; a.innerHTML = tag.tag; a.addEventListener("click", editTagEvent); // tmp const span = document.createElement("span"); span.classList.add("badge", "mr-2"); span.setAttribute('tooltip', tag.user); tag.badge.split(" ").forEach(b => span.classList.add(b)); const delbutton = document.createElement("a"); delbutton.innerHTML = " ×"; delbutton.href = "#"; delbutton.addEventListener("click", deleteEvent); span.insertAdjacentElement("beforeend", a); span.innerHTML += ' '; span.insertAdjacentElement("beforeend", delbutton); document.querySelector("#tags").insertAdjacentElement("afterbegin", span); }); }; const addtagClick = (ae = false) => { if(ae) ae.preventDefault(); const insert = document.querySelector("a#a_addtag"); const span = document.createElement("span"); span.classList.add("badge", "badge-light", "mr-2"); const input = document.createElement("input"); input.size = "10"; input.value = ""; input.setAttribute("list", "testlist"); input.setAttribute("autoComplete", "off"); span.insertAdjacentElement("afterbegin", input); insert.insertAdjacentElement("beforebegin", span); input.focus(); let tt = null; let lastInput = ''; const testList = document.querySelector('#testlist'); input.addEventListener("keyup", async e => { if(e.key === "Enter") { const tmptag = input.value?.trim(); if(tags.includes(tmptag)) return alert("tag already exists"); const res = await post("/api/v2/admin/" + postid + "/tags", { tagname: tmptag }); if(!res.success) { return flash({ type: "error", msg: res.msg }); } tags = res.tags.map(t => t.tag); renderTags(res.tags); addtagClick(); testList.innerText = ""; } else if(e.key === "Escape") { span.parentElement.removeChild(span); testList.innerText = ""; } else { if(tt != null) clearTimeout(tt); tt = setTimeout(async () => { tt = null; const tmptag = input.value?.trim(); if(tmptag == lastInput || tmptag.length <= 1) return false; testList.innerText = ""; lastInput = tmptag; const res = await get('/api/v2/admin/tags/suggest', { q: tmptag }); for(const entry of res.suggestions) { const option = document.createElement('option'); option.value = entry.tag; if(!/fox/.test(navigator.userAgent)) option.innerText = `tagged ${entry.tagged} times (score: ${entry.score.toFixed(2)})`; testList.insertAdjacentElement('beforeEnd', option); }; }, 500); } return true; }); input.addEventListener("focusout", ie => { if(input.value.length === 0) input.parentElement.parentElement.removeChild(input.parentElement); }); }; const toggleEvent = async (e = false) => { if(e) e.preventDefault(); const res = await (await fetch('/api/v2/admin/' + encodeURIComponent(postid) + '/tags/toggle', { method: 'PUT' })).json(); renderTags(res.tags); tags = res.tags.map(t => t.tag); flash({ type: "success", msg: tags.join() }); }; const deleteButtonEvent = async e => { if(e) e.preventDefault(); if(!confirm(`Reason for deleting f0ckpost ${postid} by ${poster} (Weihnachten™)`)) return; const res = await post("/api/v2/admin/deletepost", { postid: postid }); if(res.success) { flash({ type: "success", msg: "post was successfully deleted" }); } else { flash({ type: "error", msg: res.msg }); } }; const toggleFavEvent = async e => { const res = await post('/api/v2/admin/togglefav', { postid: postid }); if(res.success) { const fav = document.querySelector("svg#a_favo > use").href; fav.baseVal = '/s/img/iconset.svg#heart_' + (fav.baseVal.match(/heart_(regular|solid)$/)[1] == "solid" ? "regular" : "solid"); // span#favs const favcontainer = document.querySelector('span#favs'); favcontainer.innerHTML = ""; favcontainer.hidden = !(favcontainer.hidden || res.favs.length > 0); res.favs.forEach(f => { const a = document.createElement('a'); a.href = `/user/${f.user}/favs`; a.setAttribute('tooltip', f.user); a.setAttribute('flow', 'up'); const img = document.createElement('img'); img.src = `/t/${f.avatar}.webp`; img.style.height = "32px"; img.style.width = "32px"; a.insertAdjacentElement('beforeend', img); favcontainer.insertAdjacentElement('beforeend', a); favcontainer.innerHTML += " "; }); } else { // lul } }; let tmptt = null; const editTagEvent = async e => { // mousedown e.preventDefault(); if(e.detail === 2) { clearTimeout(tmptt); const old = e.target; const parent = e.target.parentElement; const oldtag = e.target.innerText; const textfield = document.createElement('input'); textfield.value = e.target.innerText; textfield.size = 10; parent.insertAdjacentElement('afterbegin', textfield); textfield.focus(); parent.removeChild(e.target); parent.querySelector('a:last-child').style.display = 'none'; textfield.addEventListener("keyup", async e => { if(e.key === 'Enter') { parent.removeChild(textfield); // send let res = await fetch('/api/v2/admin/tags/' + encodeURIComponent(oldtag), { method: 'PUT', headers: { "Content-Type": "application/json" }, body: JSON.stringify({ newtag: textfield.value }) }); const status = res.status; res = await res.json(); switch(status) { case 200: // success, change case 201: //parent.removeChild(textfield); parent.insertAdjacentElement('afterbegin', old); parent.querySelector('a:last-child').style.display = ''; old.href = `/tag/${res.tag}`; old.innerText = res.tag.trim(); break; default: console.log(res); break; } } else if(e.key === 'Escape') { parent.removeChild(textfield); parent.insertAdjacentElement('afterbegin', old); parent.querySelector('a:last-child').style.display = ''; } }); } else tmptt = setTimeout(() => location.href = e.target.href, 250); return false; }; _addtag.addEventListener("click", addtagClick); document.querySelector("a#a_toggle").addEventListener("click", toggleEvent); [...document.querySelectorAll("#tags > .badge > a:first-child")].map(t => t.addEventListener("click", editTagEvent)); [...document.querySelectorAll("#tags > .badge > a:last-child")].map(t => t.addEventListener("click", deleteEvent)); document.querySelector("svg#a_delete").addEventListener("click", deleteButtonEvent); document.querySelector("svg#a_favo").addEventListener("click", toggleFavEvent); document.addEventListener("keyup", e => { if(e.target.tagName === "INPUT") return; if(e.key === "p") toggleEvent(); else if(e.key === "i") addtagClick(); else if(e.key === "x") deleteButtonEvent(); else if(e.key === "f") toggleFavEvent(); }); } if(document.location.pathname === '/settings') { const saveAvatar = async e => { e.preventDefault(); const avatar = +document.querySelector('input[name="i_avatar"]').value; let res = await fetch('/api/v2/settings/setAvatar', { method: 'PUT', headers: { "Content-Type": "application/json" }, body: JSON.stringify({ avatar }) }); const code = res.status; res = await res.json(); switch(code) { case 200: document.querySelector('#img_avatar').src = `/t/${avatar}.webp`; document.querySelector('img.avatar').src = `/t/${avatar}.webp`; break; default: console.log(res); break; } }; document.querySelector('input#s_avatar').addEventListener('click', saveAvatar); document.querySelector('input[name="i_avatar"]').addEventListener('keyup', async e => { if(e.key === 'Enter') await saveAvatar(e); }); } })();