Move JS files to /static/js/

This commit is contained in:
rubenwardy
2023-08-26 13:08:11 +01:00
parent 2f458ba40e
commit c8a30a27dc
31 changed files with 23 additions and 23 deletions

View File

@@ -0,0 +1,211 @@
// @author rubenwardy
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
"use strict";
function updateOrder() {
const elements = [...document.querySelector(".sortable").children];
const ids = elements
.filter(x => !x.classList.contains("d-none"))
.map(x => x.dataset.id)
.filter(x => x);
document.querySelector("input[name='order']").value = ids.join(",");
}
function removePackage(card) {
const message = document.getElementById("confirm_delete").innerText.trim();
const title = card.querySelector("h5 a").innerText.trim();
if (!confirm(message.replace("{title}", title))) {
return;
}
card.querySelector("input[name^=package_removed]").value = "1";
card.classList.add("d-none");
onPackageQueryUpdate();
updateOrder();
}
function restorePackage(id) {
const idElement = document.querySelector(`[value='${id}']`);
if (!idElement) {
return false;
}
const card = idElement.parentNode.parentNode.parentNode.parentNode;
console.assert(card.classList.contains("card"));
card.classList.remove("d-none");
card.querySelector("input[name^=package_removed]").value = "0";
card.scrollIntoView();
onPackageQueryUpdate();
updateOrder();
return true;
}
function getAddedPackages() {
const ids = document.querySelectorAll("#package_list > article:not(.d-none) input[name^=package_ids]");
return [...ids].map(x => x.value);
}
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
function addPackage(pkg) {
document.getElementById("add_package").value = "";
document.getElementById("add_package_results").innerHTML = "";
const id = `${pkg.author}/${pkg.name}`;
if (restorePackage(id)) {
return;
}
const nextId = document.querySelectorAll("input[name^=package_ids-]").length;
const url = `/packages/${id}/`;
const temp = document.createElement("div");
temp.innerHTML = `
<article class="card my-3" data-id="${escapeHtml(id)}">
<div class="card-body">
<div class="row">
<div class="col-auto text-muted pe-2">
<i class="fas fa-bars"></i>
</div>
<div class="col">
<button class="btn btn-sm btn-danger remove-package float-end" type="button" aria-label="Remove">
<i class="fas fa-trash"></i>
</button>
<h5>
<a href="${escapeHtml(url)}" target="_blank">
${escapeHtml(pkg.title)} by ${escapeHtml(pkg.author)}
</a>
</h5>
<p class="text-muted">
${escapeHtml(pkg.short_description)}
</p>
<input id="package_ids-${nextId}" name="package_ids-${nextId}" type="hidden" value="${id}">
<input id="package_removed-${nextId}" name="package_removed-${nextId}" type="hidden" value="0">
<div>
<label for="descriptions-${nextId}" class="form-label">Short Description</label>
<input class="form-control" id="descriptions-${nextId}" maxlength="500" minlength="0"
name="descriptions-${nextId}" type="text" value="">
<small class="form-text text-muted">You can replace the description with your own</small>
</div>
</div>
</div>
</div>
</article>
`;
const card = temp.children[0];
document.getElementById("package_list").appendChild(card);
card.scrollIntoView();
const button = card.querySelector(".btn-danger");
button.addEventListener("click", () => removePackage(card));
updateOrder();
}
function updateResults(packages) {
const results = document.getElementById("add_package_results");
results.innerHTML = "";
document.getElementById("add_package_empty").style.display = packages.length === 0 ? "block" : "none";
const alreadyAdded = getAddedPackages();
packages.slice(0, 5).forEach(pkg => {
const result = document.createElement("a");
result.classList.add("list-group-item");
result.classList.add("list-group-item-action");
result.innerText = `${pkg.title} by ${pkg.author}`;
if (alreadyAdded.includes(`${pkg.author}/${pkg.name}`)) {
result.classList.add("active");
result.innerHTML = "<i class='fas fa-check me-3 text-success'></i>" + result.innerHTML;
}
result.addEventListener("click", () => addPackage(pkg));
results.appendChild(result);
});
}
let currentRequestId;
async function fetchPackagesAndUpdateResults(query) {
const requestId = Math.random() * 1000000;
currentRequestId = requestId;
if (query === "") {
updateResults([]);
return;
}
const url = new URL("/api/packages/", window.location.origin);
url.searchParams.set("q", query);
const resp = await fetch(url.toString());
if (!resp.ok) {
return;
}
const packages = await resp.json();
if (currentRequestId !== requestId) {
return;
}
updateResults(packages);
}
let timeoutHandle;
function onPackageQueryUpdate() {
const query = document.getElementById("add_package").value.trim();
if (timeoutHandle) {
clearTimeout(timeoutHandle);
}
timeoutHandle = setTimeout(
() => fetchPackagesAndUpdateResults(query).catch(console.error),
200);
}
window.addEventListener("load", () => {
document.querySelectorAll(".remove-package").forEach(button => {
const card = button.parentNode.parentNode.parentNode.parentNode;
console.assert(card.classList.contains("card"));
const field = card.querySelector("input[name^=package_removed]");
// Reloading/validation errors will cause this to be 1 at load
if (field && field.value === "1") {
card.classList.add("d-none");
} else {
button.addEventListener("click", () => removePackage(card));
}
});
const addPackageQuery = document.getElementById("add_package");
addPackageQuery.value = "";
addPackageQuery.classList.remove("d-none");
addPackageQuery.addEventListener("input", onPackageQueryUpdate);
addPackageQuery.addEventListener('keydown',(e)=>{
if (e.key === "Enter") {
onPackageQueryUpdate();
e.preventDefault();
}
})
updateOrder();
$(".sortable").sortable({
update: updateOrder,
});
});

