Refactor all templates to dictpress v5 (Rust/Tera templates).
This commit is contained in:
+84
-120
@@ -1,15 +1,3 @@
|
||||
async function screenshotElement(element) {
|
||||
const canvas = await html2canvas(element);
|
||||
canvas.toBlob(blob => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'screenshot.png';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
});
|
||||
}
|
||||
|
||||
(() => {
|
||||
const elForm = document.querySelector("form.search-form");
|
||||
const elQ = document.querySelector("#q");
|
||||
@@ -34,7 +22,7 @@ async function screenshotElement(element) {
|
||||
let val = q.trim();
|
||||
|
||||
const uri = elForm.getAttribute("action");
|
||||
document.location.href = `${uri}/${encodeURIComponent(val).replace(/%20/g, "+")}`;
|
||||
document.location.href = `${uri}/${encodeURIComponent(val).replace(/%20/g, "-")}`;
|
||||
}
|
||||
|
||||
// ===================================
|
||||
@@ -82,7 +70,7 @@ async function screenshotElement(element) {
|
||||
// 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 = `<div class="loader"></div>`;
|
||||
container.innerHTML = `<div aria-busy="true"></div>`;
|
||||
|
||||
fetch(`${window._ROOT_URL}/api/dictionary/entries/${el.dataset.entryGuid}`)
|
||||
.then((resp) => resp.json())
|
||||
@@ -127,52 +115,11 @@ async function screenshotElement(element) {
|
||||
elQ.select();
|
||||
})();
|
||||
|
||||
// Screenshot sharing.
|
||||
(() => {
|
||||
document.querySelectorAll("a.export").forEach((el) => {
|
||||
el.onclick = async (e) => {
|
||||
e.preventDefault();
|
||||
const guid = el.dataset.guid;
|
||||
const entryEl = document.querySelector(`.entry[data-guid='${guid}']`);
|
||||
if (!entryEl) {
|
||||
alert("Could not find entry to export");
|
||||
return;
|
||||
}
|
||||
|
||||
const title = entryEl.dataset.head;
|
||||
// Make the filename by stripping spaces from the head word(s).
|
||||
const filename = title.replace(/\s+/g, "_").toLowerCase();
|
||||
|
||||
try {
|
||||
await shareDOM(entryEl, `${title} meaning`, `${localStorage.from_lang} to ${localStorage.to_lang} meaning\n\n${window.location.href}`, `${filename}.png`);
|
||||
} catch (err) {
|
||||
console.error("Error sharing entry:", err);
|
||||
alert(`Error sharing entry: ${err?.message || err}`);
|
||||
}
|
||||
};
|
||||
});
|
||||
})();
|
||||
|
||||
// Play audio.
|
||||
(() => {
|
||||
document.querySelectorAll("a[data-audio]").forEach((el) => {
|
||||
el.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
const audio = new Audio(el.getAttribute("href"));
|
||||
audio.play().catch((err) => {
|
||||
console.error("error playing audio:", err);
|
||||
alert("error playing audio");
|
||||
});
|
||||
};
|
||||
});
|
||||
})();
|
||||
|
||||
|
||||
// Submission form.
|
||||
(() => {
|
||||
function filterTypes(e) {
|
||||
// Filter the types select field with elements that are supported by the language.
|
||||
const types = e.target.closest("fieldset").querySelector("select[name=relation_type]");
|
||||
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;
|
||||
@@ -207,64 +154,59 @@ async function screenshotElement(element) {
|
||||
}
|
||||
})();
|
||||
|
||||
// Edit form.
|
||||
// Edit form using ot-dropdown.
|
||||
(() => {
|
||||
document.querySelectorAll(".edit").forEach((o) => {
|
||||
o.onclick = ((e) => {
|
||||
e.preventDefault();
|
||||
const btn = e.target;
|
||||
const tpl = document.querySelector("#tpl-form-comments");
|
||||
if (!tpl) return;
|
||||
|
||||
// Form is already open.
|
||||
if (btn.close) {
|
||||
btn.close();
|
||||
return;
|
||||
}
|
||||
let counter = 0;
|
||||
document.querySelectorAll("[data-edit-from]").forEach((btn) => {
|
||||
const parent = btn.parentNode;
|
||||
|
||||
const form = document.querySelector(".form-comments").cloneNode(true);
|
||||
o.parentNode.appendChild(form);
|
||||
form.style.display = "block";
|
||||
// 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;
|
||||
|
||||
const txt = form.querySelector("textarea");
|
||||
txt.focus();
|
||||
txt.onkeydown = (e) => {
|
||||
if (e.key === "Escape" && txt.value === "") {
|
||||
btn.close();
|
||||
}
|
||||
};
|
||||
// Wire the button as the popover trigger.
|
||||
btn.setAttribute("popovertarget", popoverId);
|
||||
|
||||
btn.close = () => {
|
||||
btn.close = null;
|
||||
form.remove();
|
||||
};
|
||||
// Build <ot-dropdown> 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);
|
||||
|
||||
// Handle form submission.
|
||||
form.onsubmit = () => {
|
||||
fetch(`${window._ROOT_URL}/api/submissions/comments`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
from_guid: btn.dataset.from,
|
||||
to_guid: btn.dataset.to,
|
||||
comments: txt.value
|
||||
})
|
||||
}).catch((err) => {
|
||||
alert(`Error submitting: ${err}`);
|
||||
});
|
||||
const txt = form.querySelector("textarea");
|
||||
|
||||
alert(form.dataset.success);
|
||||
btn.close();
|
||||
};
|
||||
// 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}`);
|
||||
});
|
||||
|
||||
form.querySelector("button.close").onclick = btn.close;
|
||||
});
|
||||
})
|
||||
alert(form.dataset.success);
|
||||
form.hidePopover();
|
||||
};
|
||||
|
||||
form.querySelector("button.close").onclick = () => form.hidePopover();
|
||||
});
|
||||
})();
|
||||
|
||||
// Autocomplete.
|
||||
(() => {
|
||||
if(!autocomp) {
|
||||
if (!autocomp) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -273,37 +215,25 @@ async function screenshotElement(element) {
|
||||
let debounce;
|
||||
|
||||
autocomp(elQ, {
|
||||
autoSelect: elQ.dataset.autocompAutoselect === "true",
|
||||
autoSelect: false,
|
||||
onQuery: async (val) => {
|
||||
const langCode = localStorage.from_lang;
|
||||
|
||||
if (!langCode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shortcode = autoCompLangCodes?.[langCode] ?? langCode;
|
||||
|
||||
clearTimeout(debounce);
|
||||
return new Promise(resolve => {
|
||||
debounce = setTimeout(async () => {
|
||||
const response = await fetch(`${_ROOT_URL}/atl/${shortcode}/${val.toLowerCase()}`);
|
||||
const response = await fetch(`${_ROOT_URL}/api/autocomplete/${langCode}/${val}`);
|
||||
const data = await response.json();
|
||||
|
||||
const a = data.greedy_tokenized.map(item => item.word).slice(0, 3).sort((a, b) => a.length - b.length);
|
||||
const b = data.dictionary_suggestions.map(item => item.word).slice(0, 6).sort((a, b) => a.length - b.length);
|
||||
const suggestions = data.data.map(item => item.content[0]);
|
||||
|
||||
debounce = null;
|
||||
resolve([...new Set(a.concat(b))]);
|
||||
resolve(suggestions);
|
||||
}, 50);
|
||||
});
|
||||
},
|
||||
|
||||
onSelect: (val, items) => {
|
||||
// If the val is English, then pick the first item from items and use that.
|
||||
if (/^[A-Za-z0-9\-,'" ]+$/.test(val) && items.length > 0) {
|
||||
val = items[0];
|
||||
}
|
||||
|
||||
onSelect: (val) => {
|
||||
// autocomp search isn't complete. Use the user's input instead of autocomp selection.
|
||||
if (val) {
|
||||
elQ.value = val;
|
||||
}
|
||||
@@ -313,3 +243,37 @@ async function screenshotElement(element) {
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
// 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}`);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user