mirror of
https://github.com/sune-org/store.git
synced 2026-01-14 08:38:15 +00:00
28 lines
18 KiB
JSON
28 lines
18 KiB
JSON
[
|
|
{
|
|
"id": "28qvo5s",
|
|
"name": "GitHub File History",
|
|
"pinned": true,
|
|
"avatar": "data:image/webp;base64,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",
|
|
"url": "gh://sune-org/store@main/commit-history.sune",
|
|
"updatedAt": 1757093621348,
|
|
"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": 0,
|
|
"verbosity": "",
|
|
"reasoning_effort": "default",
|
|
"system_prompt": "",
|
|
"html": "<div id=\"ghHistorySune\" class=\"p-4 bg-white rounded-lg border border-gray-200 shadow-sm max-w-2xl mx-auto font-sans\">\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.1.0</span>\n </div>\n\n <!-- Input Form -->\n <div class=\"flex flex-col sm:flex-row gap-2 mb-4\">\n <div class=\"relative flex-1\">\n <i data-lucide=\"github\" class=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400\"></i>\n <input type=\"text\" id=\"ghPathInput\" placeholder=\"org/repo@branch/file/path\" class=\"w-full pl-9 pr-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors\">\n </div>\n <button id=\"fetchHistoryBtn\" class=\"flex items-center justify-center px-4 py-2 bg-gray-800 text-white rounded-lg hover:bg-gray-900 active:scale-95 transition-all disabled:opacity-50 disabled:cursor-not-allowed\">\n <i data-lucide=\"search\" class=\"h-4 w-4\"></i>\n <span class=\"ml-2\">Fetch</span>\n </button>\n </div>\n\n <!-- Status & Results Area -->\n <div id=\"resultsArea\" class=\"space-y-3\">\n <div id=\"statusArea\" class=\"text-sm text-center text-gray-500 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 -->\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 hljs\"></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 // Ensure sune runs only once and within its container\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 ? window.SUNE.id : 'gh-history-sune';\n const CACHE_KEY = `${SUNE_ID}_gh_path`;\n\n // --- DOM Elements ---\n const pathInput = sune.querySelector('#ghPathInput');\n const fetchBtn = sune.querySelector('#fetchHistoryBtn');\n const statusArea = sune.querySelector('#statusArea');\n const commitList = sune.querySelector('#commitList');\n \n const modal = sune.querySelector('#fileContentModal');\n const modalHeader = sune.querySelector('#modalHeader');\n const modalCode = sune.querySelector('#fileContentCode');\n const copyContentBtn = sune.querySelector('#copyContentBtn');\n const closeModalBtn = sune.querySelector('#closeModalBtn');\n\n // --- Utility Functions ---\n const showStatus = (message, isError = false) => {\n statusArea.innerHTML = message;\n statusArea.classList.toggle('text-red-600', isError);\n statusArea.classList.remove('hidden');\n commitList.innerHTML = '';\n };\n\n const hideStatus = () => statusArea.classList.add('hidden');\n \n const setLoading = (isLoading) => {\n fetchBtn.disabled = isLoading;\n if (isLoading) {\n fetchBtn.innerHTML = `<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 class=\"ml-2\">Fetching...</span>`;\n } else {\n fetchBtn.innerHTML = `<i data-lucide=\"search\" class=\"h-4 w-4\"></i><span class=\"ml-2\">Fetch</span>`;\n window.lucide && window.lucide.createIcons();\n }\n };\n\n const parsePath = (input) => {\n if (!input) return null;\n const match = input.match(/^([a-zA-Z0-9\\-_]+)\\/([a-zA-Z0-9\\-_.]+)(?:@([^\\/]+))?\\/(.+)$/);\n if (!match) return null;\n\n return {\n owner: match[1],\n repo: match[2],\n branch: match[3] || null, // null will use default branch\n path: match[4]\n };\n };\n\n const b64_to_utf8 = (str) => {\n try {\n return decodeURIComponent(atob(str).split('').map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join(''));\n } catch (e) {\n console.error(\"Base64 decoding failed\", e);\n return \"Error: Could not decode file content. It might be a binary file.\";\n }\n };\n\n const copyToClipboard = async (element, text) => {\n const originalText = element.textContent;\n try {\n await navigator.clipboard.writeText(text);\n element.textContent = 'Copied!';\n } catch (err) {\n element.textContent = 'Failed!';\n console.error('Failed to copy text: ', err);\n } finally {\n setTimeout(() => { element.textContent = originalText; }, 2000);\n }\n };\n \n // --- API Functions ---\n const ghFetch = async (url) => {\n const headers = { 'Accept': 'application/vnd.github.v3+json' };\n if (window.USER && window.USER.PAT) {\n headers['Authorization'] = `token ${window.USER.PAT}`;\n }\n const response = await fetch(url, { headers });\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ message: `API Error: ${response.status}` }));\n throw new Error(errorData.message || `API Error: ${response.status}`);\n }\n return response.json();\n };\n\n const fetchHistory = async () => {\n const pathValue = pathInput.value.trim();\n if (!pathValue) {\n showStatus(\"Please enter a GitHub file path.\", true);\n return;\n }\n\n const parsed = parsePath(pathValue);\n if (!parsed) {\n showStatus(\"Invalid format. Use: org/repo@branch/file/path\", true);\n return;\n }\n \n localStorage.setItem(CACHE_KEY, pathValue);\n setLoading(true);\n hideStatus();\n commitList.innerHTML = '';\n\n try {\n const { owner, repo, branch, path } = parsed;\n let url = `https://api.github.com/repos/${owner}/${repo}/commits?path=${encodeURIComponent(path)}`;\n if (branch) {\n url += `&sha=${encodeURIComponent(branch)}`;\n }\n \n const commits = await ghFetch(url);\n \n if (!commits || commits.length === 0) {\n showStatus(\"No commits found for this file path. Check if the path and branch are correct.\", false);\n } else {\n renderCommits(commits, parsed);\n }\n } catch (error) {\n console.error(\"Error fetching history:\", error);\n showStatus(`Error: ${error.message}`, true);\n } finally {\n setLoading(false);\n }\n };\n\n const fetchFileContent = async (btn) => {\n const { owner, repo, path, sha } = btn.dataset;\n if (!owner || !repo || !path || !sha) return;\n\n modalHeader.textContent = `Loading... (${sha.substring(0, 7)})`;\n modalCode.textContent = 'Loading content...';\n modal.classList.remove('hidden');\n\n try {\n const url = `https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${sha}`;\n const fileData = await ghFetch(url);\n\n if (fileData.content) {\n const decodedContent = b64_to_utf8(fileData.content);\n modalHeader.textContent = `${path} @ ${sha.substring(0, 7)}`;\n modalCode.textContent = decodedContent;\n if (window.hljs) {\n modalCode.className = 'block p-4 text-xs font-mono leading-relaxed hljs';\n window.hljs.highlightElement(modalCode);\n }\n } else {\n modalHeader.textContent = `Error loading file @ ${sha.substring(0, 7)}`;\n modalCode.textContent = \"Could not retrieve file content. It might be a submodule or a large/binary file.\";\n }\n } catch (error) {\n console.error(\"Error fetching file content:\", error);\n modalHeader.textContent = `Error loading file @ ${sha.substring(0, 7)}`;\n modalCode.textContent = `Error: ${error.message}`;\n }\n };\n\n // --- Render Functions ---\n const renderCommits = (commits, parsedInfo) => {\n const commitHtml = commits.map(item => {\n const commit = item.commit;\n const author = item.author || commit.author;\n const date = new Date(commit.author.date).toLocaleString([], { dateStyle: 'medium', timeStyle: 'short' });\n \n return `\n <div class=\"p-3 border border-gray-200 rounded-lg bg-gray-50/50 hover:bg-gray-100 hover:border-gray-300 transition-all\">\n <p class=\"font-medium text-sm text-gray-800 truncate\">${commit.message.split('\\n')[0]}</p>\n <div class=\"flex flex-col sm:flex-row justify-between items-start sm:items-center mt-2 gap-2\">\n <div class=\"flex items-center gap-2 text-xs text-gray-500\">\n ${author && 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>'}\n <span class=\"font-medium\">${commit.author.name}</span>\n <span class=\"hidden md:inline\">•</span>\n <span class=\"flex-shrink-0\">${date}</span>\n </div>\n <button \n class=\"view-content-btn self-end sm:self-center flex-shrink-0 px-3 py-1 text-xs bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-200 active:scale-95 transition-all\"\n data-owner=\"${parsedInfo.owner}\"\n data-repo=\"${parsedInfo.repo}\"\n data-path=\"${parsedInfo.path}\"\n data-sha=\"${item.sha}\"\n >\n View File\n </button>\n </div>\n </div>\n `;\n }).join('');\n commitList.innerHTML = commitHtml;\n };\n\n // --- Initialization ---\n const init = () => {\n // Load cached path\n const cachedPath = localStorage.getItem(CACHE_KEY);\n if (cachedPath) {\n pathInput.value = cachedPath;\n }\n\n // Event Listeners\n fetchBtn.addEventListener('click', fetchHistory);\n pathInput.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n fetchHistory();\n }\n });\n\n commitList.addEventListener('click', (e) => {\n const btn = e.target.closest('.view-content-btn');\n if (btn) {\n fetchFileContent(btn);\n }\n });\n\n closeModalBtn.addEventListener('click', () => modal.classList.add('hidden'));\n modal.addEventListener('click', (e) => {\n if (e.target === modal) {\n modal.classList.add('hidden');\n }\n });\n\n copyContentBtn.addEventListener('click', (e) => {\n copyToClipboard(e.target, modalCode.textContent);\n });\n\n // Initial icon render\n window.lucide && window.lucide.createIcons();\n };\n\n // Run after DOM is ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', init);\n } else {\n init();\n }\n})();\n</script>\n",
|
|
"extension_html": "\n<sune src='https://raw.githubusercontent.com/sune-org/store/refs/heads/main/sync.sune' private></sune"
|
|
},
|
|
"storage": {}
|
|
}
|
|
] |