View File

@@ -0,0 +1,58 @@
// @author rubenwardy
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
"use strict";
document.querySelectorAll("textarea.markdown").forEach((element) => {
async function render(plainText, preview) {
const response = await fetch(new Request("/api/markdown/", {
method: "POST",
credentials: "same-origin",
body: plainText,
headers: {
"Accept": "text/html; charset=UTF-8",
},
}));
preview.innerHTML = await response.text();
}
let timeout_id = null;
element.easy_mde = new EasyMDE({
element: element,
hideIcons: ["image"],
showIcons: ["code", "table"],
forceSync: true,
toolbar: [
"bold",
"italic",
"heading",
"|",
"code",
"quote",
"unordered-list",
"ordered-list",
"|",
"link",
"table",
"|",
"preview",
"side-by-side",
"fullscreen",
"|",
"guide",
],
previewRender: (plainText, preview) => {
if (timeout_id) {
clearTimeout(timeout_id);
}
timeout_id = setTimeout(() => {
render(plainText, preview).catch(console.error);
timeout_id = null;
}, 500);
return preview.innerHTML;
}
});
})

View File

@@ -0,0 +1,306 @@
// @author rubenwardy
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
"use strict";
const labelColor = "#bbb";
const annotationColor = "#bbb";
const annotationLabelBgColor = "#444";
const gridColor = "#333";
const chartColors = [
"#7eb26d",
"#eab839",
"#6ed0e0",
"#e24d42",
"#1f78c1",
"#ba43a9",
];
const annotationNov5 = {
type: "line",
borderColor: annotationColor,
borderWidth: 1,
click: function({chart, element}) {
document.location = "https://fosstodon.org/@rubenwardy/109303281233703275";
},
label: {
backgroundColor: annotationLabelBgColor,
content: "YouTube Video",
display: true,
position: "end",
color: "#00bc8c",
rotation: "auto",
backgroundShadowColor: "rgba(0, 0, 0, 0.4)",
shadowBlur: 3,
},
scaleID: "x",
value: "2022-11-05",
};
function hexToRgb(hex) {
var bigint = parseInt(hex, 16);
var r = (bigint >> 16) & 255;
var g = (bigint >> 8) & 255;
var b = bigint & 255;
return r + "," + g + "," + b;
}
function sum(list) {
return list.reduce((acc, x) => acc + x, 0);
}
const chartColorsBg = chartColors.map(color => `rgba(${hexToRgb(color.slice(1))}, 0.2)`);
const SECONDS_IN_A_DAY = 1000 * 3600 * 24;
function format_message(id, values) {
let format = document.getElementById(id).textContent;
values.forEach((value, i) => {
format = format.replace("$" + (i + 1), value);
})
return format;
}
function add_summary_card(title, icon, value, extra) {
const ele = document.createElement("div");
ele.innerHTML = `
<div class="col-md-4">
<div class="card h-100">
<div class="card-body align-items-center text-center">
<div class="mt-0 mb-3">
<i class="fas fa-${icon} me-1"></i>
<span class="summary-title"></span>
</div>
<div class="my-0 h4">
<span class="summary-value"></span>
<small class="text-muted ms-2 summary-extra"></small>
</div>
</div>
</div>
</div>`;
ele.querySelector(".summary-title").textContent = title;
ele.querySelector(".summary-value").textContent = value;
ele.querySelector(".summary-extra").textContent = extra;
document.getElementById("stats-summaries").appendChild(ele.children[0]);
}
async function load_data() {
const root = document.getElementById("stats-root");
const source = root.getAttribute("data-source");
const is_range = root.getAttribute("data-is-range") == "true";
const response = await fetch(source);
const json = await response.json();
document.getElementById("loading").style.display = "none";
if (json == null) {
document.getElementById("empty-view").style.display = "block";
return;
}
const startDate = new Date(json.start);
const endDate = new Date(json.end);
const numberOfDays = Math.round((endDate.valueOf() - startDate.valueOf()) / SECONDS_IN_A_DAY) + 1;
const dates = [...Array(numberOfDays)].map((_, i) => {
const date = new Date(startDate.valueOf() + i*SECONDS_IN_A_DAY);
return date.toISOString().split("T")[0];
});
if (!is_range) {
if (json.platform_minetest.length >= 30) {
const total30 = sum(json.platform_minetest.slice(-30)) + sum(json.platform_other.slice(-30));
add_summary_card(format_message("downloads-30days", []), "download", total30,
format_message("downloads-per-day", [ (total30 / 30).toFixed(0) ]));
}
const total7 = sum(json.platform_minetest.slice(-7)) + sum(json.platform_other.slice(-7));
add_summary_card(format_message("downloads-7days", []), "download", total7,
format_message("downloads-per-day", [ (total7 / 7).toFixed(0) ]));
} else {
const total = sum(json.platform_minetest) + sum(json.platform_other);
const days = Math.max(json.platform_minetest.length, json.platform_other.length);
const title = format_message("downloads-range", [ json.start, json.end ]);
add_summary_card(title, "download", total,
format_message("downloads-per-day", [ (total / days).toFixed(0) ]));
}
const jsonOther = json.platform_minetest.map((value, i) =>
value + json.platform_other[i]
- json.reason_new[i] - json.reason_dependency[i]
- json.reason_update[i]);
root.style.display = "block";
function getData(list) {
return list.map((value, i) => ({ x: dates[i], y: value }));
}
const annotations = {};
if (new Date(json.start) < new Date("2022-11-05")) {
annotations.annotationNov5 = annotationNov5;
}
if (json.package_downloads) {
const packageRecentDownloads = Object.fromEntries(Object.entries(json.package_downloads)
.map(([label, values]) => [label, sum(values.slice(-30))]));
document.getElementById("downloads-by-package").classList.remove("d-none");
const ctx = document.getElementById("chart-packages").getContext("2d");
const data = {
datasets: Object.entries(json.package_downloads)
.sort((a, b) => packageRecentDownloads[a[0]] - packageRecentDownloads[b[0]])
.map(([label, values]) => ({ label, data: getData(values) })),
};
setup_chart(ctx, data, annotations);
}
{
const ctx = document.getElementById("chart-platform").getContext("2d");
const data = {
datasets: [
{ label: "Web / other", data: getData(json.platform_other) },
{ label: "Minetest", data: getData(json.platform_minetest) },
],
};
setup_chart(ctx, data, annotations);
}
{
const ctx = document.getElementById("chart-reason").getContext("2d");
const data = {
datasets: [
{ label: "Other / Unknown", data: getData(jsonOther) },
{ label: "Update", data: getData(json.reason_update) },
{ label: "Dependency", data: getData(json.reason_dependency) },
{ label: "New Install", data: getData(json.reason_new) },
],
};
setup_chart(ctx, data, annotations);
}
{
const ctx = document.getElementById("chart-reason-pie").getContext("2d");
const data = {
labels: [
"New Install",
"Dependency",
"Update",
"Other / Unknown",
],
datasets: [{
label: "My First Dataset",
data: [
sum(json.reason_new),
sum(json.reason_dependency),
sum(json.reason_update),
sum(jsonOther),
],
backgroundColor: chartColors,
hoverOffset: 4,
borderWidth: 0,
}]
};
const config = {
type: "doughnut",
data: data,
options: {
responsive: true,
plugins: {
legend: {
labels: {
color: labelColor,
},
},
},
}
};
new Chart(ctx, config);
}
}
function setup_chart(ctx, data, annotations) {
data.datasets = data.datasets.map((set, i) => {
const colorIdx = (data.datasets.length - i - 1) % chartColors.length;
return {
fill: true,
backgroundColor: chartColorsBg[colorIdx],
borderColor: chartColors[colorIdx],
pointBackgroundColor: chartColors[colorIdx],
...set,
};
});
const config = {
type: "line",
data: data,
options: {
responsive: true,
plugins: {
tooltip: {
mode: "index"
},
legend: {
reverse: true,
labels: {
color: labelColor,
}
},
annotation: {
annotations,
},
},
interaction: {
mode: "nearest",
axis: "x",
intersect: false
},
scales: {
x: {
type: "time",
time: {
// min: start,
// max: end,
unit: "day",
},
ticks: {
color: labelColor,
},
grid: {
color: gridColor,
}
},
y: {
stacked: true,
min: 0,
precision: 0,
ticks: {
color: labelColor,
},
grid: {
color: gridColor,
}
},
}
}
};
new Chart(ctx, config);
}
window.addEventListener("load", load_data);

