Merge pull request #858 from ae-utbm/jsstandard

Add biome to format js files
This commit is contained in:
thomas girod
2024-10-08 23:45:20 +02:00
committed by GitHub
43 changed files with 10820 additions and 6681 deletions

View File

@ -1 +1,14 @@
[{"pk": 1, "fields": {"permissions": [1, 2, 3, 7, 8, 9, 4, 5, 6, 10, 11, 12, 19, 20, 21, 22, 25, 23, 24, 16, 17, 18, 13, 14, 15]}, "model": "core.group"}, {"pk": 2, "fields": {"permissions": [25]}, "model": "core.group"}, {"pk": 3, "fields": {"permissions": []}, "model": "core.group"}]
[
{
"pk": 1,
"fields": {
"permissions": [
1, 2, 3, 7, 8, 9, 4, 5, 6, 10, 11, 12, 19, 20, 21, 22, 25, 23, 24, 16, 17, 18,
13, 14, 15
]
},
"model": "core.group"
},
{ "pk": 2, "fields": { "permissions": [25] }, "model": "core.group" },
{ "pk": 3, "fields": { "permissions": [] }, "model": "core.group" }
]

View File

@ -1 +1,74 @@
[{"pk": 1, "model": "core.page", "fields": {"full_name": "guy2", "owner_group": 1, "parent": null, "edit_groups": [], "name": "guy2", "view_groups": []}}, {"pk": 2, "model": "core.page", "fields": {"full_name": "guy2/bibou", "owner_group": 1, "parent": 1, "edit_group": [], "name": "bibou", "view_group": []}}, {"pk": 3, "model": "core.page", "fields": {"full_name": "guy2/bibou/troll", "owner_group": 1, "parent": 2, "edit_group": [], "name": "troll", "view_group": []}}, {"pk": 4, "model": "core.page", "fields": {"full_name": "guy", "owner_group": 1, "parent": null, "edit_group": [1], "name": "guy", "view_group": [1]}}, {"pk": 5, "model": "core.page", "fields": {"full_name": "bibou", "owner_group": 3, "parent": null, "edit_group": [1], "name": "bibou", "view_group": []}}, {"pk": 6, "model": "core.page", "fields": {"full_name": "guy2/guy", "owner_group": 1, "parent": 1, "edit_group": [], "name": "guy", "view_group": []}}]
[
{
"pk": 1,
"model": "core.page",
"fields": {
"full_name": "guy2",
"owner_group": 1,
"parent": null,
"edit_groups": [],
"name": "guy2",
"view_groups": []
}
},
{
"pk": 2,
"model": "core.page",
"fields": {
"full_name": "guy2/bibou",
"owner_group": 1,
"parent": 1,
"edit_group": [],
"name": "bibou",
"view_group": []
}
},
{
"pk": 3,
"model": "core.page",
"fields": {
"full_name": "guy2/bibou/troll",
"owner_group": 1,
"parent": 2,
"edit_group": [],
"name": "troll",
"view_group": []
}
},
{
"pk": 4,
"model": "core.page",
"fields": {
"full_name": "guy",
"owner_group": 1,
"parent": null,
"edit_group": [1],
"name": "guy",
"view_group": [1]
}
},
{
"pk": 5,
"model": "core.page",
"fields": {
"full_name": "bibou",
"owner_group": 3,
"parent": null,
"edit_group": [1],
"name": "bibou",
"view_group": []
}
},
{
"pk": 6,
"model": "core.page",
"fields": {
"full_name": "guy2/guy",
"owner_group": 1,
"parent": 1,
"edit_group": [],
"name": "guy",
"view_group": []
}
}
]

View File

@ -1 +1,42 @@
[{"model": "core.user", "pk": 1, "fields": {"first_name": "Ro", "date_joined": "2015-11-19T16:05:51.764Z", "groups": [], "password": "pbkdf2_sha256$20000$MDukCN5X8Bof$rYdhppKiusj+W/1Rxpy0yuYsEyWocESEjtRsopkOc5c=", "last_name": "Ot", "nick_name": "", "username": "root", "user_permissions": [], "email": "bibou@git.an", "last_login": "2015-11-26T16:28:36.464Z", "date_of_birth": "1969-12-31T23:00:00Z", "is_superuser": true, "is_active": true, "is_staff": true}}, {"model": "core.user", "pk": 2, "fields": {"first_name": "Skia", "date_joined": "2015-11-19T16:06:29.556Z", "groups": [3], "password": "pbkdf2_sha256$20000$UK9a29p5bBEh$Jzv7xs0W9njJZiXfIdYXDydim/3YHs6awKwDmN7gSAc=", "last_name": "Kia", "nick_name": "", "username": "skia", "user_permissions": [], "email": "plop@libskia.so", "last_login": "2015-11-26T16:37:01.671Z", "date_of_birth": "1969-12-31T23:00:00Z", "is_superuser": false, "is_active": true, "is_staff": false}}]
[
{
"model": "core.user",
"pk": 1,
"fields": {
"first_name": "Ro",
"date_joined": "2015-11-19T16:05:51.764Z",
"groups": [],
"password": "pbkdf2_sha256$20000$MDukCN5X8Bof$rYdhppKiusj+W/1Rxpy0yuYsEyWocESEjtRsopkOc5c=",
"last_name": "Ot",
"nick_name": "",
"username": "root",
"user_permissions": [],
"email": "bibou@git.an",
"last_login": "2015-11-26T16:28:36.464Z",
"date_of_birth": "1969-12-31T23:00:00Z",
"is_superuser": true,
"is_active": true,
"is_staff": true
}
},
{
"model": "core.user",
"pk": 2,
"fields": {
"first_name": "Skia",
"date_joined": "2015-11-19T16:06:29.556Z",
"groups": [3],
"password": "pbkdf2_sha256$20000$UK9a29p5bBEh$Jzv7xs0W9njJZiXfIdYXDydim/3YHs6awKwDmN7gSAc=",
"last_name": "Kia",
"nick_name": "",
"username": "skia",
"user_permissions": [],
"email": "plop@libskia.so",
"last_login": "2015-11-26T16:37:01.671Z",
"date_of_birth": "1969-12-31T23:00:00Z",
"is_superuser": false,
"is_active": true,
"is_staff": false
}
}
]

View File

