Compare commits

...

19 Commits

Author SHA1 Message Date
Damien Broqua e0f227af08 1.4.4 (#84)
Fonctionnalités :
- #82 - Utilisateur artists plutôt que artists_sort

Co-authored-by: dbroqua <contact@darkou.fr>
Reviewed-on: #84
2 months ago
Damien Broqua fbeb1a67c5 Version 1.4.3 (#83)
Fonctionnalités :

    #80 - Ajout des boutons pages de début et de fin sur la pagination

Correction de bugs :

    #79 - Correction d'un bug empêchant de filtrer sur un artiste contenant un "+" dans son nom

Co-authored-by: dbroqua <contact@darkou.fr>
Reviewed-on: #83
2 months ago
Damien Broqua 2389d7d731 1.4.2 (#78)
- #68 - Unifier les vues pour la liste des albums
- #77 - Je suis capable de trier ma collection par date d'ajout
- #74 - Lors du changement de page on connait l'ordre de tri
- #73 - Savoir sur quelle page on est
- #76 - Avoir plus de détails sur le support physique sur la modale d'ajout
- #75 - Numérotation de la tracklist

Co-authored-by: dbroqua <contact@darkou.fr>
Reviewed-on: #78
2 months ago
Damien Broqua 9fe49eca27 Version 1.4.1 (#70)
- #69 Partager ma collection

Co-authored-by: dbroqua <contact@darkou.fr>
Reviewed-on: #70
7 months ago
Damien Broqua 1d59ee3b71 Version 1.4 (#67)
Co-authored-by: dbroqua <contact@darkou.fr>
Reviewed-on: #67
7 months ago
Damien Broqua d03394bee7 #60 - Bug au refresh d'un album 9 months ago
Damien Broqua 4da4dd9423 #62 - Échaper les & dans les urls des pages 9 months ago
Damien Broqua 6454f5f8d6 Correction wording 9 months ago
Damien Broqua bc3bb3b554 Merge branch 'develop' of git.darkou.fr:dbroqua/MusicTopus 9 months ago
Damien Broqua da08aa0222 #41 - Ajouter une sécurité sur la page nous contacter (#59)
Co-authored-by: dbroqua <contact@darkou.fr>
Reviewed-on: #59
9 months ago
Damien Broqua 2da6afa06d #40 - Formulaire de contact via SMTP (#58)
Co-authored-by: dbroqua <contact@darkou.fr>
Reviewed-on: #58
9 months ago
Damien Broqua b8b3df2932 #52 - Afficher toute les infos du format d'un album 9 months ago
Damien Broqua adea857666 #49 - Mise à jour d'une fiche 9 months ago
Damien Broqua 6320764743 #56 (#57)
Co-authored-by: dbroqua <contact@darkou.fr>
Reviewed-on: #57
9 months ago
Damien Broqua d473899b20 Update 'README.md' 9 months ago
Damien Broqua 827dcb9ccc #50 - Sur ma collection les filtres génèrent une nouvelle url (#55)
Co-authored-by: dbroqua <contact@darkou.fr>
Reviewed-on: #55
9 months ago
Damien Broqua 1377b4c0c1 #51 - Avoir une animation au chargement de la liste (#54)
Co-authored-by: dbroqua <contact@darkou.fr>
Reviewed-on: #54
9 months ago
Damien Broqua 080471eb37 #47 - Changer la couleur du burger en fonction du thème (#53)
Co-authored-by: dbroqua <contact@darkou.fr>
Reviewed-on: #53
9 months ago
Damien Broqua befdfa35a6 Correction d'un wording 10 months ago

@ -1,36 +1,43 @@
module.exports = {
env: {
browser: true,
es2020: true,
node: true,
jquery: true,
},
extends: ['airbnb-base', 'prettier'],
plugins: ['prettier'],
parserOptions: {
ecmaVersion: 11,
sourceType: 'module',
},
rules: {
'prettier/prettier': ['error'],
'no-underscore-dangle': [
'error',
{
allow: ['_id', 'artists_sort', 'type_'],
},
],
'camelcase': [
'error',
{
allow: ['artists_sort',]
},
env: {
browser: true,
es2020: true,
node: true,
jquery: true,
},
extends: ["airbnb-base", "prettier"],
plugins: ["prettier"],
parserOptions: {
ecmaVersion: 11,
sourceType: "module",
},
rules: {
"prettier/prettier": ["error"],
"no-underscore-dangle": [
"error",
{
allow: ["_id", "artists_sort", "type_"],
},
],
camelcase: [
"error",
{
allow: ["artists_sort"],
},
],
},
ignorePatterns: ["public/libs/**/*.js", "public/js/main.js", "dist/**"],
overrides: [
{
files: ["**/*.js"],
excludedFiles: "*.ejs",
},
],
},
ignorePatterns: ['public/libs/**/*.js', 'public/js/main.js', 'dist/**'],
overrides: [
{
files: ['**/*.js'],
excludedFiles: '*.ejs',
globals: {
Vue: true,
axios: true,
showToastr: true,
protocol: true,
host: true,
},
],
};

2
.gitignore vendored

@ -121,6 +121,6 @@ dist
dist
yarn.lock
public/css
public/css
public/js
docker-compose.yml
dump

@ -18,7 +18,9 @@ Vous pouvez, si vous le souhaitez héberger l'application sur votre propre serve
### Prérequis
Il existe 2 méthodes d'installation, soit via docker soit en mode standalone. Peu importe la méthode il vous faudra un compte sur [https://formspree.io/](https://formspree.io/) afin d'avoir une page nous-contacter fonctionnelle.
Il existe 2 méthodes d'installation, soit via docker soit en mode standalone.
Peu importe la méthode il vous faudra un compte sur [https://formspree.io/](https://formspree.io/) afin d'avoir une page nous-contacter fonctionnelle ou configurer le SMTP tel que défini dans la section [variables d'environnements](#env-file).
Pour la méthode docker il ne vous faudra rien de plus que `docker` et `docker-compose`.
@ -90,7 +92,7 @@ C'est terminé !
Le site est accessible sur [http://localhost:3001](http://localhost:3001).
:information_source: Information : Vous pouvez, et vous dreviez, également regarder du côté de `systemd`, `pm2` ou encore `supervisor` pour que le service démarre en même temps que votre serveur.
:information_source: Information : Vous pouvez, et vous devriez, également regarder du côté de `systemd`, `pm2` ou encore `supervisor` pour que le service démarre en même temps que votre serveur.
### Aller plus loin
@ -227,6 +229,13 @@ S3_BUCKET # Nom du bucket (musictopus par défaut, à changer impérativement si
JOBS_HEADER_KEY # Nom du header utilisé pour l'identification des tâches cron (musictopus par défaut)
JOBS_HEADER_VALUE # Valeur de la clé (ooYee9xok7eigo2shiePohyoGh1eepew par défaut)
REGISTRATION_OPEN # true/false en fonction de si vous souhaitez activer ou non l'inscription à votre instance (true par défaut)
MAIL_METHOD # permet de définir la façon dont les mails de la page contact sont envoyés (formspree ou smtp)
MAIL_HOST # Adresse du server mail (dams le cas ou MAIL_METHOD est défini sur smtp)
MAIL_PORT # Port d'écoute du serveur smtp (dams le cas ou MAIL_METHOD est défini sur smtp)
MAIL_USER # Adresse mail du compte permettant d'envoyer les mails (dams le cas ou MAIL_METHOD est défini sur smtp)
MAIL_PASSWORD # Mot de passe du compte email (dams le cas ou MAIL_METHOD est défini sur smtp)
MAIL_TO # Adresse mail du contact qui recevra les messages de la page "nous contacter" (dams le cas ou MAIL_METHOD est défini sur smtp)
```
## Contributeurs

@ -37,6 +37,12 @@ services:
JOBS_HEADER_KEY: ${JOBS_HEADER_KEY}
JOBS_HEADER_VALUE: ${JOBS_HEADER_VALUE}
REGISTRATION_OPEN: ${REGISTRATION_OPEN}
MAIL_METHOD: ${MAIL_METHOD}
MAIL_HOST: ${MAIL_HOST}
MAIL_PORT: ${MAIL_PORT}
MAIL_USER: ${MAIL_USER}
MAIL_PASSWORD: ${MAIL_PASSWORD}
MAIL_TO: ${MAIL_TO}
networks:
- musictopus
musictopus-db:

@ -0,0 +1,118 @@
{
"name": "icon",
"css_prefix_text": "icon-",
"css_use_suffix": false,
"hinting": true,
"units_per_em": 1000,
"ascent": 850,
"glyphs": [
{
"uid": "44e04715aecbca7f266a17d5a7863c68",
"css": "plus",
"code": 59392,
"src": "fontawesome"
},
{
"uid": "8b80d36d4ef43889db10bc1f0dc9a862",
"css": "user",
"code": 59393,
"src": "fontawesome"
},
{
"uid": "9dd9e835aebe1060ba7190ad2b2ed951",
"css": "search",
"code": 59394,
"src": "fontawesome"
},
{
"uid": "bf882b30900da12fca090d9796bc3030",
"css": "mail",
"code": 59395,
"src": "fontawesome"
},
{
"uid": "0ddd3e8201ccc7d41f7b7c9d27eca6c1",
"css": "link",
"code": 59396,
"src": "fontawesome"
},
{
"uid": "e15f0d620a7897e2035c18c80142f6d9",
"css": "link-ext",
"code": 61582,
"src": "fontawesome"
},
{
"uid": "9bc2902722abb366a213a052ade360bc",
"css": "spin",
"code": 59449,
"src": "fontelico"
},
{
"uid": "bbfb51903f40597f0b70fd75bc7b5cac",
"css": "trash",
"code": 61944,
"src": "fontawesome"
},
{
"uid": "d73eceadda1f594cec0536087539afbf",
"css": "heart",
"code": 59397,
"src": "fontawesome"
},
{
"uid": "cce5e05853d0798a4d10077ef613387c",
"css": "blind",
"code": 62109,
"src": "fontawesome"
},
{
"uid": "567e3e257f2cc8fba2c12bf691c9f2d8",
"css": "moon",
"code": 61830,
"src": "fontawesome"
},
{
"uid": "aa035df0908c4665c269b7b09a5596f3",
"css": "sun",
"code": 61829,
"src": "fontawesome"
},
{
"uid": "c5fd349cbd3d23e4ade333789c29c729",
"css": "eye",
"code": 59398,
"src": "fontawesome"
},
{
"uid": "d870630ff8f81e6de3958ecaeac532f2",
"css": "left-open",
"code": 59399,
"src": "fontawesome"
},
{
"uid": "399ef63b1e23ab1b761dfbb5591fa4da",
"css": "right-open",
"code": 59400,
"src": "fontawesome"
},
{
"uid": "895405dfac8a3b7b2f23b183c6608ee6",
"css": "export",
"code": 59401,
"src": "fontawesome"
},
{
"uid": "4aad6bb50b02c18508aae9cbe14e784e",
"css": "share",
"code": 61920,
"src": "fontawesome"
},
{
"uid": "a73c5deb486c8d66249811642e5d719a",
"css": "refresh",
"code": 59402,
"src": "fontawesome"
}
]
}

@ -0,0 +1,46 @@
const { parallel, src, dest } = require("gulp");
const sourcemaps = require("gulp-sourcemaps");
const concat = require("gulp-concat");
const gulp = require("gulp");
const uglify = require("gulp-uglify");
const babel = require("gulp-babel");
const sourceJs = "javascripts/**/*.js";
const sourceRemoteJS = [
"./node_modules/vue/dist/vue.global.prod.js",
"./node_modules/axios/dist/axios.min.js",
];
const destination = "public/js";
// TASKS ----------------------------------------------------------------------
const compileJs = function () {
return gulp
.src(sourceJs)
.pipe(sourcemaps.init())
.pipe(concat("main.js"))
.pipe(
babel({
presets: ["@babel/env"],
})
)
.pipe(uglify())
.pipe(sourcemaps.write("."))
.pipe(gulp.dest(destination));
};
const compileRemoteJs = function () {
return gulp
.src(sourceRemoteJS)
.pipe(sourcemaps.init())
.pipe(concat("libs.js"))
.pipe(sourcemaps.write("."))
.pipe(gulp.dest(destination));
};
// ----------------------------------------------------------------------------
// COMMANDS -------------------------------------------------------------------
exports.default = parallel(compileJs, compileRemoteJs);
// ----------------------------------------------------------------------------

@ -0,0 +1,179 @@
Vue.createApp({
data() {
return {
q: "",
year: "",
country: "",
format: "",
loading: false,
items: [],
details: {},
modalIsVisible: false,
formats: [
"Vinyl",
"Acetate",
"Flexi-disc",
"Lathe Cut",
"Mighty Tiny",
"Shellac",
"Sopic",
"Pathé Disc",
"Edison Disc",
"Cylinder",
"CD",
"CDr",
"CDV",
"DVD",
"DVDr",
"HD DVD",
"HD DVD-R",
"Blu-ray",
"Blu-ray-R",
"Ultra HD Blu-ray",
"SACD",
"4-Track Cartridge",
"8-Track Cartridge",
"Cassette",
"DC-International",
"Elcaset",
"PlayTape",
"RCA Tape Cartridge",
"DAT",
"DCC",
"Microcassette",
"NT Cassette",
"Pocket Rocker",
"Revere Magnetic Stereo Tape Ca",
"Tefifon",
"Reel-To-Reel",
"Sabamobil",
"Betacam",
"Betacam SP",
"Betamax",
"Cartrivision",
"MiniDV",
"Super VHS",
"U-matic",
"VHS",
"Video 2000",
"Video8",
"Film Reel",
"HitClips",
"Laserdisc",
"SelectaVision",
"VHD",
"Wire Recording",
"Minidisc",
"MVD",
"UMD",
"Floppy Disk",
"File",
"Memory Stick",
"Hybrid",
"All Media",
"Box Set",
],
};
},
methods: {
search(event) {
event.preventDefault();
if (this.loading) {
return false;
}
this.loading = true;
let url = `/api/v1/search?q=${this.q}`;
if (this.year) {
url += `&year=${this.year}`;
}
if (this.country) {
url += `&country=${this.country}`;
}
if (this.format) {
url += `&format=${this.format}`;
}
return axios
.get(url)
.then((response) => {
const { results } = response.data;
const items = [];
for (let i = 0; i < results.length; i += 1) {
const {
id,
title,
thumb,
year,
country,
format,
genre,
style,
} = results[i];
items.push({
id,
title,
thumb,
year,
country,
format,
genre,
style,
});
}
this.items = items;
})
.catch((err) => {
showToastr(
err.response?.data?.message ||
"Aucun résultat trouvé :/"
);
})
.finally(() => {
this.loading = false;
});
},
toggleModal() {
this.modalIsVisible = !this.modalIsVisible;
},
loadDetails(discogsId) {
axios
.get(`/api/v1/search/${discogsId}`)
.then((response) => {
const { data } = response;
this.details = data;
this.toggleModal();
})
.catch((err) => {
showToastr(
err.response?.data?.message ||
"Impossible de charger les détails de cet album"
);
})
.finally(() => {
this.loading = false;
});
},
add() {
axios
.post("/api/v1/albums", this.details)
.then(() => {
window.location.href = "/ma-collection";
})
.catch((err) => {
showToastr(
err.response?.data?.message ||
"Impossible d'ajouter cet album pour le moment…"
);
});
},
orderedItems(items) {
return items.sort();
},
},
}).mount("#ajouter-album");

@ -0,0 +1,237 @@
Vue.createApp({
data() {
return {
loading: false,
moreFilters: false,
items: [],
total: 0,
// eslint-disable-next-line no-undef
page: query.page || 1,
totalPages: 1,
limit: 16,
artist: "",
format: "",
year: "",
genre: "",
style: "",
sortOrder: "artists_sort-asc",
sort: "artists_sort",
order: "asc",
itemId: null,
showModalDelete: false,
showModalShare: false,
// eslint-disable-next-line no-undef
shareLink: `/collection/${userId}`,
// eslint-disable-next-line no-undef
isPublicCollection,
// eslint-disable-next-line no-undef
query,
};
},
created() {
this.fetch();
},
methods: {
formatParams(param) {
return param.replace("&", "%26").replace("+", "%2B");
},
fetch() {
this.loading = true;
this.total = 0;
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const entries = urlParams.entries();
const sortOrder = {
sort: "artists_sort",
order: "asc",
};
// eslint-disable-next-line no-restricted-syntax
for (const entry of entries) {
const [key, value] = entry;
switch (key) {
case "artists_sort":
this.artist = value;
break;
default:
if (["order", "sort"].indexOf(key) !== -1) {
sortOrder[key] = value;
}
this[key] = value;
}
}
this.sortOrder = `${sortOrder.sort}-${sortOrder.order}`;
let url = `/api/v1/albums?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
if (this.artist) {
url += `&artist=${this.formatParams(this.artist)}`;
}
if (this.format) {
url += `&format=${this.formatParams(this.format)}`;
}
if (this.year) {
url += `&year=${this.year}`;
}
if (this.genre) {
url += `&genre=${this.formatParams(this.genre)}`;
}
if (this.style) {
url += `&style=${this.formatParams(this.style)}`;
}
axios
.get(url)
.then((response) => {
this.items = response.data.rows;
this.total = response.data.count || 0;
this.totalPages =
parseInt(response.data.count / this.limit, 10) +
(response.data.count % this.limit > 0 ? 1 : 0);
})
.catch((err) => {
showToastr(
err.response?.data?.message ||
"Impossible de charger votre collection"
);
})
.finally(() => {
this.loading = false;
});
},
changeUrl() {
let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
if (this.artist) {
url += `&artists_sort=${this.formatParams(this.artist)}`;
}
if (this.format) {
url += `&format=${this.formatParams(this.format)}`;
}
if (this.year) {
url += `&year=${this.year}`;
}
if (this.genre) {
url += `&genre=${this.formatParams(this.genre)}`;
}
if (this.style) {
url += `&style=${this.formatParams(this.style)}`;
}
window.location.href = url;
},
next(event) {
event.preventDefault();
this.page += 1;
this.changeUrl();
},
previous(event) {
event.preventDefault();
this.page -= 1;
this.changeUrl();
},
goTo(page) {
this.page = page;
this.changeUrl();
},
changeSort() {
const [sort, order] = this.sortOrder.split("-");
this.sort = sort;
this.order = order;
this.page = 1;
this.changeUrl();
},
changeFilter() {
this.page = 1;
this.changeUrl();
},
showMoreFilters() {
this.moreFilters = !this.moreFilters;
},
toggleModal() {
this.showModalDelete = !this.showModalDelete;
},
toggleModalShare() {
this.showModalShare = !this.showModalShare;
},
showConfirmDelete(itemId) {
this.itemId = itemId;
this.toggleModal();
},
deleteItem() {
if ( vueType === 'private' ) {
return false;
}
axios
.delete(`/api/v1/albums/${this.itemId}`)
.then(() => {
this.fetch();
})
.catch((err) => {
showToastr(
err.response?.data?.message ||
"Impossible de supprimer cet album"
);
})
.finally(() => {
this.toggleModal();
});
},
shareCollection() {
if ( vueType === 'private' ) {
return false;
}
axios
.patch(`/api/v1/me`, {
isPublicCollection: !this.isPublicCollection,
})
.then((res) => {
this.isPublicCollection = res.data.isPublicCollection;
if (this.isPublicCollection) {
showToastr(
"Votre collection est désormais publique",
true
);
} else {
showToastr(
"Votre collection n'est plus partagée",
true
);
}
})
.catch((err) => {
showToastr(
err.response?.data?.message ||
"Impossible de supprimer cet album"
);
})
.finally(() => {
this.toggleModalShare();
});
},
renderAlbumTitle(item) {
let render = '';
for ( let i = 0 ; i < item.artists.length ; i += 1 ) {
const {
name,
join,
} = item.artists[i];
render += `${name} ${join ? `${join} ` : ''}`;
}
render += `- ${item.title}`;
return render;
}
},
}).mount("#collection");

@ -0,0 +1,43 @@
// eslint-disable-next-line no-undef
if (typeof contactMethod !== "undefined" && contactMethod === "smtp") {
Vue.createApp({
data() {
return {
email: "",
name: "",
message: "",
captcha: "",
loading: false,
};
},
methods: {
send(event) {
event.preventDefault();
if (this.loading) {
return false;
}
this.loading = true;
const { email, message, name, captcha } = this;
return axios
.post("/api/v1/contact", { email, name, message, captcha })
.then(() => {
showToastr("Message correctement envoyé", true);
})
.catch((err) => {
showToastr(
err.response?.data?.message ||
"Impossible d'envoyer votre message",
false
);
})
.finally(() => {
this.loading = false;
});
},
},
}).mount("#contact");
}

@ -0,0 +1,151 @@
/* eslint-disable no-unused-vars */
const { protocol, host } = window.location;
/**
* Fonction permettant d'afficher un message dans un toastr
* @param {String} message
*/
function showToastr(message, success = false) {
const x = document.getElementById("toastr");
if (message) {
x.getElementsByTagName("SPAN")[0].innerHTML = message;
}
x.className = `${x.className} show`.replace("sucess", "");
if (success) {
x.className = `${x.className} success`;
}
setTimeout(() => {
x.className = x.className.replace("show", "");
}, 3000);
}
/**
* Fonction permettant de masquer le toastr
*/
function hideToastr() {
const x = document.getElementById("toastr");
x.className = x.className.replace("show", "");
x.getElementsByTagName("SPAN")[0].innerHTML = "";
}
/**
* Fonction permettant de récupérer la valeur d'un cookie
* @param {String} cname
* @param {String} defaultValue
*
* @return {String}
*/
function getCookie(cname, defaultValue = "false") {
const name = `${cname}=`;
const decodedCookie = decodeURIComponent(document.cookie);
const ca = decodedCookie.split(";");
for (let i = 0; i < ca.length; i += 1) {
let c = ca[i];
while (c.charAt(0) === " ") {
c = c.substring(1);
}
if (c.indexOf(name) === 0) {
return c.substring(name.length, c.length);
}
}
return defaultValue;
}
/**
* Fonction permettant de créer un cookie
* @param {String} cname
* @param {String} cvalue
* @param {Number} exdays
*/
function setCookie(cname, cvalue, exdays = 30) {
const d = new Date();
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
const expires = `expires=${d.toUTCString()}`;
document.cookie = `${cname}=${cvalue};${expires};path=/`;
}
/**
* Fonction de ()charger le thème accessible
* @param {String} value
*/
function setAriaTheme(value) {
const { body } = document;
if (value === "true") {
const classesString = body.className || "";
if (classesString.indexOf("is-accessible") === -1) {
body.classList.add("is-accessible");
}
} else {
body.classList.remove("is-accessible");
}
}
/**
* Fonction de ()charger le thème accessible
*/
function switchAriaTheme() {
const { body } = document;
body.classList.toggle("is-accessible");
setCookie("ariatheme", body.classList.contains("is-accessible"));
}
/**
* Fonction permettant de switcher de thème clair/sombre
* @param {Object} e
*/
function switchTheme(e) {
const theme = e.target.checked ? "dark" : "light";
document.documentElement.setAttribute("data-theme", theme);
setCookie("theme", theme);
}
/**
* Ensemble d'actions effectuées au chargement de la page
*/
document.addEventListener("DOMContentLoaded", () => {
const $navbarBurgers = Array.prototype.slice.call(
document.querySelectorAll(".navbar-burger"),
0
);
if ($navbarBurgers.length > 0) {
$navbarBurgers.forEach((el) => {
el.addEventListener("click", () => {
const { target } = el.dataset;
const $target = document.getElementById(target);
el.classList.toggle("is-active");
$target.classList.toggle("is-active");
});
});
}
const switchAriaThemeBtn = document.querySelector("#switchAriaTheme");
if (switchAriaThemeBtn) {
switchAriaThemeBtn.addEventListener("click", switchAriaTheme);
}
setAriaTheme(getCookie("ariatheme"));
const toggleSwitch = document.querySelector(
'.theme-switch input[type="checkbox"]'
);
if (toggleSwitch) {
toggleSwitch.addEventListener("change", switchTheme, false);
}
let currentThemeIsDark = getCookie("theme");
if (currentThemeIsDark === "false" && window.matchMedia) {
currentThemeIsDark = window.matchMedia("(prefers-color-scheme: dark)")
.matches
? "dark"
: "light";
}
switchTheme({ target: { checked: currentThemeIsDark === "dark" } });
if (toggleSwitch) {
toggleSwitch.checked = currentThemeIsDark === "dark";
}
});

@ -0,0 +1,29 @@
if (typeof email !== "undefined" && typeof username !== "undefined") {
Vue.createApp({
data() {
return {
// eslint-disable-next-line no-undef
email,
// eslint-disable-next-line no-undef
username,
oldPassword: "",
password: "",
passwordConfirm: "",
loading: false,
};
},
methods: {
// eslint-disable-next-line no-unused-vars
async updateProfil(event) {
// try {
// if ( this.password !== this.passwordConfirm ) {
// throw "La confirnation du mot de passe ne correspond pas";
// }
// } catch(err) {
// event.preventDefault();
// showToastr(err);
// }
},
},
}).mount("#mon-compte");
}

@ -0,0 +1,194 @@
if (typeof item !== "undefined") {
Vue.createApp({
data() {
return {
// eslint-disable-next-line no-undef
item,
tracklist: [],
identifiers: [],
modalIsVisible: false,
identifiersMode: "preview",
identifiersPreviewLength: 16,
preview: null,
index: null,
showModalDelete: false,
};
},
created() {
this.setTrackList();
this.setIdentifiers();
window.addEventListener("keydown", this.changeImage);
},
destroyed() {
window.removeEventListener("keydown", this.changeImage);
},
methods: {
setIdentifiers() {
this.identifiers = [];
const max =
this.identifiersMode === "preview" &&
this.item.identifiers.length > this.identifiersPreviewLength
? this.identifiersPreviewLength
: this.item.identifiers.length;
for (let i = 0; i < max; i += 1) {
this.identifiers.push(this.item.identifiers[i]);
}
},
setTrackList() {
this.tracklist = [];
let subTrack = {
type: null,
title: null,
tracks: [],
};
for (let i = 0; i < this.item.tracklist.length; i += 1) {
const {
type_,
title,
position,
duration,
artists,
extraartists,
} = this.item.tracklist[i];
if (type_ === "heading") {
if (subTrack.type) {
this.tracklist.push(subTrack);
subTrack = {
type: null,
title: null,
tracks: [],
};
}
subTrack.type = type_;
subTrack.title = title;
} else {
subTrack.tracks.push({
title,
position,
duration,
extraartists,
artists,
});
}
}
this.tracklist.push(subTrack);
},
setImage() {
this.preview = this.item.images[this.index].uri;
},
showGallery(event) {
const item =
event.target.tagName === "IMG"
? event.target.parentElement
: event.target;
const { index } = item.dataset;
this.index = Number(index);
this.modalIsVisible = true;
this.setImage();
},
toggleModal() {
this.modalIsVisible = !this.modalIsVisible;
},
previous() {
this.index =
this.index > 0
? this.index - 1
: this.item.images.length - 1;
this.setImage();
},
next() {
this.index =
this.index + 1 === this.item.images.length
? 0
: this.index + 1;
this.setImage();
},
changeImage(event) {
const direction = event.code;
if (
this.modalIsVisible &&
["ArrowRight", "ArrowLeft", "Escape"].indexOf(direction) !==
-1
) {
switch (direction) {
case "ArrowRight":
return this.next();
case "ArrowLeft":
return this.previous();
default:
this.modalIsVisible = false;
return true;
}
}
return true;
},
showAllIdentifiers() {
this.identifiersMode = "all";
this.setIdentifiers();
},
showLessIdentifiers() {
this.identifiersMode = "preview";
this.setIdentifiers();
document
.querySelector("#identifiers")
.scrollIntoView({ behavior: "smooth" });
},
showConfirmDelete() {
this.toggleModalDelete();
},
toggleModalDelete() {
this.showModalDelete = !this.showModalDelete;
},
updateItem() {
showToastr("Mise à jour en cours…", true);
axios
.patch(`/api/v1/albums/${this.item._id}`)
.then((res) => {
showToastr("Mise à jour réalisée avec succès", true);
this.item = res.data;
this.setTrackList();
this.setIdentifiers();
this.showLessIdentifiers();
})
.catch((err) => {
showToastr(
err.response?.data?.message ||
"Impossible de mettre à jour cet album",
false
);
});
},
deleteItem() {
axios
.delete(`/api/v1/albums/${this.item._id}`)
.then(() => {
window.location.href = "/ma-collection";
})
.catch((err) => {
showToastr(
err.response?.data?.message ||
"Impossible de supprimer cet album"
);
})
.finally(() => {
this.toggleModalDelete();
});
},
goToArtist() {
return "";
},
},
}).mount("#ma-collection-details");
}

@ -0,0 +1,16 @@
Vue.createApp({
data() {
return {
format: "xml",
};
},
created() {},
destroyed() {},
methods: {
exportCollection(event) {
event.preventDefault();
window.open(`/api/v1/albums?exportFormat=${this.format}`, "_blank");
},
},
}).mount("#exporter");

@ -4,13 +4,14 @@
"description": "Simple application to manage your CD/Vinyl collection",
"scripts": {
"start": "node ./dist/bin/www",
"run:all": "npm-run-all build sass start",
"watch": "nodemon -e js,scss",
"run:all": "npm-run-all build sass uglify start",
"watch": "npx nodemon -e js,scss",
"sass": "npx sass sass/index.scss public/css/main.css -s compressed --color",
"uglify": "npx gulp",
"prebuild": "rimraf dist",
"build": "babel ./src --out-dir dist --copy-files",
"build": "npx babel ./src --out-dir dist --copy-files",
"test": "jest",
"prepare": "husky install"
"prepare": "npx husky install"
},
"engines": {
"node": "16.x",
@ -55,10 +56,16 @@
"excel4node": "^1.7.2",
"express": "^4.17.2",
"express-session": "^1.17.2",
"gulp": "^4.0.2",
"gulp-babel": "^8.0.0",
"gulp-concat": "^2.6.1",
"gulp-sourcemaps": "^3.0.0",
"gulp-uglify": "^3.0.2",
"joi": "^17.6.0",
"knacss": "^8.0.4",
"mongoose": "^6.2.1",
"mongoose-unique-validator": "^3.0.0",
"nodemailer": "^6.7.8",
"npm-run-all": "^4.1.5",
"passport": "^0.5.2",
"passport-custom": "^1.1.1",
@ -66,14 +73,16 @@
"passport-local": "^1.0.0",
"rimraf": "^3.0.2",
"sass": "^1.49.7",
"svg-captcha": "^1.4.0",
"uuid": "^8.3.2",
"vue": "^3.2.31"
},
"nodemonConfig": {
"exec": "yarn run:all",
"exec": "npm run run:all",
"watch": [
"src/*",
"sass/*"
"sass/*",
"javascripts/*"
],
"ignore": [
"**/__tests__/**",

Binary file not shown.

@ -26,6 +26,8 @@
<glyph glyph-name="export" unicode="&#xe809;" d="M786 298v-144q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h142q7 0 13-6t5-12q0-15-15-18-43-15-74-34-5-2-9-2h-62q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v119q0 11 10 16 16 7 31 21 8 9 19 4 12-5 12-16z m132 277l-214-214q-10-11-25-11-7 0-14 3-22 9-22 33v107h-89q-181 0-245-73-66-77-41-264 2-13-11-19-5-1-7-1-9 0-14 7-6 8-12 17t-22 39-28 55-21 64-10 68q0 27 2 51t8 50 15 49 27 45 38 42 52 34 70 27 89 17 110 6h89v107q0 24 22 33 7 3 14 3 14 0 25-11l214-214q11-10 11-25t-11-25z" horiz-adv-x="928.6" />
<glyph glyph-name="refresh" unicode="&#xe80a;" d="M843 261q0-3 0-4-36-150-150-243t-267-93q-81 0-157 31t-136 88l-72-72q-11-11-25-11t-25 11-11 25v250q0 14 11 25t25 11h250q14 0 25-11t10-25-10-25l-77-77q40-36 90-57t105-20q74 0 139 37t104 99q6 10 30 66 4 13 16 13h107q8 0 13-6t5-12z m14 446v-250q0-14-10-25t-26-11h-250q-14 0-25 11t-10 25 10 25l77 77q-82 77-194 77-75 0-140-37t-104-99q-6-10-29-66-5-13-17-13h-111q-7 0-13 6t-5 12v4q36 150 151 243t268 93q81 0 158-31t137-88l72 72q11 11 25 11t26-11 10-25z" horiz-adv-x="857.1" />
<glyph glyph-name="spin" unicode="&#xe839;" d="M855 9c-189-190-520-172-705 13-190 190-200 494-28 695 11 13 21 26 35 34 36 23 85 18 117-13 30-31 35-76 16-112-5-9-9-15-16-22-140-151-145-379-8-516 153-153 407-121 542 34 106 122 142 297 77 451-83 198-305 291-510 222l0 1c236 82 492-24 588-252 71-167 37-355-72-493-11-15-23-29-36-42z" horiz-adv-x="1000" />
<glyph glyph-name="link-ext" unicode="&#xf08e;" d="M786 332v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

@ -1,138 +0,0 @@
/**
* Fonction permettant d'afficher un message dans un toastr
* @param {String} message
*/
function showToastr(message, success = false) {
let x = document.getElementById("toastr");
if ( message ) {
x.getElementsByTagName("SPAN")[0].innerHTML = message;
}
x.className = `${x.className} show`.replace("sucess", "");
if ( success ) {
x.className = `${x.className} success`;
}
setTimeout(function(){ x.className = x.className.replace("show", ""); }, 3000);
};
/**
* Fonction permettant de masquer le toastr
*/
function hideToastr() {
let x = document.getElementById("toastr");
x.className = x.className.replace("show", "");
x.getElementsByTagName("SPAN")[0].innerHTML = "";
}
/**
* Fonction permettant de récupérer la valeur d'un cookie
* @param {String} cname
* @param {String} defaultValue
*
* @return {String}
*/
function getCookie(cname, defaultValue = 'false') {
let name = cname + "=";
let decodedCookie = decodeURIComponent(document.cookie);
let ca = decodedCookie.split(';');
for(let i = 0; i < ca.length; i+=1) {
let c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return defaultValue;
}
/**
* Fonction permettant de créer un cookie
* @param {String} cname
* @param {String} cvalue
* @param {Number} exdays
*/
function setCookie(cname, cvalue, exdays = 30) {
const d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
let expires = "expires="+ d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}
/**
* Fonction de ()charger le thème accessible
* @param {String} value
*/
function setAriaTheme(value) {
let body = document.body;
if ( value === 'true' ) {
let classesString = body.className || "";
if (classesString.indexOf("is-accessible") === -1) {
body.classList.add("is-accessible");
}
} else {
body.classList.remove("is-accessible");
}
}
/**
* Fonction de ()charger le thème accessible
*/
function switchAriaTheme() {
let body = document.body;
body.classList.toggle("is-accessible");
setCookie('ariatheme', body.classList.contains("is-accessible"));
}
/**
* Fonction permettant de switcher de thème clair/sombre
* @param {Object} e
*/
function switchTheme(e) {
const theme = e.target.checked ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme', theme);
setCookie('theme', theme);
}
/**
* Ensemble d'actions effectuées au chargement de la page
*/
document.addEventListener('DOMContentLoaded', () => {
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
if ($navbarBurgers.length > 0) {
$navbarBurgers.forEach( el => {
el.addEventListener('click', () => {
const target = el.dataset.target;
const $target = document.getElementById(target);
el.classList.toggle('is-active');
$target.classList.toggle('is-active');
});
});
}
const switchAriaThemeBtn = document.querySelector("#switchAriaTheme");
if ( switchAriaThemeBtn ) {
switchAriaThemeBtn.addEventListener("click", switchAriaTheme);
}
setAriaTheme(getCookie('ariatheme'));
const toggleSwitch = document.querySelector('.theme-switch input[type="checkbox"]');
if ( toggleSwitch ) {
toggleSwitch.addEventListener('change', switchTheme, false);
}
let currentThemeIsDark = getCookie('theme');
if ( currentThemeIsDark === 'false' && window.matchMedia ) {
currentThemeIsDark = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
switchTheme({target: {checked: currentThemeIsDark === 'dark'}});
if ( toggleSwitch) {
toggleSwitch.checked = currentThemeIsDark === 'dark';
}
});

@ -56,6 +56,8 @@ $pagination-hover-color: rgb(115, 151, 186);
--button-link-text-color: #2C364A;
--loader-img: url('/img/loading-light.gif');
--nord0: #{$nord0};
--nord1: #{$nord1};
--nord2: #{$nord2};
@ -94,4 +96,6 @@ $pagination-hover-color: rgb(115, 151, 186);
--border-color: #{$nord1};
--button-link-text-color: #{$white};
--loader-img: url('/img/loading-dark.gif');
}

@ -1,11 +1,11 @@
@font-face {
font-family: 'icon';
src: url('/font/icon.eot?80770511');