View File

@@ -0,0 +1,74 @@
// @author rubenwardy
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
"use strict";
function hide(sel) {
document.querySelectorAll(sel).forEach(x => x.classList.add("d-none"));
}
function show(sel) {
document.querySelectorAll(sel).forEach(x => x.classList.remove("d-none"));
}
window.addEventListener("load", () => {
function finish() {
hide(".pkg_wiz_1");
hide(".pkg_wiz_2");
show(".pkg_repo");
show(".pkg_meta");
}
hide(".pkg_meta");
show(".pkg_wiz_1");
document.getElementById("pkg_wiz_1_skip").addEventListener("click", finish);
document.getElementById("pkg_wiz_1_next").addEventListener("click", () => {
const repoURL = document.getElementById("repo").value;
if (repoURL.trim() !== "") {
hide(".pkg_wiz_1");
show(".pkg_wiz_2");
hide(".pkg_repo");
function setField(sel, value) {
if (value && value !== "") {
const ele = document.querySelector(sel);
ele.value = value;
ele.dispatchEvent(new Event("change"));
// EasyMDE doesn't always refresh the codemirror correctly
if (ele.easy_mde) {
setTimeout(() => {
ele.easy_mde.value(value);
ele.easy_mde.codemirror.refresh()
}, 100);
}
}
}
performTask("/tasks/getmeta/new/?url=" + encodeURI(repoURL)).then(function(result) {
setField("#name", result.name);
setField("#title", result.title);
setField("#repo", result.repo || repoURL);
setField("#issueTracker", result.issueTracker);
setField("#desc", result.desc);
setField("#short_desc", result.short_desc);
setField("#forums", result.forums);
if (result.type && result.type.length > 2) {
setField("[name='type']", result.type);
}
finish();
}).catch(function(e) {
alert(e);
show(".pkg_wiz_1");
hide(".pkg_wiz_2");
show(".pkg_repo");
// finish()
});
} else {
finish();
}
})
})

