mirror of
https://github.com/sune-org/store.git
synced 2026-01-14 16:48:14 +00:00
1 line
18 KiB
JSON
1 line
18 KiB
JSON
[{"id":"37uz2i8","name":"GitHub","pinned":true,"avatar":"","url":"gh://sune-org/store/github.sune","updatedAt":1757193711895,"settings":{"model":"openai/gpt-5-chat","temperature":"","top_p":"","top_k":"","frequency_penalty":"","presence_penalty":"","repetition_penalty":"","min_p":"","top_a":"","max_tokens":"","verbosity":"","reasoning_effort":"default","system_prompt":"","html":"<div id=\"ghRepoBrowserSune\" class=\"relative p-4 bg-white/80 backdrop-blur-xl rounded-lg border border-gray-200 shadow-sm max-w-2xl mx-auto font-sans overflow-hidden\">\n <!-- Background Blob -->\n <div class=\"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-gradient-to-tr from-cyan-200 via-blue-300 to-purple-300 rounded-full opacity-30 filter blur-3xl -z-10\" aria-hidden=\"true\"></div>\n\n <!-- Header -->\n <div class=\"flex justify-between items-center mb-4\">\n <h2 id=\"viewTitle\" class=\"font-bold text-lg text-gray-800 transition-all\">GitHub Repositories</h2>\n <span class=\"text-xs text-gray-400\">v-0.6.1</span>\n </div>\n\n <!-- View: Repository List -->\n <div id=\"repoListView\">\n <div id=\"repoList\" class=\"space-y-2 mb-3\">\n <!-- Repo input rows will be injected here -->\n </div>\n <button id=\"addRepoBtn\" class=\"w-full flex items-center justify-center gap-2 px-4 py-2 text-xs text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 active:scale-[0.98] transition-all\">\n <i data-lucide=\"plus\" class=\"h-4 w-4\"></i>\n <span>Add Repository</span>\n </button>\n </div>\n\n <!-- View: Repository Browser -->\n <div id=\"repoBrowserView\" class=\"hidden\">\n <!-- Browser Header & Back Button -->\n <div class=\"flex items-center mb-3 -mt-2\">\n <button id=\"backBtn\" class=\"flex items-center gap-1.5 px-3 py-1 text-sm text-gray-600 rounded-lg hover:bg-gray-100 active:scale-[0.98] transition-all\">\n <i data-lucide=\"arrow-left\" class=\"h-4 w-4\"></i>\n <span>Back</span>\n </button>\n </div>\n\n <!-- Sub-View: File Browser -->\n <div id=\"fileBrowserContainer\">\n <div id=\"breadcrumbs\" class=\"flex items-center gap-1.5 text-sm text-gray-500 mb-2 overflow-x-auto pb-2 -mr-4 pr-4\"></div>\n <div id=\"fileTree\" class=\"space-y-1 max-h-[60vh] overflow-y-auto border border-gray-200 rounded-lg p-2 bg-gray-50/50\"></div>\n </div>\n \n <!-- Sub-View: File History -->\n <div id=\"fileHistoryContainer\" class=\"hidden\">\n <div id=\"fileHistoryHeader\" class=\"text-sm font-medium text-gray-700 mb-2 truncate\"></div>\n <div id=\"commitList\" class=\"space-y-2 max-h-[60vh] overflow-y-auto -mr-2 pr-2\"></div>\n </div>\n\n <!-- General Status Area for Browser View -->\n <div id=\"statusArea\" class=\"text-sm text-center text-gray-600 p-4 border-2 border-dashed border-gray-200 rounded-lg hidden mt-4\"></div>\n </div>\n\n <!-- File Content Modal (FIXED) -->\n <div id=\"fileContentModal\" class=\"fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-start justify-center p-4 pt-8 sm:pt-16 hidden\">\n <div class=\"bg-white rounded-xl shadow-2xl w-full max-w-4xl flex flex-col max-h-[85vh]\">\n <div class=\"flex justify-between items-center p-3 border-b border-gray-200 flex-shrink-0\">\n <p id=\"modalHeader\" class=\"text-sm font-medium text-gray-700 truncate\">File Content</p>\n <button id=\"closeModalBtn\" class=\"p-1 rounded-full hover:bg-gray-200 active:bg-gray-300 transition-colors\"><i data-lucide=\"x\" class=\"h-5 w-5 text-gray-600\"></i></button>\n </div>\n <div class=\"flex-1 overflow-auto relative bg-gray-900\">\n <pre><code id=\"fileContentCode\" class=\"block p-4 text-xs font-mono leading-relaxed\"></code></pre>\n <button id=\"copyContentBtn\" class=\"absolute top-2 right-12 px-3 py-1 text-xs bg-gray-700/80 text-white rounded-md hover:bg-gray-600\">Copy</button>\n </div>\n </div>\n </div>\n</div>\n\n<script>\n(function() {\n const sune = document.getElementById('ghRepoBrowserSune');\n if (!sune || sune.dataset.initialized) return;\n sune.dataset.initialized = 'true';\n\n const SUNE_ID = window.SUNE?.id || 'gh-repo-browser-sune';\n const CACHE_KEY = `${SUNE_ID}_gh_repos`;\n\n // --- DOM Elements ---\n const viewTitle = sune.querySelector('#viewTitle');\n const repoListView = sune.querySelector('#repoListView'), repoBrowserView = sune.querySelector('#repoBrowserView');\n const repoList = sune.querySelector('#repoList'), addRepoBtn = sune.querySelector('#addRepoBtn');\n const backBtn = sune.querySelector('#backBtn');\n const fileBrowserContainer = sune.querySelector('#fileBrowserContainer'), fileHistoryContainer = sune.querySelector('#fileHistoryContainer');\n const statusArea = sune.querySelector('#statusArea');\n const breadcrumbs = sune.querySelector('#breadcrumbs'), fileTree = sune.querySelector('#fileTree');\n const fileHistoryHeader = sune.querySelector('#fileHistoryHeader'), commitList = sune.querySelector('#commitList');\n const modal = sune.querySelector('#fileContentModal'), modalHeader = sune.querySelector('#modalHeader');\n const modalCode = sune.querySelector('#fileContentCode'), copyContentBtn = sune.querySelector('#copyContentBtn'), closeModalBtn = sune.querySelector('#closeModalBtn');\n\n // --- State ---\n let currentRepoInfo = {}, pathHistory = [];\n\n // --- Utility Functions ---\n const showStatus=(m,e=!1)=>{statusArea.innerHTML=m;statusArea.classList.toggle('text-red-600',e);statusArea.classList.remove('hidden');fileBrowserContainer.classList.add('hidden');fileHistoryContainer.classList.add('hidden');};\n const setLoading=(b,l)=>{if(!b)return;b.disabled=l;b.innerHTML=l?`<svg class=\"animate-spin h-4 w-4 text-white\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"O 0 24 24\"><circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle><path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"></path></svg>`:'Browse';};\n const parseRepoPath=p=>p?.match(/^([a-z\\d\\-_]+)\\/([a-z\\d\\-_.]+)(?:@([^\\/]+))?$/i)?.slice(1);\n const b64_to_utf8=s=>{try{return decodeURIComponent(atob(s).split('').map(c=>'%'+('00'+c.charCodeAt(0).toString(16)).slice(-2)).join(''))}catch(e){return\"Err: Could not decode.\"}};\n const copyToClipboard=async(l,t)=>{const o=l.textContent;try{await navigator.clipboard.writeText(t);l.textContent='Copied!'}catch(r){l.textContent='Failed!'}finally{setTimeout(()=>{l.textContent=o},2e3)}};\n const highlightModalCode=()=>{if(window.hljs){delete modalCode.dataset.highlighted;window.hljs.highlightElement(modalCode)}};\n const saveRepos=()=>localStorage.setItem(CACHE_KEY,JSON.stringify([...sune.querySelectorAll('.repo-path-input')].map(i=>i.value.trim())));\n\n // --- View Management ---\n const resetBrowserView=()=>{pathHistory=[];breadcrumbs.innerHTML='';fileTree.innerHTML='';statusArea.classList.add('hidden');showBrowserSubView('tree')};\n const showView=v=>{repoListView.classList.toggle('hidden',v!=='list');repoBrowserView.classList.toggle('hidden',v!=='browser');if(v==='list'){viewTitle.textContent='GitHub Repositories';resetBrowserView()}else{viewTitle.textContent=`${currentRepoInfo.owner}/${currentRepoInfo.repo}`}};\n const showBrowserSubView=v=>{fileBrowserContainer.classList.toggle('hidden',v!=='tree');fileHistoryContainer.classList.toggle('hidden',v!=='history');if(v==='tree'){commitList.innerHTML='';fileHistoryHeader.innerHTML=''}};\n\n // --- API & Fetching ---\n const ghFetch=async(u)=>{const h={'Accept':'application/vnd.github.v3+json'};if(window.USER?.PAT)h['Authorization']=`token ${window.USER.PAT}`;const r=await fetch(u,{headers:h});if(!r.ok){const e=await r.json().catch(()=>({message:`API Error: ${r.status}`}));throw new Error(e.message||`API Err: ${r.status}`)};return r.json()};\n const fetchHistory=async(fPath,btn)=>{fileHistoryHeader.innerHTML=`<div class=\"flex items-center gap-2\"><svg class=\"animate-spin h-4 w-4\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\"><circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle><path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"></path></svg> <span>Loading history for <b>${fPath.split('/').pop()}</b>...</span></div>`;commitList.innerHTML='';try{const{owner,repo,branch}=currentRepoInfo;const commits=await ghFetch(`https://api.github.com/repos/${owner}/${repo}/commits?path=${encodeURIComponent(fPath)}&sha=${encodeURIComponent(branch)}`);fileHistoryHeader.innerHTML=`Commit history for <b>${fPath}</b>`;if(!commits?.length)commitList.innerHTML=`<div class=\"text-xs text-center text-gray-500 p-3 bg-gray-50 rounded-md\">No commits found.</div>`;else renderCommits(commits,fPath)}catch(e){fileHistoryHeader.innerHTML=`Error loading history`;commitList.innerHTML=`<div class=\"text-xs text-center text-red-600 p-3 bg-red-50 rounded-md\">${e.message}</div>`}finally{btn?.classList.add('bg-blue-100','font-semibold')}};\n const fetchFileContent=async(b)=>{const{path,sha}=b.dataset, {owner,repo}=currentRepoInfo;modalHeader.textContent=`Loading...`;modalCode.textContent='';modal.classList.remove('hidden');try{const d=await ghFetch(`https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${sha}`);modalHeader.textContent=`${path} @ ${sha.substring(0,7)}`;modalCode.textContent=d.content?b64_to_utf8(d.content):\"Could not get content.\"}catch(e){modalHeader.textContent=`Error`;modalCode.textContent=`Error: ${e.message}`}finally{highlightModalCode()}};\n const fetchDiffContent=async(b)=>{const{path,sha}=b.dataset, {owner,repo}=currentRepoInfo;modalHeader.textContent=`Loading diff...`;modalCode.textContent='';modalCode.className='block p-4 text-xs font-mono leading-relaxed hljs language-diff';modal.classList.remove('hidden');try{const d=await ghFetch(`https://api.github.com/repos/${owner}/${repo}/commits/${sha}`);const f=d.files?.find(f=>f.filename===path);modalHeader.textContent=`Diff for ${path} @ ${sha.substring(0,7)}`;modalCode.textContent=f?.patch||\"No diff found.\"}catch(e){modalHeader.textContent=`Error`;modalCode.textContent=`Error: ${e.message}`}finally{highlightModalCode()}};\n \n // --- Render Functions ---\n const renderCommits=(c,p)=>{commitList.innerHTML=c.map(i=>{const m=i.commit,a=i.author||m.author,d=new Date(m.author.date).toLocaleString([],{dateStyle:'medium',timeStyle:'short'});return`<div class=\"p-3 border border-gray-200 rounded-lg bg-gray-50/50 hover:bg-white hover:shadow-md transition-all\"><p class=\"font-medium text-sm text-gray-800 truncate\">${m.message.split('\\n')[0]}</p><div class=\"flex flex-col sm:flex-row justify-between items-start sm:items-center mt-2 gap-2\"><div class=\"flex items-center gap-2 text-xs text-gray-500\">${a?.avatar_url?`<img src=\"${a.avatar_url}\" class=\"h-5 w-5 rounded-full\" alt=\"${a.login||''}\">`:'<div class=\"h-5 w-5 rounded-full bg-gray-300\"></div>'}<span class=\"font-medium\">${m.author.name}</span><span class=\"hidden md:inline\">•</span><span class=\"flex-shrink-0\">${d}</span></div><div class=\"flex items-center gap-2 self-end sm:self-center flex-shrink-0\"><button class=\"view-diff-btn px-3 py-1 text-xs bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-100 active:scale-95\" data-path=\"${p}\" data-sha=\"${i.sha}\">Diff</button><button class=\"view-content-btn px-3 py-1 text-xs bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-100 active:scale-95\" data-path=\"${p}\" data-sha=\"${i.sha}\">File</button></div></div></div>`}).join('')};\n const renderBreadcrumbs=()=>{breadcrumbs.innerHTML=pathHistory.map((p,i)=>`<button class=\"breadcrumb-item inline-flex items-center gap-1.5 hover:text-blue-600 disabled:text-gray-800 disabled:hover:text-gray-800\" data-index=\"${i}\" ${i===pathHistory.length-1?'disabled':''}>${p.name==='root'?'<i data-lucide=\"git-branch\" class=\"h-4 w-4\"></i>':''}${p.name}${i<pathHistory.length-1?'<i data-lucide=\"chevron-right\" class=\"h-4 w-4 text-gray-400 -mr-1\"></i>':''}</button>`).join('');window.lucide?.createIcons()};\n const renderTree=items=>{const sorted=items.sort((a,b)=>{if(a.type===b.type)return a.path.localeCompare(b.path);return a.type==='tree'?-1:1});fileTree.innerHTML=sorted.map(i=>`<button class=\"file-item w-full flex items-center gap-2 p-2 text-left text-sm rounded-md hover:bg-gray-200 transition-colors\" data-path=\"${i.path}\" data-sha=\"${i.sha}\" data-type=\"${i.type}\"><i data-lucide=\"${i.type==='tree'?'folder':'file-text'}\" class=\"h-4 w-4 text-gray-500 flex-shrink-0\"></i><span class=\"truncate\">${i.path.split('/').pop()}</span></button>`).join('')||'<div class=\"text-xs text-center text-gray-500 p-3\">This directory is empty.</div>';window.lucide?.createIcons()};\n\n // --- Core Logic ---\n const addRepoRow=p=>{const r=document.createElement('div');r.className='repo-row relative flex w-full items-center';r.innerHTML=`<i data-lucide=\"github\" class=\"pointer-events-none absolute left-3 top-1/2 z-10 h-4 w-4 -translate-y-1/2 text-gray-400\"></i><input type=\"text\" value=\"${p}\" placeholder=\"org/repo@branch\" class=\"repo-path-input w-full rounded-lg border border-gray-300 py-2 pl-9 pr-32 text-sm outline-none transition-colors focus:border-blue-500 focus:ring-2 focus:ring-blue-500/50\"><div class=\"absolute right-1.5 flex items-center gap-1\"><button class=\"browse-repo-btn flex items-center justify-center gap-2 rounded-md bg-blue-600 px-3 py-1.5 text-xs font-semibold text-white transition-all hover:bg-blue-700 active:scale-[0.98] disabled:bg-blue-400\">Browse</button><button class=\"remove-repo-btn flex items-center justify-center rounded-md p-1.5 text-red-500/80 transition-all hover:bg-red-500/10 hover:text-red-700\"><i data-lucide=\"x\" class=\"h-4 w-4\"></i></button></div>`;repoList.append(r);window.lucide?.createIcons()};\n const fetchAndRenderTree = async(sha, name) => {showBrowserSubView('tree');if(name)pathHistory.push({name,sha});else pathHistory=[{name:'root',sha}];renderBreadcrumbs();fileTree.innerHTML='<div class=\"text-sm text-center p-4\">Loading tree...</div>';try{const{tree}=await ghFetch(`https://api.github.com/repos/${currentRepoInfo.owner}/${currentRepoInfo.repo}/git/trees/${sha}`);renderTree(tree)}catch(e){showStatus(`Error fetching tree: ${e.message}`,!0)}};\n const startRepoBrowse = async (btn) => {\n const input = btn.closest('.repo-row').querySelector('.repo-path-input');\n const val = input.value.trim(); if(!val) return;\n const p = parseRepoPath(val); if(!p) { alert(\"Invalid format: org/repo@branch (branch is optional)\"); return; }\n setLoading(btn, true);\n try {\n currentRepoInfo = { owner: p[0], repo: p[1], branch: p[2] || null };\n if (!currentRepoInfo.branch) {\n const repoInfo = await ghFetch(`https://api.github.com/repos/${currentRepoInfo.owner}/${currentRepoInfo.repo}`);\n currentRepoInfo.branch = repoInfo.default_branch;\n input.value = `${currentRepoInfo.owner}/${currentRepoInfo.repo}@${currentRepoInfo.branch}`;\n saveRepos();\n }\n const branchInfo = await ghFetch(`https://api.github.com/repos/${currentRepoInfo.owner}/${currentRepoInfo.repo}/branches/${currentRepoInfo.branch}`);\n showView('browser');\n await fetchAndRenderTree(branchInfo.commit.commit.tree.sha);\n } catch(e) {\n alert(`Error: ${e.message}`); showView('list');\n } finally {\n setLoading(btn, false);\n }\n };\n \n // --- Event Listeners & Initialization ---\n const init = () => {\n const cachedRepos = JSON.parse(localStorage.getItem(CACHE_KEY) || '[]');\n if (cachedRepos.length) cachedRepos.forEach(p => addRepoRow(p)); else addRepoRow('');\n \n sune.addEventListener('click', e => {\n const target = e.target;\n const browseBtn = target.closest('.browse-repo-btn'); if(browseBtn) return startRepoBrowse(browseBtn);\n const removeBtn = target.closest('.remove-repo-btn'); if(removeBtn) { removeBtn.closest('.repo-row').remove(); saveRepos(); if(repoList.childElementCount===0)addRepoRow(''); return; }\n if(target.closest('#addRepoBtn')) return addRepoRow('');\n if(target.closest('#backBtn')) return !fileHistoryContainer.classList.contains('hidden') ? showBrowserSubView('tree') : showView('list');\n \n const fileItem = target.closest('.file-item');\n if(fileItem) {\n sune.querySelectorAll('.file-item').forEach(el=>el.classList.remove('bg-blue-100','font-semibold'));\n const { path, sha, type } = fileItem.dataset;\n if (type === 'tree') fetchAndRenderTree(sha, path.split('/').pop());\n else if (type === 'blob') { showBrowserSubView('history'); fetchHistory(path, fileItem); }\n return;\n }\n const breadcrumbItem = target.closest('.breadcrumb-item:not([disabled])');\n if(breadcrumbItem) { const i = parseInt(breadcrumbItem.dataset.index,10); pathHistory = pathHistory.slice(0,i+1); fetchAndRenderTree(pathHistory[i].sha); return; }\n \n const vContent = target.closest('.view-content-btn'); if(vContent) return fetchFileContent(vContent);\n const vDiff = target.closest('.view-diff-btn'); if(vDiff) return fetchDiffContent(vDiff);\n if(target.closest('#closeModalBtn') || target.id === 'fileContentModal') modal.classList.add('hidden');\n if(target.closest('#copyContentBtn')) copyToClipboard(target, modalCode.textContent);\n });\n sune.addEventListener('input', e => { if(e.target.classList.contains('repo-path-input')) saveRepos(); });\n sune.addEventListener('keydown', e => { if(e.key==='Enter' && e.target.classList.contains('repo-path-input')) e.target.closest('.repo-row').querySelector('.browse-repo-btn')?.click(); });\n\n window.lucide?.createIcons();\n };\n \n if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init();\n})();\n</script>\n","extension_html":"<sune src='https://raw.githubusercontent.com/sune-org/store/refs/heads/main/sync.sune' private />"},"storage":{}}] |