Files
store/commit-history.sune

1 line
17 KiB
JSON

[{"id":"28qvo5s","name":"GitHub File History","pinned":true,"avatar":"data:image/webp;base64,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","url":"gh://sune-org/store/commit-history.sune","updatedAt":1757172700710,"settings":{"model":"openai/gpt-5","temperature":"1","top_p":"0.96","top_k":"0","frequency_penalty":"0","presence_penalty":"0","repetition_penalty":"1","min_p":"0","top_a":"0","max_tokens":"","verbosity":"","reasoning_effort":"default","system_prompt":"","html":"<div id=\"ghHistorySune\" 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 class=\"font-bold text-lg text-gray-800\">GitHub File History</h2>\n <span class=\"text-xs text-gray-400\">v-0.3.1</span>\n </div>\n\n <!-- Input List -->\n <div id=\"inputList\" class=\"space-y-2 mb-3\">\n <!-- Input rows will be injected here by the script -->\n </div>\n\n <!-- Add Button -->\n <button id=\"addPathBtn\" 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 Path</span>\n </button>\n\n <!-- Divider -->\n <hr class=\"my-4 border-gray-200/80\">\n\n <!-- Status & Results Area -->\n <div id=\"resultsArea\" class=\"space-y-3\">\n <div id=\"statusArea\" class=\"text-sm text-center text-gray-600 p-4 border-2 border-dashed border-gray-200 rounded-lg hidden\"></div>\n <div id=\"commitList\" class=\"space-y-2 max-h-[50vh] overflow-y-auto -mr-2 pr-2\">\n <!-- Commit items will be injected here -->\n </div>\n </div>\n\n <!-- File Content Modal (Unchanged) -->\n <div id=\"fileContentModal\" class=\"fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4 hidden\">\n <div class=\"bg-white rounded-xl shadow-2xl w-full max-w-4xl flex flex-col max-h-[90vh]\">\n <div class=\"flex justify-between items-center p-3 border-b border-gray-200\">\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\">\n <i data-lucide=\"x\" class=\"h-5 w-5 text-gray-600\"></i>\n </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\">\n Copy\n </button>\n </div>\n </div>\n </div>\n</div>\n\n<script>\n(function() {\n const sune = document.getElementById('ghHistorySune');\n if (!sune || sune.dataset.initialized) return;\n sune.dataset.initialized = 'true';\n\n const SUNE_ID = window.SUNE?.id || 'gh-history-sune';\n const CACHE_KEY = `${SUNE_ID}_gh_paths`;\n\n // --- DOM Elements ---\n const inputList = sune.querySelector('#inputList'), addPathBtn = sune.querySelector('#addPathBtn');\n const statusArea = sune.querySelector('#statusArea'), commitList = sune.querySelector('#commitList');\n const modal = sune.querySelector('#fileContentModal'), modalHeader = sune.querySelector('#modalHeader');\n const modalCode = sune.querySelector('#fileContentCode'), copyContentBtn = sune.querySelector('#copyContentBtn');\n const closeModalBtn = sune.querySelector('#closeModalBtn');\n\n // --- Utility Functions ---\n const showStatus = (msg, isErr=false) => { (statusArea.innerHTML=msg, statusArea.classList.toggle('text-red-600', isErr), statusArea.classList.remove('hidden'), commitList.innerHTML=''); };\n const hideStatus = () => statusArea.classList.add('hidden');\n const setLoading = (btn, isLd) => {\n if (!btn) return;\n btn.disabled = isLd;\n btn.innerHTML = isLd ? `<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>` : `<i data-lucide=\"search\" class=\"h-4 w-4\"></i>`;\n if (!isLd) window.lucide?.createIcons();\n };\n const parsePath = 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 \"Error: Could not decode content.\"}};\n const copyToClipboard = async (el, txt) => { const orig = el.textContent; try { await navigator.clipboard.writeText(txt); el.textContent='Copied!'; } catch(err) {el.textContent='Failed!';} finally { setTimeout(() => {el.textContent=orig}, 2000);}};\n const highlightModalCode = () => { if (window.hljs){delete modalCode.dataset.highlighted; window.hljs.highlightElement(modalCode)} };\n const savePaths = () => { const paths=[...sune.querySelectorAll('.gh-path-input')].map(i=>i.value.trim()); localStorage.setItem(CACHE_KEY,JSON.stringify(paths))};\n \n // --- Input Management ---\n const addInputRow = (path = '') => {\n const row = document.createElement('div');\n row.className = 'input-row relative flex w-full items-center';\n row.innerHTML = `\n <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>\n <input type=\"text\" value=\"${path}\" placeholder=\"org/repo@branch/file/path\" class=\"gh-path-input w-full rounded-lg border border-gray-300 py-2 pl-9 pr-20 text-sm outline-none transition-colors focus:border-blue-500 focus:ring-2 focus:ring-blue-500\">\n <div class=\"absolute right-1.5 flex items-center gap-1\">\n <button class=\"fetch-history-btn flex items-center justify-center rounded-md p-1.5 text-gray-500 transition-all hover:bg-gray-200 hover:text-gray-900 disabled:cursor-not-allowed disabled:text-gray-400 disabled:hover:bg-transparent\">\n <i data-lucide=\"search\" class=\"h-4 w-4\"></i>\n </button>\n <button class=\"remove-path-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\">\n <i data-lucide=\"x\" class=\"h-4 w-4\"></i>\n </button>\n </div>\n `;\n inputList.append(row);\n const input = row.querySelector('.gh-path-input'), fetchBtn = row.querySelector('.fetch-history-btn');\n const toggleFetch = () => { fetchBtn.disabled = !input.value.trim() };\n input.addEventListener('input', () => { savePaths(); toggleFetch(); });\n input.addEventListener('keydown', e => { if (e.key === 'Enter' && !fetchBtn.disabled) { e.preventDefault(); fetchBtn.click(); }});\n toggleFetch();\n window.lucide?.createIcons();\n };\n\n // --- API Functions ---\n const ghFetch = async (url)=>{const h={'Accept':'application/vnd.github.v3+json'};if(window.USER?.PAT){h['Authorization']=`token ${window.USER.PAT}`};const r=await fetch(url,{headers:h});if(!r.ok){const e=await r.json().catch(()=>({message:`API Error: ${r.status}`}));throw new Error(e.message||`API Error: ${r.status}`)};return r.json()};\n const fetchHistory=async(btn)=>{const input=btn.closest('.input-row').querySelector('.gh-path-input'),val=input.value.trim();if(!val)return showStatus(\"Path cannot be empty.\",true);const p=parsePath(val);if(!p)return showStatus(\"Invalid format. Use: org/repo@branch/file/path\",true);const parsed={owner:p[0],repo:p[1],branch:p[2]||null,path:p[3]};setLoading(btn,true);hideStatus();commitList.innerHTML='';try{let url=`https://api.github.com/repos/${parsed.owner}/${parsed.repo}/commits?path=${encodeURIComponent(parsed.path)}`;if(parsed.branch)url+=`&sha=${encodeURIComponent(parsed.branch)}`;const commits=await ghFetch(url);if(!commits?.length)showStatus(\"No commits found.\",false);else renderCommits(commits,parsed)}catch(e){showStatus(`Error: ${e.message}`,true)}finally{setLoading(btn,false)}};\n const fetchFileContent=async(btn)=>{const{owner,repo,path,sha}=btn.dataset;if(!owner||!repo||!path||!sha)return;modalHeader.textContent=`Loading... (${sha.substring(0,7)})`;modalCode.textContent='Loading...';modalCode.className='block p-4 text-xs font-mono leading-relaxed hljs';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 retrieve content.\"}catch(e){modalHeader.textContent=`Error @ ${sha.substring(0,7)}`;modalCode.textContent=`Error: ${e.message}`}finally{highlightModalCode()}};\n const fetchDiffContent=async(btn)=>{const{owner,repo,path,sha}=btn.dataset;if(!owner||!repo||!path||!sha)return;modalHeader.textContent=`Loading diff... (${sha.substring(0,7)})`;modalCode.textContent='Loading...';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 file=d.files?.find(f=>f.filename===path);modalHeader.textContent=`Diff for ${path} @ ${sha.substring(0,7)}`;modalCode.textContent=file?.patch||\"No diff found.\"}catch(e){modalHeader.textContent=`Error diff @ ${sha.substring(0,7)}`;modalCode.textContent=`Error: ${e.message}`}finally{highlightModalCode()}};\n\n // --- Render Functions ---\n const renderCommits = (commits, pInfo) => {\n commitList.innerHTML = commits.map(item => {\n const c=item.commit, author=item.author||c.author, date=new Date(c.author.date).toLocaleString([],{dateStyle:'medium',timeStyle:'short'});\n 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\">${c.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\">${author?.avatar_url?`<img src=\"${author.avatar_url}\" class=\"h-5 w-5 rounded-full\" alt=\"${author.login||''}\">`:'<div class=\"h-5 w-5 rounded-full bg-gray-300\"></div>'}<span class=\"font-medium\">${c.author.name}</span><span class=\"hidden md:inline\">&bull;</span><span class=\"flex-shrink-0\">${date}</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-owner=\"${pInfo.owner}\" data-repo=\"${pInfo.repo}\" data-path=\"${pInfo.path}\" data-sha=\"${item.sha}\">View 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-owner=\"${pInfo.owner}\" data-repo=\"${pInfo.repo}\" data-path=\"${pInfo.path}\" data-sha=\"${item.sha}\">View File</button></div></div></div>`;\n }).join('');\n };\n\n // --- Initialization ---\n const init = () => {\n const cachedPaths = JSON.parse(localStorage.getItem(CACHE_KEY) || '[]');\n if (cachedPaths.length > 0) cachedPaths.forEach(p => addInputRow(p)); else addInputRow();\n addPathBtn.addEventListener('click', () => addInputRow());\n inputList.addEventListener('click', e => {\n const fBtn = e.target.closest('.fetch-history-btn'); if(fBtn) return fetchHistory(fBtn);\n const rBtn = e.target.closest('.remove-path-btn'); if(rBtn) { rBtn.closest('.input-row').remove(); savePaths(); if(inputList.childElementCount===0) addInputRow(); }\n });\n commitList.addEventListener('click', e => {\n const vBtn = e.target.closest('.view-content-btn'); if (vBtn) return fetchFileContent(vBtn);\n const dBtn = e.target.closest('.view-diff-btn'); if (dBtn) return fetchDiffContent(dBtn);\n });\n closeModalBtn.addEventListener('click', () => modal.classList.add('hidden'));\n modal.addEventListener('click', e => e.target === modal && modal.classList.add('hidden'));\n copyContentBtn.addEventListener('click', e => copyToClipboard(e.target, modalCode.textContent));\n window.lucide?.createIcons();\n };\n if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init();\n})();\n</script>\n","extension_html":"\n<sune src='https://raw.githubusercontent.com/sune-org/store/refs/heads/main/sync.sune' private></sune"},"storage":{}}]