mirror of
https://github.com/sune-org/store.git
synced 2026-01-13 16:17:58 +00:00
27 lines
12 KiB
JSON
27 lines
12 KiB
JSON
[
|
|
{
|
|
"id": "bxvj8jx",
|
|
"name": "Commit",
|
|
"pinned": false,
|
|
"avatar": "",
|
|
"url": "gh://sune-org/store@main/commit.sune",
|
|
"updatedAt": 1756931202747,
|
|
"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": "<!-- GitHub File Editor Sune v1.1.0 -->\n<div id=\"gh-editor-sune\" class=\"mx-0 border-b border-gray-200 bg-gray-50 flex flex-col\">\n\n <!-- Toolbar -->\n <div class=\"flex items-center gap-3 px-3 sm:px-4 py-2 border-b border-gray-200 bg-white/80 backdrop-blur-sm shrink-0\">\n <i data-lucide=\"github\" class=\"h-5 w-5 text-gray-500 shrink-0\"></i>\n <input\n type=\"text\"\n id=\"ghPathInput\"\n placeholder=\"owner/repo/path/to/file.ext@branch\"\n class=\"flex-1 w-full bg-transparent border-none focus:ring-0 p-0 text-sm placeholder:text-gray-400\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n />\n <button\n id=\"ghFetchBtn\"\n class=\"inline-flex items-center justify-center gap-1.5 h-8 px-3 shrink-0 rounded-lg bg-gray-100 text-gray-800 shadow-sm hover:bg-gray-200 active:scale-[.98] transition text-sm font-medium\"\n title=\"Fetch File\"\n >\n <i data-lucide=\"refresh-cw\" class=\"h-4 w-4\"></i>\n <span class=\"hidden sm:inline\">Fetch</span>\n </button>\n <button\n id=\"ghCommitBtn\"\n class=\"inline-flex items-center justify-center gap-1.5 h-8 px-3 shrink-0 rounded-lg bg-green-600 text-white shadow-sm hover:bg-green-700 active:scale-[.98] transition text-sm font-medium\"\n title=\"Commit Changes\"\n >\n <i data-lucide=\"check-circle\" class=\"h-4 w-4\"></i>\n <span class=\"hidden sm:inline\">Commit</span>\n </button>\n </div>\n\n <!-- Editor -->\n <div class=\"bg-white flex-1 relative\">\n <pre\n id=\"ghFileEditor\"\n class=\"w-full h-[65vh] p-3 overflow-auto font-mono text-[12px] leading-5 focus:outline-none whitespace-pre\"\n ></pre>\n </div>\n\n <!-- Footer -->\n <div class=\"flex items-center justify-between gap-4 px-3 sm:px-4 py-1 border-t border-gray-200 bg-white/80 backdrop-blur-sm text-xs shrink-0\">\n <div class=\"flex items-center gap-1\">\n <button id=\"ghCopyBtn\" title=\"Copy\" class=\"p-1.5 rounded-md hover:bg-gray-200 text-gray-500 hover:text-gray-800 transition-colors\"><i data-lucide=\"copy\" class=\"h-4 w-4\"></i></button>\n <button id=\"ghCutBtn\" title=\"Cut\" class=\"p-1.5 rounded-md hover:bg-gray-200 text-gray-500 hover:text-gray-800 transition-colors\"><i data-lucide=\"scissors\" class=\"h-4 w-4\"></i></button>\n <button id=\"ghPasteBtn\" title=\"Paste\" class=\"p-1.5 rounded-md hover:bg-gray-200 text-gray-500 hover:text-gray-800 transition-colors\"><i data-lucide=\"clipboard-paste\" class=\"h-4 w-4\"></i></button>\n </div>\n <div class=\"flex items-center gap-3 text-gray-500 truncate\">\n <span id=\"ghLangBadge\" class=\"font-medium\">Plain Text</span>\n <div id=\"ghStatus\" class=\"h-4 truncate\"></div>\n </div>\n <span class=\"text-gray-400\">v1.1.0</span>\n </div>\n\n</div>\n\n<script>\n(async () => {\n // Elements\n const pathInput = document.getElementById('ghPathInput');\n const fetchBtn = document.getElementById('ghFetchBtn');\n const commitBtn = document.getElementById('ghCommitBtn');\n const fileEditorEl = document.getElementById('ghFileEditor');\n const copyBtn = document.getElementById('ghCopyBtn');\n const cutBtn = document.getElementById('ghCutBtn');\n const pasteBtn = document.getElementById('ghPasteBtn');\n const statusEl = document.getElementById('ghStatus');\n const langBadge = document.getElementById('ghLangBadge');\n\n // State\n const ghSuneState = {\n owner: null, repo: null, path: null, branch: null, sha: null,\n jar: null, token: null, lang: null\n };\n \n // Use the active Sune's ID for a unique localStorage key.\n const getLsKey = () => {\n const suneId = window.SUNE?.id;\n return suneId ? `sune_gh_last_path_${suneId}` : 'sune_gh_last_path_fallback';\n };\n\n // Restore last path for this sune\n try {\n const last = localStorage.getItem(getLsKey());\n if (last) pathInput.value = last;\n } catch (e) {\n console.error(\"Sune: Failed to get path from localStorage\", e);\n }\n\n // --- Helper Functions ---\n const setStatus = (message, isError = false) => {\n statusEl.textContent = message || '';\n statusEl.className = `h-4 truncate ${isError ? 'text-red-600' : 'text-gray-500'}`;\n if (!isError && message) {\n setTimeout(() => {\n if (statusEl.textContent === message) statusEl.textContent = '';\n }, 4000);\n }\n };\n\n const formatCharCount = (count) => {\n if (count < 1000) return `${count} chars`;\n const thousands = count / 1000;\n const formatted = thousands.toFixed(2).replace(/\\.00$/, '').replace(/(\\.\\d)0$/, '$1');\n return `${formatted}k chars`;\n };\n \n const setLoading = (btn, isLoading) => {\n btn.disabled = isLoading;\n const icon = btn.querySelector('i[data-lucide]');\n if (!icon) return;\n\n if (isLoading) {\n if (!icon.dataset.originalIcon) icon.dataset.originalIcon = icon.dataset.lucide;\n icon.dataset.lucide = 'loader-circle';\n icon.classList.add('animate-spin');\n } else {\n if (icon.dataset.originalIcon) icon.dataset.lucide = icon.dataset.originalIcon;\n icon.classList.remove('animate-spin');\n }\n if (window.lucide) window.lucide.createIcons();\n };\n \n const parseGhPath = (input) => {\n const match = input.trim().match(/^([a-zA-Z0-9._-]+)\\/([a-zA-Z0-9._-]+)\\/(.+?)@([a-zA-Z0-9._.-]+)$/);\n return match ? { owner: match[1], repo: match[2], path: match[3], branch: match[4] } : null;\n };\n\n const detectLang = (filepath = '') => {\n const p = String(filepath).toLowerCase();\n if (p.endsWith('.html') || p.endsWith('.htm')) return { lang: 'xml', label: 'HTML' };\n if (p.endsWith('.js') || p.endsWith('.mjs') || p.endsWith('.cjs')) return { lang: 'javascript', label: 'JavaScript' };\n if (p.endsWith('.json')) return { lang: 'json', label: 'JSON' };\n if (p.endsWith('.css')) return { lang: 'css', label: 'CSS' };\n if (p.endsWith('.md')) return { lang: 'markdown', label: 'Markdown' };\n return { lang: 'plaintext', label: 'Plain Text' };\n };\n \n const encodeBase64 = (str) => btoa(unescape(encodeURIComponent(str)));\n const decodeBase64 = (b64) => decodeURIComponent(escape(atob(b64)));\n \n // --- Code Editor Setup ---\n const mod = await import('https://medv.io/codejar/codejar.js');\n const CodeJar = mod.CodeJar || mod.default;\n const doHighlight = (editor) => {\n const code = editor.textContent;\n const { lang } = ghSuneState;\n if (window.hljs && lang && window.hljs.getLanguage(lang)) {\n editor.innerHTML = window.hljs.highlight(code, { language: lang, ignoreIllegals: true }).value;\n } else {\n editor.textContent = code;\n }\n };\n ghSuneState.jar = CodeJar(fileEditorEl, doHighlight, { tab: ' ' });\n\n const updateLangBadge = () => {\n const { label } = detectLang(ghSuneState.path || '');\n langBadge.textContent = label;\n };\n\n // --- API Handlers ---\n const handleFetch = async () => {\n setStatus('');\n ghSuneState.token = window.USER?.PAT || '';\n if (!ghSuneState.token) {\n setStatus('Error: GitHub token not found in Account Settings.', true);\n return;\n }\n\n const parsed = parseGhPath(pathInput.value);\n if (!parsed) {\n setStatus('Error: Invalid path. Use: owner/repo/path@branch', true);\n return;\n }\n\n try { localStorage.setItem(getLsKey(), pathInput.value.trim()); } catch (e) { console.error(\"Sune: Failed to save path\", e); }\n\n Object.assign(ghSuneState, parsed);\n ghSuneState.lang = detectLang(ghSuneState.path).lang;\n updateLangBadge();\n\n const apiUrl = `https://api.github.com/repos/${ghSuneState.owner}/${ghSuneState.repo}/contents/${encodeURIComponent(ghSuneState.path).replace(/%2F/g,'/')}?ref=${encodeURIComponent(ghSuneState.branch)}`;\n setStatus('Fetching...');\n setLoading(fetchBtn, true);\n\n try {\n const response = await fetch(apiUrl, { headers: { 'Authorization': `Bearer ${ghSuneState.token}`, 'Accept': 'application/vnd.github.v3+json' } });\n const data = await response.json();\n if (!response.ok) throw new Error(data.message || 'Failed to fetch file.');\n if (data.type !== 'file') throw new Error('Path is not a file.');\n\n ghSuneState.sha = data.sha;\n const decodedContent = decodeBase64(data.content || '');\n ghSuneState.jar.updateCode(decodedContent);\n setStatus(`Loaded ${ghSuneState.path} (${formatCharCount(decodedContent.length)}).`);\n } catch (err) {\n setStatus(`Error: ${err.message}`, true);\n ghSuneState.sha = null;\n } finally {\n setLoading(fetchBtn, false);\n }\n };\n\n const handleCommit = async () => {\n setStatus('');\n const { owner, repo, path, branch, sha, token, jar } = ghSuneState;\n\n if (!token) { setStatus('Error: GitHub token not found.', true); return; }\n if (!owner || !sha) { setStatus('Error: Fetch a file first.', true); return; }\n\n const defaultCommitMsg = `Update ${path.split('/').pop() || path}`;\n const commitMsg = prompt(\"Enter commit message:\", defaultCommitMsg);\n\n if (commitMsg === null) { setStatus('Commit cancelled.'); return; }\n if (!commitMsg.trim()) { setStatus('Error: Commit message cannot be empty.', true); return; }\n\n const newContent = jar.toString();\n const encodedContent = encodeBase64(newContent);\n const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${encodeURIComponent(path).replace(/%2F/g,'/')}`;\n \n setStatus('Committing...');\n setLoading(commitBtn, true);\n\n try {\n const response = await fetch(apiUrl, {\n method: 'PUT',\n headers: { 'Authorization': `Bearer ${token}`, 'Accept': 'application/vnd.github.v3+json', 'Content-Type': 'application/json' },\n body: JSON.stringify({ message: commitMsg.trim(), content: encodedContent, sha, branch }),\n });\n const data = await response.json();\n if (!response.ok) throw new Error(data.message || 'Failed to commit.');\n\n ghSuneState.sha = data.content.sha;\n setStatus('Committed successfully!');\n } catch (err) {\n setStatus(`Error: ${err.message}`, true);\n } finally {\n setLoading(commitBtn, false);\n }\n };\n\n // --- Clipboard Handlers ---\n const handleCopy = async () => { try { await navigator.clipboard.writeText(ghSuneState.jar.toString()); setStatus('Copied.'); } catch { setStatus('Copy failed.', true); } };\n const handleCut = async () => { try { await navigator.clipboard.writeText(ghSuneState.jar.toString()); ghSuneState.jar.updateCode(''); setStatus('Cut.'); } catch { setStatus('Cut failed.', true); } };\n const handlePaste = async () => { try { const text = await navigator.clipboard.readText(); ghSuneState.jar.updateCode(text); setStatus(`Pasted (${formatCharCount(text.length)}).`); } catch { setStatus('Paste failed.', true); } };\n\n // --- Event Listeners ---\n fetchBtn.addEventListener('click', handleFetch);\n commitBtn.addEventListener('click', handleCommit);\n copyBtn.addEventListener('click', handleCopy);\n cutBtn.addEventListener('click', handleCut);\n pasteBtn.addEventListener('click', handlePaste);\n pathInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); handleFetch(); } });\n\n // Initial render\n if (window.lucide) window.lucide.createIcons();\n})();\n</script>\n",
|
|
"extension_html": "<sune src='https://raw.githubusercontent.com/sune-org/store/refs/heads/main/sync.sune' private />"
|
|
}
|
|
}
|
|
] |