Compare commits
16 Commits
Author | SHA1 | Date |
---|---|---|
|
77de7d54ca | |
|
00bb8647e1 | |
|
c32b182151 | |
|
85752c537d | |
|
3b3a4cf779 | |
|
1931bd9eda | |
|
81c61a0529 | |
|
205474a701 | |
|
e28f382c6c | |
|
4ea7b42d52 | |
|
fd0a9df724 | |
|
2f988798df | |
|
6862afda5c | |
|
4109186a47 | |
|
e0f227af08 | |
|
fbeb1a67c5 |
|
@ -22,7 +22,13 @@ module.exports = {
|
|||
camelcase: [
|
||||
"error",
|
||||
{
|
||||
allow: ["artists_sort"],
|
||||
allow: [
|
||||
"artists_sort",
|
||||
"access_token",
|
||||
"api_url",
|
||||
"media_ids",
|
||||
"release_id",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -3,7 +3,7 @@ version: "2.4"
|
|||
services:
|
||||
musictopus-www:
|
||||
container_name: musictopus-www
|
||||
image: "node:16"
|
||||
image: "node:18"
|
||||
restart: always
|
||||
user: "node"
|
||||
working_dir: /home/node/app
|
||||
|
|
|
@ -3,7 +3,7 @@ version: "2.4"
|
|||
services:
|
||||
musictopus-www:
|
||||
container_name: musictopus-www
|
||||
image: "node:16"
|
||||
image: "node:18"
|
||||
restart: always
|
||||
user: "node"
|
||||
working_dir: /home/node/app
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
Vue.createApp({
|
||||
data() {
|
||||
return {
|
||||
// eslint-disable-next-line no-undef
|
||||
share: canPublish,
|
||||
q: "",
|
||||
year: "",
|
||||
country: "",
|
||||
|
@ -9,6 +11,7 @@ Vue.createApp({
|
|||
items: [],
|
||||
details: {},
|
||||
modalIsVisible: false,
|
||||
submitting: false,
|
||||
formats: [
|
||||
"Vinyl",
|
||||
"Acetate",
|
||||
|
@ -112,6 +115,7 @@ Vue.createApp({
|
|||
format,
|
||||
genre,
|
||||
style,
|
||||
inCollection,
|
||||
} = results[i];
|
||||
items.push({
|
||||
id,
|
||||
|
@ -122,6 +126,7 @@ Vue.createApp({
|
|||
format,
|
||||
genre,
|
||||
style,
|
||||
inCollection,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -160,12 +165,21 @@ Vue.createApp({
|
|||
});
|
||||
},
|
||||
add() {
|
||||
axios
|
||||
.post("/api/v1/albums", this.details)
|
||||
if (this.submitting) {
|
||||
return true;
|
||||
}
|
||||
this.submitting = true;
|
||||
|
||||
return axios
|
||||
.post("/api/v1/albums", {
|
||||
album: this.details,
|
||||
share: this.share,
|
||||
})
|
||||
.then(() => {
|
||||
window.location.href = "/ma-collection";
|
||||
})
|
||||
.catch((err) => {
|
||||
this.submitting = false;
|
||||
showToastr(
|
||||
err.response?.data?.message ||
|
||||
"Impossible d'ajouter cet album pour le moment…"
|
||||
|
|
|
@ -21,7 +21,7 @@ Vue.createApp({
|
|||
showModalDelete: false,
|
||||
showModalShare: false,
|
||||
// eslint-disable-next-line no-undef
|
||||
shareLink: `${protocol}//${host}/collection/${userId}`,
|
||||
shareLink: `/collection/${userId}`,
|
||||
// eslint-disable-next-line no-undef
|
||||
isPublicCollection,
|
||||
// eslint-disable-next-line no-undef
|
||||
|
@ -32,6 +32,9 @@ Vue.createApp({
|
|||
this.fetch();
|
||||
},
|
||||
methods: {
|
||||
formatParams(param) {
|
||||
return param.replace("&", "%26").replace("+", "%2B");
|
||||
},
|
||||
fetch() {
|
||||
this.loading = true;
|
||||
this.total = 0;
|
||||
|
@ -64,19 +67,19 @@ Vue.createApp({
|
|||
|
||||
let url = `/api/v1/albums?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
|
||||
if (this.artist) {
|
||||
url += `&artists_sort=${this.artist.replace("&", "%26")}`;
|
||||
url += `&artist=${this.formatParams(this.artist)}`;
|
||||
}
|
||||
if (this.format) {
|
||||
url += `&format=${this.format.replace("&", "%26")}`;
|
||||
url += `&format=${this.formatParams(this.format)}`;
|
||||
}
|
||||
if (this.year) {
|
||||
url += `&year=${this.year}`;
|
||||
}
|
||||
if (this.genre) {
|
||||
url += `&genre=${this.genre.replace("&", "%26")}`;
|
||||
url += `&genre=${this.formatParams(this.genre)}`;
|
||||
}
|
||||
if (this.style) {
|
||||
url += `&style=${this.style.replace("&", "%26")}`;
|
||||
url += `&style=${this.formatParams(this.style)}`;
|
||||
}
|
||||
|
||||
axios
|
||||
|
@ -101,19 +104,19 @@ Vue.createApp({
|
|||
changeUrl() {
|
||||
let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
|
||||
if (this.artist) {
|
||||
url += `&artists_sort=${this.artist.replace("&", "%26")}`;
|
||||
url += `&artists_sort=${this.formatParams(this.artist)}`;
|
||||
}
|
||||
if (this.format) {
|
||||
url += `&format=${this.format.replace("&", "%26")}`;
|
||||
url += `&format=${this.formatParams(this.format)}`;
|
||||
}
|
||||
if (this.year) {
|
||||
url += `&year=${this.year}`;
|
||||
}
|
||||
if (this.genre) {
|
||||
url += `&genre=${this.genre.replace("&", "%26")}`;
|
||||
url += `&genre=${this.formatParams(this.genre)}`;
|
||||
}
|
||||
if (this.style) {
|
||||
url += `&style=${this.style.replace("&", "%26")}`;
|
||||
url += `&style=${this.formatParams(this.style)}`;
|
||||
}
|
||||
|
||||
window.location.href = url;
|
||||
|
@ -164,10 +167,11 @@ Vue.createApp({
|
|||
this.toggleModal();
|
||||
},
|
||||
deleteItem() {
|
||||
if ( vueType === 'private' ) {
|
||||
// eslint-disable-next-line no-undef
|
||||
if (vueType !== "private") {
|
||||
return false;
|
||||
}
|
||||
axios
|
||||
return axios
|
||||
.delete(`/api/v1/albums/${this.itemId}`)
|
||||
.then(() => {
|
||||
this.fetch();
|
||||
|
@ -183,10 +187,11 @@ Vue.createApp({
|
|||
});
|
||||
},
|
||||
shareCollection() {
|
||||
if ( vueType === 'private' ) {
|
||||
// eslint-disable-next-line no-undef
|
||||
if (vueType !== "private") {
|
||||
return false;
|
||||
}
|
||||
axios
|
||||
return axios
|
||||
.patch(`/api/v1/me`, {
|
||||
isPublicCollection: !this.isPublicCollection,
|
||||
})
|
||||
|
@ -215,5 +220,17 @@ Vue.createApp({
|
|||
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");
|
||||
}).mount("#collection");
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
/* eslint-disable no-unused-vars */
|
||||
const { protocol, host } = window.location;
|
||||
|
||||
let timeout = null;
|
||||
|
||||
/**
|
||||
* Fonction permettant d'afficher un message dans un toastr
|
||||
* @param {String} message
|
||||
|
@ -11,12 +13,23 @@ function showToastr(message, success = false) {
|
|||
x.getElementsByTagName("SPAN")[0].innerHTML = message;
|
||||
}
|
||||
|
||||
x.className = `${x.className} show`.replace("sucess", "");
|
||||
if (success) {
|
||||
x.className = `${x.className} success`;
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
x.classList.remove("show");
|
||||
}
|
||||
setTimeout(() => {
|
||||
x.className = x.className.replace("show", "");
|
||||
|
||||
x.classList.remove("success");
|
||||
x.classList.remove("error");
|
||||
if (success) {
|
||||
x.classList.add("success");
|
||||
} else {
|
||||
x.classList.add("error");
|
||||
}
|
||||
|
||||
x.classList.add("show");
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
x.classList.remove("show");
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
|
@ -30,80 +43,6 @@ function hideToastr() {
|
|||
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 (dé)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 (dé)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
|
||||
*/
|
||||
|
@ -123,29 +62,4 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,28 +1,102 @@
|
|||
|
||||
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: "",
|
||||
formData: {
|
||||
// eslint-disable-next-line no-undef
|
||||
email,
|
||||
// eslint-disable-next-line no-undef
|
||||
username,
|
||||
oldPassword: "",
|
||||
password: "",
|
||||
passwordConfirm: "",
|
||||
// eslint-disable-next-line no-undef
|
||||
mastodon: mastodon || {
|
||||
publish: false,
|
||||
url: "",
|
||||
token: "",
|
||||
message:
|
||||
"Je viens d'ajouter {artist} - {album} à ma collection !",
|
||||
},
|
||||
},
|
||||
loading: false,
|
||||
errors: [],
|
||||
};
|
||||
},
|
||||
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);
|
||||
// }
|
||||
async testMastodon() {
|
||||
const { url, token } = this.formData.mastodon;
|
||||
|
||||
if (!url) {
|
||||
this.errors.push("emptyUrl");
|
||||
}
|
||||
if (!token) {
|
||||
this.errors.push("emptyToken");
|
||||
}
|
||||
|
||||
if (this.errors.length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.post(`/api/v1/mastodon`, { url, token });
|
||||
|
||||
showToastr("Configuration valide !", true);
|
||||
} catch (err) {
|
||||
showToastr(
|
||||
err.response?.data?.message ||
|
||||
"Impossible de tester cette configuration",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
async updateProfil() {
|
||||
this.errors = [];
|
||||
const { oldPassword, password, passwordConfirm, mastodon } =
|
||||
this.formData;
|
||||
|
||||
if (password && !oldPassword) {
|
||||
this.errors.push("emptyPassword");
|
||||
}
|
||||
|
||||
if (password !== passwordConfirm) {
|
||||
this.errors.push("passwordsDiffer");
|
||||
}
|
||||
|
||||
if (this.errors.length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
const data = {
|
||||
mastodon,
|
||||
};
|
||||
|
||||
if (password) {
|
||||
data.password = password;
|
||||
data.oldPassword = oldPassword;
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.patch(`/api/v1/me`, data);
|
||||
|
||||
showToastr("Profil mis à jour", true);
|
||||
} catch (err) {
|
||||
showToastr(
|
||||
err.response?.data?.message ||
|
||||
"Impossible de mettre à jour votre profil"
|
||||
);
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
}).mount("#mon-compte");
|
||||
|
|
|
@ -4,6 +4,8 @@ if (typeof item !== "undefined") {
|
|||
return {
|
||||
// eslint-disable-next-line no-undef
|
||||
item,
|
||||
// eslint-disable-next-line no-undef
|
||||
canShareItem,
|
||||
tracklist: [],
|
||||
identifiers: [],
|
||||
modalIsVisible: false,
|
||||
|
@ -12,6 +14,11 @@ if (typeof item !== "undefined") {
|
|||
preview: null,
|
||||
index: null,
|
||||
showModalDelete: false,
|
||||
showModalShare: false,
|
||||
shareMessage: "",
|
||||
shareMessageTransformed: "",
|
||||
shareMessageLength: 0,
|
||||
shareSubmiting: false,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
|
@ -23,6 +30,26 @@ if (typeof item !== "undefined") {
|
|||
destroyed() {
|
||||
window.removeEventListener("keydown", this.changeImage);
|
||||
},
|
||||
watch: {
|
||||
shareMessage(message) {
|
||||
const video =
|
||||
this.item.videos && this.item.videos.length > 0
|
||||
? this.item.videos[0].uri
|
||||
: "";
|
||||
|
||||
this.shareMessageTransformed = message
|
||||
.replaceAll("{artist}", this.item.artists[0].name)
|
||||
.replaceAll("{format}", this.item.formats[0].name)
|
||||
.replaceAll("{year}", this.item.year)
|
||||
.replaceAll("{video}", video)
|
||||
.replaceAll("{album}", this.item.title);
|
||||
|
||||
this.shareMessageLength = this.shareMessageTransformed.replace(
|
||||
video,
|
||||
new Array(36).join("#")
|
||||
).length;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setIdentifiers() {
|
||||
this.identifiers = [];
|
||||
|
@ -189,6 +216,33 @@ if (typeof item !== "undefined") {
|
|||
goToArtist() {
|
||||
return "";
|
||||
},
|
||||
shareAlbum() {
|
||||
if (this.shareSubmiting) {
|
||||
return false;
|
||||
}
|
||||
this.shareSubmiting = true;
|
||||
axios
|
||||
.post(`/api/v1/albums/${this.item._id}/share`, {
|
||||
message: this.shareMessageTransformed,
|
||||
})
|
||||
.then(() => {
|
||||
showToastr("Album partagé", true);
|
||||
this.shareMessage = "";
|
||||
this.showModalShare = false;
|
||||
})
|
||||
.catch((err) => {
|
||||
showToastr(
|
||||
err.response?.data?.message ||
|
||||
"Impossible de partager cet album",
|
||||
false
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
this.shareSubmiting = false;
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
}).mount("#ma-collection-details");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
Vue.createApp({
|
||||
data() {
|
||||
return {
|
||||
file: "",
|
||||
content: [],
|
||||
parsed: false,
|
||||
imported: 0,
|
||||
disabled: true,
|
||||
state: "default",
|
||||
};
|
||||
},
|
||||
created() {},
|
||||
destroyed() {},
|
||||
methods: {
|
||||
handleFileUpload(event) {
|
||||
const { files } = event.target;
|
||||
const [csv] = files;
|
||||
|
||||
this.file = csv;
|
||||
|
||||
this.file = csv;
|
||||
// this.parseFile();
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (content) => {
|
||||
this.content = [];
|
||||
this.state = "parse";
|
||||
const lines = content.target.result.split(/\r\n|\n/);
|
||||
for (let line = 1; line < lines.length - 1; line += 1) {
|
||||
this.parseLine(lines[0], lines[line]);
|
||||
}
|
||||
|
||||
this.state = "default";
|
||||
this.disabled = false;
|
||||
};
|
||||
|
||||
reader.readAsText(csv);
|
||||
},
|
||||
parseLine(header, line) {
|
||||
const row = {};
|
||||
let currentHeaderIndex = 0;
|
||||
|
||||
let separant = ",";
|
||||
let value = "";
|
||||
for (let i = 0; i < line.length; i += 1) {
|
||||
const char = line[i];
|
||||
|
||||
if (char !== separant) {
|
||||
if (char === '"') {
|
||||
separant = '"';
|
||||
} else {
|
||||
value += char;
|
||||
}
|
||||
} else if (char === '"') {
|
||||
separant = ",";
|
||||
} else {
|
||||
row[header.split(",")[currentHeaderIndex]] = value;
|
||||
currentHeaderIndex += 1;
|
||||
value = "";
|
||||
}
|
||||
}
|
||||
this.content.push(row);
|
||||
},
|
||||
async addOne(index) {
|
||||
const { Artist, Title, release_id } = this.content[index];
|
||||
|
||||
try {
|
||||
const res = await axios.get(
|
||||
`/api/v1/albums?discogsId=${release_id}`
|
||||
);
|
||||
|
||||
if (res.status === 204) {
|
||||
await axios.post("/api/v1/albums", {
|
||||
discogsId: release_id,
|
||||
share: false,
|
||||
});
|
||||
}
|
||||
|
||||
this.imported += 1;
|
||||
if (this.content.length > index + 1) {
|
||||
await this.addOne(index + 1);
|
||||
}
|
||||
} catch (err) {
|
||||
showToastr(
|
||||
`Impossible d'ajouter l'album ${Title} de ${Artist}`
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
async importCollection(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.disabled = true;
|
||||
this.state = "submit";
|
||||
this.imported = 0;
|
||||
|
||||
const imported = await this.addOne(0);
|
||||
|
||||
this.disabled = false;
|
||||
this.state = imported ? "done" : "default";
|
||||
},
|
||||
},
|
||||
}).mount("#importer");
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* Fonction permettant de sauvegarder dans le stockage local le choix du thème
|
||||
* @param {String} scheme
|
||||
*/
|
||||
function saveColorScheme(scheme) {
|
||||
localStorage.setItem("theme", scheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fonction permettant de changer le thème du site
|
||||
* @param {String} scheme
|
||||
*/
|
||||
function setColorScheme(scheme) {
|
||||
document.documentElement.setAttribute("data-theme", scheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fonction permettant de récupérer le thème du système
|
||||
* @return {String}
|
||||
*/
|
||||
function getPreferredColorScheme() {
|
||||
if (window.matchMedia) {
|
||||
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
return "dark";
|
||||
}
|
||||
return "light";
|
||||
}
|
||||
return "light";
|
||||
}
|
||||
|
||||
// INFO: On place un event sur le bouton
|
||||
const toggleSwitch = document.querySelector(
|
||||
'.theme-switch input[type="checkbox"]'
|
||||
);
|
||||
|
||||
/**
|
||||
* Event permettant de détecter les changements de thème du système
|
||||
*/
|
||||
if (window.matchMedia) {
|
||||
const colorSchemeQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
colorSchemeQuery.addEventListener("change", () => {
|
||||
const selectedColorScheme = localStorage.getItem("theme") || "system";
|
||||
|
||||
if (selectedColorScheme === "system") {
|
||||
const preferedColorScheme = getPreferredColorScheme();
|
||||
setColorScheme(preferedColorScheme);
|
||||
|
||||
toggleSwitch.checked = preferedColorScheme === "dark";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const currentTheme = localStorage.getItem("theme") || getPreferredColorScheme();
|
||||
|
||||
// INFO: Au chargement de la page on détecte le thème à charger
|
||||
setColorScheme(currentTheme);
|
||||
|
||||
toggleSwitch.checked = currentTheme === "dark";
|
||||
|
||||
toggleSwitch.addEventListener(
|
||||
"change",
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const scheme = e.target.checked ? "dark" : "light";
|
||||
|
||||
saveColorScheme(scheme);
|
||||
setColorScheme(scheme);
|
||||
},
|
||||
false
|
||||
);
|
|
@ -14,7 +14,7 @@
|
|||
"prepare": "npx husky install"
|
||||
},
|
||||
"engines": {
|
||||
"node": "16.x",
|
||||
"node": "16.x || 18.x",
|
||||
"yarn": "1.x"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -24,7 +24,7 @@
|
|||
"author": {
|
||||
"name": "Damien Broqua",
|
||||
"email": "contact@darkou.fr",
|
||||
"url": "https://www.darkou.fr"
|
||||
"url": "https://www.darkou.link"
|
||||
},
|
||||
"license": "GPL-3.0-or-later",
|
||||
"devDependencies": {
|
||||
|
@ -63,6 +63,7 @@
|
|||
"gulp-uglify": "^3.0.2",
|
||||
"joi": "^17.6.0",
|
||||
"knacss": "^8.0.4",
|
||||
"mastodon": "^1.2.2",
|
||||
"mongoose": "^6.2.1",
|
||||
"mongoose-unique-validator": "^3.0.0",
|
||||
"nodemailer": "^6.7.8",
|
||||
|
|
|
@ -7,10 +7,18 @@
|
|||
.list {
|
||||
margin-top: 2rem;
|
||||
|
||||
.item{
|
||||
.item {
|
||||
img {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.in-collection {
|
||||
opacity: 0.6;
|
||||
|
||||
small {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,10 +22,12 @@ $nord15: #b48ead;
|
|||
|
||||
$primary-color: $nord8;
|
||||
$danger-color: $nord11;
|
||||
$error-color: $nord12;
|
||||
$warning-color: $nord13;
|
||||
$success-color: $nord14;
|
||||
$primary-color-hl: darken($primary-color, $hoverAmount);
|
||||
$danger-color-hl: darken($danger-color, $hoverAmount);
|
||||
$error-color-hl: darken($error-color, $hoverAmount);
|
||||
$warning-color-hl: darken($warning-color, $hoverAmount);
|
||||
$success-color-hl: darken($success-color, $hoverAmount);
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
.error {
|
||||
min-height: calc(100vh - 3.25rem - 100px);
|
||||
padding-top: 4rem;
|
||||
main {
|
||||
&.error {
|
||||
min-height: calc(100vh - 3.25rem - 100px);
|
||||
padding-top: 4rem;
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
margin: 2rem auto;
|
||||
|
||||
.header {
|
||||
font-weight: 800;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&.info {
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -24,9 +24,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: 800;
|
||||
}
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
|
@ -34,13 +31,12 @@
|
|||
max-width: 100%;
|
||||
width: 100%;
|
||||
background-color: var(--input-color);
|
||||
border: 1px solid transparent !important;
|
||||
border: 1px solid var(--input-active-color) !important;
|
||||
color: var(--input-font-color);
|
||||
@include transition() {}
|
||||
|
||||
&:focus-visible {
|
||||
outline: unset;
|
||||
border-color: var(--input-active-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,19 +7,10 @@ html {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 3.5rem;
|
||||
font-family: 'open_sansregular';
|
||||
font-weight: 400;
|
||||
font-family: 'lucioleregular';
|
||||
min-height: 100vh;
|
||||
color: var(--font-color);
|
||||
@include transition() {}
|
||||
|
||||
&.is-accessible {
|
||||
font-family: 'lucioleregular';
|
||||
|
||||
.text-justify {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
@include transition();
|
||||
|
||||
footer.footer {
|
||||
margin-top: auto;
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
@import './loader';
|
||||
|
||||
@import './error';
|
||||
@import './messages.scss';
|
||||
@import './500';
|
||||
@import './home';
|
||||
@import './ajouter-un-album';
|
||||
|
|
|
@ -42,7 +42,6 @@
|
|||
}
|
||||
|
||||
.title {
|
||||
font-weight: 800;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
.message {
|
||||
margin: 8px 0;
|
||||
padding: 0;
|
||||
font-size: 0.8rem;
|
||||
|
||||
&.error {
|
||||
color: $error-color-hl;
|
||||
}
|
||||
}
|
|
@ -116,6 +116,17 @@
|
|||
border-bottom-left-radius: 6px;
|
||||
border-bottom-right-radius: 6px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
justify-content: end;
|
||||
align-items: baseline;
|
||||
|
||||
.field {
|
||||
flex-direction: row;
|
||||
|
||||
padding: 6px;
|
||||
span {
|
||||
padding-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.button:not(:last-child) {
|
||||
margin-right: .5em;
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
word-break: break-word;
|
||||
color: var(--font-color);
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.125;
|
||||
margin-left: .5rem !important;
|
||||
@include transition() {}
|
||||
|
|
|
@ -3,16 +3,19 @@
|
|||
min-width: 250px;
|
||||
max-width: 360px;
|
||||
position: fixed;
|
||||
z-index: 31;
|
||||
z-index: 66;
|
||||
right: 30px;
|
||||
top: 30px;
|
||||
font-size: 17px;
|
||||
|
||||
padding: 1.25rem 2.5rem 1.25rem 1.5rem;
|
||||
background-color: $danger-color;
|
||||
color: $button-alternate-color;
|
||||
border-radius: 6px;
|
||||
|
||||
&.error {
|
||||
background-color: $danger-color;
|
||||
color: $button-alternate-color;
|
||||
}
|
||||
|
||||
&.success {
|
||||
background-color: $success-color;
|
||||
color: $button-font-color;
|
||||
|
|
|
@ -22,11 +22,13 @@ import importJobsRouter from "./routes/jobs";
|
|||
|
||||
import importAlbumRouterApiV1 from "./routes/api/v1/albums";
|
||||
import importSearchRouterApiV1 from "./routes/api/v1/search";
|
||||
import importMastodonRouterApiV1 from "./routes/api/v1/mastodon";
|
||||
import importMeRouterApiV1 from "./routes/api/v1/me";
|
||||
import importContactRouterApiV1 from "./routes/api/v1/contact";
|
||||
|
||||
passportConfig(passport);
|
||||
|
||||
mongoose.set("strictQuery", false);
|
||||
mongoose
|
||||
.connect(mongoDbUri, { useNewUrlParser: true, useUnifiedTopology: true })
|
||||
.catch(() => {
|
||||
|
@ -83,6 +85,7 @@ app.use("/collection", collectionRouter);
|
|||
app.use("/jobs", importJobsRouter);
|
||||
app.use("/api/v1/albums", importAlbumRouterApiV1);
|
||||
app.use("/api/v1/search", importSearchRouterApiV1);
|
||||
app.use("/api/v1/mastodon", importMastodonRouterApiV1);
|
||||
app.use("/api/v1/me", importMeRouterApiV1);
|
||||
app.use("/api/v1/contact", importContactRouterApiV1);
|
||||
|
||||
|
|
|
@ -62,11 +62,15 @@ export const uploadFromUrl = async (url) => {
|
|||
const filename = `${uuid()}.jpg`;
|
||||
const file = `/tmp/${filename}`;
|
||||
|
||||
const { data } = await axios.get(url, { responseType: "arraybuffer" });
|
||||
const { data } = await axios.get(url, {
|
||||
headers: {
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0",
|
||||
},
|
||||
responseType: "arraybuffer",
|
||||
});
|
||||
|
||||
fs.writeFileSync(file, data);
|
||||
|
||||
return uploadFromFile(filename, file, true);
|
||||
|
||||
// return s3Object;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import { format as formatDate } from "date-fns";
|
||||
import fs from "fs";
|
||||
|
||||
import Mastodon from "mastodon";
|
||||
import { v4 } from "uuid";
|
||||
import axios from "axios";
|
||||
import Pages from "./Pages";
|
||||
import Export from "./Export";
|
||||
|
||||
|
@ -21,9 +25,21 @@ class Albums extends Pages {
|
|||
*/
|
||||
static async postAddOne(req) {
|
||||
const { body, user } = req;
|
||||
const { share, discogsId } = body;
|
||||
|
||||
let albumDetails = body.album;
|
||||
if (discogsId) {
|
||||
albumDetails = await getAlbumDetails(discogsId);
|
||||
body.id = discogsId;
|
||||
}
|
||||
|
||||
if (!albumDetails) {
|
||||
throw new ErrorEvent(406, "Aucun album à ajouter");
|
||||
}
|
||||
|
||||
const data = {
|
||||
...body,
|
||||
discogsId: body.id,
|
||||
...albumDetails,
|
||||
discogsId: albumDetails.id,
|
||||
User: user._id,
|
||||
};
|
||||
data.released = data.released
|
||||
|
@ -39,11 +55,88 @@ class Albums extends Pages {
|
|||
model: "Albums",
|
||||
id: album._id,
|
||||
};
|
||||
|
||||
const job = new JobsModel(jobData);
|
||||
|
||||
job.save();
|
||||
|
||||
try {
|
||||
const User = await UsersModel.findOne({ _id: user._id });
|
||||
|
||||
const { mastodon: mastodonConfig } = User;
|
||||
|
||||
const { publish, token, url, message } = mastodonConfig;
|
||||
|
||||
if (share && publish && url && token) {
|
||||
const M = new Mastodon({
|
||||
access_token: token,
|
||||
api_url: url,
|
||||
});
|
||||
|
||||
const video =
|
||||
data.videos && data.videos.length > 0
|
||||
? data.videos[0].uri
|
||||
: "";
|
||||
|
||||
const status = `${(
|
||||
message ||
|
||||
"Je viens d'ajouter {artist} - {album} à ma collection !"
|
||||
)
|
||||
.replaceAll("{artist}", data.artists[0].name)
|
||||
.replaceAll("{format}", data.formats[0].name)
|
||||
.replaceAll("{year}", data.year)
|
||||
.replaceAll("{video}", video)
|
||||
.replaceAll("{album}", data.title)}
|
||||
|
||||
Publié automatiquement via #musictopus`;
|
||||
|
||||
const media_ids = [];
|
||||
|
||||
if (data.images.length > 0) {
|
||||
for (let i = 0; i < data.images.length; i += 1) {
|
||||
if (media_ids.length === 4) {
|
||||
break;
|
||||
}
|
||||
|
||||
const filename = `${v4()}.jpg`;
|
||||
const file = `/tmp/${filename}`;
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const { data: buff } = await axios.get(
|
||||
data.images[i].uri,
|
||||
{
|
||||
headers: {
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0",
|
||||
},
|
||||
responseType: "arraybuffer",
|
||||
}
|
||||
);
|
||||
|
||||
fs.writeFileSync(file, buff);
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const { data: media } = await M.post("media", {
|
||||
file: fs.createReadStream(file),
|
||||
});
|
||||
|
||||
const { id } = media;
|
||||
|
||||
media_ids.push(id);
|
||||
|
||||
fs.unlinkSync(file);
|
||||
}
|
||||
}
|
||||
|
||||
await M.post("statuses", { status, media_ids });
|
||||
}
|
||||
} catch (err) {
|
||||
throw new ErrorEvent(
|
||||
500,
|
||||
"Mastodon",
|
||||
"Album ajouté à votre collection mais impossible de publier sur Mastodon"
|
||||
);
|
||||
}
|
||||
|
||||
return album;
|
||||
}
|
||||
|
||||
|
@ -80,20 +173,22 @@ class Albums extends Pages {
|
|||
exportFormat = "json",
|
||||
sort = "artists_sort",
|
||||
order = "asc",
|
||||
artists_sort,
|
||||
artist,
|
||||
format,
|
||||
year,
|
||||
genre,
|
||||
style,
|
||||
userId: collectionUserId,
|
||||
discogsIds,
|
||||
discogsId,
|
||||
} = this.req.query;
|
||||
|
||||
let userId = this.req.user?._id;
|
||||
|
||||
const where = {};
|
||||
|
||||
if (artists_sort) {
|
||||
where.artists_sort = artists_sort;
|
||||
if (artist) {
|
||||
where["artists.name"] = artist;
|
||||
}
|
||||
if (format) {
|
||||
where["formats.name"] = format;
|
||||
|
@ -135,6 +230,13 @@ class Albums extends Pages {
|
|||
userId = userIsSharingCollection._id;
|
||||
}
|
||||
|
||||
if (discogsIds) {
|
||||
where.discogsId = { $in: discogsIds };
|
||||
}
|
||||
if (discogsId) {
|
||||
where.discogsId = Number(discogsId);
|
||||
}
|
||||
|
||||
const count = await AlbumsModel.count({
|
||||
User: userId,
|
||||
...where,
|
||||
|
@ -216,7 +318,7 @@ class Albums extends Pages {
|
|||
*/
|
||||
async deleteOne() {
|
||||
const res = await AlbumsModel.findOneAndDelete({
|
||||
user: this.req.user._id,
|
||||
User: this.req.user._id,
|
||||
_id: this.req.params.itemId,
|
||||
});
|
||||
|
||||
|
@ -231,12 +333,89 @@ class Albums extends Pages {
|
|||
);
|
||||
}
|
||||
|
||||
async shareOne() {
|
||||
const { message: status } = this.req.body;
|
||||
const { itemId: _id } = this.req.params;
|
||||
const { _id: User } = this.req.user;
|
||||
const query = {
|
||||
_id,
|
||||
User,
|
||||
};
|
||||
|
||||
const album = await AlbumsModel.findOne(query);
|
||||
|
||||
if (!album) {
|
||||
throw new ErrorEvent(
|
||||
404,
|
||||
"Mise à jour",
|
||||
"Impossible de trouver cet album"
|
||||
);
|
||||
}
|
||||
|
||||
const { mastodon: mastodonConfig } = this.req.user;
|
||||
const { publish, token, url } = mastodonConfig;
|
||||
|
||||
if (publish && url && token) {
|
||||
const M = new Mastodon({
|
||||
access_token: token,
|
||||
api_url: url,
|
||||
});
|
||||
|
||||
const media_ids = [];
|
||||
|
||||
if (album.images.length > 0) {
|
||||
for (let i = 0; i < album.images.length; i += 1) {
|
||||
if (media_ids.length === 4) {
|
||||
break;
|
||||
}
|
||||
|
||||
const filename = `${v4()}.jpg`;
|
||||
const file = `/tmp/${filename}`;
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const { data: buff } = await axios.get(
|
||||
album.images[i].uri,
|
||||
{
|
||||
headers: {
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0",
|
||||
},
|
||||
responseType: "arraybuffer",
|
||||
}
|
||||
);
|
||||
|
||||
fs.writeFileSync(file, buff);
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const { data: media } = await M.post("media", {
|
||||
file: fs.createReadStream(file),
|
||||
});
|
||||
|
||||
const { id } = media;
|
||||
|
||||
media_ids.push(id);
|
||||
|
||||
fs.unlinkSync(file);
|
||||
}
|
||||
}
|
||||
|
||||
await M.post("statuses", { status, media_ids });
|
||||
} else {
|
||||
throw new ErrorEvent(
|
||||
406,
|
||||
`Vous n'avez pas configuré vos options de partage sur votre compte`
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode permettant de créer la page "ma-collection"
|
||||
*/
|
||||
async loadMyCollection() {
|
||||
const artists = await Albums.getAllDistincts(
|
||||
"artists_sort",
|
||||
"artists.name",
|
||||
this.req.user._id
|
||||
);
|
||||
const formats = await Albums.getAllDistincts(
|
||||
|
@ -301,7 +480,7 @@ class Albums extends Pages {
|
|||
);
|
||||
}
|
||||
|
||||
const artists = await Albums.getAllDistincts("artists_sort", userId);
|
||||
const artists = await Albums.getAllDistincts("artists.name", userId);
|
||||
const formats = await Albums.getAllDistincts("formats.name", userId);
|
||||
const years = await Albums.getAllDistincts("year", userId);
|
||||
const genres = await Albums.getAllDistincts("genres", userId);
|
||||
|
|