const REPO_OWNER = 'ItsSkaiya'; const REPO_NAME = 'WaifuBoard-Extensions'; const API_URL = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/contents/extensions?ref=main`; document.addEventListener('DOMContentLoaded', async () => { const browseGrid = document.getElementById('marketplace-grid'); const installedGrid = document.getElementById('installed-grid'); const statTotal = document.getElementById('stat-total'); const statInstalled = document.getElementById('stat-installed'); const tabBrowse = document.getElementById('tab-browse'); const tabInstalled = document.getElementById('tab-installed'); const viewBrowse = document.getElementById('view-browse'); const viewInstalled = document.getElementById('view-installed'); let allRemoteExtensions = []; let installedExtensionsList = []; const messageBar = document.getElementById('message-bar'); function showMessage(msg, type = 'success') { if (!messageBar) return; messageBar.textContent = msg; messageBar.className = `toast ${type === 'error' ? 'error' : ''}`; messageBar.classList.remove('hidden'); setTimeout(() => messageBar.classList.add('hidden'), 3000); } function updateStats() { if(statTotal) statTotal.textContent = allRemoteExtensions.length; if(statInstalled) statInstalled.textContent = installedExtensionsList.length; } function showRestartToast() { if (document.getElementById('restart-toast')) return; const toast = document.createElement('div'); toast.id = 'restart-toast'; toast.style.cssText = ` position: fixed; bottom: 20px; right: 20px; background: #1f2937; border: 1px solid var(--accent); border-radius: 12px; padding: 1rem 1.5rem; display: flex; align-items: center; gap: 1rem; box-shadow: 0 10px 30px rgba(0,0,0,0.5); z-index: 2000; animation: slideUp 0.3s ease-out; `; toast.innerHTML = `
Restart Required Changes will apply after restart.
`; document.body.appendChild(toast); const btn = document.getElementById('btn-restart-now'); btn.onmouseover = () => btn.style.opacity = '0.9'; btn.onmouseout = () => btn.style.opacity = '1'; btn.onclick = () => { if (window.api && typeof window.api.restartApp === 'function') { window.api.restartApp(); } else { console.error("Restart API not found in window.api"); alert("Please close and reopen the application manually."); } }; } function switchTab(tab) { if (tab === 'browse') { tabBrowse.classList.add('active'); tabInstalled.classList.remove('active'); viewBrowse.classList.remove('hidden'); viewInstalled.classList.add('hidden'); } else { tabBrowse.classList.remove('active'); tabInstalled.classList.add('active'); viewBrowse.classList.add('hidden'); viewInstalled.classList.remove('hidden'); renderInstalledGrid(); } } if(tabBrowse) tabBrowse.onclick = () => switchTab('browse'); if(tabInstalled) tabInstalled.onclick = () => switchTab('installed'); async function fetchInstalledExtensions() { try { const extensions = await window.api.getInstalledExtensions(); installedExtensionsList = extensions || []; } catch (e) { console.error("Failed to fetch installed extensions:", e); installedExtensionsList = []; } } async function fetchRemoteExtensions() { try { const res = await fetch(API_URL); if (!res.ok) throw new Error(`GitHub API Error: ${res.status}`); const data = await res.json(); allRemoteExtensions = data.filter(item => item.name.endsWith('.js')); renderBrowseGrid(allRemoteExtensions); updateStats(); } catch (e) { if(browseGrid) browseGrid.innerHTML = `

Failed to load marketplace.
${e.message}

`; } } async function getExtensionDetails(url) { try { const res = await fetch(url); const text = await res.text(); const baseUrlMatch = text.match(/baseUrl\s*=\s*["']([^"']+)["']/); let baseUrl = baseUrlMatch ? baseUrlMatch[1] : null; if (baseUrl) { try { const urlObj = new URL(baseUrl); baseUrl = urlObj.hostname; } catch(e) { console.warn("Invalid URL in extension:", baseUrl); } } const classMatch = text.match(/class\s+(\w+)/); const name = classMatch ? classMatch[1] : null; let type = 'Image'; if (text.includes('type = "book-board"') || text.includes("type = 'book-board'")) { type = 'Book'; } return { baseUrl, name, type }; } catch (e) { return { baseUrl: null, name: null, type: 'Unknown' }; } } async function createCard(item, isLocalOnly = false) { const isInstalled = installedExtensionsList.includes(item.name); const downloadUrl = item.download_url || null; let sizeKB = item.size ? (item.size / 1024).toFixed(1) + ' KB' : 'Local'; let iconUrl = ''; let displayName = item.name.replace('.js', ''); let typeLabel = 'Extension'; let typeClass = 'type-image'; if (downloadUrl) { const details = await getExtensionDetails(downloadUrl); displayName = details.name || displayName; const domain = details.baseUrl || 'github.com'; iconUrl = `https://www.google.com/s2/favicons?domain=${domain}&sz=128`; if (details.type === 'Book') { typeLabel = 'Book Board'; typeClass = 'type-book'; } else { typeLabel = 'Image Board'; typeClass = 'type-image'; } } else { iconUrl = `https://ui-avatars.com/api/?name=${displayName}&background=18181b&color=fff&length=1`; } const card = document.createElement('div'); card.className = 'extension-card'; card.dataset.name = item.name; const downloadIcon = ''; const trashIcon = ''; const checkIcon = ''; const renderInner = (installed) => `
${typeLabel}

${displayName}

${item.name}
`; card.innerHTML = renderInner(isInstalled); card.addEventListener('click', async (e) => { const btn = e.target.closest('.install-btn'); if (!btn) return; const currentlyInstalled = installedExtensionsList.includes(item.name); if (currentlyInstalled) { const success = await window.api.uninstallExtension(item.name); if (success) { installedExtensionsList = installedExtensionsList.filter(n => n !== item.name); card.innerHTML = renderInner(false); updateStats(); if (tabInstalled.classList.contains('active')) { card.remove(); if (installedGrid.children.length === 0) { installedGrid.innerHTML = '

No extensions installed.

'; } } showRestartToast(); } else { showMessage('Failed to uninstall', 'error'); } } else { if (!downloadUrl) return; const originalHTML = btn.innerHTML; btn.innerHTML = ''; const success = await window.api.installExtension(item.name, downloadUrl); if (success) { installedExtensionsList.push(item.name); card.innerHTML = renderInner(true); updateStats(); showMessage(`Installed ${displayName}`); showRestartToast(); } else { showMessage('Failed to install', 'error'); btn.innerHTML = originalHTML; } } }); return card; } async function renderBrowseGrid(list) { browseGrid.innerHTML = ''; if (list.length === 0) { browseGrid.innerHTML = '

No extensions found.

'; return; } for (const item of list) { const card = await createCard(item); browseGrid.appendChild(card); } } async function renderInstalledGrid() { installedGrid.innerHTML = ''; if (installedExtensionsList.length === 0) { installedGrid.innerHTML = '

No extensions installed.

'; return; } for (const filename of installedExtensionsList) { const remoteData = allRemoteExtensions.find(r => r.name === filename); const item = remoteData || { name: filename, download_url: null, size: 0 }; const card = await createCard(item, !remoteData); installedGrid.appendChild(card); } } await fetchInstalledExtensions(); await fetchRemoteExtensions(); });