diff --git a/desktop/src/api/config/config.controller.ts b/desktop/src/api/config/config.controller.ts index 4788a2b..7bdaf37 100644 --- a/desktop/src/api/config/config.controller.ts +++ b/desktop/src/api/config/config.controller.ts @@ -1,43 +1,53 @@ -import {FastifyReply, FastifyRequest} from 'fastify'; -import {getConfig, setConfig} from '../../shared/config'; +import { FastifyReply, FastifyRequest } from 'fastify'; +import { getConfig, setConfig } from '../../shared/config'; export async function getFullConfig(req: FastifyRequest, reply: FastifyReply) { try { - return getConfig(); - } catch (err) { + const { values, schema } = getConfig(); + return { values, schema }; + } catch { return { error: "Error loading config" }; } } -export async function getConfigSection(req: FastifyRequest<{ Params: { section: string } }>, reply: FastifyReply) { +export async function getConfigSection( + req: FastifyRequest<{ Params: { section: string } }>, + reply: FastifyReply +) { try { const { section } = req.params; - const config = getConfig(); + const { values } = getConfig(); - if (config[section] === undefined) { + if (values[section] === undefined) { return { error: "Section not found" }; } - return { [section]: config[section] }; - } catch (err) { + return { [section]: values[section] }; + } catch { return { error: "Error loading config section" }; } } -export async function updateConfig(req: FastifyRequest<{ Body: any }>, reply: FastifyReply) { +export async function updateConfig( + req: FastifyRequest<{ Body: any }>, + reply: FastifyReply +) { try { - return setConfig(req.body); - } catch (err) { + return setConfig(req.body); // schema nunca se toca + } catch { return { error: "Error updating config" }; } } -export async function updateConfigSection(req: FastifyRequest<{ Params: { section: string }, Body: any }>, reply: FastifyReply) { +export async function updateConfigSection( + req: FastifyRequest<{ Params: { section: string }, Body: any }>, + reply: FastifyReply +) { try { const { section } = req.params; - const updatedConfig = setConfig({ [section]: req.body }); - return { [section]: updatedConfig[section] }; - } catch (err) { + const updatedValues = setConfig({ [section]: req.body }); + return { [section]: updatedValues[section] }; + } catch { return { error: "Error updating config section" }; } } \ No newline at end of file diff --git a/desktop/src/api/local/download.service.ts b/desktop/src/api/local/download.service.ts index b2f2a18..c5f6534 100644 --- a/desktop/src/api/local/download.service.ts +++ b/desktop/src/api/local/download.service.ts @@ -70,8 +70,8 @@ async function getOrCreateEntry( throw new Error('METADATA_NOT_FOUND'); } - const config = loadConfig(); - const basePath = config.library?.[type]; + const { values } = loadConfig(); + const basePath = values.library?.[type]; if (!basePath) { throw new Error(`NO_LIBRARY_PATH_FOR_${type.toUpperCase()}`); diff --git a/desktop/src/api/local/local.service.ts b/desktop/src/api/local/local.service.ts index 1cddfd4..4384b21 100644 --- a/desktop/src/api/local/local.service.ts +++ b/desktop/src/api/local/local.service.ts @@ -127,9 +127,9 @@ export async function resolveEntryMetadata(entry: any, type: string) { } export async function performLibraryScan(mode: 'full' | 'incremental' = 'incremental') { - const config = loadConfig(); + const { values } = loadConfig(); - if (!config.library) { + if (!values.library) { throw new Error('NO_LIBRARY_CONFIGURED'); } @@ -138,7 +138,7 @@ export async function performLibraryScan(mode: 'full' | 'incremental' = 'increme await run(`DELETE FROM local_entries`, [], 'local_library'); } - for (const [type, basePath] of Object.entries(config.library)) { + for (const [type, basePath] of Object.entries(values.library)) { if (!basePath || !fs.existsSync(basePath)) continue; const dirs = fs.readdirSync(basePath, { withFileTypes: true }).filter(d => d.isDirectory()); diff --git a/desktop/src/scripts/settings.js b/desktop/src/scripts/settings.js index d1bb295..c160c61 100644 --- a/desktop/src/scripts/settings.js +++ b/desktop/src/scripts/settings.js @@ -1,5 +1,6 @@ const API_BASE = '/api/config'; let currentConfig = {}; +let currentSchema = {}; let activeSection = ''; let modal, navContainer, formContent, form; @@ -9,96 +10,74 @@ window.toggleSettingsModal = async (forceClose = false) => { formContent = document.getElementById('config-section-content'); form = document.getElementById('config-form'); - if (!modal) { - console.error('Modal not found'); - return; - } + if (!modal) return; if (forceClose) { modal.classList.add('hidden'); } else { const isHidden = modal.classList.contains('hidden'); - if (isHidden) { - // Abrir modal modal.classList.remove('hidden'); await loadSettings(); } else { - // Cerrar modal modal.classList.add('hidden'); } } }; async function loadSettings() { - if (!formContent) { - console.error('Form content not found'); - return; - } + if (!formContent) return; - // Mostrar loading + // Loading State formContent.innerHTML = `
-
-
+
+
`; try { const res = await fetch(API_BASE); - - if (!res.ok) { - throw new Error(`HTTP error! status: ${res.status}`); - } + if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`); const data = await res.json(); - if (data.error) throw new Error(data.error); - currentConfig = data; + // --- CORRECCIÓN AQUI --- + // Tu JSON devuelve "values", el código original buscaba "config" + currentConfig = data.values || data.config || data; + currentSchema = data.schema || {}; + renderNav(); - // Seleccionar la primera sección si no hay ninguna activa if (!activeSection || !currentConfig[activeSection]) { activeSection = Object.keys(currentConfig)[0]; } switchSection(activeSection); + } catch (err) { console.error('Error loading settings:', err); formContent.innerHTML = `
-

Failed to load settings

-

${err.message}

+

Failed to load settings

+

${err.message}

`; } } -function renderNav() { - if (!navContainer) return; - - navContainer.innerHTML = ''; - Object.keys(currentConfig).forEach(section => { - const btn = document.createElement('div'); - btn.className = `nav-item ${section === activeSection ? 'active' : ''}`; - btn.textContent = section; - btn.onclick = () => switchSection(section); - navContainer.appendChild(btn); - }); -} - function switchSection(section) { if (!currentConfig[section]) return; - activeSection = section; renderNav(); const sectionData = currentConfig[section]; + const sectionSchema = currentSchema[section] || {}; formContent.innerHTML = ` -

+

${section.replace(/_/g, ' ')}

`; @@ -109,21 +88,34 @@ function switchSection(section) { const isBool = typeof value === 'boolean'; const inputId = `input-${section}-${key}`; - const label = key.replace(/_/g, ' '); + const labelText = key.replace(/_/g, ' '); + + // Obtener descripción + const description = sectionSchema[key]?.description || ''; + const descHtml = description + ? `

${description}

` + : ''; if (isBool) { group.innerHTML = ` -
- - -
+
+
+ + +
+ ${descHtml}
`; } else { + // --- CAMBIO PRINCIPAL AQUI --- + // Movimos ${descHtml} para que esté DESPUÉS del input group.innerHTML = ` - + + value="${value || ''}" + placeholder="Not set"> + ${descHtml} `; } @@ -131,9 +123,27 @@ function switchSection(section) { }); } -// Setup form submit handler +function renderNav() { + if (!navContainer) return; + navContainer.innerHTML = ''; + + Object.keys(currentConfig).forEach(section => { + const btn = document.createElement('div'); + btn.className = `nav-item ${section === activeSection ? 'active' : ''}`; + + // Icono opcional según la sección (puedes personalizar esto) + let icon = ''; + if(section === 'library') icon = ''; + if(section === 'paths') icon = ''; + + btn.innerHTML = `${icon} ${section}`; + btn.onclick = () => switchSection(section); + navContainer.appendChild(btn); + }); +} + +// Handler de guardado document.addEventListener('DOMContentLoaded', () => { - // Usar delegación de eventos ya que el form se carga dinámicamente document.addEventListener('submit', async (e) => { if (e.target.id === 'config-form') { e.preventDefault(); @@ -146,8 +156,9 @@ async function saveSettings() { if (!form || !activeSection) return; const updatedData = {}; + const sectionConfig = currentConfig[activeSection]; - Object.keys(currentConfig[activeSection]).forEach(key => { + Object.keys(sectionConfig).forEach(key => { const input = form.elements[key]; if (!input) return; @@ -168,51 +179,35 @@ async function saveSettings() { }); if (res.ok) { - currentConfig[activeSection] = updatedData; - - // Mostrar notificación de éxito - const notification = document.createElement('div'); - notification.style.cssText = ` - position: fixed; - top: 20px; - right: 20px; - background: var(--color-success, #10b981); - color: white; - padding: 1rem 1.5rem; - border-radius: 8px; - box-shadow: 0 4px 12px rgba(0,0,0,0.3); - z-index: 10000; - animation: slideIn 0.3s ease-out; - `; - notification.textContent = 'Settings saved successfully!'; - document.body.appendChild(notification); - - setTimeout(() => { - notification.style.animation = 'slideOut 0.3s ease-out'; - setTimeout(() => notification.remove(), 300); - }, 2000); + currentConfig[activeSection] = updatedData; // Actualizamos localmente + showNotification('Settings saved successfully!'); } else { throw new Error('Failed to save settings'); } } catch (err) { - console.error('Error saving settings:', err); - alert('Error saving settings: ' + err.message); + console.error(err); + showNotification('Error saving settings', true); } } -// Añadir estilos para las animaciones (solo si no existen) -if (!document.getElementById('settings-animations')) { - const animationStyles = document.createElement('style'); - animationStyles.id = 'settings-animations'; - animationStyles.textContent = ` - @keyframes slideIn { - from { transform: translateX(400px); opacity: 0; } - to { transform: translateX(0); opacity: 1; } - } - @keyframes slideOut { - from { transform: translateX(0); opacity: 1; } - to { transform: translateX(400px); opacity: 0; } - } +function showNotification(msg, isError = false) { + const notification = document.createElement('div'); + const bg = isError ? '#ef4444' : '#10b981'; + + notification.style.cssText = ` + position: fixed; top: 20px; right: 20px; + background: ${bg}; color: white; + padding: 1rem 1.5rem; border-radius: 8px; font-weight: 600; + box-shadow: 0 10px 30px rgba(0,0,0,0.5); z-index: 20000; + animation: slideIn 0.3s ease-out; display: flex; align-items: center; gap: 10px; `; - document.head.appendChild(animationStyles); + notification.innerHTML = isError + ? ` ${msg}` + : ` ${msg}`; + + document.body.appendChild(notification); + setTimeout(() => { + notification.style.animation = 'slideOut 0.3s ease-out'; + setTimeout(() => notification.remove(), 300); + }, 3000); } \ No newline at end of file diff --git a/desktop/src/shared/config.js b/desktop/src/shared/config.js index b42dd29..b207231 100644 --- a/desktop/src/shared/config.js +++ b/desktop/src/shared/config.js @@ -11,6 +11,22 @@ const DEFAULT_CONFIG = { anime: null, manga: null, novels: null + }, + paths: { + mpv: null, + ffmpeg: null + } +}; + +export const CONFIG_SCHEMA = { + library: { + anime: { description: "Path where anime is stored" }, + manga: { description: "Path where manga is stored" }, + novels: { description: "Path where novels are stored" } + }, + paths: { + mpv: { description: "Required to open anime episodes in mpv on desktop version." }, + ffmpeg: { description: "Required for downloading anime episodes." } } }; @@ -31,7 +47,12 @@ function ensureConfigFile() { export function getConfig() { ensureConfigFile(); const raw = fs.readFileSync(CONFIG_PATH, 'utf8'); - return yaml.load(raw) || DEFAULT_CONFIG; + const loaded = yaml.load(raw) || {}; + + return { + values: deepMerge(structuredClone(DEFAULT_CONFIG), loaded), + schema: CONFIG_SCHEMA + }; } export function setConfig(partialConfig) { diff --git a/desktop/views/books/books.html b/desktop/views/books/books.html index 51c55d6..86ca8ce 100644 --- a/desktop/views/books/books.html +++ b/desktop/views/books/books.html @@ -109,5 +109,6 @@ + \ No newline at end of file diff --git a/desktop/views/components/settings-modal.html b/desktop/views/components/settings-modal.html index 3dae9fa..b354cf9 100644 --- a/desktop/views/components/settings-modal.html +++ b/desktop/views/components/settings-modal.html @@ -42,16 +42,15 @@ \ No newline at end of file diff --git a/desktop/views/marketplace.html b/desktop/views/marketplace.html index 0d71bc9..2358e10 100644 --- a/desktop/views/marketplace.html +++ b/desktop/views/marketplace.html @@ -71,5 +71,6 @@ + \ No newline at end of file diff --git a/desktop/views/profile.html b/desktop/views/profile.html index 69babe1..ce3e058 100644 --- a/desktop/views/profile.html +++ b/desktop/views/profile.html @@ -207,5 +207,6 @@ + \ No newline at end of file diff --git a/desktop/views/schedule.html b/desktop/views/schedule.html index 6a755f5..da17df8 100644 --- a/desktop/views/schedule.html +++ b/desktop/views/schedule.html @@ -92,5 +92,6 @@ + \ No newline at end of file diff --git a/docker/src/api/config/config.controller.ts b/docker/src/api/config/config.controller.ts index 4788a2b..7bdaf37 100644 --- a/docker/src/api/config/config.controller.ts +++ b/docker/src/api/config/config.controller.ts @@ -1,43 +1,53 @@ -import {FastifyReply, FastifyRequest} from 'fastify'; -import {getConfig, setConfig} from '../../shared/config'; +import { FastifyReply, FastifyRequest } from 'fastify'; +import { getConfig, setConfig } from '../../shared/config'; export async function getFullConfig(req: FastifyRequest, reply: FastifyReply) { try { - return getConfig(); - } catch (err) { + const { values, schema } = getConfig(); + return { values, schema }; + } catch { return { error: "Error loading config" }; } } -export async function getConfigSection(req: FastifyRequest<{ Params: { section: string } }>, reply: FastifyReply) { +export async function getConfigSection( + req: FastifyRequest<{ Params: { section: string } }>, + reply: FastifyReply +) { try { const { section } = req.params; - const config = getConfig(); + const { values } = getConfig(); - if (config[section] === undefined) { + if (values[section] === undefined) { return { error: "Section not found" }; } - return { [section]: config[section] }; - } catch (err) { + return { [section]: values[section] }; + } catch { return { error: "Error loading config section" }; } } -export async function updateConfig(req: FastifyRequest<{ Body: any }>, reply: FastifyReply) { +export async function updateConfig( + req: FastifyRequest<{ Body: any }>, + reply: FastifyReply +) { try { - return setConfig(req.body); - } catch (err) { + return setConfig(req.body); // schema nunca se toca + } catch { return { error: "Error updating config" }; } } -export async function updateConfigSection(req: FastifyRequest<{ Params: { section: string }, Body: any }>, reply: FastifyReply) { +export async function updateConfigSection( + req: FastifyRequest<{ Params: { section: string }, Body: any }>, + reply: FastifyReply +) { try { const { section } = req.params; - const updatedConfig = setConfig({ [section]: req.body }); - return { [section]: updatedConfig[section] }; - } catch (err) { + const updatedValues = setConfig({ [section]: req.body }); + return { [section]: updatedValues[section] }; + } catch { return { error: "Error updating config section" }; } } \ No newline at end of file diff --git a/docker/src/api/local/download.service.ts b/docker/src/api/local/download.service.ts index b2f2a18..c5f6534 100644 --- a/docker/src/api/local/download.service.ts +++ b/docker/src/api/local/download.service.ts @@ -70,8 +70,8 @@ async function getOrCreateEntry( throw new Error('METADATA_NOT_FOUND'); } - const config = loadConfig(); - const basePath = config.library?.[type]; + const { values } = loadConfig(); + const basePath = values.library?.[type]; if (!basePath) { throw new Error(`NO_LIBRARY_PATH_FOR_${type.toUpperCase()}`); diff --git a/docker/src/api/local/local.service.ts b/docker/src/api/local/local.service.ts index 1cddfd4..4384b21 100644 --- a/docker/src/api/local/local.service.ts +++ b/docker/src/api/local/local.service.ts @@ -127,9 +127,9 @@ export async function resolveEntryMetadata(entry: any, type: string) { } export async function performLibraryScan(mode: 'full' | 'incremental' = 'incremental') { - const config = loadConfig(); + const { values } = loadConfig(); - if (!config.library) { + if (!values.library) { throw new Error('NO_LIBRARY_CONFIGURED'); } @@ -138,7 +138,7 @@ export async function performLibraryScan(mode: 'full' | 'incremental' = 'increme await run(`DELETE FROM local_entries`, [], 'local_library'); } - for (const [type, basePath] of Object.entries(config.library)) { + for (const [type, basePath] of Object.entries(values.library)) { if (!basePath || !fs.existsSync(basePath)) continue; const dirs = fs.readdirSync(basePath, { withFileTypes: true }).filter(d => d.isDirectory()); diff --git a/docker/src/scripts/settings.js b/docker/src/scripts/settings.js index d1bb295..c160c61 100644 --- a/docker/src/scripts/settings.js +++ b/docker/src/scripts/settings.js @@ -1,5 +1,6 @@ const API_BASE = '/api/config'; let currentConfig = {}; +let currentSchema = {}; let activeSection = ''; let modal, navContainer, formContent, form; @@ -9,96 +10,74 @@ window.toggleSettingsModal = async (forceClose = false) => { formContent = document.getElementById('config-section-content'); form = document.getElementById('config-form'); - if (!modal) { - console.error('Modal not found'); - return; - } + if (!modal) return; if (forceClose) { modal.classList.add('hidden'); } else { const isHidden = modal.classList.contains('hidden'); - if (isHidden) { - // Abrir modal modal.classList.remove('hidden'); await loadSettings(); } else { - // Cerrar modal modal.classList.add('hidden'); } } }; async function loadSettings() { - if (!formContent) { - console.error('Form content not found'); - return; - } + if (!formContent) return; - // Mostrar loading + // Loading State formContent.innerHTML = `
-
-
+
+
`; try { const res = await fetch(API_BASE); - - if (!res.ok) { - throw new Error(`HTTP error! status: ${res.status}`); - } + if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`); const data = await res.json(); - if (data.error) throw new Error(data.error); - currentConfig = data; + // --- CORRECCIÓN AQUI --- + // Tu JSON devuelve "values", el código original buscaba "config" + currentConfig = data.values || data.config || data; + currentSchema = data.schema || {}; + renderNav(); - // Seleccionar la primera sección si no hay ninguna activa if (!activeSection || !currentConfig[activeSection]) { activeSection = Object.keys(currentConfig)[0]; } switchSection(activeSection); + } catch (err) { console.error('Error loading settings:', err); formContent.innerHTML = `
-

Failed to load settings

-

${err.message}

+

Failed to load settings

+

${err.message}

`; } } -function renderNav() { - if (!navContainer) return; - - navContainer.innerHTML = ''; - Object.keys(currentConfig).forEach(section => { - const btn = document.createElement('div'); - btn.className = `nav-item ${section === activeSection ? 'active' : ''}`; - btn.textContent = section; - btn.onclick = () => switchSection(section); - navContainer.appendChild(btn); - }); -} - function switchSection(section) { if (!currentConfig[section]) return; - activeSection = section; renderNav(); const sectionData = currentConfig[section]; + const sectionSchema = currentSchema[section] || {}; formContent.innerHTML = ` -

+

${section.replace(/_/g, ' ')}

`; @@ -109,21 +88,34 @@ function switchSection(section) { const isBool = typeof value === 'boolean'; const inputId = `input-${section}-${key}`; - const label = key.replace(/_/g, ' '); + const labelText = key.replace(/_/g, ' '); + + // Obtener descripción + const description = sectionSchema[key]?.description || ''; + const descHtml = description + ? `

${description}

` + : ''; if (isBool) { group.innerHTML = ` -
- - -
+
+
+ + +
+ ${descHtml}
`; } else { + // --- CAMBIO PRINCIPAL AQUI --- + // Movimos ${descHtml} para que esté DESPUÉS del input group.innerHTML = ` - + + value="${value || ''}" + placeholder="Not set"> + ${descHtml} `; } @@ -131,9 +123,27 @@ function switchSection(section) { }); } -// Setup form submit handler +function renderNav() { + if (!navContainer) return; + navContainer.innerHTML = ''; + + Object.keys(currentConfig).forEach(section => { + const btn = document.createElement('div'); + btn.className = `nav-item ${section === activeSection ? 'active' : ''}`; + + // Icono opcional según la sección (puedes personalizar esto) + let icon = ''; + if(section === 'library') icon = ''; + if(section === 'paths') icon = ''; + + btn.innerHTML = `${icon} ${section}`; + btn.onclick = () => switchSection(section); + navContainer.appendChild(btn); + }); +} + +// Handler de guardado document.addEventListener('DOMContentLoaded', () => { - // Usar delegación de eventos ya que el form se carga dinámicamente document.addEventListener('submit', async (e) => { if (e.target.id === 'config-form') { e.preventDefault(); @@ -146,8 +156,9 @@ async function saveSettings() { if (!form || !activeSection) return; const updatedData = {}; + const sectionConfig = currentConfig[activeSection]; - Object.keys(currentConfig[activeSection]).forEach(key => { + Object.keys(sectionConfig).forEach(key => { const input = form.elements[key]; if (!input) return; @@ -168,51 +179,35 @@ async function saveSettings() { }); if (res.ok) { - currentConfig[activeSection] = updatedData; - - // Mostrar notificación de éxito - const notification = document.createElement('div'); - notification.style.cssText = ` - position: fixed; - top: 20px; - right: 20px; - background: var(--color-success, #10b981); - color: white; - padding: 1rem 1.5rem; - border-radius: 8px; - box-shadow: 0 4px 12px rgba(0,0,0,0.3); - z-index: 10000; - animation: slideIn 0.3s ease-out; - `; - notification.textContent = 'Settings saved successfully!'; - document.body.appendChild(notification); - - setTimeout(() => { - notification.style.animation = 'slideOut 0.3s ease-out'; - setTimeout(() => notification.remove(), 300); - }, 2000); + currentConfig[activeSection] = updatedData; // Actualizamos localmente + showNotification('Settings saved successfully!'); } else { throw new Error('Failed to save settings'); } } catch (err) { - console.error('Error saving settings:', err); - alert('Error saving settings: ' + err.message); + console.error(err); + showNotification('Error saving settings', true); } } -// Añadir estilos para las animaciones (solo si no existen) -if (!document.getElementById('settings-animations')) { - const animationStyles = document.createElement('style'); - animationStyles.id = 'settings-animations'; - animationStyles.textContent = ` - @keyframes slideIn { - from { transform: translateX(400px); opacity: 0; } - to { transform: translateX(0); opacity: 1; } - } - @keyframes slideOut { - from { transform: translateX(0); opacity: 1; } - to { transform: translateX(400px); opacity: 0; } - } +function showNotification(msg, isError = false) { + const notification = document.createElement('div'); + const bg = isError ? '#ef4444' : '#10b981'; + + notification.style.cssText = ` + position: fixed; top: 20px; right: 20px; + background: ${bg}; color: white; + padding: 1rem 1.5rem; border-radius: 8px; font-weight: 600; + box-shadow: 0 10px 30px rgba(0,0,0,0.5); z-index: 20000; + animation: slideIn 0.3s ease-out; display: flex; align-items: center; gap: 10px; `; - document.head.appendChild(animationStyles); + notification.innerHTML = isError + ? ` ${msg}` + : ` ${msg}`; + + document.body.appendChild(notification); + setTimeout(() => { + notification.style.animation = 'slideOut 0.3s ease-out'; + setTimeout(() => notification.remove(), 300); + }, 3000); } \ No newline at end of file diff --git a/docker/src/shared/config.js b/docker/src/shared/config.js index b42dd29..b207231 100644 --- a/docker/src/shared/config.js +++ b/docker/src/shared/config.js @@ -11,6 +11,22 @@ const DEFAULT_CONFIG = { anime: null, manga: null, novels: null + }, + paths: { + mpv: null, + ffmpeg: null + } +}; + +export const CONFIG_SCHEMA = { + library: { + anime: { description: "Path where anime is stored" }, + manga: { description: "Path where manga is stored" }, + novels: { description: "Path where novels are stored" } + }, + paths: { + mpv: { description: "Required to open anime episodes in mpv on desktop version." }, + ffmpeg: { description: "Required for downloading anime episodes." } } }; @@ -31,7 +47,12 @@ function ensureConfigFile() { export function getConfig() { ensureConfigFile(); const raw = fs.readFileSync(CONFIG_PATH, 'utf8'); - return yaml.load(raw) || DEFAULT_CONFIG; + const loaded = yaml.load(raw) || {}; + + return { + values: deepMerge(structuredClone(DEFAULT_CONFIG), loaded), + schema: CONFIG_SCHEMA + }; } export function setConfig(partialConfig) { diff --git a/docker/views/anime/animes.html b/docker/views/anime/animes.html index efc15c8..73b23b4 100644 --- a/docker/views/anime/animes.html +++ b/docker/views/anime/animes.html @@ -9,7 +9,6 @@ - @@ -119,7 +118,6 @@ - diff --git a/docker/views/books/books.html b/docker/views/books/books.html index 1af5b21..ba28db8 100644 --- a/docker/views/books/books.html +++ b/docker/views/books/books.html @@ -89,5 +89,6 @@ + \ No newline at end of file diff --git a/docker/views/components/settings-modal.html b/docker/views/components/settings-modal.html index 3dae9fa..b354cf9 100644 --- a/docker/views/components/settings-modal.html +++ b/docker/views/components/settings-modal.html @@ -42,16 +42,15 @@ \ No newline at end of file diff --git a/docker/views/marketplace.html b/docker/views/marketplace.html index f3dfd75..b833f55 100644 --- a/docker/views/marketplace.html +++ b/docker/views/marketplace.html @@ -57,5 +57,6 @@ + \ No newline at end of file diff --git a/docker/views/profile.html b/docker/views/profile.html index 58e95c4..bf8e775 100644 --- a/docker/views/profile.html +++ b/docker/views/profile.html @@ -194,5 +194,6 @@ + \ No newline at end of file diff --git a/docker/views/schedule.html b/docker/views/schedule.html index 6b0f75a..050448e 100644 --- a/docker/views/schedule.html +++ b/docker/views/schedule.html @@ -78,5 +78,6 @@ + \ No newline at end of file