added updateall btn to marketplace

This commit is contained in:
2025-12-20 00:13:10 +01:00
parent a26f03f024
commit 90231f6608
12 changed files with 340 additions and 152 deletions

View File

@@ -1,6 +1,59 @@
import { FastifyReply, FastifyRequest } from 'fastify'; import { FastifyReply, FastifyRequest } from 'fastify';
import { getExtension, getExtensionsList, getGalleryExtensionsMap, getBookExtensionsMap, getAnimeExtensionsMap, saveExtensionFile, deleteExtensionFile } from '../../shared/extensions'; import { getExtension, getExtensionsList, getGalleryExtensionsMap, getBookExtensionsMap, getAnimeExtensionsMap, saveExtensionFile, deleteExtensionFile } from '../../shared/extensions';
import { ExtensionNameRequest } from '../types'; import { ExtensionNameRequest } from '../types';
import path from 'path';
import fs from 'fs/promises';
const TYPE_MAP: Record<string, string> = {
'anime-board': 'anime',
'image-board': 'image',
'book-board': 'book',
};
function extractProp(source: string, prop: string): string | null {
const m = source.match(new RegExp(`this\\.${prop}\\s*=\\s*["']([^"']+)["']`));
return m ? m[1] : null;
}
function isNewer(remote: string, local?: string | null) {
if (!local) return true;
return remote !== local;
}
export async function updateExtensions(req: any, reply: FastifyReply) {
const updated: string[] = [];
for (const name of getExtensionsList()) {
const ext = getExtension(name);
if (!ext) continue;
const type = ext.type;
if (!TYPE_MAP[type]) continue;
const fileName = ext.__fileName;
const remoteUrl = `https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/${TYPE_MAP[type]}/${fileName}`;
let remoteSrc: string;
try {
const res = await fetch(remoteUrl);
if (!res.ok) continue;
remoteSrc = await res.text();
} catch {
continue;
}
const remoteVersion = extractProp(remoteSrc, 'version');
const localVersion = ext.version ?? null;
if (!remoteVersion) continue;
if (isNewer(remoteVersion, localVersion)) {
await saveExtensionFile(fileName, remoteUrl);
updated.push(name);
}
}
return { updated };
}
export async function getExtensions(req: FastifyRequest, reply: FastifyReply) { export async function getExtensions(req: FastifyRequest, reply: FastifyReply) {
return { extensions: getExtensionsList() }; return { extensions: getExtensionsList() };

View File

@@ -4,6 +4,7 @@ import * as controller from './extensions.controller';
async function extensionsRoutes(fastify: FastifyInstance) { async function extensionsRoutes(fastify: FastifyInstance) {
fastify.get('/extensions', controller.getExtensions); fastify.get('/extensions', controller.getExtensions);
fastify.get('/extensions/anime', controller.getAnimeExtensions); fastify.get('/extensions/anime', controller.getAnimeExtensions);
fastify.post('/extensions/update', controller.updateExtensions);
fastify.get('/extensions/book', controller.getBookExtensions); fastify.get('/extensions/book', controller.getBookExtensions);
fastify.get('/extensions/gallery', controller.getGalleryExtensions); fastify.get('/extensions/gallery', controller.getGalleryExtensions);
fastify.get('/extensions/:name/settings', controller.getExtensionSettings); fastify.get('/extensions/:name/settings', controller.getExtensionSettings);

View File

@@ -1,9 +1,11 @@
const ORIGINAL_MARKETPLACE_URL = 'https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/marketplace.json'; const ORIGINAL_MARKETPLACE_URL = 'https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/marketplace.json';
const MARKETPLACE_JSON_URL = `/api/proxy?url=${encodeURIComponent(ORIGINAL_MARKETPLACE_URL)}`; const MARKETPLACE_JSON_URL = `/api/proxy?url=${encodeURIComponent(ORIGINAL_MARKETPLACE_URL)}`;
const INSTALLED_EXTENSIONS_API = '/api/extensions'; const INSTALLED_EXTENSIONS_API = '/api/extensions';
const UPDATE_EXTENSIONS_API = '/api/extensions/update';
const marketplaceContent = document.getElementById('marketplace-content'); const marketplaceContent = document.getElementById('marketplace-content');
const filterSelect = document.getElementById('extension-filter'); const filterSelect = document.getElementById('extension-filter');
const updateAllBtn = document.getElementById('btn-update-all');
const modal = document.getElementById('customModal'); const modal = document.getElementById('customModal');
const modalTitle = document.getElementById('modalTitle'); const modalTitle = document.getElementById('modalTitle');
@@ -32,6 +34,10 @@ async function loadMarketplace() {
if (filterSelect) { if (filterSelect) {
filterSelect.addEventListener('change', () => renderGroupedView()); filterSelect.addEventListener('change', () => renderGroupedView());
} }
if (updateAllBtn) {
updateAllBtn.onclick = handleUpdateAll;
}
} catch (error) { } catch (error) {
console.error('Error loading marketplace:', error); console.error('Error loading marketplace:', error);
marketplaceContent.innerHTML = `<div class="error-msg">Error al cargar el marketplace.</div>`; marketplaceContent.innerHTML = `<div class="error-msg">Error al cargar el marketplace.</div>`;
@@ -45,11 +51,49 @@ function initTabs() {
tabs.forEach(t => t.classList.remove('active')); tabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active'); tab.classList.add('active');
currentTab = tab.dataset.tab; currentTab = tab.dataset.tab;
if (updateAllBtn) {
if (currentTab === 'installed') {
updateAllBtn.classList.remove('hidden');
} else {
updateAllBtn.classList.add('hidden');
}
}
renderGroupedView(); renderGroupedView();
}; };
}); });
} }
async function handleUpdateAll() {
const originalText = updateAllBtn.innerText;
try {
updateAllBtn.disabled = true;
updateAllBtn.innerText = 'Updating...';
const res = await fetch(UPDATE_EXTENSIONS_API, { method: 'POST' });
if (!res.ok) throw new Error('Update failed');
const data = await res.json();
if (data.updated && data.updated.length > 0) {
const list = data.updated.join(', ');
window.NotificationUtils.success(`Updated: ${list}`);
await loadMarketplace();
} else {
window.NotificationUtils.info('Everything is up to date.');
}
} catch (error) {
console.error('Update All Error:', error);
window.NotificationUtils.error('Failed to perform bulk update.');
} finally {
updateAllBtn.disabled = false;
updateAllBtn.innerText = originalText;
}
}
function renderGroupedView() { function renderGroupedView() {
marketplaceContent.innerHTML = ''; marketplaceContent.innerHTML = '';
const activeFilter = filterSelect.value; const activeFilter = filterSelect.value;
@@ -58,7 +102,6 @@ function renderGroupedView() {
let listToRender = []; let listToRender = [];
if (currentTab === 'marketplace') { if (currentTab === 'marketplace') {
for (const [id, data] of Object.entries(marketplaceMetadata)) { for (const [id, data] of Object.entries(marketplaceMetadata)) {
listToRender.push({ listToRender.push({
id, id,
@@ -67,7 +110,6 @@ function renderGroupedView() {
}); });
} }
} else { } else {
for (const [id, data] of Object.entries(marketplaceMetadata)) { for (const [id, data] of Object.entries(marketplaceMetadata)) {
if (installedExtensions.includes(id.toLowerCase())) { if (installedExtensions.includes(id.toLowerCase())) {
listToRender.push({ id, ...data, isInstalled: true }); listToRender.push({ id, ...data, isInstalled: true });
@@ -90,9 +132,7 @@ function renderGroupedView() {
listToRender.forEach(ext => { listToRender.forEach(ext => {
const type = ext.type || 'Other'; const type = ext.type || 'Other';
if (activeFilter !== 'All' && type !== activeFilter) return; if (activeFilter !== 'All' && type !== activeFilter) return;
if (!groups[type]) groups[type] = []; if (!groups[type]) groups[type] = [];
groups[type].push(ext); groups[type].push(ext);
}); });
@@ -127,7 +167,7 @@ function createCard(ext) {
const card = document.createElement('div'); const card = document.createElement('div');
card.className = `extension-card ${ext.nsfw ? 'nsfw-ext' : ''} ${ext.broken ? 'broken-ext' : ''}`; card.className = `extension-card ${ext.nsfw ? 'nsfw-ext' : ''} ${ext.broken ? 'broken-ext' : ''}`;
const iconUrl = `https://www.google.com/s2/favicons?domain=${ext.domain}&sz=128` const iconUrl = `https://www.google.com/s2/favicons?domain=${ext.domain}&sz=128`;
let buttonHtml = ''; let buttonHtml = '';
if (ext.isInstalled) { if (ext.isInstalled) {
@@ -187,9 +227,9 @@ async function handleInstall(ext) {
if (res.ok) { if (res.ok) {
installedExtensions.push(ext.id.toLowerCase()); installedExtensions.push(ext.id.toLowerCase());
renderGroupedView(); renderGroupedView();
showModal('Success', `${ext.name} installed!`); window.NotificationUtils.success(`${ext.name} installed!`);
} }
} catch (e) { showModal('Error', 'Install failed.'); } } catch (e) { window.NotificationUtils.error('Install failed.'); }
} }
function promptUninstall(ext) { function promptUninstall(ext) {
@@ -206,8 +246,9 @@ async function handleUninstall(ext) {
if (res.ok) { if (res.ok) {
installedExtensions = installedExtensions.filter(id => id !== ext.id.toLowerCase()); installedExtensions = installedExtensions.filter(id => id !== ext.id.toLowerCase());
renderGroupedView(); renderGroupedView();
window.NotificationUtils.info(`${ext.name} uninstalled.`);
} }
} catch (e) { showModal('Error', 'Uninstall failed.'); } } catch (e) { window.NotificationUtils.error('Uninstall failed.'); }
} }
function showSkeletons() { function showSkeletons() {

View File

@@ -46,7 +46,6 @@ async function loadExtensions() {
} }
} }
async function loadExtension(fileName) { async function loadExtension(fileName) {
const homeDir = os.homedir(); const homeDir = os.homedir();
const extensionsDir = path.join(homeDir, 'WaifuBoards', 'extensions'); const extensionsDir = path.join(homeDir, 'WaifuBoards', 'extensions');
@@ -77,6 +76,7 @@ async function loadExtension(fileName) {
} }
const name = instance.constructor.name; const name = instance.constructor.name;
instance.__fileName = fileName;
instance.scrape = scrape; instance.scrape = scrape;
instance.cheerio = cheerio; instance.cheerio = cheerio;
extensions.set(name, instance); extensions.set(name, instance);
@@ -114,6 +114,14 @@ async function saveExtensionFile(fileName, downloadUrl) {
file.on('finish', async () => { file.on('finish', async () => {
file.close(async () => { file.close(async () => {
try { try {
const extName = fileName.replace('.js', '');
for (const key of extensions.keys()) {
if (key.toLowerCase() === extName.toLowerCase()) {
extensions.delete(key);
break;
}
}
await loadExtension(fileName); await loadExtension(fileName);
resolve(); resolve();
} catch (err) { } catch (err) {

View File

@@ -97,6 +97,9 @@
</div> </div>
<div class="filter-controls"> <div class="filter-controls">
<button id="btn-update-all" class="btn-blur hidden" style="margin-right: 10px; width: auto; padding: 0 15px;">
Update All
</button>
<label for="extension-filter" class="filter-label">Filter by Type:</label> <label for="extension-filter" class="filter-label">Filter by Type:</label>
<select id="extension-filter" class="filter-select"> <select id="extension-filter" class="filter-select">
<option value="All">All Categories</option> <option value="All">All Categories</option>
@@ -124,6 +127,7 @@
</div> </div>
</main> </main>
<script src="/src/scripts/utils/notification-utils.js"></script>
<script src="/src/scripts/updateNotifier.js"></script> <script src="/src/scripts/updateNotifier.js"></script>
<script src="/src/scripts/marketplace.js"></script> <script src="/src/scripts/marketplace.js"></script>
<script src="/src/scripts/titlebar.js"></script> <script src="/src/scripts/titlebar.js"></script>

View File

@@ -1,6 +1,59 @@
import { FastifyReply, FastifyRequest } from 'fastify'; import { FastifyReply, FastifyRequest } from 'fastify';
import { getExtension, getExtensionsList, getGalleryExtensionsMap, getBookExtensionsMap, getAnimeExtensionsMap, saveExtensionFile, deleteExtensionFile } from '../../shared/extensions'; import { getExtension, getExtensionsList, getGalleryExtensionsMap, getBookExtensionsMap, getAnimeExtensionsMap, saveExtensionFile, deleteExtensionFile } from '../../shared/extensions';
import { ExtensionNameRequest } from '../types'; import { ExtensionNameRequest } from '../types';
import path from 'path';
import fs from 'fs/promises';
const TYPE_MAP: Record<string, string> = {
'anime-board': 'anime',
'image-board': 'image',
'book-board': 'book',
};
function extractProp(source: string, prop: string): string | null {
const m = source.match(new RegExp(`this\\.${prop}\\s*=\\s*["']([^"']+)["']`));
return m ? m[1] : null;
}
function isNewer(remote: string, local?: string | null) {
if (!local) return true;
return remote !== local;
}
export async function updateExtensions(req: any, reply: FastifyReply) {
const updated: string[] = [];
for (const name of getExtensionsList()) {
const ext = getExtension(name);
if (!ext) continue;
const type = ext.type;
if (!TYPE_MAP[type]) continue;
const fileName = ext.__fileName;
const remoteUrl = `https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/${TYPE_MAP[type]}/${fileName}`;
let remoteSrc: string;
try {
const res = await fetch(remoteUrl);
if (!res.ok) continue;
remoteSrc = await res.text();
} catch {
continue;
}
const remoteVersion = extractProp(remoteSrc, 'version');
const localVersion = ext.version ?? null;
if (!remoteVersion) continue;
if (isNewer(remoteVersion, localVersion)) {
await saveExtensionFile(fileName, remoteUrl);
updated.push(name);
}
}
return { updated };
}
export async function getExtensions(req: FastifyRequest, reply: FastifyReply) { export async function getExtensions(req: FastifyRequest, reply: FastifyReply) {
return { extensions: getExtensionsList() }; return { extensions: getExtensionsList() };

View File

@@ -4,6 +4,7 @@ import * as controller from './extensions.controller';
async function extensionsRoutes(fastify: FastifyInstance) { async function extensionsRoutes(fastify: FastifyInstance) {
fastify.get('/extensions', controller.getExtensions); fastify.get('/extensions', controller.getExtensions);
fastify.get('/extensions/anime', controller.getAnimeExtensions); fastify.get('/extensions/anime', controller.getAnimeExtensions);
fastify.post('/extensions/update', controller.updateExtensions);
fastify.get('/extensions/book', controller.getBookExtensions); fastify.get('/extensions/book', controller.getBookExtensions);
fastify.get('/extensions/gallery', controller.getGalleryExtensions); fastify.get('/extensions/gallery', controller.getGalleryExtensions);
fastify.get('/extensions/:name/settings', controller.getExtensionSettings); fastify.get('/extensions/:name/settings', controller.getExtensionSettings);

View File

@@ -1,9 +1,11 @@
const ORIGINAL_MARKETPLACE_URL = 'https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/marketplace.json'; const ORIGINAL_MARKETPLACE_URL = 'https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/marketplace.json';
const MARKETPLACE_JSON_URL = `/api/proxy?url=${encodeURIComponent(ORIGINAL_MARKETPLACE_URL)}`; const MARKETPLACE_JSON_URL = `/api/proxy?url=${encodeURIComponent(ORIGINAL_MARKETPLACE_URL)}`;
const INSTALLED_EXTENSIONS_API = '/api/extensions'; const INSTALLED_EXTENSIONS_API = '/api/extensions';
const UPDATE_EXTENSIONS_API = '/api/extensions/update';
const marketplaceContent = document.getElementById('marketplace-content'); const marketplaceContent = document.getElementById('marketplace-content');
const filterSelect = document.getElementById('extension-filter'); const filterSelect = document.getElementById('extension-filter');
const updateAllBtn = document.getElementById('btn-update-all');
const modal = document.getElementById('customModal'); const modal = document.getElementById('customModal');
const modalTitle = document.getElementById('modalTitle'); const modalTitle = document.getElementById('modalTitle');
@@ -32,6 +34,10 @@ async function loadMarketplace() {
if (filterSelect) { if (filterSelect) {
filterSelect.addEventListener('change', () => renderGroupedView()); filterSelect.addEventListener('change', () => renderGroupedView());
} }
if (updateAllBtn) {
updateAllBtn.onclick = handleUpdateAll;
}
} catch (error) { } catch (error) {
console.error('Error loading marketplace:', error); console.error('Error loading marketplace:', error);
marketplaceContent.innerHTML = `<div class="error-msg">Error al cargar el marketplace.</div>`; marketplaceContent.innerHTML = `<div class="error-msg">Error al cargar el marketplace.</div>`;
@@ -45,11 +51,49 @@ function initTabs() {
tabs.forEach(t => t.classList.remove('active')); tabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active'); tab.classList.add('active');
currentTab = tab.dataset.tab; currentTab = tab.dataset.tab;
if (updateAllBtn) {
if (currentTab === 'installed') {
updateAllBtn.classList.remove('hidden');
} else {
updateAllBtn.classList.add('hidden');
}
}
renderGroupedView(); renderGroupedView();
}; };
}); });
} }
async function handleUpdateAll() {
const originalText = updateAllBtn.innerText;
try {
updateAllBtn.disabled = true;
updateAllBtn.innerText = 'Updating...';
const res = await fetch(UPDATE_EXTENSIONS_API, { method: 'POST' });
if (!res.ok) throw new Error('Update failed');
const data = await res.json();
if (data.updated && data.updated.length > 0) {
const list = data.updated.join(', ');
window.NotificationUtils.success(`Updated: ${list}`);
await loadMarketplace();
} else {
window.NotificationUtils.info('Everything is up to date.');
}
} catch (error) {
console.error('Update All Error:', error);
window.NotificationUtils.error('Failed to perform bulk update.');
} finally {
updateAllBtn.disabled = false;
updateAllBtn.innerText = originalText;
}
}
function renderGroupedView() { function renderGroupedView() {
marketplaceContent.innerHTML = ''; marketplaceContent.innerHTML = '';
const activeFilter = filterSelect.value; const activeFilter = filterSelect.value;
@@ -58,7 +102,6 @@ function renderGroupedView() {
let listToRender = []; let listToRender = [];
if (currentTab === 'marketplace') { if (currentTab === 'marketplace') {
for (const [id, data] of Object.entries(marketplaceMetadata)) { for (const [id, data] of Object.entries(marketplaceMetadata)) {
listToRender.push({ listToRender.push({
id, id,
@@ -67,7 +110,6 @@ function renderGroupedView() {
}); });
} }
} else { } else {
for (const [id, data] of Object.entries(marketplaceMetadata)) { for (const [id, data] of Object.entries(marketplaceMetadata)) {
if (installedExtensions.includes(id.toLowerCase())) { if (installedExtensions.includes(id.toLowerCase())) {
listToRender.push({ id, ...data, isInstalled: true }); listToRender.push({ id, ...data, isInstalled: true });
@@ -90,9 +132,7 @@ function renderGroupedView() {
listToRender.forEach(ext => { listToRender.forEach(ext => {
const type = ext.type || 'Other'; const type = ext.type || 'Other';
if (activeFilter !== 'All' && type !== activeFilter) return; if (activeFilter !== 'All' && type !== activeFilter) return;
if (!groups[type]) groups[type] = []; if (!groups[type]) groups[type] = [];
groups[type].push(ext); groups[type].push(ext);
}); });
@@ -127,7 +167,7 @@ function createCard(ext) {
const card = document.createElement('div'); const card = document.createElement('div');
card.className = `extension-card ${ext.nsfw ? 'nsfw-ext' : ''} ${ext.broken ? 'broken-ext' : ''}`; card.className = `extension-card ${ext.nsfw ? 'nsfw-ext' : ''} ${ext.broken ? 'broken-ext' : ''}`;
const iconUrl = `https://www.google.com/s2/favicons?domain=${ext.domain}&sz=128` const iconUrl = `https://www.google.com/s2/favicons?domain=${ext.domain}&sz=128`;
let buttonHtml = ''; let buttonHtml = '';
if (ext.isInstalled) { if (ext.isInstalled) {
@@ -187,9 +227,9 @@ async function handleInstall(ext) {
if (res.ok) { if (res.ok) {
installedExtensions.push(ext.id.toLowerCase()); installedExtensions.push(ext.id.toLowerCase());
renderGroupedView(); renderGroupedView();
showModal('Success', `${ext.name} installed!`); window.NotificationUtils.success(`${ext.name} installed!`);
} }
} catch (e) { showModal('Error', 'Install failed.'); } } catch (e) { window.NotificationUtils.error('Install failed.'); }
} }
function promptUninstall(ext) { function promptUninstall(ext) {
@@ -206,8 +246,9 @@ async function handleUninstall(ext) {
if (res.ok) { if (res.ok) {
installedExtensions = installedExtensions.filter(id => id !== ext.id.toLowerCase()); installedExtensions = installedExtensions.filter(id => id !== ext.id.toLowerCase());
renderGroupedView(); renderGroupedView();
window.NotificationUtils.info(`${ext.name} uninstalled.`);
} }
} catch (e) { showModal('Error', 'Uninstall failed.'); } } catch (e) { window.NotificationUtils.error('Uninstall failed.'); }
} }
function showSkeletons() { function showSkeletons() {

View File

@@ -46,7 +46,6 @@ async function loadExtensions() {
} }
} }
async function loadExtension(fileName) { async function loadExtension(fileName) {
const homeDir = os.homedir(); const homeDir = os.homedir();
const extensionsDir = path.join(homeDir, 'WaifuBoards', 'extensions'); const extensionsDir = path.join(homeDir, 'WaifuBoards', 'extensions');
@@ -77,6 +76,7 @@ async function loadExtension(fileName) {
} }
const name = instance.constructor.name; const name = instance.constructor.name;
instance.__fileName = fileName;
instance.scrape = scrape; instance.scrape = scrape;
instance.cheerio = cheerio; instance.cheerio = cheerio;
extensions.set(name, instance); extensions.set(name, instance);
@@ -114,6 +114,14 @@ async function saveExtensionFile(fileName, downloadUrl) {
file.on('finish', async () => { file.on('finish', async () => {
file.close(async () => { file.close(async () => {
try { try {
const extName = fileName.replace('.js', '');
for (const key of extensions.keys()) {
if (key.toLowerCase() === extName.toLowerCase()) {
extensions.delete(key);
break;
}
}
await loadExtension(fileName); await loadExtension(fileName);
resolve(); resolve();
} catch (err) { } catch (err) {

View File

@@ -300,21 +300,6 @@ body {
transform: scale(1.05); transform: scale(1.05);
} }
Here is the CSS you need to add to the very bottom of your file.
I have targeted the specific classes you provided (.card, .section, .back-btn) to make them touch-friendly, and I added a specific section to fix the "Who's exploring?" screen from your screenshot.
1. First: The HTML Tag (Crucial)
Ensure this is in your HTML <head> or the CSS below will not work:
HTML
<meta name="viewport" content="width=device-width, initial-scale=1.0">
2. The CSS Update
Copy and paste this entire block to the bottom of your CSS file:
CSS
@media (max-width: 768px) { @media (max-width: 768px) {
:root { :root {
--nav-height: 60px; --nav-height: 60px;

View File

@@ -25,7 +25,7 @@
border-radius: 999px; border-radius: 999px;
background: var(--color-bg-elevated-hover); background: var(--color-bg-elevated-hover);
color: var(--color-text-primary); color: var(--color-text-primary);
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid rgba(255,255,255,0.1);
appearance: none; appearance: none;
font-weight: 600; font-weight: 600;
@@ -51,14 +51,14 @@
.extension-card { .extension-card {
background: var(--bg-surface); background: var(--bg-surface);
border: 1px solid rgba(255, 255, 255, 0.05); border: 1px solid rgba(255,255,255,0.05);
border-radius: var(--radius-md); border-radius: var(--radius-md);
padding: 1rem; padding: 1rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
transition: all 0.2s; transition: all 0.2s;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); box-shadow: 0 4px 15px rgba(0,0,0,0.3);
min-height: 140px; min-height: 140px;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
@@ -67,7 +67,7 @@
.extension-card:hover { .extension-card:hover {
background: var(--color-bg-elevated-hover); background: var(--color-bg-elevated-hover);
transform: translateY(-4px); transform: translateY(-4px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.5); box-shadow: 0 8px 25px rgba(0,0,0,0.5);
} }
.card-content-wrapper { .card-content-wrapper {
@@ -128,10 +128,7 @@
font-size: 0.9rem; font-size: 0.9rem;
border: none; border: none;
cursor: pointer; cursor: pointer;
transition: transition: background 0.2s, transform 0.2s, box-shadow 0.2s;
background 0.2s,
transform 0.2s,
box-shadow 0.2s;
margin-top: auto; margin-top: auto;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
@@ -179,7 +176,7 @@
border-radius: 8px; border-radius: 8px;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
border: 2px solid rgba(255, 255, 255, 0.05); border: 2px solid rgba(255,255,255,0.05);
} }
.skeleton-text.title-skeleton { .skeleton-text.title-skeleton {
height: 1.1em; height: 1.1em;
@@ -231,14 +228,10 @@
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
width: 90%; width: 90%;
max-width: 450px; max-width: 450px;
box-shadow: box-shadow: 0 15px 50px rgba(0, 0, 0, 0.8), 0 0 20px var(--color-primary-glow);
0 15px 50px rgba(0, 0, 0, 0.8), border: 1px solid rgba(255,255,255,0.1);
0 0 20px var(--color-primary-glow);
border: 1px solid rgba(255, 255, 255, 0.1);
transform: translateY(-50px); transform: translateY(-50px);
transition: transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.3s ease-in-out;
transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1),
opacity 0.3s ease-in-out;
opacity: 0; opacity: 0;
} }
@@ -252,7 +245,7 @@
font-weight: 800; font-weight: 800;
margin-top: 0; margin-top: 0;
color: var(--color-primary); color: var(--color-primary);
border-bottom: 2px solid rgba(255, 255, 255, 0.05); border-bottom: 2px solid rgba(255,255,255,0.05);
padding-bottom: 0.75rem; padding-bottom: 0.75rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
@@ -271,15 +264,14 @@
} }
.modal-button { .modal-button {
padding: 0.6rem 1.5rem; padding: 0.6rem 1.5rem;
border-radius: 999px; border-radius: 999px;
font-weight: 700; font-weight: 700;
font-size: 0.9rem; font-size: 0.9rem;
border: none; border: none;
cursor: pointer; cursor: pointer;
transition: transition: background 0.2s, transform 0.2s;
background 0.2s,
transform 0.2s;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
} }
@@ -302,102 +294,100 @@
transform: scale(1.02); transform: scale(1.02);
} }
@media (max-width: 768px) { .extension-author {
.section-header {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.filter-controls {
width: 100%;
overflow-x: auto;
white-space: nowrap;
padding-bottom: 0.5rem;
-ms-overflow-style: none;
scrollbar-width: none;
}
.filter-controls::-webkit-scrollbar {
display: none;
}
.filter-select {
flex-shrink: 0;
padding: 0.6rem 2.2rem 0.6rem 1rem;
font-size: 0.9rem;
}
.marketplace-grid {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 0.75rem;
}
.extension-card {
padding: 0.75rem;
min-height: auto;
}
.extension-icon {
width: 40px;
height: 40px;
}
.extension-name {
font-size: 1rem;
}
.extension-status-badge {
font-size: 0.65rem;
}
.extension-card:hover {
transform: none;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
}
.extension-action-button {
padding: 0.5rem;
font-size: 0.8rem; font-size: 0.8rem;
} color: var(--color-text-secondary);
display: block;
.modal-content { margin-bottom: 0.5rem;
width: 95%; }
max-width: none;
padding: 1.5rem; .extension-tags {
display: flex;
position: absolute; gap: 0.5rem;
bottom: 0; flex-wrap: wrap;
border-bottom-left-radius: 0; }
border-bottom-right-radius: 0;
transform: translateY(100%); .badge-available {
margin-bottom: 0; background: rgba(59, 130, 246, 0.2);
} color: #60a5fa;
border: 1px solid rgba(59, 130, 246, 0.3);
.modal-overlay:not(.hidden) .modal-content { }
transform: translateY(0);
} .nsfw-ext {
border-color: rgba(220, 38, 38, 0.3);
#modalTitle { }
font-size: 1.3rem;
margin-bottom: 1rem; .broken-ext {
} filter: grayscale(0.8);
opacity: 0.7;
#modalMessage { border: 1px dashed #ef4444; /* Borde rojo discontinuo */
font-size: 0.95rem; }
margin-bottom: 1.5rem;
} .broken-ext:hover {
transform: none; /* Evitamos que se mueva al pasar el ratón si está rota */
.modal-actions { }
flex-direction: column;
gap: 0.75rem; /* Estilos para los Tabs */
} .tabs-container {
display: flex;
.modal-button { gap: 1rem;
width: 100%; margin-bottom: 1.5rem;
padding: 0.8rem; border-bottom: 1px solid rgba(255,255,255,0.1);
justify-content: center; padding-bottom: 0.5rem;
display: flex; }
}
.tab-button {
background: none;
border: none;
color: var(--color-text-secondary);
font-size: 1.1rem;
font-weight: 700;
padding: 0.5rem 1rem;
cursor: pointer;
transition: all 0.3s;
position: relative;
}
.tab-button.active {
color: var(--color-primary);
}
.tab-button.active::after {
content: '';
position: absolute;
bottom: -0.6rem;
left: 0;
width: 100%;
height: 3px;
background: var(--color-primary);
border-radius: 999px;
box-shadow: 0 0 10px var(--color-primary-glow);
}
.marketplace-section-title {
font-size: 1.4rem;
font-weight: 800;
margin: 2rem 0 1rem 0;
color: var(--color-text-primary);
display: flex;
align-items: center;
gap: 0.5rem;
text-transform: capitalize;
}
.marketplace-section-title::before {
content: '';
display: inline-block;
width: 4px;
height: 20px;
background: var(--color-primary);
border-radius: 2px;
}
.category-group {
margin-bottom: 3rem;
}
.hidden {
display: none !important;
} }

View File

@@ -85,6 +85,9 @@
</div> </div>
<div class="filter-controls"> <div class="filter-controls">
<button id="btn-update-all" class="btn-blur hidden" style="margin-right: 10px; width: auto; padding: 0 15px;">
Update All
</button>
<label for="extension-filter" class="filter-label">Filter by Type:</label> <label for="extension-filter" class="filter-label">Filter by Type:</label>
<select id="extension-filter" class="filter-select"> <select id="extension-filter" class="filter-select">
<option value="All">All Categories</option> <option value="All">All Categories</option>
@@ -112,9 +115,9 @@
</div> </div>
</main> </main>
<script src="/src/scripts/utils/notification-utils.js"></script>
<script src="/src/scripts/updateNotifier.js"></script> <script src="/src/scripts/updateNotifier.js"></script>
<script src="/src/scripts/marketplace.js"></script> <script src="/src/scripts/marketplace.js"></script>
<script src="/src/scripts/rpc-inapp.js"></script>
<script src="/src/scripts/auth-guard.js"></script> <script src="/src/scripts/auth-guard.js"></script>
</body> </body>
</html> </html>