(() => { const elForm = document.querySelector("form.search-form"); const elQ = document.querySelector("#q"); const elSelDict = document.querySelector("form.search-form select"); const defaultLang = elSelDict.value; // =================================== // Helper functions. function selectDict(dict) { // dict is in the format '$fromLang/$toLang'. const langs = dict.split("/"); Object.assign(localStorage, { dict, from_lang: langs[0], to_lang: langs[1] }); elSelDict.value = dict; elForm.setAttribute("action", `${_ROOT_URL}/dictionary/${dict}`); } // Capture the form submit and send it as a canonical URL instead // of the ?q query param. function search(q) { let val = q.trim(); const uri = elForm.getAttribute("action"); document.location.href = `${uri}/${encodeURIComponent(val).replace(/%20/g, "-")}`; } // =================================== // Events // On ~ press, focus search input. document.onkeydown = (function (e) { if (e.key !== "`" && e.key !== "~") { return; } e.preventDefault(); q.focus(); q.select(); }); // Bind to language change. elSelDict.addEventListener("change", function (e) { selectDict(e.target.value); }); // Bind to form submit. elForm.addEventListener("submit", function (e) { e.preventDefault(); search(elQ.value); }); // "More" link next to entries/defs that expands an entry. document.querySelectorAll(".more-toggle").forEach((el) => { el.onclick = (e) => { e.preventDefault(); let state = "block"; if (el.dataset.open) { delete (el.dataset.open); state = "none"; } else { el.dataset.open = true; state = "block"; } const container = document.getElementById(el.dataset.id); container.style.display = state; // fetch() content from the dataset data.guid from /api/entry/{guid} // and populate the div.more innerHTML with the content. if (state === "block" && !el.dataset.fetched) { container.innerHTML = `
`; fetch(`${window._ROOT_URL}/api/dictionary/entries/${el.dataset.entryGuid}`) .then((resp) => resp.json()) .then((data) => { const d = data.data; container.innerHTML = ""; // If there's data.data.meta.synonyms, add them to the synonyms div. if (d.meta.synonyms) { const syns = document.createElement("div"); syns.classList.add("synonyms"); syns.innerHTML = d.meta.synonyms.map((s) => `${s}`).join(""); container.appendChild(syns); } // Each data.content[] word, add to target. const more = document.createElement("div"); more.classList.add("words"); more.innerHTML = d.content.slice(window._MAX_CONTENT_ITEMS).map((c) => `${c}`).join(""); container.appendChild(more); el.dataset.fetched = true; }) .catch((err) => { console.error("Error fetching entry content:", err); }); } }; }); // =================================== // Select a language based on the page URL. let dict = localStorage.dict || defaultLang; const uri = /(dictionary)\/((.+?)\/(.+?))\//i.exec(document.location.href); if (uri && uri.length == 5) { dict = uri[2]; } selectDict(dict); elQ.focus(); elQ.select(); })(); // Submission form. (() => { function filterTypes(e) { // Filter the types select field with elements that are supported by the language. const types = e.target.closest("[data-relation-controls]").querySelector("select[name=relation_type]"); types.querySelectorAll("option").forEach((o) => o.style.display = "none"); types.querySelectorAll(`option[data-lang=${e.target.value}]`).forEach((o) => o.style.display = "block"); types.selectedIndex = 1; } if (document.querySelector(".form-submit")) { document.querySelectorAll("select[name=relation_lang]").forEach((e) => { e.onchange = filterTypes; }); // +definition button. document.querySelector(".btn-add-relation").onclick = (e) => { e.preventDefault(); if (document.querySelectorAll(".add-relations li").length >= 20) { return false; } // Clone and add a relation fieldset. const d = document.querySelector(".add-relations li").cloneNode(true); d.dataset.added = true d.querySelector("select[name=relation_lang]").onchange = filterTypes; document.querySelector(".add-relations").appendChild(d); // Remove definition link. d.querySelector(".btn-remove-relation").onclick = (e) => { e.preventDefault(); d.remove(); }; }; } })(); // Edit form using ot-dropdown. (() => { const tpl = document.querySelector("#tpl-form-comments"); if (!tpl) return; let counter = 0; document.querySelectorAll("[data-edit-from]").forEach((btn) => { const parent = btn.parentNode; // Clone template content and give the popover a unique ID. const popoverId = `form-comments-${counter++}`; const form = tpl.content.firstElementChild.cloneNode(true); form.id = popoverId; // Wire the button as the popover trigger. btn.setAttribute("popovertarget", popoverId); // Build with children fully assembled before inserting into DOM // so that init() finds [popovertarget] and [popover]. const dropdown = document.createElement("ot-dropdown"); dropdown.appendChild(btn); dropdown.appendChild(form); parent.appendChild(dropdown); const txt = form.querySelector("textarea"); // Handle submission. form.querySelector("button.submit-comment").onclick = () => { fetch(`${window._ROOT_URL}/api/submissions/comments`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ from_guid: btn.dataset.editFrom, to_guid: btn.dataset.editTo, comments: txt.value }) }).catch((err) => { alert(`Error submitting: ${err}`); }); alert(form.dataset.success); form.hidePopover(); }; form.querySelector("button.close").onclick = () => form.hidePopover(); }); })(); // Autocomplete. (() => { if (!autocomp) { return; } const elForm = document.querySelector("form.search-form"); const elQ = document.querySelector("#q"); let debounce; autocomp(elQ, { autoSelect: false, onQuery: async (val) => { const langCode = localStorage.from_lang; clearTimeout(debounce); return new Promise(resolve => { debounce = setTimeout(async () => { const response = await fetch(`${_ROOT_URL}/api/autocomplete/${langCode}/${val}`); const data = await response.json(); const suggestions = data.data.map(item => item.content[0]); debounce = null; resolve(suggestions); }, 50); }); }, onSelect: (val) => { // autocomp search isn't complete. Use the user's input instead of autocomp selection. if (val) { elQ.value = val; } elForm.dispatchEvent(new Event("submit", { cancelable: true })); return elQ.value; } }); })(); // Audio playback. document.querySelectorAll("[data-audio]").forEach((el) => { el.onclick = (e) => { e.preventDefault(); const audio = new Audio(el.dataset.src); audio.play().catch((err) => { console.error("error playing audio:", err); alert("error playing audio"); }); }; }); // Scroll to hash on load. window.setTimeout(() => { if (window.location.hash) { document.querySelector(window.location.hash)?.scrollIntoView(); } }, 100); // Screenshot share. document.querySelectorAll("[data-share-entry]").forEach((el) => { el.onclick = async (e) => { e.preventDefault(); const entryEl = document.getElementById(el.dataset.shareEntry); if (!entryEl) return; try { await shareEntry(entryEl); } catch (err) { console.error("Error sharing entry:", err); alert(`Error sharing: ${err?.message || err}`); } }; });