View File

@@ -0,0 +1,84 @@
// @author rubenwardy
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
"use strict";
function hide(sel) {
document.querySelectorAll(sel).forEach(x => x.classList.add("d-none"));
}
function show(sel) {
document.querySelectorAll(sel).forEach(x => x.classList.remove("d-none"));
}
window.addEventListener("load", () => {
const typeEle = document.getElementById("type");
typeEle.addEventListener("change", () => {
show(".not_mod, .not_game, .not_txp");
hide(".not_" + typeEle.value.toLowerCase());
})
show(".not_mod, .not_game, .not_txp");
hide(".not_" + typeEle.value.toLowerCase());
const forumsField = document.getElementById("forums");
forumsField.addEventListener("paste", function(e) {
try {
const pasteData = e.clipboardData.getData('text');
const url = new URL(pasteData);
if (url.hostname === "forum.minetest.net") {
forumsField.value = url.searchParams.get("t");
e.preventDefault();
}
} catch (e) {
console.log("Not a URL");
}
});
const openForums = document.getElementById("forums-button");
openForums.addEventListener("click", () => {
window.open("https://forum.minetest.net/viewtopic.php?t=" + forumsField.value, "_blank");
});
let hint = null;
function showHint(ele, text) {
if (hint) {
hint.remove();
}
hint = document.createElement("div");
hint.classList.add("alert");
hint.classList.add("alert-warning");
hint.classList.add("my-1");
hint.innerHTML = text;
ele.parentNode.appendChild(hint);
}
let hint_mtmods = `Tip:
Don't include <i>Minetest</i>, <i>mod</i>, or <i>modpack</i> anywhere in the short description.
It is unnecessary and wastes characters.`;
let hint_thegame = `Tip:
It's obvious that this adds something to Minetest,
there's no need to use phrases such as \"adds X to the game\".`;
const shortDescField = document.getElementById("short_desc");
function handleShortDescChange() {
const val = shortDescField.value.toLowerCase();
if (val.indexOf("minetest") >= 0 || val.indexOf("mod") >= 0 ||
val.indexOf("modpack") >= 0 || val.indexOf("mod pack") >= 0) {
showHint(shortDescField, hint_mtmods);
} else if (val.indexOf("the game") >= 0) {
showHint(shortDescField, hint_thegame);
} else if (hint) {
hint.remove();
hint = null;
}
}
shortDescField.addEventListener("change", handleShortDescChange);
shortDescField.addEventListener("paste", handleShortDescChange);
shortDescField.addEventListener("keyup", handleShortDescChange);
})