@ -1,45 +1,124 @@
/*--------------------------------RESET--------------------------------*/
/*--------------------------------RESET--------------------------------*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
line-height: 1;
}
ol, ul {
/* list-style: none;*/
ol,
ul {
/* list-style: none;*/
}
blockquote, q {
quotes: none;
blockquote,
q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
blockquote:before,
blockquote:after,
q:before,
q:after {
content: "";
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
border-collapse: collapse;
border-spacing: 0;
}

View File

@ -1,59 +1,66 @@
$( function() {
buttons = $(".choose_file_button");
popups = $(".choose_file_widget");
popups.dialog({
autoOpen: false,
modal: true,
width: "90%",
create: function (event) {
target = $(event.target);
target.parent().css({
'position': 'fixed',
'top': '5%',
'bottom': '5%',
});
target.css("height", "300px");
console.log(target);
$(() => {
// const buttons = $('.choose_file_button')
const popups = $(".choose_file_widget");
popups.dialog({
autoOpen: false,
modal: true,
width: "90%",
create: (event) => {
const target = $(event.target);
target.parent().css({
position: "fixed",
top: "5%",
bottom: "5%",
});
target.css("height", "300px");
},
buttons: [
{
text: "Choose",
click: function () {
$(`input[name=${$(this).attr("name")}]`).attr(
"value",
$("#file_id").attr("value"),
);
$(this).dialog("close");
},
buttons: [
{
text: "Choose",
click: function() {
console.log($("#file_id"));
$("input[name="+$(this).attr('name')+"]").attr('value', $("#file_id").attr('value'));
$( this ).dialog( "close" );
},
disabled: true,
}
],
disabled: true,
},
],
});
$(".choose_file_button")
.button()
.on("click", function () {
const popup = popups.filter(`[name=${$(this).attr("name")}]`);
popup.html(
'<iframe src="/file/popup" width="100%" height="95%"></iframe><div id="file_id" value="null" />',
);
popup.dialog({ title: $(this).text() }).dialog("open");
});
$( ".choose_file_button" ).button().on( "click", function() {
popup = popups.filter("[name="+$(this).attr('name')+"]");
console.log(popup);
popup.html('<iframe src="/file/popup" width="100%" height="95%"></iframe><div id="file_id" value="null" />');
popup.dialog({title: $(this).text()}).dialog( "open" );
});
$("#quick_notif li").click(function () {
$(this).hide();
})
$("#quick_notif li").click(function () {
$(this).hide();
});
});
// biome-ignore lint/correctness/noUnusedVariables: used in other scripts
function createQuickNotif(msg) {
const el = document.createElement('li')
el.textContent = msg
el.addEventListener('click', () => el.parentNode.removeChild(el))
document.getElementById('quick_notif').appendChild(el)
const el = document.createElement("li");
el.textContent = msg;
el.addEventListener("click", () => el.parentNode.removeChild(el));
document.getElementById("quick_notif").appendChild(el);
}
// biome-ignore lint/correctness/noUnusedVariables: used in other scripts
function deleteQuickNotifs() {
const el = document.getElementById('quick_notif')
while (el.firstChild) {
el.removeChild(el.firstChild)
}
const el = document.getElementById("quick_notif");
while (el.firstChild) {
el.removeChild(el.firstChild);
}
}
function display_notif() {
$('#header_notif').toggle().parent().toggleClass("white");
// biome-ignore lint/correctness/noUnusedVariables: used in other scripts
function displayNotif() {
$("#header_notif").toggle().parent().toggleClass("white");
}
// You can't get the csrf token from the template in a widget
@ -62,11 +69,13 @@ function display_notif() {
// Sadly, getting the cookie is not possible with CSRF_COOKIE_HTTPONLY or CSRF_USE_SESSIONS is True
// So, the true workaround is to get the token from the dom
// https://docs.djangoproject.com/en/2.0/ref/csrf/#acquiring-the-token-if-csrf-use-sessions-is-true
// biome-ignore lint/style/useNamingConvention: can't find it used anywhere but I will not play with the devil
// biome-ignore lint/correctness/noUnusedVariables: used in other scripts
function getCSRFToken() {
return $("[name=csrfmiddlewaretoken]").val();
return $("[name=csrfmiddlewaretoken]").val();
}
// biome-ignore lint/correctness/noUnusedVariables: used in other scripts
const initialUrlParams = new URLSearchParams(window.location.search);
/**
@ -74,9 +83,12 @@ const initialUrlParams = new URLSearchParams(window.location.search);
* @enum {number}
*/
const History = {
NONE: 0,
PUSH: 1,
REPLACE: 2,
// biome-ignore lint/style/useNamingConvention: this feels more like an enum
NONE: 0,
// biome-ignore lint/style/useNamingConvention: this feels more like an enum
PUSH: 1,
// biome-ignore lint/style/useNamingConvention: this feels more like an enum
REPLACE: 2,
};
/**
@ -85,27 +97,31 @@ const History = {
* @param {History} action
* @param {URL | null} url
*/
function update_query_string(key, value, action = History.REPLACE, url = null) {
if (!url){
url = new URL(window.location.href);
}
if (value === undefined || value === null || value === "") {
// If the value is null, undefined or empty => delete it
url.searchParams.delete(key)
} else if (Array.isArray(value)) {
url.searchParams.delete(key)
value.forEach((v) => url.searchParams.append(key, v))
} else {
url.searchParams.set(key, value);
// biome-ignore lint/correctness/noUnusedVariables: used in other scripts
function updateQueryString(key, value, action = History.REPLACE, url = null) {
let ret = url;
if (!ret) {
ret = new URL(window.location.href);
}
if (value === undefined || value === null || value === "") {
// If the value is null, undefined or empty => delete it
ret.searchParams.delete(key);
} else if (Array.isArray(value)) {
ret.searchParams.delete(key);
for (const v of value) {
ret.searchParams.append(key, v);
}
} else {
ret.searchParams.set(key, value);
}
if (action === History.PUSH) {
history.pushState(null, "", url.toString());
} else if (action === History.REPLACE) {
history.replaceState(null, "", url.toString());
}
if (action === History.PUSH) {
window.history.pushState(null, "", ret.toString());
} else if (action === History.REPLACE) {
window.history.replaceState(null, "", ret.toString());
}
return url;
return ret;
}
// TODO : If one day a test workflow is made for JS in this project
@ -116,27 +132,28 @@ function update_query_string(key, value, action = History.REPLACE, url = null) {
* @param {string} url The paginated endpoint to fetch
* @return {Promise<Object[]>}
*/
async function fetch_paginated(url) {
const max_per_page = 199;
const paginated_url = new URL(url, document.location.origin);
paginated_url.searchParams.set("page_size", max_per_page.toString());
paginated_url.searchParams.set("page", "1");
// biome-ignore lint/correctness/noUnusedVariables: used in other scripts
async function fetchPaginated(url) {
const maxPerPage = 199;
const paginatedUrl = new URL(url, document.location.origin);
paginatedUrl.searchParams.set("page_size", maxPerPage.toString());
paginatedUrl.searchParams.set("page", "1");
let first_page = (await ( await fetch(paginated_url)).json());
let results = first_page.results;
const firstPage = await (await fetch(paginatedUrl)).json();
const results = firstPage.results;
const nb_pictures = first_page.count
const nb_pages = Math.ceil(nb_pictures / max_per_page);
const nbPictures = firstPage.count;
const nbPages = Math.ceil(nbPictures / maxPerPage);
if (nb_pages > 1) {
let promises = [];
for (let i = 2; i <= nb_pages; i++) {
paginated_url.searchParams.set("page", i.toString());
promises.push(
fetch(paginated_url).then(res => res.json().then(json => json.results))
);
}
results.push(...(await Promise.all(promises)).flat())
if (nbPages > 1) {
const promises = [];
for (let i = 2; i <= nbPages; i++) {
paginatedUrl.searchParams.set("page", i.toString());
promises.push(
fetch(paginatedUrl).then((res) => res.json().then((json) => json.results)),
);
}
results.push(...(await Promise.all(promises)).flat());
}
return results;
}

View File

@ -19,4 +19,106 @@
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
!function(e){e.fn.shorten=function(s){"use strict";var t={showChars:100,minHideChars:10,ellipsesText:"...",moreText:"more",lessText:"less",onLess:function(){},onMore:function(){},errMsg:null,force:!1};return s&&e.extend(t,s),(!e(this).data("jquery.shorten")||!!t.force)&&(e(this).data("jquery.shorten",!0),e(document).off("click",".morelink"),e(document).on({click:function(){var s=e(this);return s.hasClass("less")?(s.removeClass("less"),s.html(t.moreText),s.parent().prev().animate({},function(){s.parent().prev().prev().show()}).hide("fast",function(){t.onLess()})):(s.addClass("less"),s.html(t.lessText),s.parent().prev().animate({},function(){s.parent().prev().prev().hide()}).show("fast",function(){t.onMore()})),!1}},".morelink"),this.each(function(){var s=e(this),n=s.html();if(s.text().length>t.showChars+t.minHideChars){var r=n.substr(0,t.showChars);if(r.indexOf("<")>=0){for(var a=!1,o="",i=0,l=[],h=null,c=0,f=0;f<=t.showChars;c++)if("<"!=n[c]||a||(a=!0,"/"==(h=n.substring(c+1,n.indexOf(">",c)))[0]?h!="/"+l[0]?t.errMsg="ERROR en HTML: the top of the stack should be the tag that closes":l.shift():"br"!=h.toLowerCase()&&l.unshift(h)),a&&">"==n[c]&&(a=!1),a)o+=n.charAt(c);else if(f++,i<=t.showChars)o+=n.charAt(c),i++;else if(l.length>0){for(j=0;j<l.length;j++)o+="</"+l[j]+">";break}r=e("<div/>").html(o+'<span class="ellip">'+t.ellipsesText+"</span>").html()}else r+=t.ellipsesText;var p='<div class="shortcontent">'+r+'</div><div class="allcontent">'+n+'</div><span><a href="javascript://nop/" class="morelink">'+t.moreText+"</a></span>";s.html(p),s.find(".allcontent").hide(),e(".shortcontent p:last",s).css("margin-bottom",0)}}))}}(jQuery);
!(function (e) {
e.fn.shorten = function (s) {
"use strict";
var t = {
showChars: 100,
minHideChars: 10,
ellipsesText: "...",
moreText: "more",
lessText: "less",
onLess: function () {},
onMore: function () {},
errMsg: null,
force: !1,
};
return (
s && e.extend(t, s),
(!e(this).data("jquery.shorten") || !!t.force) &&
(e(this).data("jquery.shorten", !0),
e(document).off("click", ".morelink"),
e(document).on(
{
click: function () {
var s = e(this);
return (
s.hasClass("less")
? (s.removeClass("less"),
s.html(t.moreText),
s
.parent()
.prev()
.animate({}, function () {
s.parent().prev().prev().show();
})
.hide("fast", function () {
t.onLess();
}))
: (s.addClass("less"),
s.html(t.lessText),
s
.parent()
.prev()
.animate({}, function () {
s.parent().prev().prev().hide();
})
.show("fast", function () {
t.onMore();
})),
!1
);
},
},
".morelink",
),
this.each(function () {
var s = e(this),
n = s.html();
if (s.text().length > t.showChars + t.minHideChars) {
var r = n.substr(0, t.showChars);
if (r.indexOf("<") >= 0) {
for (
var a = !1, o = "", i = 0, l = [], h = null, c = 0, f = 0;
f <= t.showChars;
c++
)
if (
("<" != n[c] ||
a ||
((a = !0),
"/" == (h = n.substring(c + 1, n.indexOf(">", c)))[0]
? h != "/" + l[0]
? (t.errMsg =
"ERROR en HTML: the top of the stack should be the tag that closes")
: l.shift()
: "br" != h.toLowerCase() && l.unshift(h)),
a && ">" == n[c] && (a = !1),
a)
)
o += n.charAt(c);
else if ((f++, i <= t.showChars)) (o += n.charAt(c)), i++;
else if (l.length > 0) {
for (j = 0; j < l.length; j++) o += "</" + l[j] + ">";
break;
}
r = e("<div/>")
.html(o + '<span class="ellip">' + t.ellipsesText + "</span>")
.html();
} else r += t.ellipsesText;
var p =
'<div class="shortcontent">' +
r +
'</div><div class="allcontent">' +
n +
'</div><span><a href="javascript://nop/" class="morelink">' +
t.moreText +
"</a></span>";
s.html(p),
s.find(".allcontent").hide(),
e(".shortcontent p:last", s).css("margin-bottom", 0);
}
}))
);
};
})(jQuery);

View File

@ -15,7 +15,7 @@
* ];
* document.addEventListener("DOMContentLoaded", () => sithSelect2({
* element: document.getElementById("select2-input"),
* data_source: local_data_source(data)
* dataSource: localDataSource(data)
* }));
* ```
*
@ -29,7 +29,7 @@
* ];
* document.addEventListener("DOMContentLoaded", () => sithSelect2({
* element: document.getElementById("select2-input"),
* data_source: local_data_source(data, {
* dataSource: localDataSource(data, {
* excluded: () => data.filter((i) => i.text === "to exclude").map((i) => parseInt(i))
* })
* }));
@ -38,15 +38,15 @@
* # Remote data source
*
* Select2 with remote data sources are similar to those with local
* data, but with some more parameters, like `result_converter`,
* data, but with some more parameters, like `resultConverter`,
* which takes a callback that must return a `Select2Object`.
*
* ```js
* document.addEventListener("DOMContentLoaded", () => sithSelect2({
* element: document.getElementById("select2-input"),
* data_source: remote_data_source("/api/user/search", {
* dataSource: remoteDataSource("/api/user/search", {
* excluded: () => [1, 2], // exclude users 1 and 2 from the search
* result_converter: (user) => Object({id: user.id, text: user.first_name})
* resultConverter: (user) => Object({id: user.id, text: user.firstName})
* })
* }));
* ```
@ -62,8 +62,8 @@
* ```js
* document.addEventListener("DOMContentLoaded", () => sithSelect2({
* element: document.getElementById("select2-input"),
* data_source: remote_data_source("/api/user/search", {
* result_converter: (user) => Object({id: user.id, text: user.first_name}),
* dataSource: remoteDataSource("/api/user/search", {
* resultConverter: (user) => Object({id: user.id, text: user.firstName}),
* overrides: {
* delay: 500
* }
@ -85,15 +85,15 @@
*
* Sometimes, you would like to display an image besides
* the text on the select items.
* In this case, fill the `picture_getter` option :
* In this case, fill the `pictureGetter` option :
*
* ```js
* document.addEventListener("DOMContentLoaded", () => sithSelect2({
* element: document.getElementById("select2-input"),
* data_source: remote_data_source("/api/user/search", {
* result_converter: (user) => Object({id: user.id, text: user.first_name})
* dataSource: remoteDataSource("/api/user/search", {
* resultConverter: (user) => Object({id: user.id, text: user.firstName})
* })
* picture_getter: (user) => user.profile_pict,
* pictureGetter: (user) => user.profilePict,
* }));
* ```
*
@ -105,8 +105,8 @@
* <body>
* <div x-data="select2_test">
* <select x-ref="search" x-ref="select"></select>
* <p x-text="current_selection.id"></p>
* <p x-text="current_selection.text"></p>
* <p x-text="currentSelection.id"></p>
* <p x-text="currentSelection.text"></p>
* </div>
* </body>
*
@ -114,20 +114,20 @@
* document.addEventListener("alpine:init", () => {
* Alpine.data("select2_test", () => ({
* selector: undefined,
* current_select: {id: "", text: ""},
* currentSelect: {id: "", text: ""},
*
* init() {
* this.selector = sithSelect2({
* element: $(this.$refs.select),
* data_source: local_data_source(
* dataSource: localDataSource(
* [{id: 1, text: "foo"}, {id: 2, text: "bar"}]
* ),
* });
* this.selector.on("select2:select", (event) => {
* // select2 => Alpine signals here
* this.current_select = this.selector.select2("data")
* this.currentSelect = this.selector.select2("data")
* });
* this.$watch("current_selected" (value) => {
* this.$watch("currentSelected" (value) => {
* // Alpine => select2 signals here
* });
* },
@ -145,10 +145,10 @@
/**
* @typedef Select2Options
* @property {Element} element
* @property {Object} data_source
* the data source, built with `local_data_source` or `remote_data_source`
* @property {Object} dataSource
* the data source, built with `localDataSource` or `remoteDataSource`
* @property {number[]} excluded A list of ids to exclude from search
* @property {undefined | function(Object): string} picture_getter
* @property {undefined | function(Object): string} pictureGetter
* A callback to get the picture field from the API response
* @property {Object | undefined} overrides
* Any other select2 parameter to apply on the config
@ -157,13 +157,14 @@
/**
* @param {Select2Options} options
*/
// biome-ignore lint/correctness/noUnusedVariables: used in other scripts
function sithSelect2(options) {
const elem = $(options.element);
return elem.select2({
theme: elem[0].multiple ? "classic" : "default",
minimumInputLength: 2,
templateResult: select_item_builder(options.picture_getter),
...options.data_source,
templateResult: selectItemBuilder(options.pictureGetter),
...options.dataSource,
...(options.overrides || {}),
});
}
@ -179,8 +180,9 @@ function sithSelect2(options) {
* @param {Select2Object[]} source The array containing the data
* @param {RemoteSourceOptions} options
*/
function local_data_source(source, options) {
if (!!options.excluded) {
// biome-ignore lint/correctness/noUnusedVariables: used in other scripts
function localDataSource(source, options) {
if (options.excluded) {
const ids = options.excluded();
return { data: source.filter((i) => !ids.includes(i.id)) };
}
@ -191,7 +193,7 @@ function local_data_source(source, options) {
* @typedef RemoteSourceOptions
* @property {undefined | function(): number[]} excluded
* A callback to the ids to exclude from the search
* @property {undefined | function(): Select2Object} result_converter
* @property {undefined | function(): Select2Object} resultConverter
* A converter for a value coming from the remote api
* @property {undefined | Object} overrides
* Any other select2 parameter to apply on the config
@ -202,9 +204,11 @@ function local_data_source(source, options) {
* @param {string} source The url of the endpoint
* @param {RemoteSourceOptions} options
*/
function remote_data_source(source, options) {
// biome-ignore lint/correctness/noUnusedVariables: used in other scripts
function remoteDataSource(source, options) {
jQuery.ajaxSettings.traditional = true;
let params = {
const params = {
url: source,
dataType: "json",
cache: true,
@ -213,24 +217,25 @@ function remote_data_source(source, options) {
return {
search: params.term,
exclude: [
...(this.val() || []).map((i) => parseInt(i)),
...(this.val() || []).map((i) => Number.parseInt(i)),
...(options.excluded ? options.excluded() : []),
],
};
},
};
if (!!options.result_converter) {
params["processResults"] = function (data) {
return { results: data.results.map(options.result_converter) };
};
if (options.resultConverter) {
params.processResults = (data) => ({
results: data.results.map(options.resultConverter),
});
}
if (!!options.overrides) {
if (options.overrides) {
Object.assign(params, options.overrides);
}
return { ajax: params };
}
function item_formatter(user) {
// biome-ignore lint/correctness/noUnusedVariables: used in other scripts
function itemFormatter(user) {
if (user.loading) {
return user.text;
}
@ -238,23 +243,22 @@ function item_formatter(user) {
/**
* Build a function to display the results
* @param {null | function(Object):string} picture_getter
* @param {null | function(Object):string} pictureGetter
* @return {function(string): jQuery|HTMLElement}
*/
function select_item_builder(picture_getter) {
function selectItemBuilder(pictureGetter) {
return (item) => {
const picture =
typeof picture_getter === "function" ? picture_getter(item) : null;
const img_html = picture
const picture = typeof pictureGetter === "function" ? pictureGetter(item) : null;
const imgHtml = picture
? `<img
src="${picture_getter(item)}"
src="${pictureGetter(item)}"
alt="${item.text}"
onerror="this.src = '/static/core/img/unknown.jpg'"
/>`
: "";
return $(`<div class="select-item">
${img_html}
${imgHtml}
<span class="select-item-text">${item.text}</span>
</div>`);
};

View File

@ -1,7 +1,7 @@
async function get_graph_data(url, godfathers_depth, godchildren_depth) {
let data = await (
async function getGraphData(url, godfathersDepth, godchildrenDepth) {
const data = await (
await fetch(
`${url}?godfathers_depth=${godfathers_depth}&godchildren_depth=${godchildren_depth}`,
`${url}?godfathers_depth=${godfathersDepth}&godchildren_depth=${godchildrenDepth}`,
)
).json();
return [
@ -16,12 +16,13 @@ async function get_graph_data(url, godfathers_depth, godchildren_depth) {
];
}
function create_graph(container, data, active_user_id) {
let cy = cytoscape({
function createGraph(container, data, activeUserId) {
// biome-ignore lint/correctness/noUndeclaredVariables: imported by user_godphaters_tree.jinja
const cy = cytoscape({
boxSelectionEnabled: false,
autounselectify: true,
container: container,
container,
elements: data,
minZoom: 0.5,
@ -83,11 +84,9 @@ function create_graph(container, data, active_user_id) {
},
},
});
let active_user = cy
.getElementById(active_user_id)
.style("shape", "rectangle");
const activeUser = cy.getElementById(activeUserId).style("shape", "rectangle");
/* Reset graph */
let reset_graph = () => {
const resetGraph = () => {
cy.elements((element) => {
if (element.hasClass("traversed")) {
element.removeClass("traversed");
@ -98,38 +97,33 @@ function create_graph(container, data, active_user_id) {
});
};
let on_node_tap = (el) => {
reset_graph();
const onNodeTap = (el) => {
resetGraph();
/* Create path on graph if selected isn't the targeted user */
if (el === active_user) {
if (el === activeUser) {
return;
}
cy.elements((element) => {
element.addClass("not-traversed");
});
cy.elements()
.aStar({
root: el,
goal: active_user,
})
.path.forEach((el) => {
el.removeClass("not-traversed");
el.addClass("traversed");
});
for (const traversed of cy.elements().aStar({
root: el,
goal: activeUser,
}).path) {
traversed.removeClass("not-traversed");
traversed.addClass("traversed");
}
};
cy.on("tap", "node", (tapped) => {
on_node_tap(tapped.target);
onNodeTap(tapped.target);
});
cy.zoomingEnabled(false);
/* Add context menu */
if (cy.cxtmenu === undefined) {
console.error(
"ctxmenu isn't loaded, context menu won't be available on graphs",
);
return cy;
throw new Error("ctxmenu isn't loaded, context menu won't be available on graphs");
}
cy.cxtmenu({
selector: "node",
@ -137,22 +131,22 @@ function create_graph(container, data, active_user_id) {
commands: [
{
content: '<i class="fa fa-external-link fa-2x"></i>',
select: function (el) {
select: (el) => {
window.open(el.data().profile_url, "_blank").focus();
},
},
{
content: '<span class="fa fa-mouse-pointer fa-2x"></span>',
select: function (el) {
on_node_tap(el);
select: (el) => {
onNodeTap(el);
},
},
{
content: '<i class="fa fa-eraser fa-2x"></i>',
select: function (el) {
reset_graph();
select: (_) => {
resetGraph();
},
},
],
@ -165,68 +159,77 @@ document.addEventListener("alpine:init", () => {
/*
This needs some constants to be set before the document has been loaded
api_url: base url for fetching the tree as a string
active_user: id of the user to fetch the tree from
depth_min: minimum tree depth for godfathers and godchildren as an int
depth_max: maximum tree depth for godfathers and godchildren as an int
apiUrl: base url for fetching the tree as a string
activeUser: id of the user to fetch the tree from
depthMin: minimum tree depth for godfathers and godchildren as an int
depthMax: maximum tree depth for godfathers and godchildren as an int
*/
const default_depth = 2;
const defaultDepth = 2;
if (
typeof api_url === "undefined" ||
typeof active_user === "undefined" ||
typeof depth_min === "undefined" ||
typeof depth_max === "undefined"
// biome-ignore lint/correctness/noUndeclaredVariables: defined by user_godfathers_tree.jinja
typeof apiUrl === "undefined" ||
// biome-ignore lint/correctness/noUndeclaredVariables: defined by user_godfathers_tree.jinja
typeof activeUser === "undefined" ||
// biome-ignore lint/correctness/noUndeclaredVariables: defined by user_godfathers_tree.jinja
typeof depthMin === "undefined" ||
// biome-ignore lint/correctness/noUndeclaredVariables: defined by user_godfathers_tree.jinja
typeof depthMax === "undefined"
) {
console.error(
throw new Error(
"Some constants are not set before using the family_graph script, please look at the documentation",
);
return;
}
function get_initial_depth(prop) {
let value = parseInt(initialUrlParams.get(prop));
if (isNaN(value) || value < depth_min || value > depth_max) {
return default_depth;
function getInitialDepth(prop) {
// biome-ignore lint/correctness/noUndeclaredVariables: defined by script.js
const value = Number.parseInt(initialUrlParams.get(prop));
// biome-ignore lint/correctness/noUndeclaredVariables: defined by user_godfathers_tree.jinja
if (Number.isNaN(value) || value < depthMin || value > depthMax) {
return defaultDepth;
}
return value;
}
Alpine.data("graph", () => ({
loading: false,
godfathers_depth: get_initial_depth("godfathers_depth"),
godchildren_depth: get_initial_depth("godchildren_depth"),
godfathersDepth: getInitialDepth("godfathersDepth"),
godchildrenDepth: getInitialDepth("godchildrenDepth"),
// biome-ignore lint/correctness/noUndeclaredVariables: defined by script.js
reverse: initialUrlParams.get("reverse")?.toLowerCase?.() === "true",
graph: undefined,
graph_data: {},
graphData: {},
async init() {
let delayed_fetch = Alpine.debounce(async () => {
this.fetch_graph_data();
const delayedFetch = Alpine.debounce(async () => {
await this.fetchGraphData();
}, 100);
["godfathers_depth", "godchildren_depth"].forEach((param) => {
for (const param of ["godfathersDepth", "godchildrenDepth"]) {
this.$watch(param, async (value) => {
if (value < depth_min || value > depth_max) {
// biome-ignore lint/correctness/noUndeclaredVariables: defined by user_godfathers_tree.jinja
if (value < depthMin || value > depthMax) {
return;
}
update_query_string(param, value, History.REPLACE);
delayed_fetch();
// biome-ignore lint/correctness/noUndeclaredVariables: defined by script.js
updateQueryString(param, value, History.REPLACE);
await delayedFetch();
});
});
}
this.$watch("reverse", async (value) => {
update_query_string("reverse", value, History.REPLACE);
this.reverse_graph();
// biome-ignore lint/correctness/noUndeclaredVariables: defined by script.js
updateQueryString("reverse", value, History.REPLACE);
await this.reverseGraph();
});
this.$watch("graph_data", async () => {
await this.generate_graph();
this.$watch("graphData", async () => {
await this.generateGraph();
if (this.reverse) {
await this.reverse_graph();
await this.reverseGraph();
}
});
this.fetch_graph_data();
await this.fetchGraphData();
},
async screenshot() {
screenshot() {
const link = document.createElement("a");
link.href = this.graph.jpg();
link.download = interpolate(
@ -239,34 +242,32 @@ document.addEventListener("alpine:init", () => {
document.body.removeChild(link);
},
async reset() {
reset() {
this.reverse = false;
this.godfathers_depth = default_depth;
this.godchildren_depth = default_depth;
this.godfathersDepth = defaultDepth;
this.godchildrenDepth = defaultDepth;
},
async reverse_graph() {
async reverseGraph() {
this.graph.elements((el) => {
el.position(new Object({ x: -el.position().x, y: -el.position().y }));
el.position({ x: -el.position().x, y: -el.position().y });
});
this.graph.center(this.graph.elements());
},
async fetch_graph_data() {
this.graph_data = await get_graph_data(
api_url,
this.godfathers_depth,
this.godchildren_depth,
async fetchGraphData() {
this.graphData = await getGraphData(
// biome-ignore lint/correctness/noUndeclaredVariables: defined by user_godfathers_tree.jinja
apiUrl,
this.godfathersDepth,
this.godchildrenDepth,
);
},
async generate_graph() {
async generateGraph() {
this.loading = true;
this.graph = create_graph(
$(this.$refs.graph),
this.graph_data,
active_user,
);
// biome-ignore lint/correctness/noUndeclaredVariables: defined by user_godfathers_tree.jinja
this.graph = await createGraph($(this.$refs.graph), this.graphData, activeUser);
this.loading = false;
},
}));

View File

@ -1,28 +1,25 @@
function alpine_webcam_builder(
default_picture,
delete_url,
can_delete_picture,
) {
// biome-ignore lint/correctness/noUnusedVariables: used in user_edit.jinja
function alpineWebcamBuilder(defaultPicture, deleteUrl, canDeletePicture) {
return () => ({
can_edit_picture: false,
canEditPicture: false,
loading: false,
is_camera_enabled: false,
is_camera_error: false,
isCameraEnabled: false,
isCameraError: false,
picture: null,
video: null,
picture_form: null,
pictureForm: null,
init() {
this.video = this.$refs.video;
this.picture_form = this.$refs.form.getElementsByTagName("input");
if (this.picture_form.length > 0) {
this.picture_form = this.picture_form[0];
this.can_edit_picture = true;
this.pictureForm = this.$refs.form.getElementsByTagName("input");
if (this.pictureForm.length > 0) {
this.pictureForm = this.pictureForm[0];
this.canEditPicture = true;
// Link the displayed element to the form input
this.picture_form.onchange = (event) => {
let files = event.srcElement.files;
this.pictureForm.onchange = (event) => {
const files = event.srcElement.files;
if (files.length > 0) {
this.picture = (window.URL || window.webkitURL).createObjectURL(
event.srcElement.files[0],
@ -34,77 +31,78 @@ function alpine_webcam_builder(
}
},
get_picture() {
return this.picture || default_picture;
getPicture() {
return this.picture || defaultPicture;
},
delete_picture() {
deletePicture() {
// Only remove currently displayed picture
if (!!this.picture) {
let list = new DataTransfer();
this.picture_form.files = list.files;
this.picture_form.dispatchEvent(new Event("change"));
if (this.picture) {
const list = new DataTransfer();
this.pictureForm.files = list.files;
this.pictureForm.dispatchEvent(new Event("change"));
return;
}
if (!can_delete_picture) {
if (!canDeletePicture) {
return;
}
// Remove user picture if correct rights are available
window.open(delete_url, "_self");
window.open(deleteUrl, "_self");
},
enable_camera() {
enableCamera() {
this.picture = null;
this.loading = true;
this.is_camera_error = false;
this.isCameraError = false;
navigator.mediaDevices
.getUserMedia({ video: true, audio: false })
.then((stream) => {
this.loading = false;
this.is_camera_enabled = true;
this.isCameraEnabled = true;
this.video.srcObject = stream;
this.video.play();
})
.catch((err) => {
this.is_camera_error = true;
this.isCameraError = true;
this.loading = false;
throw err;
});
},
take_picture() {
let canvas = document.createElement("canvas");
takePicture() {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
/* Create the image */
let settings = this.video.srcObject.getTracks()[0].getSettings();
const settings = this.video.srcObject.getTracks()[0].getSettings();
canvas.width = settings.width;
canvas.height = settings.height;
context.drawImage(this.video, 0, 0, canvas.width, canvas.height);
/* Stop camera */
this.video.pause();
this.video.srcObject.getTracks().forEach((track) => {
for (const track of this.video.srcObject.getTracks()) {
if (track.readyState === "live") {
track.stop();
}
});
}
canvas.toBlob((blob) => {
const filename = interpolate(gettext("captured.%s"), ["webp"]);
let file = new File([blob], filename, {
const file = new File([blob], filename, {
type: "image/webp",
});
let list = new DataTransfer();
const list = new DataTransfer();
list.items.add(file);
this.picture_form.files = list.files;
this.pictureForm.files = list.files;
// No change event is triggered, we trigger it manually #}
this.picture_form.dispatchEvent(new Event("change"));
this.pictureForm.dispatchEvent(new Event("change"));
}, "image/webp");
canvas.remove();
this.is_camera_enabled = false;
this.isCameraEnabled = false;
},
});
}

View File

@ -2,6 +2,6 @@ import Alpine from "alpinejs";
window.Alpine = Alpine;
addEventListener("DOMContentLoaded", (event) => {
window.addEventListener("DOMContentLoaded", () => {
Alpine.start();
});

View File

@ -1,6 +1,7 @@
// biome-ignore lint/correctness/noUndeclaredDependencies: shipped by easymde
import "codemirror/lib/codemirror.css";
import "easymde/src/css/easymde.css";
import EasyMDE from "easymde";
import easyMde from "easymde";
// This scripts dependens on Alpine but it should be loaded on every page
@ -9,13 +10,13 @@ import EasyMDE from "easymde";
* @param {HTMLTextAreaElement} textarea to use
* @param {string} link to the markdown api
**/
function easymde_factory (textarea, markdown_api_url) {
const easymde = new EasyMDE({
function easymdeFactory(textarea, markdownApiUrl) {
const easymde = new easyMde({
element: textarea,
spellChecker: false,
autoDownloadFontAwesome: false,
previewRender: Alpine.debounce(async (plainText, preview) => {
const res = await fetch(markdown_api_url, {
const res = await fetch(markdownApiUrl, {
method: "POST",
body: JSON.stringify({ text: plainText }),
});
@ -26,33 +27,33 @@ function easymde_factory (textarea, markdown_api_url) {
toolbar: [
{
name: "heading-smaller",
action: EasyMDE.toggleHeadingSmaller,
action: easyMde.toggleHeadingSmaller,
className: "fa fa-header",
title: gettext("Heading"),
},
{
name: "italic",
action: EasyMDE.toggleItalic,
action: easyMde.toggleItalic,
className: "fa fa-italic",
title: gettext("Italic"),
},
{
name: "bold",
action: EasyMDE.toggleBold,
action: easyMde.toggleBold,
className: "fa fa-bold",
title: gettext("Bold"),
},
{
name: "strikethrough",
action: EasyMDE.toggleStrikethrough,
action: easyMde.toggleStrikethrough,
className: "fa fa-strikethrough",
title: gettext("Strikethrough"),
},
{
name: "underline",
action: function customFunction(editor) {
let cm = editor.codemirror;
cm.replaceSelection("__" + cm.getSelection() + "__");
const cm = editor.codemirror;
cm.replaceSelection(`__${cm.getSelection()}__`);
},
className: "fa fa-underline",
title: gettext("Underline"),
@ -60,8 +61,8 @@ function easymde_factory (textarea, markdown_api_url) {
{
name: "superscript",
action: function customFunction(editor) {
let cm = editor.codemirror;
cm.replaceSelection("^" + cm.getSelection() + "^");
const cm = editor.codemirror;
cm.replaceSelection(`^${cm.getSelection()}^`);
},
className: "fa fa-superscript",
title: gettext("Superscript"),
@ -69,80 +70,80 @@ function easymde_factory (textarea, markdown_api_url) {
{
name: "subscript",
action: function customFunction(editor) {
let cm = editor.codemirror;
cm.replaceSelection("~" + cm.getSelection() + "~");
const cm = editor.codemirror;
cm.replaceSelection(`~${cm.getSelection()}~`);
},
className: "fa fa-subscript",
title: gettext("Subscript"),
},
{
name: "code",
action: EasyMDE.toggleCodeBlock,
action: easyMde.toggleCodeBlock,
className: "fa fa-code",
title: gettext("Code"),
},
"|",
{
name: "quote",
action: EasyMDE.toggleBlockquote,
action: easyMde.toggleBlockquote,
className: "fa fa-quote-left",
title: gettext("Quote"),
},
{
name: "unordered-list",
action: EasyMDE.toggleUnorderedList,
action: easyMde.toggleUnorderedList,
className: "fa fa-list-ul",
title: gettext("Unordered list"),
},
{
name: "ordered-list",
action: EasyMDE.toggleOrderedList,
action: easyMde.toggleOrderedList,
className: "fa fa-list-ol",
title: gettext("Ordered list"),
},
"|",
{
name: "link",
action: EasyMDE.drawLink,
action: easyMde.drawLink,
className: "fa fa-link",
title: gettext("Insert link"),
},
{
name: "image",
action: EasyMDE.drawImage,
action: easyMde.drawImage,
className: "fa-regular fa-image",
title: gettext("Insert image"),
},
{
name: "table",
action: EasyMDE.drawTable,
action: easyMde.drawTable,
className: "fa fa-table",
title: gettext("Insert table"),
},
"|",
{
name: "clean-block",
action: EasyMDE.cleanBlock,
action: easyMde.cleanBlock,
className: "fa fa-eraser fa-clean-block",
title: gettext("Clean block"),
},
"|",
{
name: "preview",
action: EasyMDE.togglePreview,
action: easyMde.togglePreview,
className: "fa fa-eye no-disable",
title: gettext("Toggle preview"),
},
{
name: "side-by-side",
action: EasyMDE.toggleSideBySide,
action: easyMde.toggleSideBySide,
className: "fa fa-columns no-disable no-mobile",
title: gettext("Toggle side by side"),
},
{
name: "fullscreen",
action: EasyMDE.toggleFullScreen,
className: "fa fa-arrows-alt no-disable no-mobile",
action: easyMde.toggleFullScreen,
className: "fa fa-expand no-mobile",
title: gettext("Toggle fullscreen"),
},
"|",
@ -155,18 +156,16 @@ function easymde_factory (textarea, markdown_api_url) {
],
});
const submits = textarea
.closest("form")
.querySelectorAll('input[type="submit"]');
const submits = textarea.closest("form").querySelectorAll('input[type="submit"]');
const parentDiv = textarea.parentElement;
let submitPressed = false;
function checkMarkdownInput(e) {
function checkMarkdownInput() {
// an attribute is null if it does not exist, else a string
const required = textarea.getAttribute("required") != null;
const length = textarea.value.trim().length;
if (required && length == 0) {
if (required && length === 0) {
parentDiv.style.boxShadow = "red 0px 0px 1.5px 1px";
} else {
parentDiv.style.boxShadow = "";
@ -181,9 +180,9 @@ function easymde_factory (textarea, markdown_api_url) {
checkMarkdownInput(e);
}
submits.forEach((submit) => {
for (const submit of submits) {
submit.addEventListener("click", onSubmitClick);
});
};
}
}
window.easymde_factory = easymde_factory;
window.easymdeFactory = easymdeFactory;

View File

@ -18,7 +18,7 @@ require("jquery-ui/themes/base/all.css");
* @param {string} selector to be passed to jQuery
* @param {Object} options object to pass to the shorten function
**/
export function shorten(selector, options) {
function shorten(selector, options) {
$(selector).shorten(options);
}

View File

@ -112,7 +112,7 @@
></a>
</div>
<div class="notification">
<a href="#" onclick="display_notif()">
<a href="#" onclick="displayNotif()">
<i class="fa-regular fa-bell"></i>
{% set notification_count = user.notifications.filter(viewed=False).count() %}

View File

@ -15,36 +15,36 @@
{% macro profile_picture(field_name) %}
{% set this_picture = form.instance[field_name] %}
<div class="profile-picture" x-data="camera_{{ field_name }}" >
<div class="profile-picture-display" :aria-busy="loading" :class="{ 'camera-error': is_camera_error }">
<div class="profile-picture-display" :aria-busy="loading" :class="{ 'camera-error': isCameraError }">
<img
x-show="!is_camera_enabled && !is_camera_error"
:src="get_picture()"
x-show="!isCameraEnabled && !isCameraError"
:src="getPicture()"
alt="{%- trans -%}Profile{%- endtrans -%}" title="{%- trans -%}Profile{%- endtrans -%}"
loading="lazy"
/>
<video
x-show="is_camera_enabled"
x-show="isCameraEnabled"
x-ref="video"
></video>
<i
x-show="is_camera_error"
x-show="isCameraError"
x-cloak
class="fa fa-eye-slash"
></i>
</div>
<div class="profile-picture-buttons" x-show="can_edit_picture">
<div class="profile-picture-buttons" x-show="canEditPicture">
<button
x-show="can_edit_picture && !is_camera_enabled"
x-show="canEditPicture && !isCameraEnabled"
class="btn btn-blue"
@click.prevent="enable_camera()"
@click.prevent="enableCamera()"
>
<i class="fa fa-camera"></i>
{% trans %}Enable camera{% endtrans %}
</button>
<button
x-show="is_camera_enabled"
x-show="isCameraEnabled"
class="btn btn-blue"
@click.prevent="take_picture()"
@click.prevent="takePicture()"
>
<i class="fa fa-camera"></i>
{% trans %}Take a picture{% endtrans %}
@ -54,7 +54,7 @@
{%- if form[field_name] -%}
<div>
{{ form[field_name] }}
<button class="btn btn-red" @click.prevent="delete_picture()"
<button class="btn btn-red" @click.prevent="deletePicture()"
{%- if not (this_picture and this_picture.is_owned_by(user)) -%}
:disabled="!picture"
{%- endif -%}
@ -86,7 +86,7 @@
document.addEventListener("alpine:init", () => {
Alpine.data(
"camera_{{ field_name }}",
alpine_webcam_builder(
alpineWebcamBuilder(
{{ default_picture }},
{{ delete_url }},
{{ (this_picture and this_picture.is_owned_by(user))|tojson }}

View File

@ -30,21 +30,21 @@
</label>
<span class="depth-choice">
<button
@click="godfathers_depth--"
:disabled="godfathers_depth <= {{ depth_min }}"
@click="godfathersDepth--"
:disabled="godfathersDepth <= {{ depth_min }}"
><i class="fa fa-minus"></i></button>
<input
x-model="godfathers_depth"
x-model="godfathersDepth"
x-ref="godfather_depth_input"
type="number"
name="godfathers_depth"
name="godfathersDepth"
id="godfather-depth-input"
min="{{ depth_min }}"
max="{{ depth_max }}"
/>
<button
@click="godfathers_depth++"
:disabled="godfathers_depth >= {{ depth_max }}"
@click="godfathersDepth++"
:disabled="godfathersDepth >= {{ depth_max }}"
><i class="fa fa-plus"
></i></button>
</span>
@ -56,22 +56,22 @@
</label>
<span class="depth-choice">
<button
@click="godchildren_depth--"
:disabled="godchildren_depth <= {{ depth_min }}"
@click="godchildrenDepth--"
:disabled="godchildrenDepth <= {{ depth_min }}"
><i
class="fa fa-minus"
></i></button>
<input
x-model="godchildren_depth"
x-model="godchildrenDepth"
type="number"
name="godchildren_depth"
name="godchildrenDepth"
id="godchild-depth-input"
min="{{ depth_min }}"
max="{{ depth_max }}"
/>
<button
@click="godchildren_depth++"
:disabled="godchildren_depth >= {{ depth_max }}"
@click="godchildrenDepth++"
:disabled="godchildrenDepth >= {{ depth_max }}"
><i class="fa fa-plus"
></i></button>
</span>
@ -96,10 +96,10 @@
</div>
<script>
const api_url = "{{ api_url }}";
const active_user = "{{ object.id }}"
const depth_min = {{ depth_min }};
const depth_max = {{ depth_max }};
const apiUrl = "{{ api_url }}";
const activeUser = "{{ object.id }}"
const depthMin = {{ depth_min }};
const depthMax = {{ depth_max }};
</script>
{% endblock %}

View File

@ -21,14 +21,14 @@
{% if user.id == object.id %}
<div x-show="pictures.length > 0" x-cloak>
<button
:disabled="is_downloading"
:disabled="isDownloading"
class="btn btn-blue"
@click="download_zip()"
@click="downloadZip()"
>
<i class="fa fa-download"></i>
{% trans %}Download all my pictures{% endtrans %}
</button>
<progress x-ref="progress" x-show="is_downloading"></progress>
<progress x-ref="progress" x-show="isDownloading"></progress>
</div>
{% endif %}
@ -92,13 +92,13 @@
document.addEventListener("alpine:init", () => {
Alpine.data("user_pictures", () => ({
is_downloading: false,
isDownloading: false,
loading: true,
pictures: [],
albums: {},
async init() {
this.pictures = await fetch_paginated("{{ url("api:pictures") }}" + "?users_identified={{ object.id }}");
this.pictures = await fetchPaginated("{{ url("api:pictures") }}" + "?users_identified={{ object.id }}");
this.albums = this.pictures.reduce((acc, picture) => {
if (!acc[picture.album]){
acc[picture.album] = [];
@ -109,8 +109,8 @@
this.loading = false;
},
async download_zip(){
this.is_downloading = true;
async downloadZip(){
this.isDownloading = true;
const bar = this.$refs.progress;
bar.value = 0;
bar.max = this.pictures.length;
@ -124,16 +124,16 @@
const zipWriter = new zip.ZipWriter(await fileHandle.createWritable());
await Promise.all(this.pictures.map(p => {
const img_name = p.album + "/IMG_" + p.date.replaceAll(/[:\-]/g, "_") + p.name.slice(p.name.lastIndexOf("."));
const imgName = p.album + "/IMG_" + p.date.replaceAll(/[:\-]/g, "_") + p.name.slice(p.name.lastIndexOf("."));
return zipWriter.add(
img_name,
imgName,
new zip.HttpReader(p.full_size_url),
{level: 9, lastModDate: new Date(p.date), onstart: () => bar.value += 1}
);
}));
await zipWriter.close();
this.is_downloading = false;
this.isDownloading = false;
}
}))
});

View File

@ -6,7 +6,7 @@
<link rel="stylesheet" type="text/css" href="{{ statics.css }}" defer>
<script type="text/javascript">
addEventListener("DOMContentLoaded", (event) => {
easymde_factory(
easymdeFactory(
document.getElementById("{{ widget.attrs.id }}"),
"{{ markdown_api_url }}",
);