diff --git a/avia_core/repofrontend.js b/avia_core/repofrontend.js new file mode 100644 index 0000000..1675eb8 --- /dev/null +++ b/avia_core/repofrontend.js @@ -0,0 +1,479 @@ +(function () { + + if (window.__AVIA_OFFICIAL_REPO_LOADED__) return; + window.__AVIA_OFFICIAL_REPO_LOADED__ = true; + + const STORAGE_KEY = "avia_plugins"; + const OFFICIAL_REPO_URL = "https://avalilac.github.io/PluginRepo/pluginrepobackend.js"; + const THEMES_REGISTRY_URL = "https://avalilac.github.io/PluginRepo/themebackend/themerepobackend.js"; + + const getPlugins = () => JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]"); + const setPlugins = (data) => localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); + + let repoContent; + let currentRepoData = []; + let currentThemeData = []; + let searchInput; + let activeTab = "plugins"; // "plugins" | "themes" + + document.getElementById("avia-official-repo-btn")?.remove(); + + function triggerManagerRefresh() { + const panel = document.getElementById("avia-plugins-panel"); + if (!panel) return; + const refreshBtn = Array.from(panel.querySelectorAll("button")) + .find(b => b.textContent.trim() === "Refresh"); + if (refreshBtn) refreshBtn.click(); + } + + function updateInstallStates() { + if (!repoContent) return; + const installed = getPlugins().map(p => p.url); + repoContent.querySelectorAll("[data-link]").forEach(row => { + const link = row.getAttribute("data-link"); + const btn = row.querySelector("button.install-btn"); + if (!btn) return; + if (installed.includes(link)) { + btn.textContent = "Installed"; + btn.disabled = true; + } else { + btn.textContent = "Install"; + btn.disabled = false; + } + }); + } + + function renderRepo(data, filter = "") { + if (!repoContent) return; + + currentRepoData = data.plugins; + repoContent.innerHTML = ""; + + const filtered = currentRepoData.filter(p => + (p.name + " " + (p.author || "") + " " + (p.description || "")) + .toLowerCase() + .includes(filter.toLowerCase()) + ); + + if (filtered.length === 0) { + repoContent.innerHTML = `
No plugins found.
`; + return; + } + + filtered.forEach(repoPlugin => { + const row = document.createElement("div"); + row.style.cssText = "display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;width:100%;min-width:0;"; + row.setAttribute("data-link", repoPlugin.link); + + const left = document.createElement("div"); + left.style.cssText = "display:flex;flex-direction:column;flex:1;min-width:0;"; + + const title = document.createElement("div"); + title.textContent = `${repoPlugin.name} — ${repoPlugin.author || "Unknown"}`; + title.style.cssText = "font-weight:500;word-break:break-word;"; + + const desc = document.createElement("div"); + desc.textContent = repoPlugin.description || ""; + desc.style.cssText = "font-size:12px;opacity:0.7;word-break:break-word;"; + + left.appendChild(title); + left.appendChild(desc); + + const installBtn = document.createElement("button"); + installBtn.className = "install-btn"; + Object.assign(installBtn.style, { + padding: "6px 10px", + borderRadius: "8px", + border: "none", + cursor: "pointer", + background: "rgba(255,255,255,0.08)", + color: "#fff", + flexShrink: "0" + }); + + installBtn.onclick = () => { + const plugins = getPlugins(); + if (!plugins.some(p => p.url === repoPlugin.link)) { + plugins.push({ name: repoPlugin.name, url: repoPlugin.link, enabled: false }); + setPlugins(plugins); + window.dispatchEvent(new Event("avia-plugin-list-changed")); + triggerManagerRefresh(); + renderRepo({ plugins: currentRepoData }, searchInput.value); + } + }; + + row.appendChild(left); + row.appendChild(installBtn); + repoContent.appendChild(row); + }); + + updateInstallStates(); + } + + function refetchPlugins() { + if (!repoContent) return; + repoContent.innerHTML = "Loading..."; + + function electronFetch() { + try { + const https = require("https"); + https.get(OFFICIAL_REPO_URL, res => { + let data = ""; + res.on("data", chunk => data += chunk); + res.on("end", () => renderRepo(JSON.parse(data))); + }).on("error", () => { + repoContent.innerHTML = "Failed to fetch repo."; + }); + } catch { + repoContent.innerHTML = "Failed to fetch repo."; + } + } + + try { + fetch(OFFICIAL_REPO_URL) + .then(res => res.json()) + .then(data => renderRepo(data)) + .catch(() => electronFetch()); + } catch { + electronFetch(); + } + } + + const THEMES_STORAGE_KEY = "avia_themes"; + const getStoredThemes = () => JSON.parse(localStorage.getItem(THEMES_STORAGE_KEY) || "[]"); + const setStoredThemes = (data) => localStorage.setItem(THEMES_STORAGE_KEY, JSON.stringify(data)); + + function buildThemeCSS(theme, rawCSS) { + + const header = `/* @name ${theme.name}\n @author ${theme.author || "Unknown"}\n @version 1.0\n @description Installed from Trusted Themes Repo\n*/\n`; + return header + rawCSS; + } + + function installThemeCSS(theme, btn) { + btn.disabled = true; + btn.textContent = "Installing…"; + + fetch(theme.download) + .then(r => r.text()) + .then(rawCSS => { + const css = buildThemeCSS(theme, rawCSS); + const themes = getStoredThemes(); + + const alreadyInstalled = themes.some(t => { + const match = t.css.match(/@name\s+(.+)/); + return match && match[1].trim() === theme.name; + }); + + if (alreadyInstalled) { + btn.textContent = "Installed"; + + return; + } + + themes.push({ id: crypto.randomUUID(), css, enabled: true }); + setStoredThemes(themes); + + document.querySelectorAll(".avia-theme-style").forEach(e => e.remove()); + getStoredThemes().forEach(t => { + if (!t.enabled) return; + const style = document.createElement("style"); + style.className = "avia-theme-style"; + style.textContent = t.css; + document.head.appendChild(style); + }); + + if (typeof window.__avia_refresh_themes_panel === "function") { + window.__avia_refresh_themes_panel(); + } + + btn.textContent = "Installed"; + + }) + .catch(() => { + btn.textContent = "Install CSS"; + btn.disabled = false; + alert("Failed to fetch theme CSS."); + }); + } + + function renderThemes(filter = "") { + if (!repoContent) return; + repoContent.innerHTML = ""; + + const filtered = currentThemeData.filter(t => + (t.name + " " + (t.author || "")) + .toLowerCase() + .includes(filter.toLowerCase()) + ); + + if (filtered.length === 0) { + repoContent.innerHTML = `
No themes found.
`; + return; + } + + filtered.forEach(theme => { + const card = document.createElement("div"); + card.style.cssText = "margin-bottom:14px;background:rgba(255,255,255,0.04);border-radius:12px;overflow:hidden;border:1px solid rgba(255,255,255,0.07);"; + + if (theme.preview) { + const img = document.createElement("img"); + img.src = theme.preview; + img.alt = theme.name; + img.style.cssText = "width:100%;display:block;background:#111;object-fit:contain;"; + img.onerror = () => img.style.display = "none"; + card.appendChild(img); + } + + const info = document.createElement("div"); + info.style.cssText = "display:flex;justify-content:space-between;align-items:center;padding:10px 12px;gap:8px;"; + + const meta = document.createElement("div"); + meta.style.cssText = "display:flex;flex-direction:column;min-width:0;flex:1;"; + + const name = document.createElement("div"); + name.textContent = theme.name; + name.style.cssText = "font-weight:500;word-break:break-word;"; + + const author = document.createElement("div"); + author.textContent = `by ${theme.author || "Unknown"}`; + author.style.cssText = "font-size:12px;opacity:0.6;"; + + meta.appendChild(name); + meta.appendChild(author); + + const alreadyInstalled = getStoredThemes().some(t => { + const match = t.css.match(/@name\s+(.+)/); + return match && match[1].trim() === theme.name; + }); + + const dlBtn = document.createElement("button"); + dlBtn.textContent = alreadyInstalled ? "Installed" : "Install CSS"; + dlBtn.disabled = alreadyInstalled; + Object.assign(dlBtn.style, { + padding: "6px 10px", + borderRadius: "8px", + border: "none", + cursor: alreadyInstalled ? "default" : "pointer", + background: "rgba(255,255,255,0.08)", + color: "#fff", + flexShrink: "0", + fontSize: "12px", + whiteSpace: "nowrap" + }); + dlBtn.onclick = () => installThemeCSS(theme, dlBtn); + + info.appendChild(meta); + info.appendChild(dlBtn); + card.appendChild(info); + repoContent.appendChild(card); + }); + } + + function refetchThemes() { + if (!repoContent) return; + repoContent.innerHTML = "Loading themes..."; + currentThemeData = []; + + fetch(THEMES_REGISTRY_URL) + .then(r => r.json()) + .then(async registry => { + const sources = registry.sources || []; + const results = await Promise.allSettled( + sources.map(s => fetch(s.url).then(r => r.json())) + ); + results.forEach(r => { + if (r.status === "fulfilled") { + currentThemeData.push(...(r.value.themes || [])); + } + }); + renderThemes(searchInput.value); + }) + .catch(() => { + if (repoContent) repoContent.innerHTML = "Failed to fetch themes."; + }); + } + + function switchTab(tab, tabPluginsBtn, tabThemesBtn) { + activeTab = tab; + const isPlugins = tab === "plugins"; + + tabPluginsBtn.style.background = isPlugins ? "rgba(255,255,255,0.12)" : "transparent"; + tabPluginsBtn.style.color = isPlugins ? "#fff" : "rgba(255,255,255,0.45)"; + tabThemesBtn.style.background = !isPlugins ? "rgba(255,255,255,0.12)" : "transparent"; + tabThemesBtn.style.color = !isPlugins ? "#fff" : "rgba(255,255,255,0.45)"; + + searchInput.placeholder = isPlugins + ? "Search plugins, authors, or descriptions" + : "Search themes or authors"; + searchInput.value = ""; + + if (isPlugins) { + if (currentRepoData.length > 0) renderRepo({ plugins: currentRepoData }); + else refetchPlugins(); + } else { + if (currentThemeData.length > 0) renderThemes(); + else refetchThemes(); + } + } + + function openWindow() { + let panel = document.getElementById("avia-official-repo-window"); + if (panel) { + panel.style.display = panel.style.display === "none" ? "flex" : "none"; + return; + } + + panel = document.createElement("div"); + panel.id = "avia-official-repo-window"; + Object.assign(panel.style, { + position: "fixed", + bottom: "40px", + right: "40px", + width: "420px", + height: "520px", + background: "#1e1e1e", + color: "#fff", + borderRadius: "20px", + boxShadow: "0 12px 35px rgba(0,0,0,0.45)", + zIndex: 999999, + display: "flex", + flexDirection: "column", + overflow: "hidden", + border: "1px solid rgba(255,255,255,0.08)" + }); + + const header = document.createElement("div"); + header.textContent = "Plugins & Themes Repo"; + Object.assign(header.style, { + padding: "18px", + fontWeight: "600", + fontSize: "16px", + background: "rgba(255,255,255,0.04)", + borderBottom: "1px solid rgba(255,255,255,0.08)", + cursor: "move", + position: "relative", + textAlign: "center", + userSelect: "none" + }); + + let isDragging = false, offsetX = 0, offsetY = 0; + header.addEventListener("mousedown", (e) => { + isDragging = true; + const rect = panel.getBoundingClientRect(); + offsetX = e.clientX - rect.left; + offsetY = e.clientY - rect.top; + panel.style.bottom = "auto"; + panel.style.right = "auto"; + panel.style.left = rect.left + "px"; + panel.style.top = rect.top + "px"; + document.body.style.userSelect = "none"; + }); + document.addEventListener("mousemove", (e) => { + if (!isDragging) return; + panel.style.left = e.clientX - offsetX + "px"; + panel.style.top = e.clientY - offsetY + "px"; + }); + document.addEventListener("mouseup", () => { + isDragging = false; + document.body.style.userSelect = ""; + }); + + const close = document.createElement("div"); + close.textContent = "✕"; + Object.assign(close.style, { position: "absolute", right: "18px", top: "16px", cursor: "pointer" }); + close.onclick = () => panel.style.display = "none"; + header.appendChild(close); + + const tabs = document.createElement("div"); + tabs.style.cssText = "display:flex;gap:6px;padding:10px 12px 0;background:rgba(255,255,255,0.02);border-bottom:1px solid rgba(255,255,255,0.08);"; + + const tabStyle = "padding:6px 16px;border-radius:8px 8px 0 0;border:none;cursor:pointer;font-size:13px;font-weight:500;transition:background 0.15s,color 0.15s;font-family:inherit;"; + + const tabPluginsBtn = document.createElement("button"); + tabPluginsBtn.textContent = "Plugins"; + tabPluginsBtn.style.cssText = tabStyle; + + const tabThemesBtn = document.createElement("button"); + tabThemesBtn.textContent = "Themes"; + tabThemesBtn.style.cssText = tabStyle; + + tabPluginsBtn.onclick = () => switchTab("plugins", tabPluginsBtn, tabThemesBtn); + tabThemesBtn.onclick = () => switchTab("themes", tabPluginsBtn, tabThemesBtn); + + tabs.appendChild(tabPluginsBtn); + tabs.appendChild(tabThemesBtn); + + searchInput = document.createElement("input"); + searchInput.placeholder = "Search plugins, authors, or descriptions"; + Object.assign(searchInput.style, { + margin: "12px", + padding: "8px", + borderRadius: "8px", + border: "none", + outline: "none", + background: "rgba(255,255,255,0.06)", + color: "#fff" + }); + searchInput.addEventListener("input", () => { + if (activeTab === "plugins") renderRepo({ plugins: currentRepoData }, searchInput.value); + else renderThemes(searchInput.value); + }); + + repoContent = document.createElement("div"); + Object.assign(repoContent.style, { + flex: "1", + overflowY: "auto", + overflowX: "hidden", + padding: "0 12px 12px" + }); + + const container = document.createElement("div"); + Object.assign(container.style, { flex: "1", display: "flex", flexDirection: "column", overflow: "hidden" }); + container.appendChild(searchInput); + container.appendChild(repoContent); + + panel.appendChild(header); + panel.appendChild(tabs); + panel.appendChild(container); + document.body.appendChild(panel); + + switchTab("plugins", tabPluginsBtn, tabThemesBtn); + refetchPlugins(); + } + + function injectSettingsButton() { + if (document.getElementById("avia-official-repo-btn-settings")) return; + + const appearanceBtn = [...document.querySelectorAll("a")] + .find(a => a.textContent.trim() === "Appearance"); + const referenceNode = document.getElementById("stoat-fake-quickcss"); + if (!appearanceBtn || !referenceNode) return; + + const clone = appearanceBtn.cloneNode(true); + clone.id = "avia-official-repo-btn-settings"; + + const label = [...clone.querySelectorAll("div")].find(d => d.children.length === 0); + if (label) label.textContent = "(Avia) Plugins/Themes Repo"; + + const iconSpan = clone.querySelector("span.material-symbols-outlined"); + if (iconSpan) { + iconSpan.textContent = "extension"; + iconSpan.style.fontVariationSettings = "'FILL' 0,'wght' 400,'GRAD' 0"; + } + + clone.onclick = openWindow; + referenceNode.parentElement.insertBefore(clone, referenceNode.nextSibling); + } + + window.addEventListener("avia-plugin-list-changed", () => { + if (document.getElementById("avia-official-repo-window")) { + updateInstallStates(); + } + }); + + new MutationObserver(() => injectSettingsButton()) + .observe(document.body, { childList: true, subtree: true }); + + injectSettingsButton(); + +})();