View File

@@ -0,0 +1,64 @@
// @author rubenwardy
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
"use strict";
function getJSON(url, method) {
return new Promise((resolve, reject) => {
fetch(new Request(url, {
method: method || "get",
credentials: "same-origin",
headers: {
"Accept": "application/json",
},
})).then((response) => {
response.text().then((txt) => {
resolve(JSON.parse(txt))
}).catch(reject)
}).catch(reject)
})
}
function pollTask(poll_url, disableTimeout) {
return new Promise((resolve, reject) => {
let tries = 0;
function retry() {
tries++;
if (!disableTimeout && tries > 30) {
reject("timeout")
} else {
const interval = Math.min(tries*100, 1000)
console.log("Polling task in " + interval + "ms")
setTimeout(step, interval)
}
}
function step() {
getJSON(poll_url).then((res) => {
if (res.status === "SUCCESS") {
console.log("Got result")
resolve(res.result)
} else if (res.status === "FAILURE" || res.status === "REVOKED") {
reject(res.error || "Unknown server error")
} else {
retry()
}
}).catch(retry)
}
retry()
})
}
function performTask(url) {
return new Promise((resolve, reject) => {
getJSON(url, "post").then((startResult) => {
console.log(startResult)
if (typeof startResult.poll_url == "string") {
pollTask(startResult.poll_url).then(resolve).catch(reject)
} else {
reject("Start task didn't return string!")
}
}).catch(reject)
})
}

View File

@@ -0,0 +1,87 @@
// @author rubenwardy
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
"use strict";
function getVoteCount(button) {
const badge = button.querySelector(".badge");
return badge ? parseInt(badge.textContent) : 0;
}
function setVoteCount(button, count) {
let badge = button.querySelector(".badge");
if (count == 0) {
if (badge) {
badge.remove();
}
return;
}
if (!badge) {
badge = document.createElement("span")
badge.classList.add("badge");
badge.classList.add("bg-light");
badge.classList.add("text-dark");
badge.classList.add("ms-1");
button.appendChild(badge);
}
badge.textContent = count.toString();
}
async function submitForm(form, is_helpful) {
const data = new URLSearchParams();
for (const pair of new FormData(form)) {
data.append(pair[0], pair[1]);
}
data.set("is_positive", is_helpful ? "yes" : "no");
const res = await fetch(form.getAttribute("action"), {
method: "post",
body: data,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});
if (!res.ok) {
console.error(await res.text());
}
}
window.addEventListener("load", () => {
document.querySelectorAll(".review-helpful-vote").forEach((helpful_form) => {
const yes = helpful_form.querySelector("button[name='is_positive'][value='yes']");
const no = helpful_form.querySelector("button[name='is_positive'][value='no']");
function setVote(is_helpful) {
const selected = is_helpful ? yes : no;
const not_selected = is_helpful ? no : yes;
if (not_selected.classList.contains("btn-primary")) {
setVoteCount(not_selected, Math.max(getVoteCount(not_selected) - 1, 0));
}
if (selected.classList.contains("btn-secondary")) {
setVoteCount(selected, getVoteCount(selected) + 1);
}
selected.classList.add("btn-primary");
selected.classList.remove("btn-secondary");
not_selected.classList.add("btn-secondary");
not_selected.classList.remove("btn-primary");
submitForm(helpful_form, is_helpful).catch(console.error);
}
yes.addEventListener("click", (e) => {
setVote(true);
e.preventDefault();
});
no.addEventListener("click", (e) => {
setVote(false)
e.preventDefault();
});
});
});

