/**
* Provides utility functions for formatting and displaying JSON data,
* as well as updating UI elements based on user input.
*
* This module includes functions to apply syntax highlighting to JSON strings,
* extract and clean the inner content of a JSON object, and dynamically toggle
* the visibility of form fields depending on the selected media type.
*
* @module add
*/
const typeSelect = document.getElementById("type-add");
const filmFields = document.getElementById("filmFields-add");
const seriesFields = document.getElementById("seriesFields-add");
const seasonsInput = document.getElementById("seasons-add");
const episodesContainer = document.getElementById("episodesContainer-add");
/**
* Applies syntax highlighting to a JSON string or object.
*
* This function takes a JSON object or string, escapes HTML-sensitive characters,
* and wraps values (keys, strings, numbers, booleans, null) in <span> elements
* with specific CSS classes for visual syntax highlighting. This is typically used
* to display JSON data in a readable and styled format within HTML.
*
* @function
* @param {string|Object} json - The JSON string or object to highlight. If an object is provided, it is stringified using `JSON.stringify`.
* @returns {string} The highlighted HTML string representing the formatted JSON.
*/
function syntaxHighlight(json) {
if (typeof json != "string") {
json = JSON.stringify(json, null, 2);
}
json = json
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
let cls = "token-number";
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = "token-key";
} else {
cls = "token-string";
}
} else if (/true|false/.test(match)) {
cls = "token-boolean";
} else if (/null/.test(match)) {
cls = "token-null";
}
return `<span class="${cls}">${match}</span>`;
});
}
/**
* Extracts the inner JSON content from an object, removing the outer braces and base indentation.
*
* Converts the given object into a pretty-printed JSON string, then strips the first and last lines
* (which contain the opening and closing braces) and removes the leading indentation from each remaining line.
* This is useful for embedding only the core content of an object in another context without the outer structure.
*
* @function
* @param {Object} obj - The object to convert and extract from.
* @returns {string} A formatted string representing the inner JSON content (without outer braces or base indentation).
*/
function getInnerJsonString(obj) {
let json = JSON.stringify(obj, null, 2);
let lines = json.split("\n");
if (lines.length > 2) {
lines = lines.slice(1, -1);
lines = lines.map((line) => line.replace(/^ {2}/, ""));
}
return lines.join("\n");
}
/**
* Updates the visibility of input fields based on the selected media type.
*
* Displays the film-related fields if "film" is selected, the series-related fields if "serie" is selected,
* and hides both if no valid type is selected.
*
* @function
*/
function updateFields() {
if (typeSelect.value === "film") {
filmFields.style.display = "block";
seriesFields.style.display = "none";
} else if (typeSelect.value === "serie") {
filmFields.style.display = "none";
seriesFields.style.display = "block";
} else {
filmFields.style.display = "none";
seriesFields.style.display = "none";
}
}
typeSelect.addEventListener("change", updateFields);
updateFields();
seasonsInput && seasonsInput.addEventListener("input", function () {
episodesContainer.innerHTML = "";
const nbSeasons = parseInt(seasonsInput.value, 10);
if (isNaN(nbSeasons) || nbSeasons < 1) return;
for (let s = 1; s <= nbSeasons; s++) {
const seasonDiv = document.createElement("div");
seasonDiv.className = "season-block-add";
seasonDiv.innerHTML = `
<h4>Saison ${s}</h4>
<label>Nombre d'épisodes :</label>
<input type="number" min="1" class="episodesCount-add" data-season="${s}" /><br /><br />
<div class="add-episodesFields" id="add-episodesFields${s}"></div>
`;
episodesContainer.appendChild(seasonDiv);
}
document.querySelectorAll(".episodesCount-add").forEach((input) => {
input.addEventListener("input", function () {
const seasonNum = parseInt(this.getAttribute("data-season"), 10);
if (isNaN(seasonNum) || seasonNum < 1) return;
const count = parseInt(this.value, 10);
const fieldsDiv = document.getElementById(`add-episodesFields${seasonNum}`);
fieldsDiv.innerHTML = "";
if (isNaN(count) || count < 1) return;
for (let e = 1; e <= count; e++) {
const episodeLabel = document.createElement("label");
episodeLabel.textContent = `Titre épisode ${e} :`;
fieldsDiv.appendChild(episodeLabel);
const episodeTitleInput = document.createElement("input");
episodeTitleInput.type = "text";
episodeTitleInput.name = `season${seasonNum}_episode${e}_title`;
episodeTitleInput.required = true;
fieldsDiv.appendChild(episodeTitleInput);
fieldsDiv.appendChild(document.createElement("br"));
const descLabel = document.createElement("label");
descLabel.textContent = `Description épisode ${e} :`;
fieldsDiv.appendChild(descLabel);
const episodeDescInput = document.createElement("input");
episodeDescInput.type = "text";
episodeDescInput.name = `season${seasonNum}_episode${e}_desc`;
episodeDescInput.required = true;
fieldsDiv.appendChild(episodeDescInput);
fieldsDiv.appendChild(document.createElement("br"));
fieldsDiv.appendChild(document.createElement("br"));
}
});
});
});
document.getElementById("addForm").addEventListener("submit", function (event) {
event.preventDefault();
const folder = document.getElementById("folder-add").value;
const title = document.getElementById("title-add").value;
const type = document.getElementById("type-add").value;
const year = document.getElementById("year-add").value;
const genres = document.getElementById("genres-add").value;
const trailer = document.getElementById("trailer-add").value;
const imdb = document.getElementById("imdb-add").value;
const note = document.getElementById("note-add").value;
const desc = document.getElementById("description-add").value;
let data = {
folder, title, type, year, genres, trailer, imdb, note, description: desc,
};
if (type === "film") {
data.directors = document.getElementById("directors-add").value;
data.writers = document.getElementById("writers-add").value;
data.stars = document.getElementById("stars-add").value;
} else if (type === "serie") {
data.directors = document.getElementById("directorsSerie-add").value;
data.writers = document.getElementById("writersSerie-add").value;
data.stars = document.getElementById("starsSerie-add").value;
data.seasons = [];
const nbSeasons = parseInt(document.getElementById("seasons-add").value, 10);
for (let s = 1; s <= nbSeasons; s++) {
const season = {episodes: []};
const episodesCountInput = document.querySelector(`.episodesCount-add[data-season="${s}"]`);
if (!episodesCountInput) continue;
const nbEpisodes = parseInt(episodesCountInput.value, 10);
for (let e = 1; e <= nbEpisodes; e++) {
const epTitle = document.querySelector(`[name="season${s}_episode${e}_title"]`)?.value || "";
const epDesc = document.querySelector(`[name="season${s}_episode${e}_desc"]`)?.value || "";
season.episodes.push({title: epTitle, description: epDesc});
}
data.seasons.push(season);
}
}
document.getElementById("addForm").remove();
let formatted = {};
if (type === "film") {
formatted[title] = {
title: title,
description: desc,
banner: `medias/films/${folder}/${folder}.jpg`,
IMDb: note ? parseFloat(note) : undefined,
IMDb_link: imdb ? `${imdb}` : "",
year: year ? parseInt(year, 10) : undefined,
genres: genres ? genres.split(",").map((g) => g.trim()) : [],
directors: data.directors ? data.directors.split(",").map((d) => d.trim()) : [],
writers: data.writers ? data.writers.split(",").map((w) => w.trim()) : [],
stars: data.stars ? data.stars.split(",").map((s) => s.trim()) : [],
trailer: trailer,
video: `medias/films/${folder}/${folder}.mp4`,
};
} else if (type === "serie") {
let seasonsObj = {};
data.seasons.forEach((season, idx) => {
const seasonNum = (idx + 1).toString();
seasonsObj[seasonNum] = season.episodes.map((ep, epIdx) => ({
title: ep.title, desc: ep.description, video: `medias/series/${folder}/${seasonNum}-${epIdx + 1}.mp4`,
}));
});
formatted[title] = {
title: title,
description: desc,
banner: `medias/series/${folder}/${folder}.jpg`,
IMDb: note ? parseFloat(note) : undefined,
IMDb_link: imdb ? `${imdb}/` : "",
year: year ? parseInt(year, 10) : undefined,
genres: genres ? genres.split(",").map((g) => g.trim()) : [],
creators: data.writers ? data.writers.split(",").map((w) => w.trim()) : [],
stars: data.stars ? data.stars.split(",").map((s) => s.trim()) : [],
trailer: trailer,
seasons: seasonsObj,
};
}
const jsonOutput = document.getElementById("jsonOutput-add");
const jsonResultDiv = document.getElementById("json-result-add");
if (jsonOutput) {
const innerJson = getInnerJsonString(formatted);
jsonOutput.innerHTML = syntaxHighlight(innerJson);
jsonOutput.style.display = "";
if (jsonResultDiv) {
jsonResultDiv.style.display = "block";
}
}
const copyBtn = document.getElementById("copyButton-add");
if (copyBtn && jsonOutput) {
copyBtn.onclick = function () {
navigator.clipboard.writeText(jsonOutput.textContent);
copyBtn.textContent = "Copié !";
setTimeout(() => (copyBtn.textContent = "Copier le JSON"), 1500);
};
}
});