Update lufi to only use Lufi API

This commit is contained in:
Booteille 2024-11-12 13:34:50 +01:00
parent e372e660cb
commit 51f3181b4a
No known key found for this signature in database
GPG Key ID: 0AB6C6CA01272646
22 changed files with 24038 additions and 470 deletions

4
.gitignore vendored
View File

@ -13,3 +13,7 @@ stop-upload
themes/* themes/*
!themes/default !themes/default
!themes/default/* !themes/default/*
.stignore
.stfolder/
TODO

View File

@ -307,7 +307,7 @@
# Set to '' to disable CSP header # Set to '' to disable CSP header
# https://content-security-policy.com/ provides a good documentation about CSP. # https://content-security-policy.com/ provides a good documentation about CSP.
# https://report-uri.com/home/generate provides a tool to generate a CSP header. # https://report-uri.com/home/generate provides a tool to generate a CSP header.
# optional, default is "base-uri 'self'; connect-src 'self' ws://YOUR_HOST; default-src 'none'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' blob:; media-src blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'" # optional, default is "base-uri 'self'; connect-src 'self' ws://YOUR_HOST; default-src 'none'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' blob:; media-src blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'"
#csp => "", #csp => "",
# X-Frame-Options header that will be sent by Lufi # X-Frame-Options header that will be sent by Lufi

View File

@ -1,6 +1,10 @@
Revision history for Lufi Revision history for Lufi
0.08.0 ????-??-?? 0.08.0 ????-??-??
- Use Lufi API
- Password encryption is now done client side, through Lufi API
- Update default CSP to allow "blob:"
0.07.0 2023-12-25 0.07.0 2023-12-25
- ⬆️ — Update jQuery - ⬆️ — Update jQuery

0
README.md Normal file → Executable file
View File

View File

@ -11,7 +11,7 @@ sub register {
if (!defined($app->config('csp')) || (defined($app->config('csp')) && $app->config('csp') ne '')) { if (!defined($app->config('csp')) || (defined($app->config('csp')) && $app->config('csp') ne '')) {
my $directives = { my $directives = {
'default-src' => "'none'", 'default-src' => "'none'",
'script-src' => "'self' 'unsafe-inline' 'unsafe-eval'", 'script-src' => "'self' 'unsafe-inline' 'unsafe-eval' blob:",
'style-src' => "'self' 'unsafe-inline'", 'style-src' => "'self' 'unsafe-inline'",
'img-src' => "'self' blob:", 'img-src' => "'self' blob:",
'media-src' => "blob:", 'media-src' => "blob:",

0
lufi-provisioning.lock Normal file
View File

View File

@ -337,7 +337,7 @@
# Set to '' to disable CSP header # Set to '' to disable CSP header
# https://content-security-policy.com/ provides a good documentation about CSP. # https://content-security-policy.com/ provides a good documentation about CSP.
# https://report-uri.com/home/generate provides a tool to generate a CSP header. # https://report-uri.com/home/generate provides a tool to generate a CSP header.
# optional, default is "base-uri 'self'; connect-src 'self' ws://YOUR_HOST; default-src 'none'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' blob:; media-src blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'" # optional, default is "base-uri 'self'; connect-src 'self' ws://YOUR_HOST; default-src 'none'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' blob:; media-src blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'"
#csp => "", #csp => "",
# X-Frame-Options header that will be sent by Lufi # X-Frame-Options header that will be sent by Lufi

File diff suppressed because one or more lines are too long

View File

@ -4,9 +4,9 @@ var entityMap = {
"&": "&", "&": "&",
"<": "&lt;", "<": "&lt;",
">": "&gt;", ">": "&gt;",
'"': '&quot;', '"': "&quot;",
"'": '&#39;', "'": "&#39;",
"/": '&#x2F;' "/": "&#x2F;",
}; };
function escapeHtml(string) { function escapeHtml(string) {
@ -18,17 +18,20 @@ function changeLang() {
window.location = langUrl + $(this).val(); window.location = langUrl + $(this).val();
} }
function formatDate(unixTimestamp) { function formatDate(unixTimestamp) {
return new Date(unixTimestamp * 1000).toLocaleString(window.navigator.language, { return new Date(unixTimestamp * 1000).toLocaleString(
year: 'numeric', window.navigator.language,
month: 'long', {
day: 'numeric', year: "numeric",
weekday: 'long', month: "long",
hour: '2-digit', day: "numeric",
minute: '2-digit', weekday: "long",
}) hour: "2-digit",
minute: "2-digit",
}
);
} }
$(document).ready(function () { $(document).ready(function () {
$('select').material_select(); $("select").material_select();
$(".select-lang select").on('change', changeLang); $(".select-lang select").on("change", changeLang);
$(".select-lang-mobile select").on('change', changeLang); $(".select-lang-mobile select").on("change", changeLang);
}); });

View File

@ -17,14 +17,17 @@ const filesizeDOM = document.getElementById("filesize");
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
let go = true; let go = true;
filesizeDOM.innerHTML = filesize(filesizeDOM.attributes.getNamedItem("data-filesize").value, { filesizeDOM.innerHTML = filesize(
filesizeDOM.attributes.getNamedItem("data-filesize").value,
{
base: 10, base: 10,
}); }
);
if (isPasswordNeeded()) { if (isPasswordNeeded()) {
go = false; go = false;
passwordDOM.focus() passwordDOM.focus();
onPasswordEvents(); onPasswordEvents();
} }
@ -34,12 +37,12 @@ document.addEventListener("DOMContentLoaded", () => {
} }
}); });
const isPasswordNeeded = () => document.querySelectorAll("#file_pwd").length === 1 const isPasswordNeeded = () =>
document.querySelectorAll("#file_pwd").length === 1;
const startDownload = () => { const startDownload = () => {
warnOnReload(); warnOnReload();
lufi lufi
.download(window.location, passwordDOM?.value) .download(window.location, passwordDOM?.value)
.andThen((job) => { .andThen((job) => {
@ -53,7 +56,7 @@ const startDownload = () => {
job.terminate(); job.terminate();
filesizeDOM.parentElement.append(abortedDOM); filesizeDOM.parentElement.append(abortedDOM);
warnOnReload(false) warnOnReload(false);
document.getElementById("reloadLocation").onclick = (e) => { document.getElementById("reloadLocation").onclick = (e) => {
e.preventDefault(); e.preventDefault();
@ -65,8 +68,8 @@ const startDownload = () => {
}) })
.mapErr((error) => { .mapErr((error) => {
addAlert(error.message); addAlert(error.message);
warnOnReload(false) warnOnReload(false);
remove(["abort"]) remove(["abort"]);
}) })
.andThen((job) => { .andThen((job) => {
notify(i18n.fileDownloaded, job.lufiFile.name); notify(i18n.fileDownloaded, job.lufiFile.name);
@ -115,20 +118,22 @@ const remove = (elements) => {
if (document.getElementById(id)) { if (document.getElementById(id)) {
document.getElementById(id).remove(); document.getElementById(id).remove();
} else { } else {
console.error(`${id} does not exist`) console.error(`${id} does not exist`);
} }
}); });
}; };
const onPasswordEvents = () => { const onPasswordEvents = () => {
const callback = () => { const callback = () => {
document.getElementsByClassName("file-progress")[0].classList.remove("hide"); document
.getElementsByClassName("file-progress")[0]
.classList.remove("hide");
document.getElementsByClassName("file-abort")[0].classList.remove("hide"); document.getElementsByClassName("file-abort")[0].classList.remove("hide");
passwordDOM.parentElement.parentElement.classList.add("hide"); passwordDOM.parentElement.parentElement.classList.add("hide");
startDownload(); startDownload();
} };
document.getElementById("go").onclick = () => { document.getElementById("go").onclick = () => {
callback(); callback();
@ -138,8 +143,8 @@ const onPasswordEvents = () => {
if (event.key === "Enter") { if (event.key === "Enter") {
callback(); callback();
} }
} };
} };
// Something's wring // Something's wring
const addAlert = (msg) => { const addAlert = (msg) => {
@ -156,7 +161,7 @@ const addAlert = (msg) => {
<strong>${msg}</strong> <strong>${msg}</strong>
</div> </div>
</div>`; </div>`;
} };
const warnOnReload = (toWarn = true) => { const warnOnReload = (toWarn = true) => {
if (toWarn) { if (toWarn) {
@ -164,14 +169,15 @@ const warnOnReload = (toWarn = true) => {
} else { } else {
window.onbeforeunload = null; window.onbeforeunload = null;
} }
} };
const updateProgress = (lufiFile) => { const updateProgress = (lufiFile) => {
// Update loading text // Update loading text
loadingDOM.textContent = i18n.loading.replace(/XX1/, lufiFile.chunksReady); loadingDOM.textContent = i18n.loading.replace(/XX1/, lufiFile.chunksReady);
// Update progress bar // Update progress bar
const percent = Math.round((1000 * lufiFile.chunksReady) / lufiFile.totalChunks) / 10; const percent =
Math.round((1000 * lufiFile.chunksReady) / lufiFile.totalChunks) / 10;
const wClass = percent.toString().replace(".", "-"); const wClass = percent.toString().replace(".", "-");
const pb = document.getElementById("pb"); const pb = document.getElementById("pb");
@ -179,59 +185,61 @@ const updateProgress = (lufiFile) => {
pb.attributes.getNamedItem("aria-valuenow").value = percent; pb.attributes.getNamedItem("aria-valuenow").value = percent;
document.getElementById("pbt").innerHTML = `${percent}%`; document.getElementById("pbt").innerHTML = `${percent}%`;
} };
const showZipContent = (blob) => { const showZipContent = (blob) => {
const showZipContentDOM = document.getElementById('showZipContent'); const showZipContentDOM = document.getElementById("showZipContent");
const showZipContentDOMListener = () => { const showZipContentDOMListener = () => {
JSZip.loadAsync(blob) JSZip.loadAsync(blob).then((zip) => {
.then((zip) => {
const newElement = document.createElement("div"); const newElement = document.createElement("div");
let innerHTML = `<h3>${i18n.zipContent}</h3><ul>`; let innerHTML = `<h3>${i18n.zipContent}</h3><ul>`;
zip.forEach(function (_relativePath, zipEntry) { zip.forEach(function (_relativePath, zipEntry) {
innerHTML += `<li> innerHTML += `<li>
${escapeHtml(zipEntry.name)} ${escapeHtml(zipEntry.name)}
(${filesize(zipEntry._data.uncompressedSize, { base: 10 })}) (${filesize(zipEntry._data.uncompressedSize, {
base: 10,
})})
<a href="#" <a href="#"
download="${escapeHtml(zipEntry.name)}" download="${escapeHtml(zipEntry.name)}"
class="download-zip-content" class="download-zip-content"
title="${i18n.download}"> title="${i18n.download}">
<i class="mdi-file-file-download"></i> <i class="mdi-file-file-download"></i>
</a> </a>
</li>` </li>`;
}); });
innerHTML += '</ul>'; innerHTML += "</ul>";
newElement.innerHTML = innerHTML newElement.innerHTML = innerHTML;
pbd.append(newElement); pbd.append(newElement);
console.debug() console.debug();
document.querySelectorAll('.download-zip-content').forEach((element) => { document.querySelectorAll(".download-zip-content").forEach((element) => {
const elementListener = (e) => { const elementListener = (e) => {
e.preventDefault(); e.preventDefault();
var filename = element.getAttribute('download'); var filename = element.getAttribute("download");
zip.files[filename].async('blob').then((blob) => { zip.files[filename].async("blob").then((blob) => {
element.removeEventListener('click', elementListener); element.removeEventListener("click", elementListener);
element.setAttribute('href', URL.createObjectURL(blob)); element.setAttribute("href", URL.createObjectURL(blob));
element.click(); element.click();
}); });
}; };
element.addEventListener('click', elementListener); element.addEventListener("click", elementListener);
showZipContentDOM.style.display = "none"; showZipContentDOM.style.display = "none";
showZipContentDOM.removeEventListener('click', showZipContentDOMListener); showZipContentDOM.removeEventListener(
"click",
showZipContentDOMListener
);
});
}); });
})
}; };
showZipContentDOM.onclick = showZipContentDOMListener showZipContentDOM.onclick = showZipContentDOMListener;
};
}

View File

@ -1,39 +1,32 @@
// vim:set sw=4 ts=4 sts=4 ft=javascript expandtab: // vim:set sw=4 ts=4 sts=4 ft=javascript expandtab:
// Add item to localStorage // Add item to localStorage
function addItem(item) { const addItem = (item) => {
var files = localStorage.getItem(`${window.prefix}files`); const files = JSON.parse(localStorage.getItem(`${window.prefix}files`)) || [];
if (files === null) {
files = new Array();
} else {
files = JSON.parse(files);
}
files.push(item); files.push(item);
localStorage.setItem(`${window.prefix}files`, JSON.stringify(files)); localStorage.setItem(`${window.prefix}files`, JSON.stringify(files));
} };
function delItem(name) { const delItem = (name) => {
var files = localStorage.getItem(`${window.prefix}files`); const files = JSON.parse(localStorage.getItem(`${window.prefix}files`)) || [];
if (files === null) {
files = new Array(); let i;
} else {
files = JSON.parse(files);
}
var i;
for (i = 0; i < files.length; i++) { for (i = 0; i < files.length; i++) {
if (files[i].short === name) { if (files[i].short === name) {
files.splice(i, 1); files.splice(i, 1);
} }
} }
localStorage.setItem(`${window.prefix}files`, JSON.stringify(files)); localStorage.setItem(`${window.prefix}files`, JSON.stringify(files));
} };
function itemExists(name) { const itemExists = (name) => {
var files = localStorage.getItem(`${window.prefix}files`); let files = localStorage.getItem(`${window.prefix}files`);
if (files === null) { if (files === null) {
return false; return false;
} else { } else {
files = JSON.parse(files); files = JSON.parse(files);
var i;
let i;
for (i = 0; i < files.length; i++) { for (i = 0; i < files.length; i++) {
if (files[i].short === name) { if (files[i].short === name) {
return true; return true;
@ -41,72 +34,96 @@ function itemExists(name) {
} }
return false; return false;
} }
} };
function invertSelection(event) { const invertSelection = (event) => {
event.preventDefault(); event.preventDefault();
$('input[type="checkbox"]').each(function() { document.querySelectorAll('input[type="checkbox"]').forEach((element) => {
var el = $(this); element.click();
el.click();
if (el.attr('data-checked') && el.attr('data-checked') === 'data-checked') { if (element.getAttribute("data-checked") === "data-checked") {
el.attr('data-checked', null); element.setAttribute("data-checked", null);
} else { } else {
el.attr('data-checked', 'data-checked'); element.setAttribute("data-checked", "data-checked");
} }
}); });
evaluateMassDelete(); evaluateMassDelete();
} };
function purgeExpired(event) { const purgeExpired = (event) => {
event.preventDefault(); event.preventDefault();
var files = JSON.parse(localStorage.getItem(`${window.prefix}files`));
files.forEach(function(element, index, array) { const files = JSON.parse(localStorage.getItem(`${window.prefix}files`));
$.ajax({
url: counterURL, files.forEach(function (element) {
method: 'POST', fetch(counterURL, {
dataType: 'json', method: "POST",
data: { headers: {
short: element.short, "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
token: element.token
}, },
success: function(data, textStatus, jqXHR) { body: new URLSearchParams({
short: element.short,
token: element.token,
}),
})
.then((response) => {
if (!response.ok) {
throw new Error("Request error.");
}
return response.json();
})
.then((data) => {
if (data.success) { if (data.success) {
if (data.deleted) { if (data.deleted) {
$(`#count-${data.short}`).parent().remove(); const elementToRemove = document.querySelector(
`#count-${data.short}`
);
if (elementToRemove) {
elementToRemove.parentElement.remove();
}
delItem(data.short); delItem(data.short);
} }
} }
}
}); });
}); });
} };
function exportStorage(event) { const exportStorage = (event) => {
event.preventDefault(); event.preventDefault();
var a = $('<a id="data-json">');
a.hide();
$('body').append(a);
var storageData = [localStorage.getItem(`${window.prefix}files`)]; const a = document.createElement("a");
var exportFile = new Blob(storageData, {type : 'application/json'}); a.id = "data-json";
var url = window.URL.createObjectURL(exportFile);
a.attr('href', url); a.style.display = "none";
a.attr('download', 'data.json');
$('#data-json')[0].click();
$('#data-json').remove();
}
function importStorage(f) { document.body.append(a);
var reader = new FileReader();
reader.addEventListener("loadend", function() { const storageData = [localStorage.getItem(`${window.prefix}files`)];
const exportFile = new Blob(storageData, { type: "application/json" });
const url = window.URL.createObjectURL(exportFile);
a.setAttribute("href", url);
a.setAttribute("download", "data.json");
a.click();
a.remove();
};
const importStorage = (f) => {
let reader = new FileReader();
reader.addEventListener("loadend", () => {
try { try {
var newFiles = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(reader.result))); const newFiles = JSON.parse(
var i; String.fromCharCode.apply(null, new Uint8Array(reader.result))
var hasImported = 0; );
let i;
let hasImported = 0;
for (i = 0; i < newFiles.length; i++) { for (i = 0; i < newFiles.length; i++) {
var item = newFiles[i]; const item = newFiles[i];
if (validURL(item.url) && !itemExists(item.short)) { if (validURL(item.url) && !itemExists(item.short)) {
addItem(item); addItem(item);
hasImported++; hasImported++;
@ -115,103 +132,117 @@ function importStorage(f) {
populateFilesTable(); populateFilesTable();
Materialize.toast(i18n.importProcessed); Materialize.toast(i18n.importProcessed);
} catch(err) { } catch (err) {
alert(err); alert(err);
} }
}); });
reader.readAsArrayBuffer(f[0]); reader.readAsArrayBuffer(f[0]);
} };
function validURL(str) { const validURL = (str) => {
try { try {
var url = new URL(str); return new URL(str).host ? true : false;
if (url.host) { } catch (e) {
return true;
} else {
return false; return false;
} }
} catch(e) { };
return false;
}
}
function delFile() { const delFile = (element) => {
var dlink = $(this).attr('data-dlink'); const deleteUrl = new URL(element.getAttribute("data-dlink"));
var short = $(this).attr('data-short'); const short = element.getAttribute("data-short");
$.ajax({
url: dlink, deleteUrl.searchParams.append("_format", "json");
method: 'GET',
data: { fetch(deleteUrl, {
_format: 'json' method: "GET",
}, })
success: function(data) { .then((response) => {
if (!response.ok) {
throw new Error("Network error while deleting file");
}
return response.json();
})
.then((data) => {
if (data.success) { if (data.success) {
$(`#row-${short}`).remove(); document.getElementById(`row-${short}`).remove();
delItem(short); delItem(short);
} else { } else {
alert(data.msg); alert(data.msg);
} }
evaluateMassDelete(); evaluateMassDelete();
},
error: function() {
},
complete: function() {
}
}); });
} };
function evaluateMassDelete() { const evaluateMassDelete = () => {
if ($('input[data-checked="data-checked"]').length > 0) { const massDeleteDOM = document.getElementById("mass-delete");
$('#mass-delete').removeAttr('disabled');
$('#mass-delete').removeClass('disabled'); if (
document.querySelectorAll('input[data-checked="data-checked"]').length > 0
) {
massDeleteDOM.removeAttribute("disabled");
massDeleteDOM.classList.remove("disabled");
} else { } else {
$('#mass-delete').attr('disabled'); massDeleteDOM.setAttribute("disabled", "disabled");
$('#mass-delete').addClass('disabled'); massDeleteDOM.classList.add("disabled");
} }
} };
function massDelete(event) { const massDelete = (event) => {
event.preventDefault(); event.preventDefault();
$('input[data-checked="data-checked"]').each(delFile); document
} .querySelectorAll('input[data-checked="data-checked"]')
.forEach(delFile);
};
function populateFilesTable() { const populateFilesTable = () => {
$('#myfiles').empty(); const myFilesDOM = document.getElementById("myfiles");
var files = localStorage.getItem(`${window.prefix}files`); myFilesDOM.innerHTML = "";
let files = localStorage.getItem(`${window.prefix}files`);
if (files === null) { if (files === null) {
var filesWithoutPrefix = localStorage.getItem('files'); var filesWithoutPrefix = localStorage.getItem("files");
if (filesWithoutPrefix !== null) { if (filesWithoutPrefix !== null) {
if (window.confirm(i18n.importFilesWithoutPrefix)) { if (window.confirm(i18n.importFilesWithoutPrefix)) {
localStorage.setItem(`${window.prefix}files`, filesWithoutPrefix); localStorage.setItem(`${window.prefix}files`, filesWithoutPrefix);
files = JSON.parse(filesWithoutPrefix); files = JSON.parse(filesWithoutPrefix);
} else { } else {
localStorage.setItem(`${window.prefix}files`, JSON.stringify([])); localStorage.setItem(`${window.prefix}files`, JSON.stringify([]));
files = new Array(); files = [];
} }
} else { } else {
files = new Array(); files = [];
} }
} else { } else {
files = JSON.parse(files); files = JSON.parse(files);
} }
files.sort(function(a, b) {
files.sort(function (a, b) {
if (a.created_at < b.created_at) { if (a.created_at < b.created_at) {
return -1; return -1;
} else if (a.created_at > b.created_at) { } else if (a.created_at > b.created_at) {
return 1; return 1;
} else { } else {
return 0 return 0;
} }
}); });
files.forEach(function(element, index, array) {
var del_view = (element.del_at_first_view) ? '<i class="small mdi-action-done"></i>' : '<i class="small mdi-navigation-close"></i>';
var dlink = `${actionURL}d/${element.short}/${element.token}`;
var limit = (element.delay === 0) ? i18n.noExpiration : formatDate(element.delay * 86400 + element.created_at);
var created_at = formatDate(element.created_at);
var tr = $(`<tr id="row-${element.short}">`); files.forEach(function (element) {
tr.html(`<td class="center-align"> const del_view = element.del_at_first_view
? '<i class="small mdi-action-done"></i>'
: '<i class="small mdi-navigation-close"></i>';
const dlink = `${actionURL}d/${element.short}/${element.token}`;
const limit =
element.delay === 0
? i18n.noExpiration
: formatDate(element.delay * 86400 + element.created_at);
const created_at = formatDate(element.created_at);
const tr = document.createElement("tr");
tr.id = `row-${element.short}`;
tr.innerHTML = `<td class="center-align">
<input type="checkbox" <input type="checkbox"
id="check-${element.short}" id="check-${element.short}"
data-short="${element.short}" data-short="${element.short}"
@ -249,47 +280,72 @@ function populateFilesTable() {
</a> </a>
</td> </td>
<td class="center-align"> <td class="center-align">
<a href="${actionURL}m?links=[&quot;${element.short}&quot;]" <a href="${actionURL}m?links=[&quot;${
element.short
}&quot;]"
class="classic"><i class="small mdi-communication-email"></i></a> class="classic"><i class="small mdi-communication-email"></i></a>
</td>`); </td>`;
$('#myfiles').append(tr);
$(`#del-${element.short}`).on('click', delFile);
$(`label[for="check-${element.short}"]`).on('click', function(){
if ($(`#check-${element.short}`).attr('data-checked') && $(`#check-${element.short}`).attr('data-checked') === 'data-checked') {
$(`#check-${element.short}`).attr('data-checked', null);
} else {
$(`#check-${element.short}`).attr('data-checked', 'data-checked');
}
evaluateMassDelete();
});
$.ajax({ myFilesDOM.append(tr);
url: counterURL,
method: 'POST', document.getElementById(`del-${element.short}`).onclick = (event) =>
dataType: 'json', delFile(event.target.parentElement);
data: {
short: element.short, document.querySelector(`label[for="check-${element.short}"]`).onclick = (
token: element.token event
}, ) => {
success: function(data, textStatus, jqXHR) { const checkDOM = document.getElementById(`check-${element.short}`);
if (data.success) {
$(`#count-${data.short}`).html(data.counter); if (checkDOM.getAttribute("data-checked") === "data-checked") {
if (data.deleted) { checkDOM.setAttribute("data-checked", null);
$(`#count-${data.short}`).parent().addClass('purple lighten-4'); } else {
checkDOM.setAttribute("data-checked", "data-checked");
} }
evaluateMassDelete();
};
fetch(counterURL, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
},
body: new URLSearchParams({
short: element.short,
token: element.token,
}),
})
.then((response) => {
if (!response.ok) {
throw new Error("Request error.");
}
return response.json();
})
.then((data) => {
if (data.success) {
if (data.deleted) {
const countDOM = document.getElementById(`count-${data.short}`);
countDOM.innerHTML = data.counter;
if (data.deleted) {
countDOM.parentElement.classList.add("purple", "lighten-4");
} else { } else {
alert(data.msg); alert(data.msg);
$(`#count-${data.short}`).parent().remove(); countDOM.parentElement.remove();
if (data.missing) { if (data.missing) {
delItem(data.short); delItem(data.short);
} }
} }
} }
}
}); });
}); });
} };
function clickImport(event) { const clickImport = (event) => {
event.preventDefault(); event.preventDefault();
$('#import').click(); document.getElementById("import").click();
} };

View File

@ -1,24 +1,25 @@
// vim:set sw=4 ts=4 sts=4 ft=javascript expandtab: // vim:set sw=4 ts=4 sts=4 ft=javascript expandtab:
import { lufi, okAsync, errAsync } from "/js/lufi.js"; import {
lufi,
okAsync,
ResultAsync,
isSecureContext,
CryptoAlgorithm,
} from "/js/lufi.js";
// Cancelled files indexes // Cancelled files indexes
window.cancelled = []; window.cancelled = [];
// Set websocket
// window.ws = spawnWebsocket(0, function () {
// return null;
// });
// Use slice of 0.75MB
window.sliceLength = 750000;
// Global zip objects for currently created zip file // Global zip objects for currently created zip file
window.zip = null;
window.zipSize = 0; window.zipSize = 0;
// Init the list of files (used by LDAP invitation feature) // Init the list of files (used by LDAP invitation feature)
window.filesURLs = []; window.filesURLs = [];
let archiveEntries;
// Copy a link to clipboard // Copy a link to clipboard
function copyToClipboard(txt) { function copyToClipboard(txt) {
var textArea = document.createElement("textarea"); const textArea = document.createElement("textarea");
textArea.className = "textarea-hidden"; textArea.className = "textarea-hidden";
textArea.value = txt; textArea.value = txt;
@ -56,7 +57,16 @@ function copyAllToClipboard(event) {
} }
// Add item to localStorage // Add item to localStorage
function addItem(name, url, size, del_at_first_view, created_at, delay, short, token) { function addItem(
name,
url,
size,
del_at_first_view,
created_at,
delay,
short,
token
) {
let files = localStorage.getItem(`${window.prefix}files`); let files = localStorage.getItem(`${window.prefix}files`);
files = JSON.parse(files) || []; files = JSON.parse(files) || [];
@ -79,7 +89,10 @@ function destroyBlock(clientKey) {
if (document.querySelectorAll(".link-input").length === 0) { if (document.querySelectorAll(".link-input").length === 0) {
document.getElementById("misc").innerHTML = ""; document.getElementById("misc").innerHTML = "";
if (document.querySelectorAll("#results li").length === 0 && window.fileList === null) { if (
document.querySelectorAll("#results li").length === 0 &&
window.fileList === null
) {
document.getElementById("results").style.display = "none"; document.getElementById("results").style.display = "none";
} }
} else { } else {
@ -93,7 +106,8 @@ function firstViewClicking() {
.getElementById("first-view") .getElementById("first-view")
.setAttribute( .setAttribute(
"data-checked", "data-checked",
document.getElementById("first-view").getAttribute("data-checked") === "data-checked" document.getElementById("first-view").getAttribute("data-checked") ===
"data-checked"
? null ? null
: "data-checked" : "data-checked"
); );
@ -101,9 +115,11 @@ function firstViewClicking() {
// When clicking on zip checkbox // When clicking on zip checkbox
function zipClicking() { function zipClicking() {
if (document.getElementById("zip-files").getAttribute("data-checked") === "data-checked") { if (
document.getElementById("zip-files").getAttribute("data-checked") ===
"data-checked"
) {
window.zipSize = 0; window.zipSize = 0;
window.zip = null;
document.getElementById("zip-files").removeAttribute("data-checked"); document.getElementById("zip-files").removeAttribute("data-checked");
document.getElementById("zipname").value = "documents.zip"; document.getElementById("zipname").value = "documents.zip";
document.getElementById("zipname-input").classList.add("hide"); document.getElementById("zipname-input").classList.add("hide");
@ -112,7 +128,9 @@ function zipClicking() {
document.getElementById("zip-parts").innerHTML = ""; document.getElementById("zip-parts").innerHTML = "";
document.getElementById("delete-day").removeAttribute("disabled"); document.getElementById("delete-day").removeAttribute("disabled");
} else { } else {
document.getElementById("zip-files").setAttribute("data-checked", "data-checked"); document
.getElementById("zip-files")
.setAttribute("data-checked", "data-checked");
document.getElementById("zipname-input").classList.remove("hide"); document.getElementById("zipname-input").classList.remove("hide");
document.getElementById("zip-size").innerText = filesize(window.zipSize); document.getElementById("zip-size").innerText = filesize(window.zipSize);
} }
@ -120,14 +138,10 @@ function zipClicking() {
// Get the zip file name // Get the zip file name
const getZipname = () => { const getZipname = () => {
var zipname = document.getElementById("zipname").value || "documents.zip"; let zipname = document.getElementById("zipname").value || "documents.zip";
if (!zipname.endsWith(".zip")) { if (!zipname.endsWith(".zip")) {
if (zipname.endsWith(".")) { zipname += zipname.endsWith(".") ? "zip" : ".zip";
zipname += "zip";
} else {
zipname += ".zip";
}
} }
return escapeHtml(zipname); return escapeHtml(zipname);
@ -141,8 +155,8 @@ const updateZipname = () => {
// Create blob from zip // Create blob from zip
const uploadZip = (e) => { const uploadZip = (e) => {
e.preventDefault(); e.preventDefault();
var delay = document.getElementById("delete-day"); const delay = document.getElementById("delete-day");
var del_at_first_view = document.getElementById("first-view"); const del_at_first_view = document.getElementById("first-view");
document.getElementById("zip-files").disabled = true; document.getElementById("zip-files").disabled = true;
document.getElementById("file-browser-button").disabled = true; document.getElementById("file-browser-button").disabled = true;
document.getElementById("file-browser-span").classList.add("disabled"); document.getElementById("file-browser-span").classList.add("disabled");
@ -150,10 +164,14 @@ const uploadZip = (e) => {
document.getElementById("zip-parts").textContent = ""; document.getElementById("zip-parts").textContent = "";
document.getElementById("zip-compressing").classList.remove("hide"); document.getElementById("zip-compressing").classList.remove("hide");
window.zip.generateAsync({ type: "blob" }).then((zipFile) => {
const zipname = getZipname();
lufi
.compress(archiveEntries, zipname)
.andThen((zipFile) => {
// if '#zipping' is hidden, the zipping has been aborted // if '#zipping' is hidden, the zipping has been aborted
if (!document.getElementById("zipping").classList.contains("hide")) { if (!document.getElementById("zipping").classList.contains("hide")) {
window.zip = null;
document.getElementById("zipping").classList.add("hide"); document.getElementById("zipping").classList.add("hide");
document.getElementById("files").classList.remove("m6"); document.getElementById("files").classList.remove("m6");
document.getElementById("files").classList.add("m12"); document.getElementById("files").classList.add("m12");
@ -162,21 +180,36 @@ const uploadZip = (e) => {
document.getElementById("uploadZip").classList.remove("hide"); document.getElementById("uploadZip").classList.remove("hide");
document.getElementById("results").style.display = "block"; document.getElementById("results").style.display = "block";
document.getElementById("zip-files").disabled = false; document.getElementById("zip-files").disabled = false;
document.getElementById("zip-files").removeAttribute("data-checked");
document.getElementById("zip-files").checked = false;
var zipname = getZipname();
const password = document.getElementById("file_pwd").value; const password = document.getElementById("file_pwd").value;
var file = new File([zipFile], zipname, { type: "application/zip" }); Materialize.toast(
i18n.enqueued.replace("XXX", zipname),
3000,
"teal accent-3"
);
Materialize.toast(i18n.enqueued.replace("XXX", zipname), 3000, "teal accent-3"); window.fileList = window.fileList || [zipFile];
window.fileList = window.fileList || [file]; startUpload(
[zipFile],
delay.value,
del_at_first_view.checked,
true,
zipname,
password
);
startUpload(file, delay.value, del_at_first_view.checked, true, password); archiveEntries = undefined;
} }
document.getElementById("file-browser-button").disabled = false; document.getElementById("file-browser-button").disabled = false;
document.getElementById("file-browser-span").classList.remove("disabled"); document.getElementById("file-browser-span").classList.remove("disabled");
});
return okAsync(undefined);
})
.orElse((error) => console.error(error.message));
}; };
// Update the mail link // Update the mail link
@ -239,8 +272,7 @@ const handleDrop = (evt) => {
evt.stopPropagation(); evt.stopPropagation();
evt.preventDefault(); evt.preventDefault();
var f = evt.dataTransfer.files; // FileList object handleFiles(evt.dataTransfer.files);
handleFiles(f);
}; };
const handleDragOver = (evt) => { const handleDragOver = (evt) => {
@ -259,7 +291,9 @@ const bindDropZone = () => {
document.getElementById("file-browser-span").classList.add("cyan"); document.getElementById("file-browser-span").classList.add("cyan");
document.getElementById("file-browser-button").disabled = false; document.getElementById("file-browser-button").disabled = false;
document.getElementById("file-browser-button").addEventListener("change", (e) => { document
.getElementById("file-browser-button")
.addEventListener("change", (e) => {
handleFiles(e.target.files); handleFiles(e.target.files);
}); });
}; };
@ -278,28 +312,51 @@ document.addEventListener("DOMContentLoaded", () => {
); );
} }
document.querySelector('label[for="first-view"]').addEventListener("click", firstViewClicking); document
document.querySelector('label[for="zip-files"]').addEventListener("click", zipClicking); .querySelector('label[for="first-view"]')
.addEventListener("click", firstViewClicking);
document
.querySelector('label[for="zip-files"]')
.addEventListener("click", zipClicking);
document.getElementById("zipname").addEventListener("input", updateZipname); document.getElementById("zipname").addEventListener("input", updateZipname);
document.getElementById("uploadZip").addEventListener("click", uploadZip); document.getElementById("uploadZip").addEventListener("click", uploadZip);
document.getElementById("reset-zipping").addEventListener("click", () => { document.getElementById("reset-zipping").addEventListener("click", () => {
window.zip = null; archiveEntries = undefined;
document.querySelector('label[for="zip-files"]').click(); document.querySelector('label[for="zip-files"]').click();
document.getElementById("zip-files").disabled = false; document.getElementById("zip-files").disabled = false;
document.getElementById("zip-files").removeAttribute("data-checked");
document.getElementById("zip-compressing").classList.add("hide"); document.getElementById("zip-compressing").classList.add("hide");
document.getElementById("file-browser-button").disabled = false; document.getElementById("file-browser-button").disabled = false;
document.getElementById("file-browser-span").classList.remove("disabled"); document.getElementById("file-browser-span").classList.remove("disabled");
document.getElementById("files").classList.remove("m6").classList.add("m12"); document.getElementById("files").classList.replace("m6", "m12");
}); });
}); });
const startUpload = (file, delay, delAtFirstView, isZipped, password) => { const startUpload = (
files,
delay,
delAtFirstView,
isZipped,
zipName,
password
) => {
let clientKey; let clientKey;
lufi return lufi
.upload(window.location, file, delay, delAtFirstView, isZipped, password) .upload(
.andThen((job) => { window.location,
files,
delay,
delAtFirstView,
isZipped,
zipName,
password,
isSecureContext() ? CryptoAlgorithm.WebCrypto : CryptoAlgorithm.Sjcl
)
.andThen((jobs) =>
ResultAsync.combine(
jobs.map((job) => {
clientKey = job.lufiFile.keys.client; clientKey = job.lufiFile.keys.client;
createUploadBox(job); createUploadBox(job);
@ -308,8 +365,8 @@ const startUpload = (file, delay, delAtFirstView, isZipped, password) => {
updateProgressBar(job.lufiFile); updateProgressBar(job.lufiFile);
}); });
return job.waitForCompletion(); return job
}) .waitForCompletion()
.andThen((job) => { .andThen((job) => {
notify(i18n.fileUploaded, job.lufiFile.name); notify(i18n.fileUploaded, job.lufiFile.name);
@ -360,17 +417,24 @@ const startUpload = (file, delay, delAtFirstView, isZipped, password) => {
sendFilesURLs(); sendFilesURLs();
} }
}); });
})
)
);
}; };
const handleFiles = (files = []) => { const handleFiles = (files = []) => {
const filesArray = Array.from(files); const filesArray = Array.from(files);
const isZipped = const isZipped =
document.getElementById("zip-files").getAttribute("data-checked") === "data-checked"; document.getElementById("zip-files").getAttribute("data-checked") ===
"data-checked";
document.body.style.cursor = "wait";
if (!isZipped) { if (!isZipped) {
const delay = document.getElementById("delete-day").value; const delay = document.getElementById("delete-day").value;
const delAtFirstView = const delAtFirstView =
document.getElementById("first-view").getAttribute("data-checked") === "data-checked"; document.getElementById("first-view").getAttribute("data-checked") ===
"data-checked";
const password = document.getElementById("file_pwd").value; const password = document.getElementById("file_pwd").value;
if (window.fileList === undefined || window.fileList === null) { if (window.fileList === undefined || window.fileList === null) {
@ -392,43 +456,43 @@ const handleFiles = (files = []) => {
window.fileList = window.fileList.concat(filesArray); // Concatenate new files window.fileList = window.fileList.concat(filesArray); // Concatenate new files
} }
filesArray.forEach((file) => { document.body.style.cursor = "default";
startUpload(file, delay, delAtFirstView, isZipped, password);
}); startUpload(filesArray, delay, delAtFirstView, isZipped, password);
} else { } else {
window.zip = window.zip || new JSZip(); lufi
.addFilesToArchive(filesArray, archiveEntries)
.andThen((entries) => {
archiveEntries = entries;
document.getElementById("zipping").classList.remove("hide"); document.getElementById("zipping").classList.remove("hide");
const filesElement = document.getElementById("files"); const filesElement = document.getElementById("files");
filesElement.classList.remove("m12"); filesElement.classList.replace("m12", "m6");
filesElement.classList.add("m6");
for (let i = 0; i < files.length; i++) { const zipPartsDOM = document.getElementById("zip-parts");
const element = files.item(i);
let filename = element.name;
const originalName = filename;
let counter = 0;
// Ensure unique filename zipPartsDOM.replaceChildren();
while (window.zip.files[filename] !== undefined) {
counter++; for (const [name, file] of Object.entries(archiveEntries)) {
const extensionIndex = originalName.lastIndexOf("."); window.zipSize += file.length;
const baseName = originalName.substring(0, extensionIndex);
const extension = originalName.substring(extensionIndex); const listItemDOM = document.createElement("li");
filename = `${baseName}_(${counter})${extension}`; listItemDOM.innerHTML = `${escapeHtml(name)} (${filesize(
file.length
)})`;
zipPartsDOM.appendChild(listItemDOM);
} }
// Add the file to the zip document.getElementById("zip-size").textContent = filesize(
window.zip.file(filename, element); window.zipSize
window.zipSize += element.size; );
// Update DOM document.body.style.cursor = "default";
document.getElementById("zip-size").textContent = filesize(window.zipSize);
const zipPartsElement = document.getElementById("zip-parts"); return okAsync(undefined);
const listItem = document.createElement("li"); })
listItem.innerHTML = `${escapeHtml(filename)} (${filesize(element.size)})`; .orElse((error) => console.error(error.message));
zipPartsElement.appendChild(listItem);
}
} }
}; };
@ -450,7 +514,9 @@ const createUploadBox = (job) => {
<div class="card-content"> <div class="card-content">
<span class="card-title" <span class="card-title"
id="name-${clientKey}">${job.lufiFile.name}</span> id="name-${clientKey}">${job.lufiFile.name}</span>
<span id="size-${clientKey}">(${filesize(job.lufiFile.size)})</span> <span id="size-${clientKey}">(${filesize(
job.lufiFile.size
)})</span>
<p id="parts-${clientKey}"></p> <p id="parts-${clientKey}"></p>
</div> </div>
<div class="progress"> <div class="progress">
@ -501,7 +567,9 @@ const uploadBoxComplete = (lufiFile) => {
const limit = const limit =
lufiFile.delay === 0 lufiFile.delay === 0
? i18n.noLimit ? i18n.noLimit
: `${i18n.expiration} ${formatDate(lufiFile.delay * 86400 + lufiFile.createdAt)}`; : `${i18n.expiration} ${formatDate(
lufiFile.delay * 86400 + lufiFile.createdAt
)}`;
if (!isGuest) { if (!isGuest) {
nameDOM.innerHTML += `${sizeDOM.innerHTML} <a href="${actionURL}m?links=${links}"><i class="mdi-communication-email"></i></a><br>${limit}`; nameDOM.innerHTML += `${sizeDOM.innerHTML} <a href="${actionURL}m?links=${links}"><i class="mdi-communication-email"></i></a><br>${limit}`;
@ -542,7 +610,9 @@ const uploadBoxComplete = (lufiFile) => {
p1.appendChild(newDivDOM); p1.appendChild(newDivDOM);
// Copy URL to clipboard // Copy URL to clipboard
document.getElementById(`copyurl-${clientKey}`).addEventListener("click", (e) => { document
.getElementById(`copyurl-${clientKey}`)
.addEventListener("click", (e) => {
e.preventDefault(); e.preventDefault();
copyToClipboard(downloadUrl); copyToClipboard(downloadUrl);
}); });
@ -562,14 +632,17 @@ const uploadBoxComplete = (lufiFile) => {
<a href="#" id="copyall" class="btn btn-info">${i18n.copyAll}</a> <a href="#" id="copyall" class="btn btn-info">${i18n.copyAll}</a>
<a id="mailto" href="${actionURL}m?links=${links}" class="btn btn-info">${i18n.mailTo}</a> <a id="mailto" href="${actionURL}m?links=${links}" class="btn btn-info">${i18n.mailTo}</a>
`; `;
document.getElementById("copyall").addEventListener("click", copyAllToClipboard); document
.getElementById("copyall")
.addEventListener("click", copyAllToClipboard);
} else { } else {
updateMailLink(); updateMailLink();
} }
}; };
const updateProgressBar = (lufiFile) => { const updateProgressBar = (lufiFile) => {
const percent = Math.round((1000 * lufiFile.chunksReady) / lufiFile.totalChunks) / 10; const percent =
Math.round((1000 * lufiFile.chunksReady) / lufiFile.totalChunks) / 10;
const wClass = percent.toString().replace(".", "-"); const wClass = percent.toString().replace(".", "-");
const dp = document.getElementById(`progress-${lufiFile.keys.client}`); const dp = document.getElementById(`progress-${lufiFile.keys.client}`);
@ -578,5 +651,7 @@ const updateProgressBar = (lufiFile) => {
dp.classList.add(`width-${wClass}`); dp.classList.add(`width-${wClass}`);
dp.setAttribute("aria-valuenow", percent); dp.setAttribute("aria-valuenow", percent);
document.getElementById(`parts-${lufiFile.keys.client}`).innerHTML = `${percent.toFixed(1)}%`; document.getElementById(
`parts-${lufiFile.keys.client}`
).innerHTML = `${percent.toFixed(1)}%`;
}; };

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
$(document).ready(function() { $(document).ready(function () {
$(".button-collapse").sideNav(); $(".button-collapse").sideNav();
}); });

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -13,10 +13,10 @@ var i18n = {
}; };
$(document).ready(function() { $(document).ready(function() {
populateFilesTable(); populateFilesTable();
$('#invertSelection').on('click', invertSelection); document.getElementById('invertSelection').onclick = (event) => invertSelection(event);
$('#exportStorage').on('click', exportStorage); document.getElementById('exportStorage').onclick = (event) => exportStorage(event);
$('#purgeExpired').on('click', purgeExpired); document.getElementById('purgeExpired').onclick = (event) => purgeExpired(event);
$('#clickImport').on('click', clickImport); document.getElementById('clickImport').onclick = (event) => clickImport(event);
$('#mass-delete').on('click', massDelete); document.getElementById('mass-delete').onclick = (event) => massDelete(event);
}); });