View File

@@ -0,0 +1,28 @@
// @author rubenwardy
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
"use strict";
window.addEventListener("load", () => {
function setup_toggle(type) {
const toggle = document.getElementById("set_" + type);
function on_change() {
const rel = document.getElementById(type + "_rel");
if (toggle.checked) {
rel.parentElement.style.opacity = "1";
} else {
// $("#" + type + "_rel").attr("disabled", "disabled");
rel.parentElement.style.opacity = "0.4";
rel.value = document.querySelector(`#${type}_rel option:first-child`).value;
rel.dispatchEvent(new Event("change"));
}
}
toggle.addEventListener("change", on_change);
on_change();
}
setup_toggle("min");
setup_toggle("max");
});

View File

@@ -0,0 +1,25 @@
// @author rubenwardy
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
"use strict";
window.addEventListener("load", () => {
const min = document.getElementById("min_rel");
const max = document.getElementById("max_rel");
const none = parseInt(document.querySelector("#min_rel option:first-child").value);
const warning = document.getElementById("minmax_warning");
function ver_check() {
const minv = parseInt(min.value);
const maxv = parseInt(max.value);
if (minv != none && maxv != none && minv > maxv) {
warning.style.display = "block";
} else {
warning.style.display = "none";
}
}
min.addEventListener("change", ver_check);
max.addEventListener("change", ver_check);
ver_check();
});

View File

@@ -0,0 +1,19 @@
// @author rubenwardy
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
"use strict";
window.addEventListener("load", () => {
function check_opt() {
if (document.querySelector("input[name='uploadOpt']:checked").value === "vcs") {
document.getElementById("file_upload").parentElement.classList.add("d-none");
document.getElementById("vcsLabel").parentElement.classList.remove("d-none");
} else {
document.getElementById("file_upload").parentElement.classList.remove("d-none");
document.getElementById("vcsLabel").parentElement.classList.add("d-none");
}
}
document.querySelectorAll("input[name='uploadOpt']").forEach(x => x.addEventListener("change", check_opt));
check_opt();
});

View File

@@ -0,0 +1,17 @@
// @author rubenwardy
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
"use strict";
window.addEventListener("load", () => {
function update() {
const elements = [...document.querySelector(".sortable").children];
const ids = elements.map(x => x.dataset.id).filter(x => x);
document.querySelector("input[name='order']").value = ids.join(",");
}
update();
$(".sortable").sortable({
update: update
});
})

View File

