WIP state

This commit is contained in:
Booteille 2024-12-06 16:38:45 +01:00
parent 4aff334677
commit 3640e22239
No known key found for this signature in database
GPG Key ID: 0AB6C6CA01272646
10 changed files with 143 additions and 103 deletions

View File

@ -32,7 +32,7 @@ fontawesomeSubset(
const ignoreFontsPlugin: esbuild.Plugin = { const ignoreFontsPlugin: esbuild.Plugin = {
name: "file", name: "file",
setup(build) { setup(build) {
build.onResolve({ filter: /\.woff2|ttf$/ }, (args) => ({ build.onResolve({ filter: /\.woff2|ttf$/ }, () => ({
external: true, external: true,
})); }));
}, },

View File

@ -18,7 +18,7 @@ sub new_invite {
if ($c->is_user_authenticated) { if ($c->is_user_authenticated) {
my $mail_attr = $c->config('invitations')->{'mail_attr'} // 'mail'; my $mail_attr = $c->config('invitations')->{'mail_attr'} // 'mail';
my $max_expire_at = $c->config('invitations')->{'max_invitation_expiration_delay'} // 30; my $max_expire_at = $c->config('invitations')->{'max_invitation_expiration_delay'} // 30;
my $send_with_user_email = defined $c->config('invitations')->{'send_invitation_with_ldap_user_mail'}; my $send_with_user_email = defined $c->config('invitations')->{'send_invitation_with_ldap_user_mail'} && $c->config('invitations')->{'send_invitation_with_ldap_user_mail'} eq 1;
$c->render( $c->render(
template => 'invitations/invite', template => 'invitations/invite',
max_expire_at => $max_expire_at, max_expire_at => $max_expire_at,

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,7 @@ const addItem = (item) => {
const files = JSON.parse(localStorage.getItem(`${prefix}files`)) || []; const files = JSON.parse(localStorage.getItem(`${prefix}files`)) || [];
files.push(item); files.push(item);
localStorage.setItem(`${prefix}files`, JSON.stringify(files)); localStorage.setItem(`${prefix}files`, JSON.stringify(files));
}; };
@ -195,8 +196,6 @@ const populateFilesTable = () => {
itemsTableDOM.append(itemDOM); itemsTableDOM.append(itemDOM);
console.debug(file.short, file.token);
fetch(counterURL, { fetch(counterURL, {
method: "POST", method: "POST",
headers: { headers: {

View File

@ -7,6 +7,22 @@ const entityMap = {
"/": "/", "/": "/",
}; };
export const addToast = (message, type) => {
const notification = document
.querySelector("template#notification")
.content.cloneNode(true).children[0];
notification.classList.add(`is-${type}`);
notification.querySelector(".message").innerText = message;
document.getElementById("notifications-container").append(notification);
setTimeout(() => {
notification.remove();
}, 3500);
};
export const copyToClipboard = async (text) => { export const copyToClipboard = async (text) => {
try { try {
if (navigator.clipboard) { if (navigator.clipboard) {
@ -50,21 +66,11 @@ export const formatDate = (unixTimestamp) =>
minute: "2-digit", minute: "2-digit",
}); });
// export const notify = (message, type) => { export const hideNode = (node) => {
// const notification = document if (node) {
// .querySelector("template#notification") node.classList.add("is-hidden");
// .content.cloneNode(true).children[0]; }
};
// notification.classList.add(`is-${type}`);
// notification.querySelector(".message").innerText = message;
// document.getElementById("notifications-container").append(notification);
// setTimeout(() => {
// notification.remove();
// }, 3500);
// };
export const notify = (title, body) => { export const notify = (title, body) => {
if (isSecureContext) { if (isSecureContext) {
@ -86,6 +92,12 @@ export const notify = (title, body) => {
} }
}; };
export const showNode = (node) => {
if (node) {
node.classList.remove("is-hidden");
}
};
export const uuidv4 = () => export const uuidv4 = () =>
"10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) => "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) =>
( (

View File

@ -1,21 +1,23 @@
import { filesize } from "~/lib/filesize.esm.min.js"; import { filesize } from "~/lib/filesize.esm.min.js";
import { addToast, hideNode, showNode } from "~/lib/utils.js";
const updateButtonsStatus = () => { const updateButtonsStatus = () => {
const targetSelectionDOM = document.querySelectorAll(".target-selection"); const targetSelectionDOM = document.querySelectorAll(".target-selection");
if ( if (
document.querySelectorAll(".column.selection .checkbox input:checked") document.querySelectorAll(".selection .checkbox input:checked").length > 0
.length > 0
) { ) {
targetSelectionDOM.forEach((node) => (node.disabled = false)); targetSelectionDOM.forEach((node) => {
node.disabled = false;
});
} else { } else {
targetSelectionDOM.forEach((node) => (node.disabled = true)); targetSelectionDOM.forEach((node) => (node.disabled = true));
} }
}; };
const invertSelection = () => { const updateSelection = (event) => {
document.querySelectorAll(".item .column.selection input").forEach((node) => { document.querySelectorAll(".item .checkbox input").forEach((node) => {
node.click(); node.checked = event.target.checked;
}); });
updateButtonsStatus(); updateButtonsStatus();
@ -28,12 +30,12 @@ const toggleHidden = () => {
".item[data-visibility='0']" ".item[data-visibility='0']"
); );
if (invitationsListDOM.getAttribute("data-visibility") === "hidden") { if (invitationsListDOM.dataset.visibility === "hidden") {
toggleButtonDOM.innerText = i18n.hideText; toggleButtonDOM.innerText = i18n.hideText;
itemsHiddenDOM.forEach((item) => showNode(item)); itemsHiddenDOM.forEach((item) => showNode(item));
invitationsListDOM.setAttribute("data-visibility", "shown"); invitationsListDOM.dataset.visibility = "shown";
} else { } else {
toggleButtonDOM.innerText = i18n.showText; toggleButtonDOM.innerText = i18n.showText;
@ -47,7 +49,7 @@ const toggleHidden = () => {
} }
}); });
invitationsListDOM.setAttribute("data-visibility", "hidden"); invitationsListDOM.dataset.visibility = "hidden";
} }
}; };
@ -70,7 +72,7 @@ const deleteInvitation = () => {
}) })
.then((data) => { .then((data) => {
if (data.success) { if (data.success) {
data.tokens.forEach((t) => { data.success.forEach((t) => {
addToast(t.msg, "success"); addToast(t.msg, "success");
document.getElementById(`row-${t.token}`).remove(); document.getElementById(`row-${t.token}`).remove();
}); });
@ -114,13 +116,12 @@ const resendInvitation = () => {
}) })
.then((data) => { .then((data) => {
if (data.success) { if (data.success) {
data.tokens.forEach((t) => { data.success.forEach((t) => {
const itemDOM = document.getElementById(`row-${t.token}`); const itemDOM = document.getElementById(`row-${t.token}`);
itemDOM.querySelector(".column.expiration-date").innerText = itemDOM.querySelector(".expiration-date").innerText = t.expires;
t.expires;
itemDOM.querySelector(".column.selection input").click(); itemDOM.querySelector(".selection input").click();
addToast(t.msg, "success"); addToast(t.msg, "success");
}); });
@ -158,22 +159,19 @@ const toggleVisibility = () => {
if (t.show) { if (t.show) {
itemDOM.setAttribute("data-visibility", 1); itemDOM.setAttribute("data-visibility", 1);
showNode(itemDOM); showNode(itemDOM);
itemDOM itemDOM.querySelector(".selection .icon.hide-source").remove();
.querySelector(".column.selection .icon.hide-source")
.remove();
} else { } else {
itemDOM.setAttribute("data-visibility", 0); itemDOM.setAttribute("data-visibility", 0);
if ( if (
document document.querySelector(".invitations-list").dataset.visibility ===
.querySelector(".invitations-list") "hidden"
.getAttribute("data-visibility") === "hidden"
) { ) {
hideNode(itemDOM); hideNode(itemDOM);
} }
itemDOM itemDOM
.querySelector(".column.selection") .querySelector(".selection")
.appendChild( .appendChild(
document document
.querySelector("template#icon-hide-source") .querySelector("template#icon-hide-source")
@ -181,7 +179,7 @@ const toggleVisibility = () => {
); );
} }
itemDOM.querySelector(".column.selection input").click(); itemDOM.querySelector(".selection input").click();
}); });
updateButtonsStatus(); updateButtonsStatus();
@ -196,10 +194,8 @@ const getTokensBody = () => {
const tokens = new URLSearchParams(); const tokens = new URLSearchParams();
document document
.querySelectorAll(".column.selection input:checked") .querySelectorAll(".selection input:checked")
.forEach((item) => .forEach((item) => tokens.append("tokens[]", item.dataset.token));
tokens.append("tokens[]", item.getAttribute("data-token"))
);
return tokens; return tokens;
}; };
@ -212,10 +208,10 @@ const fillModal = (event) => {
modalDOM.querySelector(".files-list").replaceChildren(); modalDOM.querySelector(".files-list").replaceChildren();
modalDOM.querySelector("h1").innerText = i18n.listFiles modalDOM.querySelector("h1").innerText = i18n.listFiles
.replace("XX1", buttonDOM.getAttribute("data-token")) .replace("XX1", buttonDOM.dataset.token)
.replace("XX2", buttonDOM.getAttribute("data-guest")); .replace("XX2", buttonDOM.dataset.guest);
const files = JSON.parse(buttonDOM.getAttribute("data-files")) || []; const files = JSON.parse(buttonDOM.dataset.files) || [];
const itemList = new DocumentFragment(); const itemList = new DocumentFragment();
files.forEach((file) => { files.forEach((file) => {
@ -254,10 +250,10 @@ document.addEventListener("DOMContentLoaded", () => {
}; };
document document
.querySelectorAll(".column.selection input") .querySelectorAll(".selection input")
.forEach((node) => (node.onclick = updateButtonsStatus)); .forEach((node) => (node.onclick = updateButtonsStatus));
document.querySelector(".action-invert-selection").onclick = invertSelection; document.getElementById("action-select-all").onclick = updateSelection;
document.querySelector(".action-toggle-hidden").onclick = toggleHidden; document.querySelector(".action-toggle-hidden").onclick = toggleHidden;

View File

@ -252,6 +252,7 @@ document.addEventListener("DOMContentLoaded", () => {
// Add the file to localStorage // Add the file to localStorage
if (!isGuest) { if (!isGuest) {
console.debug(job.lufiFile.keys.server);
addItem( addItem(
job.lufiFile.name, job.lufiFile.name,
job.lufiFile.downloadUrl(), job.lufiFile.downloadUrl(),

View File

@ -53,7 +53,7 @@
<thead> <thead>
<tr> <tr>
<th class="has-text-centered"> <th class="has-text-centered">
<span><%= l('Selection') %></span> <div><%= l('Selection') %></div>
<div class="checkbox"> <div class="checkbox">
<input type="checkbox" id="action-select-all"> <input type="checkbox" id="action-select-all">
</div> </div>

View File

@ -25,7 +25,7 @@
<p> <p>
<%= l('You can invite someone to send you files through this Lufi instance even if they dont have an account on it.') %> <%= l('You can invite someone to send you files through this Lufi instance even if they dont have an account on it.') %>
</p> </p>
% if (stash('send_invitation_with_ldap_user_email')) { % if (stash('send_with_user_email')) {
<p> <p>
<%= l('The invitation mail will be send from your email address (%1).', stash('user_mail')) %> <%= l('The invitation mail will be send from your email address (%1).', stash('user_mail')) %>
</p> </p>

View File

@ -1,29 +1,40 @@
% use Number::Bytes::Human qw(format_bytes); % use Number::Bytes::Human qw(format_bytes);
% my $lang = $self->get_date_lang(); % my $lang = $self->get_date_lang();
<section class="my-invitations-section"> <div class="box">
<h1><%= l('My invitations') %></h1> <h1 class="title is-1"><%= l('My invitations') %></h1>
<div class="message is-info">
<div class="message-body">
<p> <p>
<%= l('Rows in purple mean that the invitations have expired.') %> <%= l('Rows in purple mean that the invitations have expired.') %>
</p> </p>
<div class="actions-buttons"> </div>
<button href="#" class="button action-invert-selection"><%= l('Invert selection') %></button> </div>
<div class="buttons">
<button href="#" class="button action-toggle-hidden"><%= l('Show hidden invitations') %></button> <button href="#" class="button action-toggle-hidden"><%= l('Show hidden invitations') %></button>
<button href="#" class="button target-selection action-delete-invitation" disabled="disabled"=><%= l('Delete') %></button> <button href="#" class="button target-selection action-delete-invitation" disabled="disabled"=><%= l('Delete') %></button>
<button href="#" class="button target-selection action-resend-invitation" disabled="disabled"><%= l('Resend invitation mail') %></button> <button href="#" class="button target-selection action-resend-invitation" disabled="disabled"><%= l('Resend invitation mail') %></button>
<button href="#" class="button target-selection action-toggle-visibility" disabled="disabled"><%= l('Toggle visibility') %></button> <button href="#" class="button target-selection action-toggle-visibility" disabled="disabled"><%= l('Toggle visibility') %></button>
</div> </div>
<table> <div class="table-container">
<table class="table is-stripped is-hoverable">
<thead> <thead>
<tr> <tr>
<th><%= l('Selection') %></th> <th class="has-text-centered">
<th><%= l('Guest mail') %></th> <div><%= l('Selection') %></div>
<th><%= l('URL') %></th> <div class="checkbox">
<th><%= l('Created at') %></th> <input type="checkbox" id="action-select-all">
<th><%= l('Expire at') %></th> </div>
<th><%= l('Files sent at') %></th> </th>
<th><%= l('Files') %></th> <th class="has-text-centered"><%= l('Guest mail') %></th>
<th class="has-text-centered"><%= l('URL') %></th>
<th class="has-text-centered"><%= l('Created at') %></th>
<th class="has-text-centered"><%= l('Expire at') %></th>
<th class="has-text-centered"><%= l('Files sent at') %></th>
<th class="has-text-centered"><%= l('Files') %></th>
</tr> </tr>
</thead> </thead>
<tbody class="invitations-list" data-visibility="hidden"> <tbody class="invitations-list" data-visibility="hidden">
@ -33,23 +44,23 @@
% return if $e->deleted; % return if $e->deleted;
% my $class = ''; % my $class = '';
% $class = 'deleted' unless $e->is_valid; % $class = 'deleted' unless $e->is_valid;
% $class .= ' hidden' unless $e->show_in_list; % $class .= ' is-hidden' unless $e->show_in_list;
<tr id="row-<%= $e->token %>" class="item <%= $class %>" aria-hidden="<%= ($e->show_in_list) ? 'true' : 'false' %>" data-visibility="<%= ($e->show_in_list) ? 1 : 0 %>"> <tr id="row-<%= $e->token %>" class="item <%= $class %>" aria-hidden="<%= ($e->show_in_list) ? 'true' : 'false' %>" data-visibility="<%= ($e->show_in_list) ? 1 : 0 %>">
<td class="column selection"> <td class="selection is-vcentered has-text-centered">
<div class="checkbox input-selection"> <div class="checkbox input-delete-on-first-view">
<input type="checkbox" data-token="<%= $e->token %>" aria-label="Select"> <input type="checkbox" data-token="<%= $e->token %>" autocomplete="off" aria-label="Select">
% unless ($e->show_in_list) { % unless ($e->show_in_list) {
<span class="icon hide-source" title="<%= l('This invitation is normally hidden') %>"></span> <span class="icon fas fa-eye-slash" title="<%= l('This invitation is normally hidden') %>"></span>
% } % }
</div> </div>
</td> </td>
<td class="column mail"><%= $e->guest_mail %></td> <td class="mail is-vcentered"><%= $e->guest_mail %></td>
<td class="column url"><%= url_for('guest', token => $e->token)->to_abs %></td> <td class="url is-vcentered"><%= url_for('guest', token => $e->token)->to_abs %></td>
<td class="column creation-date"><%= $lang->time2str(l('%A %d %B %Y at %T'), $e->created_at) %></td> <td class="creation-date is-vcentered"><%= $lang->time2str(l('%A %d %B %Y at %T'), $e->created_at) %></td>
<td class="column expiration-date"><%= $lang->time2str(l('%A %d %B %Y at %T'), $e->expire_at) %></td> <td class="expiration-date is-vcentered"><%= $lang->time2str(l('%A %d %B %Y at %T'), $e->expire_at) %></td>
<td class="column files-sent-data"><%= $lang->time2str(l('%A %d %B %Y at %T'), $e->files_sent_at) if $e->files_sent_at %></td> <td class="files-sent-data is-vcentered"><%= $lang->time2str(l('%A %d %B %Y at %T'), $e->files_sent_at) if $e->files_sent_at %></td>
<td class="column actions"> <td class="actions is-vcentered">
% if ($e->files) { % if ($e->files) {
<a <a
class="button modal-button action-files-info" class="button modal-button action-files-info"
@ -66,6 +77,7 @@
% }); % });
</tbody> </tbody>
</table> </table>
</div>
<dialog class="modal files-info"> <dialog class="modal files-info">
<section> <section>
@ -82,10 +94,30 @@
</footer> </footer>
</section> </section>
</dialog> </dialog>
</section> </div>
<div id="modal-language-selector" class="modal files-info">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head is-shadowless">
<p class="modal-card-title">
<%= l('Files sent in invitation XX1 by XX2') %>
<button class="delete is-pulled-right" aria-label="close"></button>
</p>
</header>
<section class="modal-card-body">
<ul class="files-list">
</li>
</section>
<footer class="modal-card-foot"></footer>
</div>
</div>
<template id="icon-hide-source"> <template id="icon-hide-source">
<span class="icon hide-source" title="<%= l('This invitation is normally hidden') %>"></span> <span class="icon fas fa-eye-slash" title="<%= l('This invitation is normally hidden') %>"></span>
</template> </template>
<template id="item"> <template id="item">
@ -109,4 +141,4 @@ const deleteURL = '<%= url_for('invite_list_delete') %>';
const resendURL = '<%= url_for('invite_list_resend') %>'; const resendURL = '<%= url_for('invite_list_resend') %>';
const toggleURL = '<%= url_for('invite_list_visibility') %>'; const toggleURL = '<%= url_for('invite_list_visibility') %>';
</script> </script>
%= javascript '/js/min.lufi-list-invitations.js', type => 'module', defer => "true" %= javascript '/js/minified/list-invitations.min.js', type => 'module', defer => "true"