diff --git a/core/static/core/style.scss b/core/static/core/style.scss index 82891031..d658d957 100644 --- a/core/static/core/style.scss +++ b/core/static/core/style.scss @@ -28,6 +28,7 @@ input[type="file"] { font-size: 1.2em; border-radius: 5px; color: black; + &:hover { background: hsl(0, 0%, 83%); } @@ -63,6 +64,7 @@ textarea[type="text"], border-radius: 5px; max-width: 95%; } + textarea { border: none; text-decoration: none; @@ -72,6 +74,7 @@ textarea { border-radius: 5px; font-family: sans-serif; } + select { border: none; text-decoration: none; @@ -85,9 +88,11 @@ select { a:not(.button) { text-decoration: none; color: $primary-dark-color; + &:hover { color: $primary-light-color; } + &:active { color: $primary-color; } @@ -116,7 +121,9 @@ a:not(.button) { } @keyframes rotate { - 100% { transform: rotate(360deg); } + 100% { + transform: rotate(360deg); + } } .ib { @@ -143,11 +150,13 @@ a:not(.button) { .collapse-header-icon { transition: all ease-in-out 150ms; + &.reverse { transform: rotate(180deg); } } } + .collapse-body { padding: 10px; } @@ -202,9 +211,11 @@ a:not(.button) { font-size: 0.9em; margin: 0.2em; border-radius: 0.6em; + .markdown { margin: 0.5em; } + &:before { font-family: FontAwesome; font-size: 4em; @@ -212,15 +223,19 @@ a:not(.button) { margin: 0.2em; } } + #info_box { background: $primary-neutral-light-color; + &:before { content: "\f05a"; color: hsl(210, 100%, 56%); } } + #alert_box { background: $second-color; + &:before { content: "\f06a"; color: $white-color; @@ -240,7 +255,8 @@ a:not(.button) { #page { width: 90%; margin: 20px auto 0; -/*---------------------------------NAV---------------------------------*/ + + /*---------------------------------NAV---------------------------------*/ .btn { font-size: 15px; font-weight: normal; @@ -252,9 +268,11 @@ a:not(.button) { &.btn-blue { background-color: $deepblue; + &:not(:disabled):hover { background-color: darken($deepblue, 10%); } + &:disabled { background-color: rgba(70, 90, 126, 0.4); } @@ -262,9 +280,11 @@ a:not(.button) { &.btn-grey { background-color: grey; + &:not(:disabled):hover { background-color: darken(gray, 15%); } + &:disabled { background-color: lighten(gray, 15%); } @@ -273,9 +293,11 @@ a:not(.button) { &.btn-red { background-color: #fc8181; color: black; + &:not(:disabled):hover { background-color: darken(#fc8181, 15%); } + &:disabled { background-color: lighten(#fc8181, 15%); color: grey; @@ -287,12 +309,13 @@ a:not(.button) { } } -/*--------------------------------CONTENT------------------------------*/ + /*--------------------------------CONTENT------------------------------*/ #quick_notif { width: 100%; margin: 0 auto; list-style-type: none; background: $second-color; + li { padding: 10px; } @@ -350,6 +373,7 @@ a:not(.button) { .tool_bar { overflow: auto; padding: 4px; + .tools { display: flex; flex-wrap: wrap; @@ -358,6 +382,7 @@ a:not(.button) { padding: 5px; border-radius: 6px; text-align: center; + a { padding: 7px; display: inline-block; @@ -367,11 +392,13 @@ a:not(.button) { flex: 1; flex-wrap: nowrap; white-space: nowrap; + &.selected_tab { background: $primary-color; color: $white-color; border-radius: 6px; } + &:hover { background: $primary-color; color: $white-color; @@ -381,7 +408,7 @@ a:not(.button) { } } -/*---------------------------------NEWS--------------------------------*/ + /*---------------------------------NEWS--------------------------------*/ #news { display: flex; @@ -394,17 +421,21 @@ a:not(.button) { margin: 0; vertical-align: top; } + #news_admin { margin-bottom: 1em; } + #right_column { flex: 20%; float: right; margin: 0.2em; } + #left_column { flex: 79%; margin: 0.2em; + h3 { background: $second-color; box-shadow: $shadow-color 1px 1px 1px; @@ -412,19 +443,22 @@ a:not(.button) { margin: 0 0 0.5em 0; text-transform: uppercase; font-size: 1.1em; + &:not(:first-of-type) { margin: 2em 0 1em 0; } } } + @media screen and (max-width: $small-devices) { + #left_column, #right_column { flex: 100%; } } -/* AGENDA/BIRTHDAYS */ + /* AGENDA/BIRTHDAYS */ #agenda, #birthdays { display: block; @@ -432,6 +466,7 @@ a:not(.button) { background: white; font-size: 70%; margin-bottom: 1em; + #agenda_title, #birthdays_title { margin: 0; @@ -444,39 +479,48 @@ a:not(.button) { text-transform: uppercase; background: $second-color; } + #agenda_content { overflow: auto; box-shadow: $shadow-color 1px 1px 1px; height: 20em; } + #agenda_content, #birthdays_content { .agenda_item { padding: 0.5em; margin-bottom: 0.5em; + &:nth-of-type(even) { background: $secondary-neutral-light-color; } + .agenda_time { font-size: 90%; color: grey; } + .agenda_item_content { p { margin-top: 0.2em; } } } + ul.birthdays_year { margin: 0; list-style-type: none; font-weight: bold; - > li { + + >li { padding: 0.5em; + &:nth-child(even) { background: $secondary-neutral-light-color; } } + ul { margin: 0; margin-left: 1em; @@ -487,13 +531,15 @@ a:not(.button) { } } } -/* END AGENDA/BIRTHDAYS */ -/* EVENTS TODAY AND NEXT FEW DAYS */ + /* END AGENDA/BIRTHDAYS */ + + /* EVENTS TODAY AND NEXT FEW DAYS */ .news_events_group { box-shadow: $shadow-color 1px 1px 1px; margin-left: 1em; margin-bottom: 0.5em; + .news_events_group_date { display: table-cell; padding: 0.6em; @@ -509,33 +555,42 @@ a:not(.button) { div { margin: 0 auto; + .day { font-size: 1.5em; } } } + .news_events_group_items { display: table-cell; width: 100%; + .news_event:nth-of-type(odd) { background: white; } + .news_event:nth-of-type(even) { background: $primary-neutral-light-color; } + .news_event { display: block; padding: 0.4em; + &:not(:last-child) { border-bottom: 1px solid grey; } + div { margin: 0.2em; } + h4 { margin-top: 1em; text-transform: uppercase; } + .club_logo { float: left; min-width: 7em; @@ -543,6 +598,7 @@ a:not(.button) { margin: 0; margin-right: 1em; margin-top: 0.8em; + img { max-height: 6em; max-width: 8em; @@ -550,16 +606,21 @@ a:not(.button) { margin: 0 auto; } } + .news_date { font-size: 100%; } + .news_content { clear: left; + .button_bar { text-align: right; + .fb { color: $faceblue; } + .twitter { color: $twitblue; } @@ -568,26 +629,30 @@ a:not(.button) { } } } -/* END EVENTS TODAY AND NEXT FEW DAYS */ -/* COMING SOON */ + /* END EVENTS TODAY AND NEXT FEW DAYS */ + + /* COMING SOON */ .news_coming_soon { display: list-item; list-style-type: square; list-style-position: inside; margin-left: 1em; padding-left: 0; + a { font-weight: bold; text-transform: uppercase; } + .news_date { font-size: 0.9em; } } -/* END COMING SOON */ -/* NOTICES */ + /* END COMING SOON */ + + /* NOTICES */ .news_notice { margin: 0 0 1em 1em; padding: 0.4em; @@ -595,16 +660,19 @@ a:not(.button) { background: $secondary-neutral-light-color; box-shadow: $shadow-color 0 0 2px; border-radius: 18px 5px 18px 5px; + h4 { margin: 0; } + .news_content { margin-left: 1em; } } -/* END NOTICES */ -/* CALLS */ + /* END NOTICES */ + + /* CALLS */ .news_call { margin: 0 0 1em 1em; padding: 0.4em; @@ -612,21 +680,26 @@ a:not(.button) { background: $secondary-neutral-light-color; border: 1px solid grey; box-shadow: $shadow-color 1px 1px 1px; + h4 { margin: 0; } + .news_date { font-size: 0.9em; } + .news_content { margin-left: 1em; } } -/* END CALLS */ + + /* END CALLS */ .news_empty { margin-left: 1em; } + .news_date { color: grey; } @@ -640,8 +713,8 @@ a:not(.button) { } -.select2 { - margin: 10px 0!important; +.tomselected { + margin: 10px 0 !important; max-width: 100%; min-width: 100%; @@ -657,7 +730,9 @@ a:not(.button) { color: black; } } -.select2-results { + +.ts-dropdown { + .select-item { display: flex; flex-direction: row; @@ -673,16 +748,39 @@ a:not(.button) { } } +.ts-control { + + .item { + .fa-times { + margin-left: 5px; + margin-right: 5px; + } + + cursor: pointer; + background-color: #e4e4e4; + border: 1px solid #aaa; + border-radius: 4px; + display: inline-block; + margin-left: 5px; + margin-top: 5px; + margin-bottom: 5px; + padding-right: 10px; + } + +} + #news_details { display: inline-block; margin-top: 20px; padding: 0.4em; width: 80%; background: $white-color; + h4 { margin-top: 1em; text-transform: uppercase; } + .club_logo { display: inline-block; text-align: center; @@ -690,6 +788,7 @@ a:not(.button) { float: left; min-width: 15em; margin: 0; + img { max-height: 15em; max-width: 12em; @@ -698,6 +797,7 @@ a:not(.button) { margin-bottom: 10px; } } + .share_button { border: none; color: white; @@ -709,6 +809,7 @@ a:not(.button) { float: right; display: block; margin-left: 0.3em; + &:hover { color: lightgrey; } @@ -740,26 +841,32 @@ a:not(.button) { #poster_edit, #screen_edit { position: relative; + #title { position: relative; padding: 10px; margin: 10px; border-bottom: 2px solid black; + h3 { display: flex; justify-content: center; align-items: center; } + #links { position: absolute; display: flex; bottom: 5px; + &.left { left: 0; } + &.right { right: 0; } + .link { padding: 5px; padding-left: 20px; @@ -768,27 +875,32 @@ a:not(.button) { border-radius: 20px; background-color: hsl(40, 100%, 50%); color: black; + &:hover { color: black; background-color: hsl(40, 58%, 50%); } + &.delete { background-color: hsl(0, 100%, 40%); } } } } + #posters, #screens { position: relative; display: flex; flex-wrap: wrap; + #no-posters, #no-screens { display: flex; justify-content: center; align-items: center; } + .poster, .screen { min-width: 10%; @@ -800,26 +912,31 @@ a:not(.button) { border-radius: 4px; padding: 10px; background-color: lightgrey; + * { display: flex; justify-content: center; align-items: center; } + .name { padding-bottom: 5px; margin-bottom: 5px; border-bottom: 1px solid whitesmoke; } + .image { flex-grow: 1; position: relative; padding-bottom: 5px; margin-bottom: 5px; border-bottom: 1px solid whitesmoke; + img { max-height: 20vw; max-width: 100%; } + &:hover { &::before { position: absolute; @@ -838,10 +955,12 @@ a:not(.button) { } } } + .dates { padding-bottom: 5px; margin-bottom: 5px; border-bottom: 1px solid whitesmoke; + * { display: flex; justify-content: center; @@ -850,15 +969,18 @@ a:not(.button) { margin-left: 5px; margin-right: 5px; } + .begin, .end { width: 48%; } + .begin { border-right: 1px solid whitesmoke; padding-right: 2%; } } + .edit, .moderate, .slideshow { @@ -866,15 +988,18 @@ a:not(.button) { border-radius: 20px; background-color: hsl(40, 100%, 50%); color: black; + &:hover { color: black; background-color: hsl(40, 58%, 50%); } + &:nth-child(2n) { margin-top: 5px; margin-bottom: 5px; } } + .tooltip { visibility: hidden; width: 120px; @@ -885,23 +1010,28 @@ a:not(.button) { border-radius: 6px; position: absolute; z-index: 10; + ul { margin-left: 0; display: inline-block; + li { display: list-item; list-style-type: none; } } } + &.not_moderated { border: 1px solid red; } + &:hover .tooltip { visibility: visible; } } } + #view { position: fixed; width: 100vw; @@ -915,9 +1045,11 @@ a:not(.button) { visibility: hidden; background-color: rgba(10, 10, 10, 0.9); overflow: hidden; + &.active { visibility: visible; } + #placeholder { width: 80vw; height: 80vh; @@ -926,6 +1058,7 @@ a:not(.button) { align-items: center; top: 0; left: 0; + img { max-width: 100%; max-height: 100%; @@ -940,14 +1073,17 @@ a:not(.button) { tbody { .neg-amount { color: red; + &:before { font-family: FontAwesome; font-size: 1em; content: "\f063"; } } + .pos-amount { color: green; + &:before { font-family: FontAwesome; font-size: 1em; @@ -1014,6 +1150,7 @@ dt { .edit-bar { display: block; margin: 4px; + a { display: inline-block; margin: 4px; @@ -1053,7 +1190,8 @@ th { vertical-align: middle; text-align: center; padding: 5px 10px; - > ul { + + >ul { margin-top: 0; } } @@ -1064,7 +1202,8 @@ td { vertical-align: top; overflow: hidden; text-overflow: ellipsis; - > ul { + + >ul { margin-top: 0; } } @@ -1080,15 +1219,17 @@ thead { color: white; } -tbody > tr { +tbody>tr { &:nth-child(even):not(.highlight) { background: $primary-neutral-light-color; } + &.clickable:hover { cursor: pointer; background: $secondary-neutral-light-color; width: 100%; } + &.highlight { color: $primary-dark-color; font-style: italic; @@ -1148,9 +1289,11 @@ u, margin: 0.2em; height: 100%; background: $secondary-neutral-light-color; + img { max-width: 70%; } + input { background: white; } @@ -1162,10 +1305,12 @@ u, .user_mini_profile { height: 100%; width: 100%; + img { max-width: 100%; max-height: 100%; } + .user_mini_profile_infos { padding: 0.2em; height: 20%; @@ -1173,16 +1318,20 @@ u, flex-wrap: nowrap; justify-content: space-around; font-size: 0.9em; + div { max-height: 100%; } + .user_mini_profile_infos_text { text-align: center; + .user_mini_profile_nick { font-style: italic; } } } + .user_mini_profile_picture { height: 80%; display: flex; @@ -1194,14 +1343,17 @@ u, .mini_profile_link { display: block; text-decoration: none; + span { display: inline-block; width: 50px; vertical-align: middle; } + em { vertical-align: middle; } + img { max-width: 40px; max-height: 60px; @@ -1223,6 +1375,7 @@ u, border: solid 1px red; text-align: center; } + img { width: 500px; } @@ -1232,6 +1385,7 @@ u, .matmat_results { display: flex; flex-wrap: wrap; + .matmat_user { flex-basis: 14em; align-self: flex-start; @@ -1240,10 +1394,12 @@ u, overflow: hidden; border: 1px solid black; box-shadow: $shadow-color 1px 1px 1px; + &:hover { box-shadow: 1px 1px 5px $second-color; } } + .matmat_user a { color: $primary-neutral-dark-color; height: 100%; @@ -1283,6 +1439,7 @@ footer { font-size: 90%; text-align: center; vertical-align: middle; + div { margin: 0.6em 0; color: $white-color; @@ -1292,18 +1449,20 @@ footer { align-items: center; background-color: $primary-neutral-dark-color; box-shadow: $shadow-color 0 0 15px; + a { padding: 0.8em; flex: 1; font-weight: bold; color: $white-color !important; + &:hover { color: $primary-dark-color; } } } - > .version { + >.version { margin-top: 3px; color: rgba(0, 0, 0, 0.3); } @@ -1335,6 +1494,7 @@ label { * { text-align: center; } + img { width: 100px; } @@ -1351,19 +1511,23 @@ label { padding: 2px; display: inline-block; font-size: 0.8em; + span { width: 70px; float: right; } + img { max-width: 50px; max-height: 50px; float: left; } + strong { font-weight: bold; font-size: 1.2em; } + button { vertical-align: middle; } @@ -1380,6 +1544,7 @@ a.ui-button:active, background: $primary-color; border-color: $primary-color; } + .ui-corner-all, .ui-corner-bottom, .ui-corner-right, @@ -1391,10 +1556,11 @@ a.ui-button:active, #club_detail { .club_logo { float: right; + img { display: block; max-height: 10em; max-width: 10em; } } -} +} \ No newline at end of file diff --git a/core/static/webpack/ajax-select-index.ts b/core/static/webpack/ajax-select-index.ts new file mode 100644 index 00000000..e64df216 --- /dev/null +++ b/core/static/webpack/ajax-select-index.ts @@ -0,0 +1,93 @@ +import "tom-select/dist/css/tom-select.css"; +import { inheritHtmlElement, registerComponent } from "#core:utils/web-components"; +import TomSelect from "tom-select"; +import type { TomItem, TomLoadCallback, TomOption } from "tom-select/dist/types/types"; +import type { escape_html } from "tom-select/dist/types/utils"; +import { type UserProfileSchema, userSearchUsers } from "#openapi"; + +@registerComponent("ajax-select") +export class AjaxSelect extends inheritHtmlElement("select") { + public widget: TomSelect; + public filter?: (items: T[]) => T[]; + + constructor() { + super(); + + window.addEventListener("DOMContentLoaded", () => { + this.loadTomSelect(); + }); + } + + loadTomSelect() { + const minCharNumberForSearch = 2; + let maxItems = 1; + + if (this.node.multiple) { + maxItems = Number.parseInt(this.node.dataset.max) ?? null; + } + + this.widget = new TomSelect(this.node, { + hideSelected: true, + diacritics: true, + duplicates: false, + maxItems: maxItems, + loadThrottle: Number.parseInt(this.node.dataset.delay) ?? null, + valueField: "id", + labelField: "display_name", + searchField: ["display_name", "nick_name", "first_name", "last_name"], + placeholder: this.node.dataset.placeholder ?? "", + shouldLoad: (query: string) => { + return query.length >= minCharNumberForSearch; // Avoid launching search with less than 2 characters + }, + load: (query: string, callback: TomLoadCallback) => { + userSearchUsers({ + query: { + search: query, + }, + }).then((response) => { + if (response.data) { + if (this.filter) { + callback(this.filter(response.data.results), []); + } else { + callback(response.data.results, []); + } + return; + } + callback([], []); + }); + }, + render: { + option: (item: UserProfileSchema, sanitize: typeof escape_html) => { + return `
+ ${sanitize(item.display_name)} + ${sanitize(item.display_name)} +
`; + }, + item: (item: UserProfileSchema, sanitize: typeof escape_html) => { + return `${sanitize(item.display_name)}`; + }, + // biome-ignore lint/style/useNamingConvention: that's how it's defined + not_loading: (data: TomOption, _sanitize: typeof escape_html) => { + return `
${interpolate(gettext("You need to type %(number)s more characters"), { number: minCharNumberForSearch - data.input.length }, true)}
`; + }, + // biome-ignore lint/style/useNamingConvention: that's how it's defined + no_results: (_data: TomOption, _sanitize: typeof escape_html) => { + return `
${gettext("No results found")}
`; + }, + }, + }); + + // Allow removing selected items by clicking on them + this.widget.on("item_select", (item: TomItem) => { + this.widget.removeItem(item); + }); + // Remove typed text once an item has been selected + this.widget.on("item_add", () => { + this.widget.setTextboxValue(""); + }); + } +} diff --git a/core/static/webpack/easymde-index.ts b/core/static/webpack/easymde-index.ts index 3e64f5fa..04a33f17 100644 --- a/core/static/webpack/easymde-index.ts +++ b/core/static/webpack/easymde-index.ts @@ -1,6 +1,7 @@ // biome-ignore lint/correctness/noUndeclaredDependencies: shipped by easymde import "codemirror/lib/codemirror.css"; import "easymde/src/css/easymde.css"; +import { inheritHtmlElement, registerComponent } from "#core:utils/web-components"; // biome-ignore lint/correctness/noUndeclaredDependencies: Imported by EasyMDE import type CodeMirror from "codemirror"; // biome-ignore lint/style/useNamingConvention: This is how they called their namespace @@ -158,26 +159,22 @@ const loadEasyMde = (textarea: HTMLTextAreaElement) => { const submits: HTMLInputElement[] = Array.from( textarea.closest("form").querySelectorAll('input[type="submit"]'), ); - const parentDiv = textarea.parentElement; - let submitPressed = false; + const parentDiv = textarea.parentElement.parentElement; - function checkMarkdownInput(_event: Event) { + function checkMarkdownInput(event: Event) { // an attribute is null if it does not exist, else a string - const required = this.getAttribute("required") != null; - const length = this.value.trim().length; + const required = textarea.getAttribute("required") != null; + const length = textarea.value.trim().length; if (required && length === 0) { parentDiv.style.boxShadow = "red 0px 0px 1.5px 1px"; + event.preventDefault(); } else { parentDiv.style.boxShadow = ""; } } function onSubmitClick(e: Event) { - if (!submitPressed) { - this.codemirror.on("change", checkMarkdownInput); - } - submitPressed = true; checkMarkdownInput(e); } @@ -186,11 +183,10 @@ const loadEasyMde = (textarea: HTMLTextAreaElement) => { } }; -class MarkdownInput extends HTMLTextAreaElement { +@registerComponent("markdown-input") +class MarkdownInput extends inheritHtmlElement("textarea") { constructor() { super(); - window.addEventListener("DOMContentLoaded", () => loadEasyMde(this)); + window.addEventListener("DOMContentLoaded", () => loadEasyMde(this.node)); } } - -window.customElements.define("markdown-input", MarkdownInput, { extends: "textarea" }); diff --git a/core/static/webpack/jquery-index.js b/core/static/webpack/jquery-index.js index 7c5159fe..569d26e8 100644 --- a/core/static/webpack/jquery-index.js +++ b/core/static/webpack/jquery-index.js @@ -13,9 +13,6 @@ require("jquery-ui/ui/widgets/tabs.js"); require("jquery-ui/themes/base/all.css"); -// We ship select2 here, otherwise it will duplicate jquery everywhere we load it -import "select2"; - /** * Simple wrapper to solve shorten not being able on legacy pages * @param {string} selector to be passed to jQuery diff --git a/core/static/webpack/utils/globals.ts b/core/static/webpack/utils/globals.ts index 905f9740..b4f9a457 100644 --- a/core/static/webpack/utils/globals.ts +++ b/core/static/webpack/utils/globals.ts @@ -3,6 +3,7 @@ import type { Alpine as AlpineType } from "alpinejs"; declare global { const Alpine: AlpineType; const gettext: (text: string) => string; + const interpolate: (fmt: string, args: string[] | T, isNamed?: boolean) => string; } /** diff --git a/core/static/webpack/utils/select2.ts b/core/static/webpack/utils/select2.ts deleted file mode 100644 index b3e5b4d3..00000000 --- a/core/static/webpack/utils/select2.ts +++ /dev/null @@ -1,286 +0,0 @@ -/** - * Builders to use Select2 in our templates. - * - * This comes with two flavours : local data or remote data. - * - * # Local data source - * - * To use local data source, you must define an array - * in your JS code, having the fields `id` and `text`. - * - * ```js - * const data = [ - * {id: 1, text: "foo"}, - * {id: 2, text: "bar"}, - * ]; - * document.addEventListener("DOMContentLoaded", () => sithSelect2({ - * element: document.getElementById("select2-input"), - * dataSource: localDataSource(data) - * })); - * ``` - * - * You can also define a callback that return ids to exclude : - * - * ```js - * const data = [ - * {id: 1, text: "foo"}, - * {id: 2, text: "bar"}, - * {id: 3, text: "to exclude"}, - * ]; - * document.addEventListener("DOMContentLoaded", () => sithSelect2({ - * element: document.getElementById("select2-input"), - * dataSource: localDataSource(data, { - * excluded: () => data.filter((i) => i.text === "to exclude").map((i) => parseInt(i)) - * }) - * })); - * ``` - * - * # Remote data source - * - * Select2 with remote data sources are similar to those with local - * data, but with some more parameters, like `resultConverter`, - * which takes a callback that must return a `Select2Object`. - * - * ```js - * import { makeUrl } from "#core:utils/api"; - * import {userSearchUsers } from "#openapi" - * document.addEventListener("DOMContentLoaded", () => sithSelect2({ - * element: document.getElementById("select2-input"), - * dataSource: remoteDataSource(await makeUrl(userSearchUsers), { - * excluded: () => [1, 2], // exclude users 1 and 2 from the search - * resultConverter: (user: AjaxResponse) => {id: user.id, text: (user.firstName as UserType)} - * }) - * })); - * ``` - * - * # Overrides - * - * Dealing with a select2 may be complex. - * That's why, when defining a select, - * you may add an override parameter, - * in which you can declare any parameter defined in the - * Select2 documentation. - * - * ```js - * import { makeUrl } from "#core:utils/api"; - * import {userSearchUsers } from "#openapi" - * document.addEventListener("DOMContentLoaded", () => sithSelect2({ - * element: document.getElementById("select2-input"), - * dataSource: remoteDataSource(await makeUrl(userSearchUsers), { - * resultConverter: (user: AjaxResponse) => {id: user.id, text: (user.firstName as UserType)} - * overrides: { - * delay: 500 - * } - * }) - * })); - * ``` - * - * # Caveats with exclude - * - * With local data source, select2 evaluates the data only once. - * Thus, modify the exclude after the initialisation is a no-op. - * - * With remote data source, the exclude list will be evaluated - * after each api response. - * It makes it possible to bind the data returned by the callback - * to some reactive data, thus making the exclude list dynamic. - * - * # Images - * - * Sometimes, you would like to display an image besides - * the text on the select items. - * In this case, fill the `pictureGetter` option : - * - * ```js - * import { makeUrl } from "#core:utils/api"; - * import {userSearchUsers } from "#openapi" - * document.addEventListener("DOMContentLoaded", () => sithSelect2({ - * element: document.getElementById("select2-input"), - * dataSource: remoteDataSource(await makeUrl(userSearchUsers), { - * resultConverter: (user: AjaxResponse) => {id: user.id, text: (user.firstName as UserType)} - * }) - * pictureGetter: (user) => user.profilePict, - * })); - * ``` - * - * # Binding with alpine - * - * You can declare your select2 component in an Alpine data. - * - * ```html - * - *
- * - *

- *

- *
- * - * - * - */ - -import type { - AjaxOptions, - DataFormat, - GroupedDataFormat, - LoadingData, - Options, -} from "select2"; -import "select2/dist/css/select2.css"; - -export interface Select2Object { - id: number; - text: string; -} - -// biome-ignore lint/suspicious/noExplicitAny: You have to do it at some point -export type RemoteResult = any; -export type AjaxResponse = AjaxOptions; - -interface DataSource { - ajax?: AjaxResponse | undefined; - data?: RemoteResult | DataFormat[] | GroupedDataFormat[] | undefined; -} - -interface Select2Options { - element: Element; - /** the data source, built with `localDataSource` or `remoteDataSource` */ - dataSource: DataSource; - excluded?: number[]; - /** A callback to get the picture field from the API response */ - pictureGetter?: (element: LoadingData | DataFormat | GroupedDataFormat) => string; - /** Any other select2 parameter to apply on the config */ - overrides?: Options; -} - -/** - * Create a new select2 with sith presets - */ -export function sithSelect2(options: Select2Options) { - const elem = $(options.element as HTMLInputElement); - return elem.select2({ - theme: elem[0].multiple ? "classic" : "default", - minimumInputLength: 2, - templateResult: selectItemBuilder(options.pictureGetter), - ...options.dataSource, - ...(options.overrides ?? {}), - }); -} - -interface LocalSourceOptions { - excluded: () => number[]; -} - -/** - * Build a data source for a Select2 from a local array - */ -export function localDataSource( - source: Select2Object[] /** Array containing the data */, - options: LocalSourceOptions, -): DataSource { - if (options.excluded) { - const ids = options.excluded(); - return { data: source.filter((i) => !ids.includes(i.id)) }; - } - return { data: source }; -} - -interface RemoteSourceOptions { - /** A callback to the ids to exclude from the search */ - excluded?: () => number[]; - /** A converter for a value coming from the remote api */ - resultConverter?: ((obj: RemoteResult) => DataFormat | GroupedDataFormat) | undefined; - /** Any other select2 parameter to apply on the config */ - overrides?: AjaxOptions; -} - -/** - * Build a data source for a Select2 from a remote url - */ -export function remoteDataSource( - source: string /** url of the endpoint */, - options: RemoteSourceOptions, -): DataSource { - $.ajaxSetup({ - traditional: true, - }); - const params: AjaxOptions = { - url: source, - dataType: "json", - cache: true, - delay: 250, - data: function (params) { - return { - search: params.term, - exclude: [ - ...(this.val() || []).map((i: string) => Number.parseInt(i)), - ...(options.excluded ? options.excluded() : []), - ], - }; - }, - }; - if (options.resultConverter) { - params.processResults = (data) => ({ - results: data.results.map(options.resultConverter), - }); - } - if (options.overrides) { - Object.assign(params, options.overrides); - } - return { ajax: params }; -} - -export function itemFormatter(user: { loading: boolean; text: string }) { - if (user.loading) { - return user.text; - } -} - -/** - * Build a function to display the results - */ -export function selectItemBuilder(pictureGetter?: (item: RemoteResult) => string) { - return (item: RemoteResult) => { - const picture = typeof pictureGetter === "function" ? pictureGetter(item) : null; - const wrapper = document.createElement("div"); - wrapper.classList.add("select-item"); - if (picture) { - const img = document.createElement("img"); - img.src = picture; - img.alt = encodeURI(item.text); - img.onerror = () => { - img.src = "/static/core/img/unknown.jpg"; - }; - wrapper.appendChild(img); - } - const textSpan = document.createElement("span"); - textSpan.classList.add("select-item-text"); - textSpan.appendChild(document.createTextNode(item.text)); - wrapper.appendChild(textSpan); - - return $(wrapper); - }; -} diff --git a/core/static/webpack/utils/web-components.ts b/core/static/webpack/utils/web-components.ts new file mode 100644 index 00000000..2899a5af --- /dev/null +++ b/core/static/webpack/utils/web-components.ts @@ -0,0 +1,50 @@ +/** + * Class decorator to register components easily + * It's a wrapper around window.customElements.define + * What's nice about it is that you don't separate the component registration + * and the class definition + **/ +export function registerComponent(name: string, options?: ElementDefinitionOptions) { + return (component: CustomElementConstructor) => { + window.customElements.define(name, component, options); + }; +} + +/** + * Safari doesn't support inheriting from HTML tags on web components + * The technique is to: + * create a new web component + * create the desired type inside + * pass all attributes to the child component + * store is at as `node` inside the parent + * + * Since we can't use the generic type to instantiate the node, we create a generator function + * + * ```js + * class MyClass extends inheritHtmlElement("select") { + * // do whatever + * } + * ``` + **/ +export function inheritHtmlElement(tagName: K) { + return class Inherited extends HTMLElement { + protected node: HTMLElementTagNameMap[K]; + + constructor() { + super(); + this.node = document.createElement(tagName); + const attributes: Attr[] = []; // We need to make a copy to delete while iterating + for (const attr of this.attributes) { + if (attr.name in this.node) { + attributes.push(attr); + } + } + + for (const attr of attributes) { + this.removeAttributeNode(attr); + this.node.setAttributeNode(attr); + } + this.appendChild(this.node); + } + }; +} diff --git a/core/templates/core/base.jinja b/core/templates/core/base.jinja index 76c6392b..8ce2eb80 100644 --- a/core/templates/core/base.jinja +++ b/core/templates/core/base.jinja @@ -5,6 +5,7 @@ {% block title %}{% trans %}Welcome!{% endtrans %}{% endblock %} - Association des Étudiants UTBM + diff --git a/core/templates/core/widgets/markdown_textarea.jinja b/core/templates/core/widgets/markdown_textarea.jinja index f7cae8c4..287e4521 100644 --- a/core/templates/core/widgets/markdown_textarea.jinja +++ b/core/templates/core/widgets/markdown_textarea.jinja @@ -1,5 +1,5 @@
- + {% if widget.value %}{{ widget.value }}{% endif %} {# The easymde script can be included twice, it's safe in the code #} diff --git a/docs/howto/translation.md b/docs/howto/translation.md index 88366492..6ae299ed 100644 --- a/docs/howto/translation.md +++ b/docs/howto/translation.md @@ -24,6 +24,12 @@ Si le mot apparaît dans le template Jinja : {% trans %}Hello{% endtrans %} ``` +Si on est dans un fichier javascript ou typescript : + +```js +gettext("Hello"); +``` + ## Générer le fichier django.po La traduction se fait en trois étapes. @@ -32,7 +38,7 @@ l'éditer et enfin le compiler au format binaire pour qu'il soit lu par le serve ```bash ./manage.py makemessages --locale=fr -e py,jinja --ignore=node_modules # Pour le backend -./manage.py makemessages --locale=fr -d djangojs --ignore=node_modules # Pour le frontend +./manage.py makemessages --locale=fr -d djangojs -e js,ts --ignore=node_modules # Pour le frontend ``` ## Éditer le fichier django.po diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 091fa388..e548dfba 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-14 00:46+0200\n" +"POT-Creation-Date: 2024-10-16 01:51+0200\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Maréchal \n" @@ -16,11 +16,11 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: accounting/models.py:62 accounting/models.py:103 accounting/models.py:136 -#: accounting/models.py:203 club/models.py:55 com/models.py:274 -#: com/models.py:293 counter/models.py:265 counter/models.py:298 -#: counter/models.py:456 forum/models.py:59 launderette/models.py:29 -#: launderette/models.py:84 launderette/models.py:122 +#: accounting/models.py:62 accounting/models.py:101 accounting/models.py:132 +#: accounting/models.py:190 club/models.py:55 com/models.py:274 +#: com/models.py:293 counter/models.py:265 counter/models.py:296 +#: counter/models.py:449 forum/models.py:60 launderette/models.py:29 +#: launderette/models.py:80 launderette/models.py:116 msgid "name" msgstr "nom" @@ -56,125 +56,125 @@ msgstr "site internet" msgid "company" msgstr "entreprise" -#: accounting/models.py:104 +#: accounting/models.py:102 msgid "iban" msgstr "IBAN" -#: accounting/models.py:105 +#: accounting/models.py:103 msgid "account number" msgstr "numéro de compte" -#: accounting/models.py:109 accounting/models.py:140 club/models.py:345 -#: com/models.py:74 com/models.py:259 com/models.py:299 counter/models.py:321 -#: counter/models.py:458 trombi/models.py:210 +#: accounting/models.py:107 accounting/models.py:136 club/models.py:345 +#: com/models.py:74 com/models.py:259 com/models.py:299 counter/models.py:319 +#: counter/models.py:451 trombi/models.py:209 msgid "club" msgstr "club" -#: accounting/models.py:114 +#: accounting/models.py:112 msgid "Bank account" msgstr "Compte en banque" -#: accounting/models.py:146 +#: accounting/models.py:142 msgid "bank account" msgstr "compte en banque" -#: accounting/models.py:151 +#: accounting/models.py:147 msgid "Club account" msgstr "Compte club" -#: accounting/models.py:192 +#: accounting/models.py:179 #, python-format msgid "%(club_account)s on %(bank_account)s" msgstr "%(club_account)s sur %(bank_account)s" -#: accounting/models.py:201 club/models.py:351 counter/models.py:944 -#: election/models.py:16 launderette/models.py:179 +#: accounting/models.py:188 club/models.py:351 counter/models.py:929 +#: election/models.py:16 launderette/models.py:165 msgid "start date" msgstr "date de début" -#: accounting/models.py:202 club/models.py:352 counter/models.py:945 +#: accounting/models.py:189 club/models.py:352 counter/models.py:930 #: election/models.py:17 msgid "end date" msgstr "date de fin" -#: accounting/models.py:204 +#: accounting/models.py:191 msgid "is closed" msgstr "est fermé" -#: accounting/models.py:209 accounting/models.py:519 +#: accounting/models.py:196 accounting/models.py:496 msgid "club account" msgstr "compte club" -#: accounting/models.py:212 accounting/models.py:272 counter/models.py:57 -#: counter/models.py:654 +#: accounting/models.py:199 accounting/models.py:255 counter/models.py:57 +#: counter/models.py:647 msgid "amount" msgstr "montant" -#: accounting/models.py:213 +#: accounting/models.py:200 msgid "effective_amount" msgstr "montant effectif" -#: accounting/models.py:216 +#: accounting/models.py:203 msgid "General journal" msgstr "Classeur" -#: accounting/models.py:264 +#: accounting/models.py:247 msgid "number" msgstr "numéro" -#: accounting/models.py:269 +#: accounting/models.py:252 msgid "journal" msgstr "classeur" -#: accounting/models.py:273 core/models.py:959 core/models.py:1479 -#: core/models.py:1524 core/models.py:1553 core/models.py:1577 -#: counter/models.py:664 counter/models.py:768 counter/models.py:980 -#: eboutic/models.py:57 eboutic/models.py:193 forum/models.py:311 -#: forum/models.py:412 +#: accounting/models.py:256 core/models.py:945 core/models.py:1456 +#: core/models.py:1501 core/models.py:1530 core/models.py:1554 +#: counter/models.py:657 counter/models.py:761 counter/models.py:965 +#: eboutic/models.py:57 eboutic/models.py:193 forum/models.py:312 +#: forum/models.py:413 msgid "date" msgstr "date" -#: accounting/models.py:274 counter/models.py:267 counter/models.py:981 -#: pedagogy/models.py:207 +#: accounting/models.py:257 counter/models.py:267 counter/models.py:966 +#: pedagogy/models.py:208 msgid "comment" msgstr "commentaire" -#: accounting/models.py:276 counter/models.py:666 counter/models.py:770 +#: accounting/models.py:259 counter/models.py:659 counter/models.py:763 #: subscription/models.py:56 msgid "payment method" msgstr "méthode de paiement" -#: accounting/models.py:281 +#: accounting/models.py:264 msgid "cheque number" msgstr "numéro de chèque" -#: accounting/models.py:286 eboutic/models.py:291 +#: accounting/models.py:269 eboutic/models.py:291 msgid "invoice" msgstr "facture" -#: accounting/models.py:291 +#: accounting/models.py:274 msgid "is done" msgstr "est fait" -#: accounting/models.py:295 +#: accounting/models.py:278 msgid "simple type" msgstr "type simplifié" -#: accounting/models.py:303 accounting/models.py:462 +#: accounting/models.py:286 accounting/models.py:441 msgid "accounting type" msgstr "type comptable" -#: accounting/models.py:311 accounting/models.py:450 accounting/models.py:483 -#: accounting/models.py:515 core/models.py:1552 core/models.py:1578 -#: counter/models.py:734 +#: accounting/models.py:294 accounting/models.py:429 accounting/models.py:460 +#: accounting/models.py:492 core/models.py:1529 core/models.py:1555 +#: counter/models.py:727 msgid "label" msgstr "étiquette" -#: accounting/models.py:317 +#: accounting/models.py:300 msgid "target type" msgstr "type de cible" -#: accounting/models.py:320 club/models.py:507 +#: accounting/models.py:303 club/models.py:505 #: club/templates/club/club_members.jinja:17 #: club/templates/club/club_old_members.jinja:8 #: club/templates/club/mailing.jinja:41 @@ -186,7 +186,7 @@ msgstr "type de cible" msgid "User" msgstr "Utilisateur" -#: accounting/models.py:321 club/models.py:410 +#: accounting/models.py:304 club/models.py:408 #: club/templates/club/club_detail.jinja:12 #: com/templates/com/mailing_admin.jinja:11 #: com/templates/com/news_admin_list.jinja:23 @@ -210,35 +210,35 @@ msgstr "Utilisateur" msgid "Club" msgstr "Club" -#: accounting/models.py:322 core/views/user.py:283 +#: accounting/models.py:305 core/views/user.py:284 msgid "Account" msgstr "Compte" -#: accounting/models.py:323 +#: accounting/models.py:306 msgid "Company" msgstr "Entreprise" -#: accounting/models.py:324 core/models.py:337 sith/settings.py:420 +#: accounting/models.py:307 core/models.py:337 sith/settings.py:420 msgid "Other" msgstr "Autre" -#: accounting/models.py:327 +#: accounting/models.py:310 msgid "target id" msgstr "id de la cible" -#: accounting/models.py:329 +#: accounting/models.py:312 msgid "target label" msgstr "nom de la cible" -#: accounting/models.py:334 +#: accounting/models.py:317 msgid "linked operation" msgstr "opération liée" -#: accounting/models.py:366 +#: accounting/models.py:349 msgid "The date must be set." msgstr "La date doit être indiquée." -#: accounting/models.py:370 +#: accounting/models.py:353 #, python-format msgid "" "The date can not be before the start date of the journal, which is\n" @@ -247,16 +247,16 @@ msgstr "" "La date ne peut pas être avant la date de début du journal, qui est\n" "%(start_date)s." -#: accounting/models.py:380 +#: accounting/models.py:363 msgid "Target does not exists" msgstr "La cible n'existe pas." -#: accounting/models.py:383 +#: accounting/models.py:366 msgid "Please add a target label if you set no existing target" msgstr "" "Merci d'ajouter un nom de cible si vous ne spécifiez pas de cible existante" -#: accounting/models.py:388 +#: accounting/models.py:371 msgid "" "You need to provide ether a simplified accounting type or a standard " "accounting type" @@ -264,41 +264,41 @@ msgstr "" "Vous devez fournir soit un type comptable simplifié ou un type comptable " "standard" -#: accounting/models.py:442 counter/models.py:308 pedagogy/models.py:41 +#: accounting/models.py:421 counter/models.py:306 pedagogy/models.py:41 msgid "code" msgstr "code" -#: accounting/models.py:446 +#: accounting/models.py:425 msgid "An accounting type code contains only numbers" msgstr "Un code comptable ne contient que des numéros" -#: accounting/models.py:452 +#: accounting/models.py:431 msgid "movement type" msgstr "type de mouvement" -#: accounting/models.py:454 +#: accounting/models.py:433 #: accounting/templates/accounting/journal_statement_nature.jinja:9 #: accounting/templates/accounting/journal_statement_person.jinja:12 #: accounting/views.py:549 msgid "Credit" msgstr "Crédit" -#: accounting/models.py:455 +#: accounting/models.py:434 #: accounting/templates/accounting/journal_statement_nature.jinja:28 #: accounting/templates/accounting/journal_statement_person.jinja:40 #: accounting/views.py:549 msgid "Debit" msgstr "Débit" -#: accounting/models.py:456 +#: accounting/models.py:435 msgid "Neutral" msgstr "Neutre" -#: accounting/models.py:487 +#: accounting/models.py:464 msgid "simplified accounting types" msgstr "type simplifié" -#: accounting/models.py:492 +#: accounting/models.py:469 msgid "simplified type" msgstr "type simplifié" @@ -375,17 +375,17 @@ msgstr "Compte en banque : " #: election/templates/election/election_detail.jinja:187 #: forum/templates/forum/macros.jinja:21 #: launderette/templates/launderette/launderette_admin.jinja:16 -#: launderette/views.py:217 pedagogy/templates/pedagogy/guide.jinja:99 +#: launderette/views.py:210 pedagogy/templates/pedagogy/guide.jinja:99 #: pedagogy/templates/pedagogy/guide.jinja:114 #: pedagogy/templates/pedagogy/uv_detail.jinja:189 #: sas/templates/sas/album.jinja:36 sas/templates/sas/moderation.jinja:18 -#: sas/templates/sas/picture.jinja:69 trombi/templates/trombi/detail.jinja:35 +#: sas/templates/sas/picture.jinja:70 trombi/templates/trombi/detail.jinja:35 #: trombi/templates/trombi/edit_profile.jinja:35 msgid "Delete" msgstr "Supprimer" #: accounting/templates/accounting/bank_account_details.jinja:18 -#: club/views.py:79 core/views/user.py:202 sas/templates/sas/picture.jinja:89 +#: club/views.py:79 core/views/user.py:202 sas/templates/sas/picture.jinja:90 msgid "Infos" msgstr "Infos" @@ -650,7 +650,7 @@ msgid "Done" msgstr "Effectuées" #: accounting/templates/accounting/journal_details.jinja:41 -#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:962 +#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:955 #: pedagogy/templates/pedagogy/moderation.jinja:13 #: pedagogy/templates/pedagogy/uv_detail.jinja:142 #: trombi/templates/trombi/comment.jinja:4 @@ -783,7 +783,7 @@ msgstr "Sauver" #: accounting/templates/accounting/refound_account.jinja:4 #: accounting/templates/accounting/refound_account.jinja:9 -#: accounting/views.py:868 +#: accounting/views.py:863 msgid "Refound account" msgstr "Remboursement de compte" @@ -876,15 +876,15 @@ msgstr "Commentaire :" msgid "Signature:" msgstr "Signature :" -#: accounting/views.py:663 +#: accounting/views.py:661 msgid "General statement" msgstr "Bilan général" -#: accounting/views.py:670 +#: accounting/views.py:668 msgid "No label operations" msgstr "Opérations sans étiquette" -#: accounting/views.py:826 +#: accounting/views.py:821 msgid "Refound this account" msgstr "Rembourser ce compte" @@ -930,7 +930,7 @@ msgstr "S'abonner" msgid "Remove" msgstr "Retirer" -#: club/forms.py:70 launderette/views.py:219 +#: club/forms.py:70 launderette/views.py:212 #: pedagogy/templates/pedagogy/moderation.jinja:15 msgid "Action" msgstr "Action" @@ -959,23 +959,23 @@ msgstr "vous devez spécifier au moins un utilisateur ou une adresse email" msgid "Begin date" msgstr "Date de début" -#: club/forms.py:156 com/views.py:83 com/views.py:202 counter/forms.py:194 -#: election/views.py:167 subscription/views.py:38 +#: club/forms.py:156 com/views.py:83 com/views.py:201 counter/forms.py:194 +#: election/views.py:171 subscription/views.py:38 msgid "End date" msgstr "Date de fin" #: club/forms.py:160 club/templates/club/club_sellings.jinja:49 #: core/templates/core/user_account_detail.jinja:17 #: core/templates/core/user_account_detail.jinja:56 -#: counter/templates/counter/cash_summary_list.jinja:33 counter/views.py:143 +#: counter/templates/counter/cash_summary_list.jinja:33 counter/views.py:137 msgid "Counter" msgstr "Comptoir" -#: club/forms.py:167 counter/views.py:690 +#: club/forms.py:167 counter/views.py:683 msgid "Products" msgstr "Produits" -#: club/forms.py:172 counter/views.py:695 +#: club/forms.py:172 counter/views.py:688 msgid "Archived products" msgstr "Produits archivés" @@ -997,7 +997,7 @@ msgstr "Vous ne pouvez pas ajouter deux fois le même utilisateur" msgid "You should specify a role" msgstr "Vous devez choisir un rôle" -#: club/forms.py:283 sas/views.py:58 sas/views.py:177 +#: club/forms.py:283 sas/views.py:58 sas/views.py:176 msgid "You do not have the permission to do that" msgstr "Vous n'avez pas la permission de faire cela" @@ -1045,58 +1045,58 @@ msgstr "Vous ne pouvez pas faire de boucles dans les clubs" msgid "A club with that unix_name already exists" msgstr "Un club avec ce nom UNIX existe déjà." -#: club/models.py:337 counter/models.py:935 counter/models.py:971 +#: club/models.py:337 counter/models.py:920 counter/models.py:956 #: eboutic/models.py:53 eboutic/models.py:189 election/models.py:183 -#: launderette/models.py:136 launderette/models.py:198 sas/models.py:274 -#: trombi/models.py:206 +#: launderette/models.py:130 launderette/models.py:184 sas/models.py:273 +#: trombi/models.py:205 msgid "user" msgstr "nom d'utilisateur" #: club/models.py:354 core/models.py:356 election/models.py:178 -#: election/models.py:212 trombi/models.py:211 +#: election/models.py:212 trombi/models.py:210 msgid "role" msgstr "rôle" #: club/models.py:359 core/models.py:89 counter/models.py:266 -#: counter/models.py:299 election/models.py:13 election/models.py:115 -#: election/models.py:188 forum/models.py:60 forum/models.py:244 +#: counter/models.py:297 election/models.py:13 election/models.py:115 +#: election/models.py:188 forum/models.py:61 forum/models.py:245 msgid "description" msgstr "description" -#: club/models.py:417 club/models.py:513 +#: club/models.py:415 club/models.py:511 msgid "Email address" msgstr "Adresse email" -#: club/models.py:425 +#: club/models.py:423 msgid "Enter a valid address. Only the root of the address is needed." msgstr "" "Entrez une adresse valide. Seule la racine de l'adresse est nécessaire." -#: club/models.py:429 com/models.py:82 com/models.py:309 core/models.py:960 +#: club/models.py:427 com/models.py:82 com/models.py:309 core/models.py:946 msgid "is moderated" msgstr "est modéré" -#: club/models.py:433 com/models.py:86 com/models.py:313 +#: club/models.py:431 com/models.py:86 com/models.py:313 msgid "moderator" msgstr "modérateur" -#: club/models.py:460 +#: club/models.py:458 msgid "This mailing list already exists." msgstr "Cette liste de diffusion existe déjà." -#: club/models.py:499 club/templates/club/mailing.jinja:23 +#: club/models.py:497 club/templates/club/mailing.jinja:23 msgid "Mailing" msgstr "Liste de diffusion" -#: club/models.py:523 +#: club/models.py:521 msgid "At least user or email is required" msgstr "Au moins un utilisateur ou un email est nécessaire" -#: club/models.py:531 club/tests.py:769 +#: club/models.py:529 club/tests.py:769 msgid "This email is already suscribed in this mailing" msgstr "Cet email est déjà abonné à cette mailing" -#: club/models.py:559 +#: club/models.py:557 msgid "Unregistered user" msgstr "Utilisateur non enregistré" @@ -1151,7 +1151,7 @@ msgstr "Il n'y a pas de membres dans ce club." #: club/templates/club/club_members.jinja:80 #: core/templates/core/file_detail.jinja:19 core/views/forms.py:312 -#: launderette/views.py:217 trombi/templates/trombi/detail.jinja:19 +#: launderette/views.py:210 trombi/templates/trombi/detail.jinja:19 msgid "Add" msgstr "Ajouter" @@ -1240,8 +1240,8 @@ msgstr "Quantité" #: counter/templates/counter/cash_summary_list.jinja:35 #: counter/templates/counter/last_ops.jinja:50 #: counter/templates/counter/stats.jinja:23 -#: subscription/templates/subscription/stats.jinja:40 -#: subscription/templates/subscription/stats.jinja:48 +#: subscription/templates/subscription/stats.jinja:42 +#: subscription/templates/subscription/stats.jinja:50 msgid "Total" msgstr "Total" @@ -1373,8 +1373,8 @@ msgstr "Anciens membres" msgid "History" msgstr "Historique" -#: club/views.py:116 core/templates/core/base.jinja:104 core/views/user.py:225 -#: sas/templates/sas/picture.jinja:108 trombi/views.py:61 +#: club/views.py:116 core/templates/core/base.jinja:105 core/views/user.py:225 +#: sas/templates/sas/picture.jinja:109 trombi/views.py:62 msgid "Tools" msgstr "Outils" @@ -1382,7 +1382,7 @@ msgstr "Outils" msgid "Edit club page" msgstr "Éditer la page de club" -#: club/views.py:145 club/views.py:451 +#: club/views.py:145 club/views.py:452 msgid "Sellings" msgstr "Vente" @@ -1390,7 +1390,7 @@ msgstr "Vente" msgid "Mailing list" msgstr "Listes de diffusion" -#: club/views.py:161 com/views.py:134 +#: club/views.py:161 com/views.py:133 msgid "Posters list" msgstr "Liste d'affiches" @@ -1429,8 +1429,8 @@ msgid "Call" msgstr "Appel" #: com/models.py:67 com/models.py:174 com/models.py:248 election/models.py:12 -#: election/models.py:114 election/models.py:152 forum/models.py:255 -#: forum/models.py:309 pedagogy/models.py:96 +#: election/models.py:114 election/models.py:152 forum/models.py:256 +#: forum/models.py:310 pedagogy/models.py:97 msgid "title" msgstr "titre" @@ -1438,17 +1438,17 @@ msgstr "titre" msgid "summary" msgstr "résumé" -#: com/models.py:69 com/models.py:249 trombi/models.py:189 +#: com/models.py:69 com/models.py:249 trombi/models.py:188 msgid "content" msgstr "contenu" -#: com/models.py:71 core/models.py:1522 launderette/models.py:92 -#: launderette/models.py:130 launderette/models.py:181 +#: com/models.py:71 core/models.py:1499 launderette/models.py:88 +#: launderette/models.py:124 launderette/models.py:167 msgid "type" msgstr "type" -#: com/models.py:79 com/models.py:253 pedagogy/models.py:56 -#: pedagogy/models.py:199 trombi/models.py:179 +#: com/models.py:79 com/models.py:253 pedagogy/models.py:57 +#: pedagogy/models.py:200 trombi/models.py:178 msgid "author" msgstr "auteur" @@ -1492,7 +1492,7 @@ msgstr "weekmail" msgid "rank" msgstr "rang" -#: com/models.py:295 core/models.py:925 core/models.py:975 +#: com/models.py:295 core/models.py:911 core/models.py:961 msgid "file" msgstr "fichier" @@ -1504,7 +1504,7 @@ msgstr "temps d'affichage" msgid "Begin date should be before end date" msgstr "La date de début doit être avant celle de fin" -#: com/templates/com/mailing_admin.jinja:4 com/views.py:127 +#: com/templates/com/mailing_admin.jinja:4 com/views.py:126 #: core/templates/core/user_tools.jinja:136 msgid "Mailing lists administration" msgstr "Administration des mailing listes" @@ -1517,7 +1517,7 @@ msgstr "Administration des mailing listes" #: com/templates/com/news_detail.jinja:39 #: core/templates/core/file_detail.jinja:65 #: core/templates/core/file_moderation.jinja:23 -#: sas/templates/sas/moderation.jinja:17 sas/templates/sas/picture.jinja:66 +#: sas/templates/sas/moderation.jinja:17 sas/templates/sas/picture.jinja:67 msgid "Moderate" msgstr "Modérer" @@ -1574,7 +1574,7 @@ msgstr "Informations affichées" #: com/templates/com/news_admin_list.jinja:248 #: com/templates/com/news_admin_list.jinja:285 #: launderette/templates/launderette/launderette_admin.jinja:42 -#: launderette/views.py:224 +#: launderette/views.py:217 msgid "Type" msgstr "Type" @@ -1613,7 +1613,7 @@ msgstr "Résumé" #: com/templates/com/news_admin_list.jinja:252 #: com/templates/com/news_admin_list.jinja:289 #: com/templates/com/weekmail.jinja:17 com/templates/com/weekmail.jinja:46 -#: forum/templates/forum/forum.jinja:55 sas/models.py:298 +#: forum/templates/forum/forum.jinja:55 sas/models.py:297 msgid "Author" msgstr "Auteur" @@ -1659,7 +1659,7 @@ msgid "Calls to moderate" msgstr "Appels à modérer" #: com/templates/com/news_admin_list.jinja:242 -#: core/templates/core/base.jinja:219 +#: core/templates/core/base.jinja:220 msgid "Events" msgstr "Événements" @@ -1819,7 +1819,7 @@ msgid "Slideshow" msgstr "Diaporama" #: com/templates/com/weekmail.jinja:5 com/templates/com/weekmail.jinja:9 -#: com/views.py:104 core/templates/core/user_tools.jinja:129 +#: com/views.py:103 core/templates/core/user_tools.jinja:129 msgid "Weekmail" msgstr "Weekmail" @@ -1862,7 +1862,7 @@ msgstr "Supprimer du Weekmail" #: com/templates/com/weekmail_preview.jinja:9 #: core/templates/core/user_account_detail.jinja:10 -#: core/templates/core/user_account_detail.jinja:116 launderette/views.py:217 +#: core/templates/core/user_account_detail.jinja:116 launderette/views.py:210 #: pedagogy/templates/pedagogy/uv_detail.jinja:16 #: pedagogy/templates/pedagogy/uv_detail.jinja:25 #: trombi/templates/trombi/comment_moderation.jinja:10 @@ -1919,56 +1919,56 @@ msgstr "Le mot de la fin" msgid "Format: 16:9 | Resolution: 1920x1080" msgstr "Format : 16:9 | Résolution : 1920x1080" -#: com/views.py:77 com/views.py:199 election/views.py:164 +#: com/views.py:77 com/views.py:198 election/views.py:168 #: subscription/views.py:35 msgid "Start date" msgstr "Date de début" -#: com/views.py:99 +#: com/views.py:98 msgid "Communication administration" msgstr "Administration de la communication" -#: com/views.py:110 core/templates/core/user_tools.jinja:130 +#: com/views.py:109 core/templates/core/user_tools.jinja:130 msgid "Weekmail destinations" msgstr "Destinataires du Weekmail" -#: com/views.py:114 +#: com/views.py:113 msgid "Info message" msgstr "Message d'info" -#: com/views.py:120 +#: com/views.py:119 msgid "Alert message" msgstr "Message d'alerte" -#: com/views.py:141 +#: com/views.py:140 msgid "Screens list" msgstr "Liste d'écrans" -#: com/views.py:204 +#: com/views.py:203 msgid "Until" msgstr "Jusqu'à" -#: com/views.py:206 +#: com/views.py:205 msgid "Automoderation" msgstr "Automodération" -#: com/views.py:213 com/views.py:217 com/views.py:231 +#: com/views.py:212 com/views.py:216 com/views.py:230 msgid "This field is required." msgstr "Ce champ est obligatoire." -#: com/views.py:227 +#: com/views.py:226 msgid "You crazy? You can not finish an event before starting it." msgstr "T'es fou? Un événement ne peut pas finir avant même de commencer." -#: com/views.py:451 +#: com/views.py:450 msgid "Delete and save to regenerate" msgstr "Supprimer et sauver pour régénérer" -#: com/views.py:466 +#: com/views.py:465 msgid "Weekmail of the " msgstr "Weekmail du " -#: com/views.py:570 +#: com/views.py:569 msgid "" "You must be a board member of the selected club to post in the Weekmail." msgstr "" @@ -2166,7 +2166,7 @@ msgstr "département" msgid "dpt option" msgstr "Filière" -#: core/models.py:380 pedagogy/models.py:69 pedagogy/models.py:293 +#: core/models.py:380 pedagogy/models.py:70 pedagogy/models.py:294 msgid "semester" msgstr "semestre" @@ -2206,7 +2206,7 @@ msgstr "profil visible par les cotisants" msgid "A user with that username already exists" msgstr "Un utilisateur de ce nom d'utilisateur existe déjà" -#: core/models.py:756 core/templates/core/macros.jinja:75 +#: core/models.py:750 core/templates/core/macros.jinja:75 #: core/templates/core/macros.jinja:77 core/templates/core/macros.jinja:78 #: core/templates/core/user_detail.jinja:100 #: core/templates/core/user_detail.jinja:101 @@ -2226,101 +2226,101 @@ msgstr "Un utilisateur de ce nom d'utilisateur existe déjà" msgid "Profile" msgstr "Profil" -#: core/models.py:875 +#: core/models.py:861 msgid "Visitor" msgstr "Visiteur" -#: core/models.py:882 +#: core/models.py:868 msgid "receive the Weekmail" msgstr "recevoir le Weekmail" -#: core/models.py:883 +#: core/models.py:869 msgid "show your stats to others" msgstr "montrez vos statistiques aux autres" -#: core/models.py:885 +#: core/models.py:871 msgid "get a notification for every click" msgstr "avoir une notification pour chaque click" -#: core/models.py:888 +#: core/models.py:874 msgid "get a notification for every refilling" msgstr "avoir une notification pour chaque rechargement" -#: core/models.py:914 sas/forms.py:89 +#: core/models.py:900 sas/forms.py:89 msgid "file name" msgstr "nom du fichier" -#: core/models.py:918 core/models.py:1271 +#: core/models.py:904 core/models.py:1257 msgid "parent" msgstr "parent" -#: core/models.py:932 +#: core/models.py:918 msgid "compressed file" msgstr "version allégée" -#: core/models.py:939 +#: core/models.py:925 msgid "thumbnail" msgstr "miniature" -#: core/models.py:947 core/models.py:964 +#: core/models.py:933 core/models.py:950 msgid "owner" msgstr "propriétaire" -#: core/models.py:951 core/models.py:1288 core/views/files.py:223 +#: core/models.py:937 core/models.py:1274 core/views/files.py:223 msgid "edit group" msgstr "groupe d'édition" -#: core/models.py:954 core/models.py:1291 core/views/files.py:226 +#: core/models.py:940 core/models.py:1277 core/views/files.py:226 msgid "view group" msgstr "groupe de vue" -#: core/models.py:956 +#: core/models.py:942 msgid "is folder" msgstr "est un dossier" -#: core/models.py:957 +#: core/models.py:943 msgid "mime type" msgstr "type mime" -#: core/models.py:958 +#: core/models.py:944 msgid "size" msgstr "taille" -#: core/models.py:969 +#: core/models.py:955 msgid "asked for removal" msgstr "retrait demandé" -#: core/models.py:971 +#: core/models.py:957 msgid "is in the SAS" msgstr "est dans le SAS" -#: core/models.py:1040 +#: core/models.py:1026 msgid "Character '/' not authorized in name" msgstr "Le caractère '/' n'est pas autorisé dans les noms de fichier" -#: core/models.py:1042 core/models.py:1046 +#: core/models.py:1028 core/models.py:1032 msgid "Loop in folder tree" msgstr "Boucle dans l'arborescence des dossiers" -#: core/models.py:1049 +#: core/models.py:1035 msgid "You can not make a file be a children of a non folder file" msgstr "" "Vous ne pouvez pas mettre un fichier enfant de quelque chose qui n'est pas " "un dossier" -#: core/models.py:1060 +#: core/models.py:1046 msgid "Duplicate file" msgstr "Un fichier de ce nom existe déjà" -#: core/models.py:1077 +#: core/models.py:1063 msgid "You must provide a file" msgstr "Vous devez fournir un fichier" -#: core/models.py:1254 +#: core/models.py:1240 msgid "page unix name" msgstr "nom unix de la page" -#: core/models.py:1260 +#: core/models.py:1246 msgid "" "Enter a valid page name. This value may contain only unaccented letters, " "numbers and ./+/-/_ characters." @@ -2328,55 +2328,55 @@ msgstr "" "Entrez un nom de page correct. Uniquement des lettres non accentuées, " "numéros, et ./+/-/_" -#: core/models.py:1278 +#: core/models.py:1264 msgid "page name" msgstr "nom de la page" -#: core/models.py:1283 +#: core/models.py:1269 msgid "owner group" msgstr "groupe propriétaire" -#: core/models.py:1296 +#: core/models.py:1282 msgid "lock user" msgstr "utilisateur bloquant" -#: core/models.py:1303 +#: core/models.py:1289 msgid "lock_timeout" msgstr "décompte du déblocage" -#: core/models.py:1353 +#: core/models.py:1339 msgid "Duplicate page" msgstr "Une page de ce nom existe déjà" -#: core/models.py:1356 +#: core/models.py:1342 msgid "Loop in page tree" msgstr "Boucle dans l'arborescence des pages" -#: core/models.py:1476 +#: core/models.py:1453 msgid "revision" msgstr "révision" -#: core/models.py:1477 +#: core/models.py:1454 msgid "page title" msgstr "titre de la page" -#: core/models.py:1478 +#: core/models.py:1455 msgid "page content" msgstr "contenu de la page" -#: core/models.py:1519 +#: core/models.py:1496 msgid "url" msgstr "url" -#: core/models.py:1520 +#: core/models.py:1497 msgid "param" msgstr "param" -#: core/models.py:1525 +#: core/models.py:1502 msgid "viewed" msgstr "vue" -#: core/models.py:1583 +#: core/models.py:1560 msgid "operation type" msgstr "type d'opération" @@ -2396,18 +2396,18 @@ msgstr "500, Erreur Serveur" msgid "Welcome!" msgstr "Bienvenue !" -#: core/templates/core/base.jinja:56 core/templates/core/login.jinja:8 +#: core/templates/core/base.jinja:57 core/templates/core/login.jinja:8 #: core/templates/core/login.jinja:18 core/templates/core/login.jinja:51 #: core/templates/core/password_reset_complete.jinja:5 msgid "Login" msgstr "Connexion" -#: core/templates/core/base.jinja:57 core/templates/core/register.jinja:7 +#: core/templates/core/base.jinja:58 core/templates/core/register.jinja:7 #: core/templates/core/register.jinja:16 core/templates/core/register.jinja:22 msgid "Register" msgstr "Inscription" -#: core/templates/core/base.jinja:63 core/templates/core/base.jinja:64 +#: core/templates/core/base.jinja:64 core/templates/core/base.jinja:65 #: forum/templates/forum/macros.jinja:179 #: forum/templates/forum/macros.jinja:183 #: matmat/templates/matmat/search_form.jinja:39 @@ -2416,52 +2416,52 @@ msgstr "Inscription" msgid "Search" msgstr "Recherche" -#: core/templates/core/base.jinja:105 +#: core/templates/core/base.jinja:106 msgid "Logout" msgstr "Déconnexion" -#: core/templates/core/base.jinja:153 +#: core/templates/core/base.jinja:154 msgid "You do not have any unread notification" msgstr "Vous n'avez aucune notification non lue" -#: core/templates/core/base.jinja:158 +#: core/templates/core/base.jinja:159 msgid "View more" msgstr "Voir plus" -#: core/templates/core/base.jinja:161 +#: core/templates/core/base.jinja:162 #: forum/templates/forum/last_unread.jinja:21 msgid "Mark all as read" msgstr "Marquer tout comme lu" -#: core/templates/core/base.jinja:209 +#: core/templates/core/base.jinja:210 msgid "Main" msgstr "Accueil" -#: core/templates/core/base.jinja:211 +#: core/templates/core/base.jinja:212 msgid "Associations & Clubs" msgstr "Associations & Clubs" -#: core/templates/core/base.jinja:213 +#: core/templates/core/base.jinja:214 msgid "AE" msgstr "L'AE" -#: core/templates/core/base.jinja:214 +#: core/templates/core/base.jinja:215 msgid "AE's clubs" msgstr "Les clubs de L'AE" -#: core/templates/core/base.jinja:215 +#: core/templates/core/base.jinja:216 msgid "Others UTBM's Associations" msgstr "Les autres associations de l'UTBM" -#: core/templates/core/base.jinja:221 core/templates/core/user_tools.jinja:172 +#: core/templates/core/base.jinja:222 core/templates/core/user_tools.jinja:172 msgid "Elections" msgstr "Élections" -#: core/templates/core/base.jinja:222 +#: core/templates/core/base.jinja:223 msgid "Big event" msgstr "Grandes Activités" -#: core/templates/core/base.jinja:225 +#: core/templates/core/base.jinja:226 #: forum/templates/forum/favorite_topics.jinja:18 #: forum/templates/forum/last_unread.jinja:18 #: forum/templates/forum/macros.jinja:90 forum/templates/forum/main.jinja:6 @@ -2470,11 +2470,11 @@ msgstr "Grandes Activités" msgid "Forum" msgstr "Forum" -#: core/templates/core/base.jinja:226 +#: core/templates/core/base.jinja:227 msgid "Gallery" msgstr "Photos" -#: core/templates/core/base.jinja:227 counter/models.py:466 +#: core/templates/core/base.jinja:228 counter/models.py:459 #: counter/templates/counter/counter_list.jinja:11 #: eboutic/templates/eboutic/eboutic_main.jinja:4 #: eboutic/templates/eboutic/eboutic_main.jinja:22 @@ -2484,75 +2484,75 @@ msgstr "Photos" msgid "Eboutic" msgstr "Eboutic" -#: core/templates/core/base.jinja:229 +#: core/templates/core/base.jinja:230 msgid "Services" msgstr "Services" -#: core/templates/core/base.jinja:231 +#: core/templates/core/base.jinja:232 msgid "Matmatronch" msgstr "Matmatronch" -#: core/templates/core/base.jinja:232 launderette/models.py:38 +#: core/templates/core/base.jinja:233 launderette/models.py:38 #: launderette/templates/launderette/launderette_book.jinja:5 #: launderette/templates/launderette/launderette_book_choose.jinja:4 #: launderette/templates/launderette/launderette_main.jinja:4 msgid "Launderette" msgstr "Laverie" -#: core/templates/core/base.jinja:233 core/templates/core/file.jinja:20 +#: core/templates/core/base.jinja:234 core/templates/core/file.jinja:20 #: core/views/files.py:116 msgid "Files" msgstr "Fichiers" -#: core/templates/core/base.jinja:234 core/templates/core/user_tools.jinja:163 +#: core/templates/core/base.jinja:235 core/templates/core/user_tools.jinja:163 msgid "Pedagogy" msgstr "Pédagogie" -#: core/templates/core/base.jinja:238 +#: core/templates/core/base.jinja:239 msgid "My Benefits" msgstr "Mes Avantages" -#: core/templates/core/base.jinja:240 +#: core/templates/core/base.jinja:241 msgid "Sponsors" msgstr "Partenaires" -#: core/templates/core/base.jinja:241 +#: core/templates/core/base.jinja:242 msgid "Subscriber benefits" msgstr "Les avantages cotisants" -#: core/templates/core/base.jinja:245 +#: core/templates/core/base.jinja:246 msgid "Help" msgstr "Aide" -#: core/templates/core/base.jinja:247 +#: core/templates/core/base.jinja:248 msgid "FAQ" msgstr "FAQ" -#: core/templates/core/base.jinja:248 core/templates/core/base.jinja:288 +#: core/templates/core/base.jinja:249 core/templates/core/base.jinja:289 msgid "Contacts" msgstr "Contacts" -#: core/templates/core/base.jinja:249 +#: core/templates/core/base.jinja:250 msgid "Wiki" msgstr "Wiki" -#: core/templates/core/base.jinja:289 +#: core/templates/core/base.jinja:290 msgid "Legal notices" msgstr "Mentions légales" -#: core/templates/core/base.jinja:290 +#: core/templates/core/base.jinja:291 msgid "Intellectual property" msgstr "Propriété intellectuelle" -#: core/templates/core/base.jinja:291 +#: core/templates/core/base.jinja:292 msgid "Help & Documentation" msgstr "Aide & Documentation" -#: core/templates/core/base.jinja:292 +#: core/templates/core/base.jinja:293 msgid "R&D" msgstr "R&D" -#: core/templates/core/base.jinja:295 +#: core/templates/core/base.jinja:296 msgid "Site created by the IT Department of the AE" msgstr "Site réalisé par le Pôle Informatique de l'AE" @@ -2615,7 +2615,7 @@ msgstr "Propriétés" #: core/templates/core/file_detail.jinja:13 #: core/templates/core/file_moderation.jinja:20 -#: sas/templates/sas/picture.jinja:101 +#: sas/templates/sas/picture.jinja:102 msgid "Owner: " msgstr "Propriétaire : " @@ -2643,7 +2643,7 @@ msgstr "Nom réel : " #: core/templates/core/file_detail.jinja:54 #: core/templates/core/file_moderation.jinja:21 -#: sas/templates/sas/picture.jinja:92 +#: sas/templates/sas/picture.jinja:93 msgid "Date: " msgstr "Date : " @@ -2760,7 +2760,7 @@ msgstr "Cotisant jusqu'au %(subscription_end)s" msgid "Account number: " msgstr "Numéro de compte : " -#: core/templates/core/macros.jinja:91 launderette/models.py:202 +#: core/templates/core/macros.jinja:91 launderette/models.py:188 msgid "Slot" msgstr "Créneau" @@ -3040,11 +3040,11 @@ msgid "Eboutic invoices" msgstr "Facture eboutic" #: core/templates/core/user_account.jinja:54 -#: core/templates/core/user_tools.jinja:58 counter/views.py:715 +#: core/templates/core/user_tools.jinja:58 counter/views.py:708 msgid "Etickets" msgstr "Etickets" -#: core/templates/core/user_account.jinja:69 core/views/user.py:640 +#: core/templates/core/user_account.jinja:69 core/views/user.py:639 msgid "User has no account" msgstr "L'utilisateur n'a pas de compte" @@ -3152,7 +3152,7 @@ msgid "Subscription end" msgstr "Fin de la cotisation" #: core/templates/core/user_detail.jinja:185 -#: subscription/templates/subscription/stats.jinja:36 +#: subscription/templates/subscription/stats.jinja:38 msgid "Subscription type" msgstr "Type de cotisation" @@ -3280,7 +3280,7 @@ msgstr "Préférences" msgid "General" msgstr "Général" -#: core/templates/core/user_preferences.jinja:21 trombi/views.py:56 +#: core/templates/core/user_preferences.jinja:21 trombi/views.py:57 msgid "Trombi" msgstr "Trombi" @@ -3376,7 +3376,7 @@ msgid "Subscription stats" msgstr "Statistiques de cotisation" #: core/templates/core/user_tools.jinja:48 counter/forms.py:164 -#: counter/views.py:685 +#: counter/views.py:678 msgid "Counters" msgstr "Comptoirs" @@ -3393,16 +3393,16 @@ msgid "Product types management" msgstr "Gestion des types de produit" #: core/templates/core/user_tools.jinja:56 -#: counter/templates/counter/cash_summary_list.jinja:23 counter/views.py:705 +#: counter/templates/counter/cash_summary_list.jinja:23 counter/views.py:698 msgid "Cash register summaries" msgstr "Relevés de caisse" #: core/templates/core/user_tools.jinja:57 -#: counter/templates/counter/invoices_call.jinja:4 counter/views.py:710 +#: counter/templates/counter/invoices_call.jinja:4 counter/views.py:703 msgid "Invoices call" msgstr "Appels à facture" -#: core/templates/core/user_tools.jinja:72 core/views/user.py:274 +#: core/templates/core/user_tools.jinja:72 core/views/user.py:277 #: counter/templates/counter/counter_list.jinja:18 #: counter/templates/counter/counter_list.jinja:34 #: counter/templates/counter/counter_list.jinja:50 @@ -3550,7 +3550,7 @@ msgstr "Parrain / Marraine" msgid "Godchild" msgstr "Fillot / Fillote" -#: core/views/forms.py:315 counter/forms.py:72 trombi/views.py:149 +#: core/views/forms.py:315 counter/forms.py:72 trombi/views.py:151 msgid "Select user" msgstr "Choisir un utilisateur" @@ -3573,12 +3573,12 @@ msgid "%s is already your godchild" msgstr "%s est déjà votre fillot/fillote" #: core/views/forms.py:359 core/views/forms.py:377 election/models.py:22 -#: election/views.py:147 +#: election/views.py:151 msgid "edit groups" msgstr "groupe d'édition" #: core/views/forms.py:362 core/views/forms.py:380 election/models.py:29 -#: election/views.py:150 +#: election/views.py:154 msgid "view groups" msgstr "groupe de vue" @@ -3608,8 +3608,8 @@ msgstr "Photos" msgid "Galaxy" msgstr "Galaxie" -#: counter/apps.py:30 counter/models.py:482 counter/models.py:941 -#: counter/models.py:977 launderette/models.py:32 +#: counter/apps.py:30 counter/models.py:475 counter/models.py:926 +#: counter/models.py:962 launderette/models.py:32 msgid "counter" msgstr "comptoir" @@ -3653,7 +3653,7 @@ msgstr "client" msgid "customers" msgstr "clients" -#: counter/models.py:74 counter/views.py:267 +#: counter/models.py:74 counter/views.py:261 msgid "Not enough money" msgstr "Solde insuffisant" @@ -3701,117 +3701,117 @@ msgstr "Mettre à True si le mail a reçu une erreur" msgid "The operation that emptied the account." msgstr "L'opération qui a vidé le compte." -#: counter/models.py:277 counter/models.py:303 +#: counter/models.py:277 counter/models.py:301 msgid "product type" msgstr "type du produit" -#: counter/models.py:309 +#: counter/models.py:307 msgid "purchase price" msgstr "prix d'achat" -#: counter/models.py:310 +#: counter/models.py:308 msgid "selling price" msgstr "prix de vente" -#: counter/models.py:311 +#: counter/models.py:309 msgid "special selling price" msgstr "prix de vente spécial" -#: counter/models.py:318 +#: counter/models.py:316 msgid "icon" msgstr "icône" -#: counter/models.py:323 +#: counter/models.py:321 msgid "limit age" msgstr "âge limite" -#: counter/models.py:324 +#: counter/models.py:322 msgid "tray price" msgstr "prix plateau" -#: counter/models.py:328 +#: counter/models.py:326 msgid "parent product" msgstr "produit parent" -#: counter/models.py:334 +#: counter/models.py:332 msgid "buying groups" msgstr "groupe d'achat" -#: counter/models.py:336 election/models.py:50 +#: counter/models.py:334 election/models.py:50 msgid "archived" msgstr "archivé" -#: counter/models.py:339 counter/models.py:1077 +#: counter/models.py:337 counter/models.py:1060 msgid "product" msgstr "produit" -#: counter/models.py:461 +#: counter/models.py:454 msgid "products" msgstr "produits" -#: counter/models.py:464 +#: counter/models.py:457 msgid "counter type" msgstr "type de comptoir" -#: counter/models.py:466 +#: counter/models.py:459 msgid "Bar" msgstr "Bar" -#: counter/models.py:466 +#: counter/models.py:459 msgid "Office" msgstr "Bureau" -#: counter/models.py:469 +#: counter/models.py:462 msgid "sellers" msgstr "vendeurs" -#: counter/models.py:477 launderette/models.py:192 +#: counter/models.py:470 launderette/models.py:178 msgid "token" msgstr "jeton" -#: counter/models.py:672 +#: counter/models.py:665 msgid "bank" msgstr "banque" -#: counter/models.py:674 counter/models.py:775 +#: counter/models.py:667 counter/models.py:768 msgid "is validated" msgstr "est validé" -#: counter/models.py:679 +#: counter/models.py:672 msgid "refilling" msgstr "rechargement" -#: counter/models.py:752 eboutic/models.py:249 +#: counter/models.py:745 eboutic/models.py:249 msgid "unit price" msgstr "prix unitaire" -#: counter/models.py:753 counter/models.py:1057 eboutic/models.py:250 +#: counter/models.py:746 counter/models.py:1040 eboutic/models.py:250 msgid "quantity" msgstr "quantité" -#: counter/models.py:772 +#: counter/models.py:765 msgid "Sith account" msgstr "Compte utilisateur" -#: counter/models.py:772 sith/settings.py:412 sith/settings.py:417 +#: counter/models.py:765 sith/settings.py:412 sith/settings.py:417 #: sith/settings.py:437 msgid "Credit card" msgstr "Carte bancaire" -#: counter/models.py:780 +#: counter/models.py:773 msgid "selling" msgstr "vente" -#: counter/models.py:884 +#: counter/models.py:877 msgid "Unknown event" msgstr "Événement inconnu" -#: counter/models.py:885 +#: counter/models.py:878 #, python-format msgid "Eticket bought for the event %(event)s" msgstr "Eticket acheté pour l'événement %(event)s" -#: counter/models.py:887 counter/models.py:910 +#: counter/models.py:880 counter/models.py:893 #, python-format msgid "" "You bought an eticket for the event %(event)s.\n" @@ -3823,63 +3823,63 @@ msgstr "" "Vous pouvez également retrouver tous vos e-tickets sur votre page de compte " "%(url)s." -#: counter/models.py:946 +#: counter/models.py:931 msgid "last activity date" msgstr "dernière activité" -#: counter/models.py:949 +#: counter/models.py:934 msgid "permanency" msgstr "permanence" -#: counter/models.py:982 +#: counter/models.py:967 msgid "emptied" msgstr "coffre vidée" -#: counter/models.py:985 +#: counter/models.py:970 msgid "cash register summary" msgstr "relevé de caisse" -#: counter/models.py:1053 +#: counter/models.py:1036 msgid "cash summary" msgstr "relevé" -#: counter/models.py:1056 +#: counter/models.py:1039 msgid "value" msgstr "valeur" -#: counter/models.py:1059 +#: counter/models.py:1042 msgid "check" msgstr "chèque" -#: counter/models.py:1061 +#: counter/models.py:1044 msgid "True if this is a bank check, else False" msgstr "Vrai si c'est un chèque, sinon Faux." -#: counter/models.py:1065 +#: counter/models.py:1048 msgid "cash register summary item" msgstr "élément de relevé de caisse" -#: counter/models.py:1081 +#: counter/models.py:1064 msgid "banner" msgstr "bannière" -#: counter/models.py:1083 +#: counter/models.py:1066 msgid "event date" msgstr "date de l'événement" -#: counter/models.py:1085 +#: counter/models.py:1068 msgid "event title" msgstr "titre de l'événement" -#: counter/models.py:1087 +#: counter/models.py:1070 msgid "secret" msgstr "secret" -#: counter/models.py:1126 +#: counter/models.py:1109 msgid "uid" msgstr "uid" -#: counter/models.py:1131 +#: counter/models.py:1114 msgid "student cards" msgstr "cartes étudiante" @@ -3971,7 +3971,7 @@ msgstr "Liste des relevés de caisse" msgid "Theoric sums" msgstr "Sommes théoriques" -#: counter/templates/counter/cash_summary_list.jinja:36 counter/views.py:963 +#: counter/templates/counter/cash_summary_list.jinja:36 counter/views.py:956 msgid "Emptied" msgstr "Coffre vidé" @@ -3997,8 +3997,8 @@ msgstr "Ce n'est pas un UID de carte étudiante valide" #: counter/templates/counter/invoices_call.jinja:16 #: launderette/templates/launderette/launderette_admin.jinja:35 #: launderette/templates/launderette/launderette_click.jinja:13 -#: sas/templates/sas/picture.jinja:160 -#: subscription/templates/subscription/stats.jinja:19 +#: sas/templates/sas/picture.jinja:167 +#: subscription/templates/subscription/stats.jinja:20 msgid "Go" msgstr "Valider" @@ -4197,101 +4197,101 @@ msgstr "Temps" msgid "Top 100 barman %(counter_name)s (all semesters)" msgstr "Top 100 barman %(counter_name)s (tous les semestres)" -#: counter/views.py:153 +#: counter/views.py:147 msgid "Cash summary" msgstr "Relevé de caisse" -#: counter/views.py:162 +#: counter/views.py:156 msgid "Last operations" msgstr "Dernières opérations" -#: counter/views.py:209 +#: counter/views.py:203 msgid "Bad credentials" msgstr "Mauvais identifiants" -#: counter/views.py:211 +#: counter/views.py:205 msgid "User is not barman" msgstr "L'utilisateur n'est pas barman." -#: counter/views.py:216 +#: counter/views.py:210 msgid "Bad location, someone is already logged in somewhere else" msgstr "Mauvais comptoir, quelqu'un est déjà connecté ailleurs" -#: counter/views.py:258 +#: counter/views.py:252 msgid "Too young for that product" msgstr "Trop jeune pour ce produit" -#: counter/views.py:261 +#: counter/views.py:255 msgid "Not allowed for that product" msgstr "Non autorisé pour ce produit" -#: counter/views.py:264 +#: counter/views.py:258 msgid "No date of birth provided" msgstr "Pas de date de naissance renseignée" -#: counter/views.py:553 +#: counter/views.py:546 msgid "You have not enough money to buy all the basket" msgstr "Vous n'avez pas assez d'argent pour acheter le panier" -#: counter/views.py:680 +#: counter/views.py:673 msgid "Counter administration" msgstr "Administration des comptoirs" -#: counter/views.py:700 +#: counter/views.py:693 msgid "Product types" msgstr "Types de produit" -#: counter/views.py:920 +#: counter/views.py:913 msgid "10 cents" msgstr "10 centimes" -#: counter/views.py:921 +#: counter/views.py:914 msgid "20 cents" msgstr "20 centimes" -#: counter/views.py:922 +#: counter/views.py:915 msgid "50 cents" msgstr "50 centimes" -#: counter/views.py:923 +#: counter/views.py:916 msgid "1 euro" msgstr "1 €" -#: counter/views.py:924 +#: counter/views.py:917 msgid "2 euros" msgstr "2 €" -#: counter/views.py:925 +#: counter/views.py:918 msgid "5 euros" msgstr "5 €" -#: counter/views.py:926 +#: counter/views.py:919 msgid "10 euros" msgstr "10 €" -#: counter/views.py:927 +#: counter/views.py:920 msgid "20 euros" msgstr "20 €" -#: counter/views.py:928 +#: counter/views.py:921 msgid "50 euros" msgstr "50 €" -#: counter/views.py:930 +#: counter/views.py:923 msgid "100 euros" msgstr "100 €" -#: counter/views.py:933 counter/views.py:939 counter/views.py:945 -#: counter/views.py:951 counter/views.py:957 +#: counter/views.py:926 counter/views.py:932 counter/views.py:938 +#: counter/views.py:944 counter/views.py:950 msgid "Check amount" msgstr "Montant du chèque" -#: counter/views.py:936 counter/views.py:942 counter/views.py:948 -#: counter/views.py:954 counter/views.py:960 +#: counter/views.py:929 counter/views.py:935 counter/views.py:941 +#: counter/views.py:947 counter/views.py:953 msgid "Check quantity" msgstr "Nombre de chèque" -#: counter/views.py:1480 +#: counter/views.py:1473 msgid "people(s)" msgstr "personne(s)" @@ -4457,11 +4457,11 @@ msgstr "début des candidatures" msgid "end candidature" msgstr "fin des candidatures" -#: election/models.py:36 election/views.py:153 +#: election/models.py:36 election/views.py:157 msgid "vote groups" msgstr "groupe de vote" -#: election/models.py:43 election/views.py:160 +#: election/models.py:43 election/views.py:164 msgid "candidature groups" msgstr "groupe de candidature" @@ -4525,7 +4525,7 @@ msgstr "Vous avez déjà soumis votre vote." msgid "You have voted in this election." msgstr "Vous avez déjà voté pour cette élection." -#: election/templates/election/election_detail.jinja:49 election/views.py:86 +#: election/templates/election/election_detail.jinja:49 election/views.py:90 msgid "Blank vote" msgstr "Vote blanc" @@ -4589,87 +4589,87 @@ msgstr "au" msgid "Polls open from" msgstr "Votes ouverts du" -#: election/views.py:37 +#: election/views.py:41 msgid "You have selected too much candidates." msgstr "Vous avez sélectionné trop de candidats." -#: election/views.py:53 +#: election/views.py:57 msgid "User to candidate" msgstr "Utilisateur se présentant" -#: election/views.py:111 +#: election/views.py:115 msgid "This role already exists for this election" msgstr "Ce rôle existe déjà pour cette élection" -#: election/views.py:170 +#: election/views.py:174 msgid "Start candidature" msgstr "Début des candidatures" -#: election/views.py:173 +#: election/views.py:177 msgid "End candidature" msgstr "Fin des candidatures" -#: forum/models.py:61 +#: forum/models.py:62 msgid "is a category" msgstr "est une catégorie" -#: forum/models.py:72 +#: forum/models.py:73 msgid "owner club" msgstr "club propriétaire" -#: forum/models.py:89 +#: forum/models.py:90 msgid "number to choose a specific forum ordering" msgstr "numéro spécifiant l'ordre d'affichage" -#: forum/models.py:94 forum/models.py:251 +#: forum/models.py:95 forum/models.py:252 msgid "the last message" msgstr "le dernier message" -#: forum/models.py:98 +#: forum/models.py:99 msgid "number of topics" msgstr "nombre de sujets" -#: forum/models.py:194 +#: forum/models.py:195 msgid "You can not make loops in forums" msgstr "Vous ne pouvez pas faire de boucles dans les forums" -#: forum/models.py:246 +#: forum/models.py:247 msgid "subscribed users" msgstr "utilisateurs abonnés" -#: forum/models.py:256 +#: forum/models.py:257 msgid "number of messages" msgstr "nombre de messages" -#: forum/models.py:310 +#: forum/models.py:311 msgid "message" msgstr "message" -#: forum/models.py:313 +#: forum/models.py:314 msgid "readers" msgstr "lecteurs" -#: forum/models.py:315 +#: forum/models.py:316 msgid "is deleted" msgstr "est supprimé" -#: forum/models.py:399 +#: forum/models.py:400 msgid "Message edited by" msgstr "Message édité par" -#: forum/models.py:400 +#: forum/models.py:401 msgid "Message deleted by" msgstr "Message supprimé par" -#: forum/models.py:401 +#: forum/models.py:402 msgid "Message undeleted by" msgstr "Message restauré par" -#: forum/models.py:413 +#: forum/models.py:414 msgid "action" msgstr "action" -#: forum/models.py:436 +#: forum/models.py:437 msgid "last read date" msgstr "dernière date de lecture" @@ -4811,31 +4811,31 @@ msgstr "Galaxie de %(user_name)s" msgid "This citizen has not yet joined the galaxy" msgstr "Ce citoyen n'a pas encore rejoint la galaxie" -#: launderette/models.py:88 launderette/models.py:126 +#: launderette/models.py:84 launderette/models.py:120 msgid "launderette" msgstr "laverie" -#: launderette/models.py:94 +#: launderette/models.py:90 msgid "is working" msgstr "fonctionne" -#: launderette/models.py:97 +#: launderette/models.py:93 msgid "Machine" msgstr "Machine" -#: launderette/models.py:132 +#: launderette/models.py:126 msgid "borrow date" msgstr "date d'emprunt" -#: launderette/models.py:143 +#: launderette/models.py:137 msgid "Token" msgstr "Jeton" -#: launderette/models.py:155 +#: launderette/models.py:149 launderette/views.py:262 msgid "Token name can not be blank" msgstr "Le nom du jeton ne peut pas être vide" -#: launderette/models.py:186 +#: launderette/models.py:172 msgid "machine" msgstr "machine" @@ -4864,12 +4864,12 @@ msgid "Washing and drying" msgstr "Lavage et séchage" #: launderette/templates/launderette/launderette_book.jinja:27 -#: sith/settings.py:653 +#: sith/settings.py:655 msgid "Washing" msgstr "Lavage" #: launderette/templates/launderette/launderette_book.jinja:31 -#: sith/settings.py:653 +#: sith/settings.py:655 msgid "Drying" msgstr "Séchage" @@ -4894,25 +4894,25 @@ msgstr "Éditer la page de présentation" msgid "Book launderette slot" msgstr "Réserver un créneau de laverie" -#: launderette/views.py:231 +#: launderette/views.py:224 msgid "Tokens, separated by spaces" msgstr "Jetons, séparés par des espaces" -#: launderette/views.py:251 launderette/views.py:273 +#: launderette/views.py:246 #, python-format msgid "Token %(token_name)s does not exists" msgstr "Le jeton %(token_name)s n'existe pas" -#: launderette/views.py:262 +#: launderette/views.py:258 #, python-format msgid "Token %(token_name)s already exists" msgstr "Un jeton %(token_name)s existe déjà" -#: launderette/views.py:325 +#: launderette/views.py:309 msgid "User has booked no slot" msgstr "L'utilisateur n'a pas réservé de créneau" -#: launderette/views.py:433 +#: launderette/views.py:417 msgid "Token not found" msgstr "Jeton non trouvé" @@ -4965,103 +4965,103 @@ msgstr "" "Le code d'une UV doit seulement contenir des caractères majuscule sans " "accents et nombres" -#: pedagogy/models.py:62 +#: pedagogy/models.py:63 msgid "credit type" msgstr "type de crédit" -#: pedagogy/models.py:67 pedagogy/models.py:97 +#: pedagogy/models.py:68 pedagogy/models.py:98 msgid "uv manager" msgstr "gestionnaire d'uv" -#: pedagogy/models.py:75 +#: pedagogy/models.py:76 msgid "language" msgstr "langue" -#: pedagogy/models.py:81 +#: pedagogy/models.py:82 msgid "credits" msgstr "crédits" -#: pedagogy/models.py:89 +#: pedagogy/models.py:90 msgid "departmenmt" msgstr "département" -#: pedagogy/models.py:98 +#: pedagogy/models.py:99 msgid "objectives" msgstr "objectifs" -#: pedagogy/models.py:99 +#: pedagogy/models.py:100 msgid "program" msgstr "programme" -#: pedagogy/models.py:100 +#: pedagogy/models.py:101 msgid "skills" msgstr "compétences" -#: pedagogy/models.py:101 +#: pedagogy/models.py:102 msgid "key concepts" msgstr "concepts clefs" -#: pedagogy/models.py:106 +#: pedagogy/models.py:107 msgid "hours CM" msgstr "heures CM" -#: pedagogy/models.py:113 +#: pedagogy/models.py:114 msgid "hours TD" msgstr "heures TD" -#: pedagogy/models.py:120 +#: pedagogy/models.py:121 msgid "hours TP" msgstr "heures TP" -#: pedagogy/models.py:127 +#: pedagogy/models.py:128 msgid "hours THE" msgstr "heures THE" -#: pedagogy/models.py:134 +#: pedagogy/models.py:135 msgid "hours TE" msgstr "heures TE" -#: pedagogy/models.py:205 pedagogy/models.py:281 +#: pedagogy/models.py:206 pedagogy/models.py:282 msgid "uv" msgstr "UE" -#: pedagogy/models.py:209 +#: pedagogy/models.py:210 msgid "global grade" msgstr "note globale" -#: pedagogy/models.py:216 +#: pedagogy/models.py:217 msgid "utility grade" msgstr "note d'utilité" -#: pedagogy/models.py:223 +#: pedagogy/models.py:224 msgid "interest grade" msgstr "note d'intérêt" -#: pedagogy/models.py:230 +#: pedagogy/models.py:231 msgid "teaching grade" msgstr "note d'enseignement" -#: pedagogy/models.py:237 +#: pedagogy/models.py:238 msgid "work load grade" msgstr "note de charge de travail" -#: pedagogy/models.py:243 +#: pedagogy/models.py:244 msgid "publish date" msgstr "date de publication" -#: pedagogy/models.py:287 +#: pedagogy/models.py:288 msgid "grade" msgstr "note" -#: pedagogy/models.py:308 +#: pedagogy/models.py:309 msgid "report" msgstr "signaler" -#: pedagogy/models.py:314 +#: pedagogy/models.py:315 msgid "reporter" msgstr "signalant" -#: pedagogy/models.py:317 +#: pedagogy/models.py:318 msgid "reason" msgstr "raison" @@ -5100,7 +5100,7 @@ msgstr "non noté" msgid "UV comment moderation" msgstr "Modération des commentaires d'UV" -#: pedagogy/templates/pedagogy/moderation.jinja:14 sas/models.py:309 +#: pedagogy/templates/pedagogy/moderation.jinja:14 sas/models.py:308 msgid "Reason" msgstr "Raison" @@ -5256,15 +5256,15 @@ msgstr "Fusionner deux utilisateurs" msgid "Merge" msgstr "Fusion" -#: rootplace/views.py:159 +#: rootplace/views.py:160 msgid "User that will be kept" msgstr "Utilisateur qui sera conservé" -#: rootplace/views.py:162 +#: rootplace/views.py:163 msgid "User that will be deleted" msgstr "Utilisateur qui sera supprimé" -#: rootplace/views.py:168 +#: rootplace/views.py:169 msgid "User to be selected" msgstr "Utilisateur à sélectionner" @@ -5289,29 +5289,29 @@ msgstr "Ajouter une personne" msgid "You already requested moderation for this picture." msgstr "Vous avez déjà déposé une demande de retrait pour cette photo." -#: sas/models.py:280 +#: sas/models.py:279 msgid "picture" msgstr "photo" -#: sas/models.py:304 +#: sas/models.py:303 msgid "Picture" msgstr "Photo" -#: sas/models.py:311 +#: sas/models.py:310 msgid "Why do you want this image to be removed ?" msgstr "Pourquoi voulez-vous retirer cette image ?" -#: sas/models.py:315 +#: sas/models.py:314 msgid "Picture moderation request" msgstr "Demande de modération de photo" -#: sas/models.py:316 +#: sas/models.py:315 msgid "Picture moderation requests" msgstr "Demandes de modération de photo" #: sas/templates/sas/album.jinja:13 #: sas/templates/sas/ask_picture_removal.jinja:4 sas/templates/sas/main.jinja:8 -#: sas/templates/sas/main.jinja:17 sas/templates/sas/picture.jinja:13 +#: sas/templates/sas/main.jinja:17 sas/templates/sas/picture.jinja:14 msgid "SAS" msgstr "SAS" @@ -5351,11 +5351,11 @@ msgstr "Toutes les catégories" msgid "SAS moderation" msgstr "Modération du SAS" -#: sas/templates/sas/picture.jinja:36 +#: sas/templates/sas/picture.jinja:37 msgid "Asked for removal" msgstr "Retrait demandé" -#: sas/templates/sas/picture.jinja:39 +#: sas/templates/sas/picture.jinja:40 msgid "" "This picture can be viewed only by root users and by SAS admins. It will be " "hidden to other users until it has been moderated." @@ -5364,26 +5364,30 @@ msgstr "" "SAS. Elle sera cachée pour les autres utilisateurs tant qu'elle ne sera pas " "modérée." -#: sas/templates/sas/picture.jinja:47 +#: sas/templates/sas/picture.jinja:48 msgid "The following issues have been raised:" msgstr "Les problèmes suivants ont été remontés :" -#: sas/templates/sas/picture.jinja:112 +#: sas/templates/sas/picture.jinja:113 msgid "HD version" msgstr "Version HD" -#: sas/templates/sas/picture.jinja:116 +#: sas/templates/sas/picture.jinja:117 msgid "Ask for removal" msgstr "Demander le retrait" -#: sas/templates/sas/picture.jinja:137 sas/templates/sas/picture.jinja:148 +#: sas/templates/sas/picture.jinja:138 sas/templates/sas/picture.jinja:149 msgid "Previous picture" msgstr "Image précédente" -#: sas/templates/sas/picture.jinja:156 +#: sas/templates/sas/picture.jinja:157 msgid "People" msgstr "Personne(s)" +#: sas/templates/sas/picture.jinja:165 +msgid "Identify users on pictures" +msgstr "Identifiez les utilisateurs sur les photos" + #: sith/settings.py:255 sith/settings.py:474 msgid "English" msgstr "Anglais" @@ -5560,204 +5564,204 @@ msgstr "Suppression de vente" msgid "Refilling deletion" msgstr "Suppression de rechargement" -#: sith/settings.py:534 +#: sith/settings.py:536 msgid "One semester" msgstr "Un semestre, 20 €" -#: sith/settings.py:535 +#: sith/settings.py:537 msgid "Two semesters" msgstr "Deux semestres, 35 €" -#: sith/settings.py:537 +#: sith/settings.py:539 msgid "Common core cursus" msgstr "Cursus tronc commun, 60 €" -#: sith/settings.py:541 +#: sith/settings.py:543 msgid "Branch cursus" msgstr "Cursus branche, 60 €" -#: sith/settings.py:542 +#: sith/settings.py:544 msgid "Alternating cursus" msgstr "Cursus alternant, 30 €" -#: sith/settings.py:543 +#: sith/settings.py:545 msgid "Honorary member" msgstr "Membre honoraire, 0 €" -#: sith/settings.py:544 +#: sith/settings.py:546 msgid "Assidu member" msgstr "Membre d'Assidu, 0 €" -#: sith/settings.py:545 +#: sith/settings.py:547 msgid "Amicale/DOCEO member" msgstr "Membre de l'Amicale/DOCEO, 0 €" -#: sith/settings.py:546 +#: sith/settings.py:548 msgid "UT network member" msgstr "Cotisant du réseau UT, 0 €" -#: sith/settings.py:547 +#: sith/settings.py:549 msgid "CROUS member" msgstr "Membres du CROUS, 0 €" -#: sith/settings.py:548 +#: sith/settings.py:550 msgid "Sbarro/ESTA member" msgstr "Membre de Sbarro ou de l'ESTA, 20 €" -#: sith/settings.py:550 +#: sith/settings.py:552 msgid "One semester Welcome Week" msgstr "Un semestre Welcome Week" -#: sith/settings.py:554 +#: sith/settings.py:556 msgid "One month for free" msgstr "Un mois gratuit" -#: sith/settings.py:555 +#: sith/settings.py:557 msgid "Two months for free" msgstr "Deux mois gratuits" -#: sith/settings.py:556 +#: sith/settings.py:558 msgid "Eurok's volunteer" msgstr "Bénévole Eurockéennes" -#: sith/settings.py:558 +#: sith/settings.py:560 msgid "Six weeks for free" msgstr "6 semaines gratuites" -#: sith/settings.py:562 +#: sith/settings.py:564 msgid "One day" msgstr "Un jour" -#: sith/settings.py:563 +#: sith/settings.py:565 msgid "GA staff member" msgstr "Membre staff GA (2 semaines), 1 €" -#: sith/settings.py:566 +#: sith/settings.py:568 msgid "One semester (-20%)" msgstr "Un semestre (-20%), 12 €" -#: sith/settings.py:571 +#: sith/settings.py:573 msgid "Two semesters (-20%)" msgstr "Deux semestres (-20%), 22 €" -#: sith/settings.py:576 +#: sith/settings.py:578 msgid "Common core cursus (-20%)" msgstr "Cursus tronc commun (-20%), 36 €" -#: sith/settings.py:581 +#: sith/settings.py:583 msgid "Branch cursus (-20%)" msgstr "Cursus branche (-20%), 36 €" -#: sith/settings.py:586 +#: sith/settings.py:588 msgid "Alternating cursus (-20%)" msgstr "Cursus alternant (-20%), 24 €" -#: sith/settings.py:592 +#: sith/settings.py:594 msgid "One year for free(CA offer)" msgstr "Une année offerte (Offre CA)" -#: sith/settings.py:612 +#: sith/settings.py:614 msgid "President" msgstr "Président⸱e" -#: sith/settings.py:613 +#: sith/settings.py:615 msgid "Vice-President" msgstr "Vice-Président⸱e" -#: sith/settings.py:614 +#: sith/settings.py:616 msgid "Treasurer" msgstr "Trésorier⸱e" -#: sith/settings.py:615 +#: sith/settings.py:617 msgid "Communication supervisor" msgstr "Responsable communication" -#: sith/settings.py:616 +#: sith/settings.py:618 msgid "Secretary" msgstr "Secrétaire" -#: sith/settings.py:617 +#: sith/settings.py:619 msgid "IT supervisor" msgstr "Responsable info" -#: sith/settings.py:618 +#: sith/settings.py:620 msgid "Board member" msgstr "Membre du bureau" -#: sith/settings.py:619 +#: sith/settings.py:621 msgid "Active member" msgstr "Membre actif⸱ve" -#: sith/settings.py:620 +#: sith/settings.py:622 msgid "Curious" msgstr "Curieux⸱euse" -#: sith/settings.py:657 +#: sith/settings.py:659 msgid "A new poster needs to be moderated" msgstr "Une nouvelle affiche a besoin d'être modérée" -#: sith/settings.py:658 +#: sith/settings.py:660 msgid "A new mailing list needs to be moderated" msgstr "Une nouvelle mailing list a besoin d'être modérée" -#: sith/settings.py:661 +#: sith/settings.py:663 msgid "A new pedagogy comment has been signaled for moderation" msgstr "" "Un nouveau commentaire de la pédagogie a été signalé pour la modération" -#: sith/settings.py:663 +#: sith/settings.py:665 #, python-format msgid "There are %s fresh news to be moderated" msgstr "Il y a %s nouvelles toutes fraîches à modérer" -#: sith/settings.py:664 +#: sith/settings.py:666 msgid "New files to be moderated" msgstr "Nouveaux fichiers à modérer" -#: sith/settings.py:665 +#: sith/settings.py:667 #, python-format msgid "There are %s pictures to be moderated in the SAS" msgstr "Il y a %s photos à modérer dans le SAS" -#: sith/settings.py:666 +#: sith/settings.py:668 msgid "You've been identified on some pictures" msgstr "Vous avez été identifié sur des photos" -#: sith/settings.py:667 +#: sith/settings.py:669 #, python-format msgid "You just refilled of %s €" msgstr "Vous avez rechargé votre compte de %s€" -#: sith/settings.py:668 +#: sith/settings.py:670 #, python-format msgid "You just bought %s" msgstr "Vous avez acheté %s" -#: sith/settings.py:669 +#: sith/settings.py:671 msgid "You have a notification" msgstr "Vous avez une notification" -#: sith/settings.py:681 +#: sith/settings.py:683 msgid "Success!" msgstr "Succès !" -#: sith/settings.py:682 +#: sith/settings.py:684 msgid "Fail!" msgstr "Échec !" -#: sith/settings.py:683 +#: sith/settings.py:685 msgid "You successfully posted an article in the Weekmail" msgstr "Article posté avec succès dans le Weekmail" -#: sith/settings.py:684 +#: sith/settings.py:686 msgid "You successfully edited an article in the Weekmail" msgstr "Article édité avec succès dans le Weekmail" -#: sith/settings.py:685 +#: sith/settings.py:687 msgid "You successfully sent the Weekmail" msgstr "Weekmail envoyé avec succès" -#: sith/settings.py:693 +#: sith/settings.py:695 msgid "AE tee-shirt" msgstr "Tee-shirt AE" @@ -5789,11 +5793,11 @@ msgstr "lieu" msgid "You can not subscribe many time for the same period" msgstr "Vous ne pouvez pas cotiser plusieurs fois pour la même période" -#: subscription/templates/subscription/stats.jinja:25 +#: subscription/templates/subscription/stats.jinja:27 msgid "Total subscriptions" msgstr "Cotisations totales" -#: subscription/templates/subscription/stats.jinja:26 +#: subscription/templates/subscription/stats.jinja:28 msgid "Subscriptions by type" msgstr "Cotisations par type" @@ -5806,7 +5810,7 @@ msgstr "" msgid "A user with that email address already exists" msgstr "Un utilisateur avec cette adresse email existe déjà" -#: subscription/views.py:101 +#: subscription/views.py:102 msgid "You must either choose an existing user or create a new one properly" msgstr "" "Vous devez soit choisir un utilisateur existant, soit en créer un proprement" @@ -5871,11 +5875,11 @@ msgstr "" "La photo de profil que vous souhaitez voir dans le Trombi (attention: cette " "photo risque d'être publiée)" -#: trombi/models.py:139 +#: trombi/models.py:140 msgid "scrub pict" msgstr "photo de blouse" -#: trombi/models.py:143 +#: trombi/models.py:144 msgid "" "The scrub picture you want in the trombi (warning: this picture may be " "published)" @@ -5883,19 +5887,19 @@ msgstr "" "La photo de blouse que vous souhaitez voir dans le Trombi (attention: cette " "photo risque d'être publiée)" -#: trombi/models.py:185 +#: trombi/models.py:184 msgid "target" msgstr "cible" -#: trombi/models.py:190 +#: trombi/models.py:189 msgid "is the comment moderated" msgstr "le commentaire est modéré" -#: trombi/models.py:212 +#: trombi/models.py:211 msgid "start" msgstr "début" -#: trombi/models.py:213 +#: trombi/models.py:212 msgid "end" msgstr "fin" @@ -6026,27 +6030,27 @@ msgstr "" msgid "Edit comment" msgstr "Éditer le commentaire" -#: trombi/views.py:68 +#: trombi/views.py:69 msgid "My profile" msgstr "Mon profil" -#: trombi/views.py:75 +#: trombi/views.py:76 msgid "My pictures" msgstr "Mes photos" -#: trombi/views.py:87 +#: trombi/views.py:91 msgid "Admin tools" msgstr "Admin Trombi" -#: trombi/views.py:213 +#: trombi/views.py:215 msgid "Explain why you rejected the comment" msgstr "Expliquez pourquoi vous refusez le commentaire" -#: trombi/views.py:244 +#: trombi/views.py:246 msgid "Rejected comment" msgstr "Commentaire rejeté" -#: trombi/views.py:246 +#: trombi/views.py:248 #, python-format msgid "" "Your comment to %(target)s on the Trombi \"%(trombi)s\" was rejected for the " @@ -6063,16 +6067,16 @@ msgstr "" "\n" "%(content)s" -#: trombi/views.py:278 +#: trombi/views.py:280 #, python-format msgid "%(name)s (deadline: %(date)s)" msgstr "%(name)s (date limite: %(date)s)" -#: trombi/views.py:288 +#: trombi/views.py:290 msgid "Select trombi" msgstr "Choisir un trombi" -#: trombi/views.py:290 +#: trombi/views.py:292 msgid "" "This allows you to subscribe to a Trombi. Be aware that you can subscribe " "only once, so don't play with that, or you will expose yourself to the " @@ -6082,19 +6086,19 @@ msgstr "" "pouvez vous inscrire qu'à un seul Trombi, donc ne jouez pas avec cet option " "ou vous encourerez la colère des admins!" -#: trombi/views.py:361 +#: trombi/views.py:363 msgid "Personal email (not UTBM)" msgstr "Email personnel (pas UTBM)" -#: trombi/views.py:362 +#: trombi/views.py:364 msgid "Phone" msgstr "Téléphone" -#: trombi/views.py:363 +#: trombi/views.py:365 msgid "Native town" msgstr "Ville d'origine" -#: trombi/views.py:471 +#: trombi/views.py:473 msgid "" "You can not yet write comment, you must wait for the subscription deadline " "to be passed." @@ -6102,11 +6106,11 @@ msgstr "" "Vous ne pouvez pas encore écrire de commentaires, vous devez attendre la fin " "des inscriptions" -#: trombi/views.py:478 +#: trombi/views.py:480 msgid "You can not write comment anymore, the deadline is already passed." msgstr "Vous ne pouvez plus écrire de commentaires, la date est passée." -#: trombi/views.py:491 +#: trombi/views.py:493 #, python-format msgid "Maximum characters: %(max_length)s" msgstr "Nombre de caractères max: %(max_length)s" diff --git a/locale/fr/LC_MESSAGES/djangojs.po b/locale/fr/LC_MESSAGES/djangojs.po index 02032e52..644f4a43 100644 --- a/locale/fr/LC_MESSAGES/djangojs.po +++ b/locale/fr/LC_MESSAGES/djangojs.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-09 11:50+0200\n" +"POT-Creation-Date: 2024-10-16 02:19+0200\n" "PO-Revision-Date: 2024-09-17 11:54+0200\n" "Last-Translator: Sli \n" "Language-Team: AE info \n" @@ -22,87 +22,95 @@ msgstr "" msgid "captured.%s" msgstr "capture.%s" -#: core/static/webpack/easymde-index.js:32 +#: core/static/webpack/ajax-select-index.ts:73 +msgid "You need to type %(number)s more characters" +msgstr "Vous devez taper %(number)s caractères de plus" + +#: core/static/webpack/ajax-select-index.ts:76 +msgid "No results found" +msgstr "Aucun résultat trouvé" + +#: core/static/webpack/easymde-index.ts:31 msgid "Heading" msgstr "Titre" -#: core/static/webpack/easymde-index.js:38 +#: core/static/webpack/easymde-index.ts:37 msgid "Italic" msgstr "Italique" -#: core/static/webpack/easymde-index.js:44 +#: core/static/webpack/easymde-index.ts:43 msgid "Bold" msgstr "Gras" -#: core/static/webpack/easymde-index.js:50 +#: core/static/webpack/easymde-index.ts:49 msgid "Strikethrough" msgstr "Barré" -#: core/static/webpack/easymde-index.js:59 +#: core/static/webpack/easymde-index.ts:58 msgid "Underline" msgstr "Souligné" -#: core/static/webpack/easymde-index.js:68 +#: core/static/webpack/easymde-index.ts:67 msgid "Superscript" msgstr "Exposant" -#: core/static/webpack/easymde-index.js:77 +#: core/static/webpack/easymde-index.ts:76 msgid "Subscript" msgstr "Indice" -#: core/static/webpack/easymde-index.js:83 +#: core/static/webpack/easymde-index.ts:82 msgid "Code" msgstr "Code" -#: core/static/webpack/easymde-index.js:90 +#: core/static/webpack/easymde-index.ts:89 msgid "Quote" msgstr "Citation" -#: core/static/webpack/easymde-index.js:96 +#: core/static/webpack/easymde-index.ts:95 msgid "Unordered list" msgstr "Liste non ordonnée" -#: core/static/webpack/easymde-index.js:102 +#: core/static/webpack/easymde-index.ts:101 msgid "Ordered list" msgstr "Liste ordonnée" -#: core/static/webpack/easymde-index.js:109 +#: core/static/webpack/easymde-index.ts:108 msgid "Insert link" msgstr "Insérer lien" -#: core/static/webpack/easymde-index.js:115 +#: core/static/webpack/easymde-index.ts:114 msgid "Insert image" msgstr "Insérer image" -#: core/static/webpack/easymde-index.js:121 +#: core/static/webpack/easymde-index.ts:120 msgid "Insert table" msgstr "Insérer tableau" -#: core/static/webpack/easymde-index.js:128 +#: core/static/webpack/easymde-index.ts:127 msgid "Clean block" msgstr "Nettoyer bloc" -#: core/static/webpack/easymde-index.js:135 +#: core/static/webpack/easymde-index.ts:134 msgid "Toggle preview" msgstr "Activer la prévisualisation" -#: core/static/webpack/easymde-index.js:141 +#: core/static/webpack/easymde-index.ts:140 msgid "Toggle side by side" msgstr "Activer la vue côte à côte" -#: core/static/webpack/easymde-index.js:147 +#: core/static/webpack/easymde-index.ts:146 msgid "Toggle fullscreen" msgstr "Activer le plein écran" -#: core/static/webpack/easymde-index.js:154 +#: core/static/webpack/easymde-index.ts:153 msgid "Markdown guide" msgstr "Guide markdown" -#: core/static/webpack/user/family-graph-index.js:222 +#: core/static/webpack/user/family-graph-index.js:233 msgid "family_tree.%(extension)s" msgstr "arbre_genealogique.%(extension)s" -#: core/static/webpack/user/pictures-index.js:67 +#: core/static/webpack/user/pictures-index.js:76 msgid "pictures.%(extension)s" msgstr "photos.%(extension)s" @@ -110,10 +118,10 @@ msgstr "photos.%(extension)s" msgid "Incorrect value" msgstr "Valeur incorrecte" -#: sas/static/sas/js/viewer.js:205 +#: sas/static/webpack/sas/viewer-index.ts:271 msgid "Couldn't moderate picture" msgstr "Il n'a pas été possible de modérer l'image" -#: sas/static/sas/js/viewer.js:217 +#: sas/static/webpack/sas/viewer-index.ts:284 msgid "Couldn't delete picture" msgstr "Il n'a pas été possible de supprimer l'image" diff --git a/package-lock.json b/package-lock.json index 5a82803d..58c7ece1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,9 +26,9 @@ "jquery-ui": "^1.14.0", "jquery.shorten": "^1.0.0", "native-file-system-adapter": "^3.0.1", - "select2": "^4.1.0-rc.0", "three": "^0.169.0", - "three-spritetext": "^1.9.0" + "three-spritetext": "^1.9.0", + "tom-select": "^2.3.1" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -37,7 +37,6 @@ "@hey-api/openapi-ts": "^0.53.8", "@types/alpinejs": "^3.13.10", "@types/jquery": "^3.5.31", - "@types/select2": "^4.0.63", "babel-loader": "^9.2.1", "css-loader": "^7.1.2", "css-minimizer-webpack-plugin": "^7.0.0", @@ -2175,6 +2174,19 @@ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" }, + "node_modules/@orchidjs/sifter": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@orchidjs/sifter/-/sifter-1.0.3.tgz", + "integrity": "sha512-zCZbwKegHytfsPm8Amcfh7v/4vHqTAaOu6xFswBYcn8nznBOuseu6COB2ON7ez0tFV0mKL0nRNnCiZZA+lU9/g==", + "dependencies": { + "@orchidjs/unicode-variants": "^1.0.4" + } + }, + "node_modules/@orchidjs/unicode-variants": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@orchidjs/unicode-variants/-/unicode-variants-1.0.4.tgz", + "integrity": "sha512-NvVBRnZNE+dugiXERFsET1JlKZfM5lJDEpSMilKW4bToYJ7pxf0Zne78xyXB2ny2c2aHfJ6WLnz1AaTNHAmQeQ==" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2384,15 +2396,6 @@ "undici-types": "~6.19.2" } }, - "node_modules/@types/select2": { - "version": "4.0.63", - "resolved": "https://registry.npmjs.org/@types/select2/-/select2-4.0.63.tgz", - "integrity": "sha512-/DXUfPSj3iVTGlRYRYPCFKKSogAGP/j+Z0fIMXbBiBtmmZj0WH7vnfNuckafq9C43KnqPPQW2TI/Rj/vTSGnQQ==", - "dev": true, - "dependencies": { - "@types/jquery": "*" - } - }, "node_modules/@types/sizzle": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", @@ -6132,11 +6135,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/select2": { - "version": "4.1.0-rc.0", - "resolved": "https://registry.npmjs.org/select2/-/select2-4.1.0-rc.0.tgz", - "integrity": "sha512-Hr9TdhyHCZUtwznEH2CBf7967mEM0idtJ5nMtjvk3Up5tPukOLXbHUNmh10oRfeNIhj+3GD3niu+g6sVK+gK0A==" - }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -6614,6 +6612,22 @@ "node": ">=8.0" } }, + "node_modules/tom-select": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tom-select/-/tom-select-2.3.1.tgz", + "integrity": "sha512-QS4vnOcB6StNGqX4sGboGXL2fkhBF2gIBB+8Hwv30FZXYPn0CyYO8kkdATRvwfCTThxiR4WcXwKJZ3cOmtI9eg==", + "dependencies": { + "@orchidjs/sifter": "^1.0.3", + "@orchidjs/unicode-variants": "^1.0.4" + }, + "engines": { + "node": "*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tom-select" + } + }, "node_modules/totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", diff --git a/package.json b/package.json index 20df6130..4a1338f7 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ "@hey-api/openapi-ts": "^0.53.8", "@types/alpinejs": "^3.13.10", "@types/jquery": "^3.5.31", - "@types/select2": "^4.0.63", "babel-loader": "^9.2.1", "css-loader": "^7.1.2", "css-minimizer-webpack-plugin": "^7.0.0", @@ -59,8 +58,8 @@ "jquery-ui": "^1.14.0", "jquery.shorten": "^1.0.0", "native-file-system-adapter": "^3.0.1", - "select2": "^4.1.0-rc.0", "three": "^0.169.0", - "three-spritetext": "^1.9.0" + "three-spritetext": "^1.9.0", + "tom-select": "^2.3.1" } } diff --git a/sas/static/sas/css/picture.scss b/sas/static/sas/css/picture.scss index b8c0f6fb..f62bb8bf 100644 --- a/sas/static/sas/css/picture.scss +++ b/sas/static/sas/css/picture.scss @@ -29,7 +29,7 @@ width: 100%; } - > .photo { + >.photo { box-sizing: border-box; height: 500px; display: flex; @@ -42,7 +42,7 @@ height: auto; } - > img { + >img { height: 100%; max-width: 100%; object-fit: contain; @@ -57,7 +57,7 @@ width: 100%; } - > .navigation { + >.navigation { display: flex; flex-direction: row; gap: 10px; @@ -66,8 +66,8 @@ width: 100%; } - > #prev, - > #next { + >#prev, + >#next { width: calc(50% - 5px); aspect-ratio: 16/9; background: #333333; @@ -80,6 +80,7 @@ object-fit: cover; opacity: 70%; } + .overlay { position: absolute; top: 50%; @@ -89,7 +90,7 @@ font-size: 40px; } - > div { + >div { display: flex; position: relative; width: 100%; @@ -98,12 +99,12 @@ } } - > .tags { + >.tags { @media (min-width: 1001px) { margin-right: 5px; } - > ul { + >ul { list-style-type: none; margin: 0; display: flex; @@ -118,7 +119,7 @@ margin-right: 5px; } - > li { + >li { box-sizing: border-box; display: flex; flex-direction: row; @@ -135,7 +136,7 @@ max-width: calc(50% - 5px); } - > a { + >a { box-sizing: border-box; display: flex; flex-direction: row; @@ -155,7 +156,7 @@ background-color: #aaa; } - > span { + >span { width: 100%; text-overflow: ellipsis; white-space: nowrap; @@ -167,14 +168,14 @@ margin-left: 10px; } - > img { + >img { width: 25px; max-height: 25px; object-fit: contain; border-radius: 50%; } - > .profile-pic { + >.profile-pic { background-position: center center; background-size: cover; background-repeat: no-repeat; @@ -187,23 +188,24 @@ } } - > form { - > p { + >form { + >p { box-sizing: border-box; } - > .results_on_deck > div { + >.results_on_deck>div { position: relative; display: flex; align-items: center; word-break: break-word; - > span { + >span { position: absolute; top: 0; right: 0; } } + input { min-width: 100%; max-width: 100%; @@ -226,17 +228,17 @@ flex-direction: column; } - > .infos { + >.infos { display: flex; flex-direction: column; width: 50%; - > div > div { + >div>div { display: flex; flex-direction: row; justify-content: space-between; - > *:first-child { + >*:first-child { min-width: 150px; @media (max-width: 1000px) { @@ -246,18 +248,18 @@ } } - > .tools { + >.tools { display: flex; flex-direction: column; width: 50%; - > div { + >div { display: flex; flex-direction: row; justify-content: space-between; - > div { - > a.button { + >div { + >a.button { box-sizing: border-box; background-color: #f2f2f2; display: flex; @@ -274,7 +276,7 @@ } } - > a.text.danger { + >a.text.danger { color: red; &:hover { @@ -289,4 +291,4 @@ } } } -} +} \ No newline at end of file diff --git a/sas/static/webpack/sas/viewer-index.ts b/sas/static/webpack/sas/viewer-index.ts index e8e5f6f4..b084810c 100644 --- a/sas/static/webpack/sas/viewer-index.ts +++ b/sas/static/webpack/sas/viewer-index.ts @@ -1,12 +1,7 @@ -import { makeUrl, paginated } from "#core:utils/api"; +import { paginated } from "#core:utils/api"; import { exportToHtml } from "#core:utils/globals"; import { History } from "#core:utils/history"; -import { - type AjaxResponse, - type RemoteResult, - remoteDataSource, - sithSelect2, -} from "#core:utils/select2"; +import type TomSelect from "tom-select"; import { type IdentifiedUserSchema, type PictureSchema, @@ -20,7 +15,6 @@ import { picturesFetchPictures, picturesIdentifyUsers, picturesModeratePicture, - userSearchUsers, usersidentifiedDeleteRelation, } from "#openapi"; @@ -182,20 +176,21 @@ exportToHtml("loadViewer", (config: ViewerConfig) => { query: { album_id: config.albumId }, } as PicturesFetchPicturesData) ).map(PictureWithIdentifications.fromPicture); - this.selector = sithSelect2({ - element: this.$refs.search, - dataSource: remoteDataSource(await makeUrl(userSearchUsers), { - excluded: () => [ - ...(this.currentPicture.identifications || []).map( - (i: IdentifiedUserSchema) => i.user.id, - ), - ], - resultConverter: (obj: AjaxResponse) => { - return { ...obj, text: (obj as UserProfileSchema).display_name }; - }, - }), - pictureGetter: (user: RemoteResult) => user.profile_pict, - }); + this.selector = this.$refs.search; + this.selector.filter = (users: UserProfileSchema[]) => { + const resp: UserProfileSchema[] = []; + const ids = [ + ...(this.currentPicture.identifications || []).map( + (i: IdentifiedUserSchema) => i.user.id, + ), + ]; + for (const user of users) { + if (!ids.includes(user.id)) { + resp.push(user); + } + } + return resp; + }; this.currentPicture = this.pictures.find( (i: PictureSchema) => i.id === config.firstPictureId, ); @@ -302,16 +297,20 @@ exportToHtml("loadViewer", (config: ViewerConfig) => { * Send the identification request and update the list of identified users. */ async submitIdentification(): Promise { + const widget: TomSelect = this.selector.widget; await picturesIdentifyUsers({ path: { // biome-ignore lint/style/useNamingConvention: api is in snake_case picture_id: this.currentPicture.id, }, - body: this.selector.val().map((i: string) => Number.parseInt(i)), + body: widget.items.map((i: string) => Number.parseInt(i)), }); // refresh the identified users list await this.currentPicture.loadIdentifications({ forceReload: true }); - this.selector.empty().trigger("change"); + + // Clear selection and cache of retrieved user so they can be filtered again + widget.clear(false); + widget.clearOptions(); }, /** diff --git a/sas/templates/sas/picture.jinja b/sas/templates/sas/picture.jinja index 915a87c0..43651383 100644 --- a/sas/templates/sas/picture.jinja +++ b/sas/templates/sas/picture.jinja @@ -1,11 +1,12 @@ {% extends "core/base.jinja" %} {%- block additional_css -%} + - {%- endblock -%} {%- block additional_js -%} + {%- endblock -%} @@ -156,7 +157,12 @@
{% trans %}People{% endtrans %}
{% if user.was_subscribed %}
- +
{% endif %} diff --git a/tsconfig.json b/tsconfig.json index 6bb7d717..deee110d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ "target": "es6", "allowJs": true, "moduleResolution": "node", + "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "esModuleInterop": true, "types": ["jquery", "alpinejs"],