@@ -0,0 +1,146 @@
/*!
* Tag Selector plugin for jQuery: Facilitates selecting multiple tags by extending jQuery UI Autocomplete.
* License: MIT
* https://petprojects.googlecode.com/svn/trunk/MIT-LICENSE.txt
*/
"use strict";
(function($) {
function make_bold(text) {
const idx = text.indexOf(":");
if (idx > 0) {
return `<b>${text.substring(0, idx)}</b><span class="text-muted">: ${text.substring(idx + 1)}`;
} else {
return `<b>${text}</b>`;
}
}
function hide_error(input) {
const err = input.parent().parent().find(".invalid-remaining");
err.hide();
}
function show_error(input, msg) {
const err = input.parent().parent().find(".invalid-remaining");
err.text(msg);
err.show();
}
$.fn.selectSelector = function(source, select) {
return this.each(function() {
const selector = $(this),
input = $('input[type=text]', this);
const lookup = {};
for (let i = 0; i < source.length; i++) {
lookup[source[i].id] = source[i];
}
selector.click(() => input.focus())
.delegate('.badge a', 'click', function() {
const id = $(this).parent().data("id");
select.find("option[value=" + id + "]").attr("selected", false)
recreate();
});
function addTag(item) {
const id = item.id;
let text = item.text;
const idx = text.indexOf(':');
if (idx > 0) {
text = text.substr(0, idx);
}
$('<span class="badge roaded-pill bg-primary"/>')
.text(text + ' ')
.data("id", id)
.append('<a>x</a>')
.insertBefore(input);
input.attr("placeholder", null);
select.find("option[value='" + id + "']").attr("selected", "selected")
hide_error(input);
}
function recreate() {
selector.find("span").remove();
select.find("option").each(function() {
if (this.hasAttribute("selected")) {
addTag(lookup[this.getAttribute("value")]);
}
});
}
recreate();
input.focusout(function() {
const value = input.val().trim();
if (value !== "") {
show_error(input, "Please select an existing tag, it's not possible to add custom ones.");
}
})
input.keydown(function(e) {
if (e.keyCode === $.ui.keyCode.TAB && $(this).data('ui-autocomplete').menu.active) {
e.preventDefault();
}
}).autocomplete({
minLength: 0,
source: source,
select: function(event, ui) {
addTag(ui.item);
input.val("");
return false;
}
}).focus(function() {
$(this).data("ui-autocomplete").search($(this).val());
});
input.data('ui-autocomplete')._renderItem = function(ul, item) {
return $('<li/>')
.data('item.autocomplete', item)
.append($('<a/>').html(item.toString()))
.appendTo(ul);
};
input.data('ui-autocomplete')._resizeMenu = function() {
const ul = this.menu.element;
ul.outerWidth(Math.max(
ul.width('').outerWidth(),
selector.outerWidth()
));
};
});
}
$(function() {
$(".multichoice_selector").each(function() {
const ele = $(this);
const sel = ele.parent().find("select");
sel.hide();
const options = [];
sel.find("option").each(function() {
const text = $(this).text();
const option = {
id: $(this).attr("value"),
text: text,
selected: !!$(this).attr("selected"),
toString: function() { return make_bold(text); },
};
const idx = text.indexOf(":");
if (idx > 0) {
option.title = text.substring(0, idx);
option.description = text.substring(idx + 1);
} else {
option.title = text
}
options.push(option);
});
ele.selectSelector(options, sel);
});
});
})(jQuery);

View File

@@ -0,0 +1,33 @@
// @author rubenwardy
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
"use strict";
document.querySelectorAll(".topic-discard").forEach(ele => ele.addEventListener("click", (e) => {
const row = ele.parentNode.parentNode;
const tid = ele.getAttribute("data-tid");
const discard = !row.classList.contains("discardtopic");
fetch(new Request("/api/topic_discard/?tid=" + tid +
"&discard=" + (discard ? "true" : "false"), {
method: "post",
credentials: "same-origin",
headers: {
"Accept": "application/json",
"X-CSRFToken": csrf_token,
},
})).then(function(response) {
response.text().then(function(txt) {
if (JSON.parse(txt).discarded) {
row.classList.add("discardtopic");
ele.classList.remove("btn-danger");
ele.classList.add("btn-success");
ele.innerText = "Show";
} else {
row.classList.remove("discardtopic");
ele.classList.remove("btn-success");
ele.classList.add("btn-danger");
ele.innerText = "Discard";
}
}).catch(console.error);
}).catch(console.error);
}));

View File

@@ -0,0 +1,39 @@
// @author rubenwardy
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
"use strict";
document.querySelectorAll(".video-embed").forEach(ele => {
try {
const href = ele.getAttribute("href");
const url = new URL(href);
if (url.host == "www.youtube.com") {
ele.addEventListener("click", () => {
ele.parentNode.classList.add("d-block");
ele.classList.add("ratio");
ele.classList.add("ratio-16x9");
ele.innerHTML = `
<iframe title="YouTube video player" frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen>
</iframe>`;
const embedURL = new URL("https://www.youtube.com/");
embedURL.pathname = "/embed/" + url.searchParams.get("v");
embedURL.searchParams.set("autoplay", "1");
const iframe = ele.children[0];
iframe.setAttribute("src", embedURL);
});
ele.setAttribute("data-src", href);
ele.removeAttribute("href");
ele.querySelector(".label").innerText = "YouTube";
}
} catch (e) {
console.error(url);
return;
}
});