mirror of
https://github.com/sune-org/store.git
synced 2026-09-18 04:15:44 +00:00
1 line
12 KiB
JSON
1 line
12 KiB
JSON
[{"id":"9ucskrw","name":"Personal","pinned":false,"avatar":"","url":"gh://sune-org/store@main/renox.sune","updatedAt":1788220862667,"settings":{"model":"","temperature":"","top_p":"","top_k":"","frequency_penalty":"","repetition_penalty":"","min_p":"","top_a":"","verbosity":"","reasoning_effort":"default","system_prompt":"","html":"<!--\nSune: Marketplace\nVersion: 2.5.6\nDescription: Discover and install sunes from a configurable catalog URL.\n-->\n<section id=\"suneMarketplace\" class=\"p-3 sm:p-5 bg-slate-50 not-prose max-w-4xl mx-auto rounded-2xl border border-slate-200 my-2\" style=\"max-height:80vh;overflow-y:auto;\">\n <header id=\"mpHeader\" class=\"mb-4 pb-3 border-b border-slate-200 flex items-center justify-between\">\n <div class=\"flex items-center gap-2\">\n <div class=\"h-8 w-8 rounded-xl bg-slate-900 text-white flex items-center justify-center\">\n <i data-lucide=\"store\" class=\"h-4 w-4\"></i>\n </div>\n <div>\n <div class=\"flex items-center gap-1.5\">\n <h1 class=\"text-base font-bold text-slate-800 leading-none\">Sune Store</h1>\n <span class=\"text-[10px] font-mono px-1.5 py-0.5 rounded bg-slate-200 text-slate-600 leading-none\">v2.5.6</span>\n </div>\n <p class=\"text-[11px] text-slate-400 mt-0.5\">Explore modules & extensions</p>\n </div>\n </div>\n <button id=\"mpRefresh\" class=\"h-8 px-2.5 rounded-lg bg-white border border-slate-200 text-slate-700 text-xs font-medium hover:bg-slate-100 active:scale-[.98] transition flex items-center gap-1 shadow-sm\" title=\"Refresh catalog\">\n <i data-lucide=\"refresh-cw\" class=\"h-3.5 w-3.5 text-slate-500\"></i>\n <span class=\"hidden sm:inline\">Refresh</span>\n </button>\n </header>\n\n <div id=\"mpNav\" class=\"mb-3\"></div>\n <div id=\"mpContent\" class=\"min-h-[120px]\">\n <div class=\"flex justify-center items-center py-12\">\n <div class=\"animate-spin rounded-full h-8 w-8 border-2 border-slate-400 border-t-transparent\"></div>\n </div>\n </div>\n\n <footer class=\"mt-6 pt-4 border-t border-slate-200\">\n <div class=\"text-center bg-white p-4 rounded-xl border border-slate-200 shadow-sm\">\n <div class=\"flex items-center justify-center gap-1.5\">\n <i data-lucide=\"git-pull-request-arrow\" class=\"h-4 w-4 text-slate-600\"></i>\n <h3 class=\"text-xs font-semibold text-slate-800\">Contribute your Sune</h3>\n </div>\n <p class=\"mt-1 text-[11px] text-slate-500 max-w-sm mx-auto\">\n Publish your sune to the community via pull request in\n <a href=\"https://github.com/sune-org/store\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"font-medium text-sky-600 hover:underline\">sune-org/store</a>.\n </p>\n </div>\n </footer>\n</section>\n\n<script>\n(()=>{\n/* =========================================================\n EDIT CATALOG URL HERE\n ========================================================= */\nconst CATALOG_URL = 'https://raw.githack.com/multipleof4/.sune/main/catalog.json';\n\nconst $ = s => document.querySelector(s);\nconst ico = () => window.lucide?.createIcons();\nconst esc = s => String(s ?? '').replace(/[&<>\"]/g, m => ({ '&':'&','<':'<','>':'>','\"':'"' }[m]));\nconst TTL = 3e5; // 5 min cache TTL\n\nconst root = $(\"#suneMarketplace\"), nav = $(\"#mpNav\"), content = $(\"#mpContent\"), refreshBtn = $(\"#mpRefresh\");\nif (!root) return;\n\nlet catalogData = [], currentView = 'main';\n\n/* Helpers */\nconst b64url2b64 = s => { let x = (s || '').replace(/-/g, '+').replace(/_/g, '/'); while (x && x.length % 4) x += '='; return x; };\nconst sniff = b => b.startsWith('iVBOR') ? 'image/png' : (b.startsWith('/9j/') ? 'image/jpeg' : (b.startsWith('UklGR') ? 'image/webp' : (b.startsWith('R0lGOD') ? 'image/gif' : 'application/octet-stream')));\nconst avatarSrc = v => { if (!v || v.length < 10) return ''; if (/^data:/.test(v)) return v; const b64 = b64url2b64(v), mime = sniff(b64); return `data:${mime};base64,${b64}`; };\nconst ghRaw = u => { try { if (!u || !u.startsWith('gh://')) return u; const s = u.slice(5), [hd, ...rest] = s.split('/'), [ur, br] = hd.split('@'), [user, repo] = ur.split('/'); return `https://raw.githack.com/${user}/${repo}/${br}/${rest.join('/')}`; } catch { return u; } };\nconst ghUrlFromRaw = u => { try { const r = new URL(u); if (!['raw.githubusercontent.com','raw.githack.com'].includes(r.hostname)) return ''; const [,u1,r1,b,...p] = r.pathname.split('/'); return `gh://${u1}/${r1}@${b}/${p.join('/')}`; } catch { return ''; } };\nconst T = s => (s || '').replace(/\\.sune$/, '').replace(/[-_]/g, ' ').replace(/\\b\\w/g, m => m.toUpperCase());\nconst installed = () => new Set((window.SUNE?.list || []).map(x => x?.id).filter(Boolean));\n\n/* UI Builders */\nconst btn = (it, ins) => ins \n ? `<button class=\"mt-3 w-full px-3 py-1.5 text-xs font-medium rounded-lg bg-slate-100 text-slate-400 cursor-default flex items-center justify-center gap-1\" disabled><i data-lucide=\"check-circle\" class=\"h-3.5 w-3.5\"></i><span>Installed</span></button>`\n : `<button class=\"mpInstall mt-3 w-full px-3 py-1.5 text-xs font-medium rounded-lg bg-slate-900 text-white hover:bg-slate-800 active:scale-[.98] transition flex items-center justify-center gap-1 shadow-sm\" data-id=\"${esc(it.id||'')}\" data-raw=\"${esc(it.raw||'')}\"><i data-lucide=\"download\" class=\"h-3.5 w-3.5\"></i><span>Install</span></button>`;\n\nconst card = (it, ins) => {\n const src = avatarSrc(it.avatar);\n return `<article class=\"bg-white border border-slate-200 rounded-xl shadow-sm p-3 flex flex-col justify-between\">\n <div>\n <div class=\"flex items-center gap-2.5\">\n ${src ? `<img alt=\"\" src=\"${src}\" loading=\"lazy\" class=\"h-8 w-8 rounded-lg object-cover bg-slate-100 border border-slate-200 shrink-0\">` : `<div class=\"h-8 w-8 rounded-lg bg-slate-100 border border-slate-200 text-slate-400 flex items-center justify-center shrink-0\"><i data-lucide=\"box\" class=\"h-4 w-4\"></i></div>`}\n <div class=\"min-w-0 flex-1\">\n <h3 class=\"text-xs font-bold text-slate-800 truncate\">${esc(T(it.name))}</h3>\n <p class=\"text-[10px] text-slate-400 truncate\">${esc(it.description || 'Utility sune.')}</p>\n </div>\n </div>\n <div class=\"mt-2 text-[9px] text-slate-400 truncate font-mono\">${esc(it.id || '')}</div>\n </div>\n ${btn(it, ins)}\n </article>`;\n};\n\nconst folderCard = f => `<button class=\"mpFolder w-full text-left p-3 bg-white border border-slate-200 rounded-xl shadow-sm hover:bg-slate-50 transition flex items-center gap-2.5\" data-folder=\"${esc(f.folder)}\">\n <div class=\"h-8 w-8 rounded-lg bg-slate-100 text-slate-600 flex items-center justify-center shrink-0\"><i data-lucide=\"folder\" class=\"h-4 w-4\"></i></div>\n <div class=\"flex-1 min-w-0\">\n <div class=\"text-xs font-semibold text-slate-800\">${esc(T(f.folder))}</div>\n <div class=\"text-[10px] text-slate-400\">${f.sunes.length} sune${f.sunes.length === 1 ? '' : 's'}</div>\n </div>\n <i data-lucide=\"chevron-right\" class=\"h-4 w-4 text-slate-400\"></i>\n</button>`;\n\nconst render = () => {\n nav.innerHTML = '';\n const ids = installed();\n if (currentView === 'main') {\n const folders = catalogData.filter(f => f.folder !== '.').sort((a, b) => a.folder.localeCompare(b.folder));\n const rootSunes = (catalogData.find(f => f.folder === '.')?.sunes || []);\n let foldersHtml = folders.length ? `<div class=\"grid gap-2 grid-cols-1 sm:grid-cols-2\">${folders.map(folderCard).join('')}</div>` : '';\n let sunesHtml = rootSunes.length ? `<div class=\"grid gap-2.5 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">${rootSunes.map(s => card(s, s.id ? ids.has(s.id) : false)).join('')}</div>` : '';\n let html = foldersHtml + (foldersHtml && sunesHtml ? '<div class=\"my-3\"></div>' : '') + sunesHtml;\n content.innerHTML = html || `<div class=\"text-center py-8 text-xs text-slate-400\">No sunes found.</div>`;\n } else {\n const folder = catalogData.find(f => f.folder === currentView);\n nav.innerHTML = `<button class=\"mpBack inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-lg bg-slate-200 text-slate-700 hover:bg-slate-300 transition\"><i data-lucide=\"arrow-left\" class=\"h-3 w-3\"></i>Back</button>`;\n content.innerHTML = folder ? `<div class=\"grid gap-2.5 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">${folder.sunes.map(s => card(s, s.id ? ids.has(s.id) : false)).join('')}</div>` : `<div class=\"text-center py-8 text-xs text-slate-400\">Folder not found.</div>`;\n }\n ico();\n};\n\n/* Cache & Fetch */\nconst loadCat = () => window.SUNE?.storage?.get ? window.SUNE.storage.get('catalog_cache', null) : null;\nconst saveCat = d => window.SUNE?.storage?.set && window.SUNE.storage.set('catalog_cache', { t: Date.now(), items: d });\n\nconst fetchCatalog = async () => {\n const U = ghRaw(CATALOG_URL);\n const r = await fetch(U, { cache: 'no-store' });\n if (!r.ok) throw new Error(`Catalog fetch failed: ${r.status}`);\n const raw = await r.json();\n if (!Array.isArray(raw)) throw new Error('Invalid catalog format.');\n const items = raw.filter(f => f && f.folder && f.sunes?.length > 0);\n saveCat(items);\n return items;\n};\n\nconst boot = async (force = false) => {\n try {\n const cache = loadCat();\n catalogData = (!force && cache?.items && Date.now() - (cache.t || 0) < TTL) ? cache.items : await fetchCatalog();\n currentView = 'main';\n render();\n } catch (e) {\n content.innerHTML = `<div class=\"text-center py-8 bg-red-50 text-red-600 text-xs rounded-xl p-3\"><p class=\"font-semibold\">Unable to load Marketplace</p><p class=\"mt-1 opacity-80\">${esc(e?.message || e)}</p></div>`;\n } finally {\n ico();\n }\n};\n\nrefreshBtn?.addEventListener('click', e => { e.preventDefault(); boot(true); });\n\nroot.addEventListener('click', async e => {\n const installBtn = e.target.closest('.mpInstall'), folderBtn = e.target.closest('.mpFolder'), backBtn = e.target.closest('.mpBack');\n if (backBtn) { currentView = 'main'; render(); return; }\n if (folderBtn) { currentView = folderBtn.dataset.folder || 'main'; render(); return; }\n if (!installBtn) return;\n\n const rawUrl = installBtn.dataset.raw;\n try {\n if (!rawUrl) throw new Error('Missing URL');\n installBtn.disabled = true;\n installBtn.innerHTML = `<i data-lucide=\"loader\" class=\"h-3.5 w-3.5 animate-spin\"></i><span>Installing...</span>`;\n ico();\n\n const githackUrl = ghRaw(rawUrl);\n const arr = await (await fetch(githackUrl)).json();\n if (!Array.isArray(arr) || !arr.length) throw new Error('Corrupt file');\n\n let upd = 0;\n for (const s of arr) {\n if (!s?.id) continue;\n s.url = s.url || ghUrlFromRaw(githackUrl);\n const ex = (window.SUNE?.list || []).find(x => x.id === s.id);\n if (!ex) window.SUNE.create(s);\n else if (+s.updatedAt > +ex.updatedAt) { Object.assign(ex, s); upd++; }\n }\n if (upd > 0) window.SUNE?.save?.();\n window.renderSidebar?.();\n installBtn.outerHTML = `<button class=\"mt-3 w-full px-3 py-1.5 text-xs font-medium rounded-lg bg-slate-100 text-slate-400 cursor-default flex items-center justify-center gap-1\" disabled><i data-lucide=\"check-circle\" class=\"h-3.5 w-3.5\"></i><span>Installed</span></button>`;\n ico();\n } catch (err) {\n installBtn.disabled = false;\n installBtn.innerHTML = `<i data-lucide=\"alert-triangle\" class=\"h-3.5 w-3.5\"></i><span>Retry</span>`;\n ico();\n }\n});\n\nboot();\n})();\n</script>\n","extension_html":"<sune src='https://raw.githubusercontent.com/sune-org/store/refs/heads/main/sync.sune' private></sune>","hide_composer":true,"include_thoughts":false,"img_output":false,"aspect_ratio":"1:1","image_size":"1K","ignore_master_prompt":false,"json_output":false,"json_schema":"","quantization":[],"presence_penalty":"0","max_tokens":""}}] |