mirror of
https://github.com/sune-org/store.git
synced 2026-01-13 16:17:58 +00:00
1 line
20 KiB
JSON
1 line
20 KiB
JSON
[{"id":"e9r2j2m","name":"Forums","pinned":false,"avatar":"data:image/webp;base64,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","url":"gh://sune-org/store/forum.sune","updatedAt":1758061083135,"settings":{"model":"","temperature":"","top_p":"","top_k":"","frequency_penalty":"","repetition_penalty":"","min_p":"","top_a":"","verbosity":"","reasoning_effort":"default","system_prompt":"","html":"<div class=\"mx-auto max-w-2xl p-4 sm:p-6 font-sans text-stone-800\" x-data=\"suneForums()\" x-init=\"init()\">\n <!-- Header -->\n <header class=\"flex items-center justify-between mb-4 sm:mb-6 pb-3 border-b border-stone-200\">\n <div class=\"flex items-center gap-3 min-w-0\">\n <button x-show=\"view !== 'list'\" @click.prevent=\"goToList()\" class=\"h-9 w-9 flex-shrink-0 flex items-center justify-center rounded-full hover:bg-stone-100 transition-colors self-start mt-0.5\" aria-label=\"Back\">\n <i data-lucide=\"arrow-left\" class=\"h-5 w-5 text-stone-600\"></i>\n </button>\n <div class=\"truncate\">\n <h1 class=\"text-xl font-bold text-stone-900 truncate\" x-text=\"view === 'list' ? 'Sune Forums' : (view === 'new_post' ? 'Create a Post' : (currentPost ? currentPost.title : '...'))\"></h1>\n <p x-show=\"view === 'post' && currentPost\" class=\"text-xs text-stone-500 mt-0.5\">By <span class=\"font-medium\" x-text=\"currentPost.author_name\"></span> · <span x-text=\"formatDate(currentPost.created_at)\"></span></p>\n </div>\n </div>\n <div class=\"flex items-center gap-2 flex-shrink-0\">\n <span class=\"inline-flex items-center rounded-md bg-stone-100 px-2 py-0.5 text-xs font-medium text-stone-600\">v1.5.0</span>\n <button x-show=\"view === 'list'\" @click.prevent=\"view = 'new_post'\" class=\"px-3 py-1.5 text-sm font-semibold bg-black text-white rounded-lg hover:bg-black/90 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black focus-visible:ring-offset-2\">\n New Post\n </button>\n </div>\n </header>\n\n <!-- Error State -->\n <div x-show=\"error\" x-transition class=\"p-3 mb-4 bg-red-50 text-red-800 border border-red-200 rounded-lg text-sm\" x-text=\"error\"></div>\n\n <!-- Main Content Area -->\n <main class=\"relative\">\n <!-- Loading Overlay -->\n <div x-show=\"isLoading\" x-transition:enter=\"transition-opacity duration-300\" x-transition:leave=\"transition-opacity duration-300\" class=\"absolute inset-0 z-10 flex items-center justify-center pt-10 bg-white/50 backdrop-blur-sm\">\n <i data-lucide=\"loader-circle\" class=\"h-6 w-6 animate-spin text-stone-400\"></i>\n </div>\n \n <!-- Views -->\n <div :class=\"{'opacity-50 blur-sm pointer-events-none': isLoading && view !== 'list'}\">\n <!-- Posts List -->\n <div x-show=\"view === 'list'\" x-transition>\n <div class=\"space-y-2\">\n <template x-for=\"post in posts\" :key=\"post.id\">\n <a href=\"#\" @click.prevent=\"selectPost(post.id)\" class=\"block p-3 rounded-lg hover:bg-stone-100 transition-colors duration-200\">\n <p class=\"font-semibold text-stone-900 truncate\" x-text=\"post.title\"></p>\n <div class=\"flex items-center justify-between mt-1.5\">\n <p class=\"text-xs text-stone-500\">By <span class=\"font-medium\" x-text=\"post.author_name\"></span> · <span x-text=\"formatDate(post.created_at)\"></span></p>\n <div class=\"flex items-center gap-1 text-xs text-stone-500\">\n <i data-lucide=\"message-square\" class=\"h-3.5 w-3.5\"></i>\n <span x-text=\"post.comment_count\"></span>\n </div>\n </div>\n </a>\n </template>\n </div>\n </div>\n\n <!-- New Post Form -->\n <div x-show=\"view === 'new_post'\" x-transition class=\"p-1\">\n <form @submit.prevent=\"submitPost()\" class=\"space-y-3\">\n <input type=\"text\" x-model=\"newPost.title\" placeholder=\"Post Title\" required class=\"w-full px-3 py-2 text-[14px] leading-6 bg-white border border-stone-200 rounded-2xl transition focus:outline-none focus:ring-2 focus:ring-black\">\n <textarea x-model=\"newPost.content\" placeholder=\"What's on your mind?\" required rows=\"8\" @input=\"autoResize($el)\" class=\"w-full px-3 py-2 text-[14px] leading-6 bg-white border border-stone-200 rounded-2xl transition resize-none focus:outline-none focus:ring-2 focus:ring-black\"></textarea>\n <div class=\"flex justify-end gap-2 pt-1\">\n <button type=\"button\" @click=\"goToList()\" :disabled=\"submitting\" class=\"px-4 py-2 font-semibold bg-stone-200 text-stone-800 rounded-2xl hover:bg-stone-300 transition-colors disabled:opacity-50\">Cancel</button>\n <button type=\"submit\" :disabled=\"submitting\" class=\"px-4 py-2 font-semibold bg-black text-white rounded-2xl hover:bg-black/90 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black focus-visible:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed\">\n <span x-show=\"!submitting\">Post</span>\n <span x-show=\"submitting\">Posting...</span>\n </button>\n </div>\n </form>\n </div>\n\n <!-- Single Post & Comments -->\n <div x-show=\"view === 'post' && currentPost\" x-transition class=\"space-y-6\">\n <article class=\"p-0\">\n <div class=\"markdown-body\" data-post-content x-html=\"render(currentPost.content)\"></div>\n </article>\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-bold text-stone-900 border-b border-stone-200 pb-2\">Comments</h2>\n <div x-show=\"!isLoading && currentPost.comments.length === 0\" class=\"text-sm text-stone-500 text-center py-4\">No comments yet.</div>\n <div class=\"space-y-3\">\n <template x-for=\"comment in currentPost.comments\" :key=\"comment.id\">\n <div class=\"p-3 bg-stone-50 rounded-lg\">\n <p class=\"text-xs text-stone-500 mb-1.5\"><span class=\"font-semibold text-stone-700\" x-text=\"comment.author_name\"></span> · <span x-text=\"formatDate(comment.created_at)\"></span></p>\n <div class=\"markdown-body bg-transparent\" :data-comment-id=\"comment.id\" x-html=\"render(comment.content)\"></div>\n </div>\n </template>\n </div>\n <form @submit.prevent=\"submitComment()\" class=\"pt-4 flex items-start gap-2\">\n <textarea x-model=\"newComment\" placeholder=\"Add a comment...\" required rows=\"1\" @input=\"autoResize($el)\" class=\"flex-1 resize-none rounded-2xl border border-stone-200 bg-white px-3 py-2 text-[14px] leading-6 placeholder:text-stone-400 focus:outline-none focus:ring-2 focus:ring-black\"></textarea>\n <button type=\"submit\" aria-label=\"Reply\" :disabled=\"submitting\" class=\"shrink-0 rounded-2xl bg-black text-white h-10 w-10 inline-flex items-center justify-center shadow-sm hover:bg-black/90 active:scale-[.98] transition-transform disabled:opacity-50\">\n <i data-lucide=\"send\" class=\"h-5 w-5\"></i>\n </button>\n </form>\n </section>\n </div>\n </div>\n </main>\n</div>\n<script>\nfunction suneForums() {\n const S_KEY = window.SUNE.id+'-forums', L_KEY = S_KEY+'-lock', L_TIMEOUT=30000, D1_API='https://d1p.awww.workers.dev';\n return {\n view:'list', posts:[], currentPost:null, isLoading:!0, error:'', submitting:!1,\n newPost:{title:'', content:''}, newComment:'',\n md:window.markdownit({html:!1, breaks:!0, linkify:!0}),\n init() {\n const lock=JSON.parse(localStorage.getItem(L_KEY));if(lock&&Date.now()-lock.ts>L_TIMEOUT)localStorage.removeItem(L_KEY);\n const c=JSON.parse(localStorage.getItem(S_KEY))||{};this.newPost=c.newPost||{title:'',content:''};this.newComment=c.newComment||'';\n this.$watch('newPost',()=>this.saveState());this.$watch('newComment',()=>this.saveState());\n this.$watch('currentPost',p=>{if(p&&this.view==='post')this.$nextTick(()=>this.enhanceAllMarkdown())});\n this.fetchPosts()\n },\n isLocked(){if(this.submitting)return!0;const lock=JSON.parse(localStorage.getItem(L_KEY));return lock&&Date.now()-lock.ts<L_TIMEOUT},\n setLock(){localStorage.setItem(L_KEY,JSON.stringify({ts:Date.now()}));this.submitting=!0},\n clearLock(){localStorage.removeItem(L_KEY);this.submitting=!1},\n saveState(){localStorage.setItem(S_KEY,JSON.stringify({newPost:this.newPost,newComment:this.newComment}))},\n async query(sql, p=[]){\n this.isLoading=!0; this.error='';\n try {\n const r=await fetch(D1_API,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({query:sql,params:p})});\n if(!r.ok){const d=await r.json().catch(()=>null);throw new Error(d?.error||`HTTP Error ${r.status}`)}\n const d=await r.json();if(d.error)throw new Error(d.details||d.error);return d.results\n } catch(e){this.error=e.message;console.error(e);return null} finally {this.isLoading=!1}\n },\n async fetchPosts(){\n const sql=\"SELECT p.id, p.author_name, p.title, p.created_at, (SELECT COUNT(*) FROM comments c WHERE c.post_id = p.id) as comment_count FROM posts p ORDER BY p.created_at DESC\";\n const r=await this.query(sql);\n if(r)this.posts=r\n },\n async selectPost(id){\n this.view='post'; this.currentPost=null;\n const[p,c]=await Promise.all([this.query(\"SELECT * FROM posts WHERE id = ?\", [id]),this.query(\"SELECT * FROM comments WHERE post_id = ? ORDER BY created_at ASC\", [id])]);\n if(p&&p.length)this.currentPost={...p[0],comments:c||[]}\n },\n async submitPost(){\n if(this.isLocked())return;const{title:t,content:c}=this.newPost;if(!t.trim()||!c.trim())return;\n this.setLock();\n try{\n await this.query(\"INSERT INTO posts(author_name,title,content)VALUES(?,?,?)\",[window.USER?.name||'Anonymous',t.trim(),c.trim()]);\n if(!this.error){this.newPost={title:'',content:''};this.goToList()}\n }finally{this.clearLock()}\n },\n async submitComment(){\n if(this.isLocked())return;const c=this.newComment.trim();if(!c||!this.currentPost)return;\n this.setLock();\n try{\n await this.query(\"INSERT INTO comments(post_id,author_name,content)VALUES(?,?,?)\",[this.currentPost.id,window.USER?.name||'Anonymous',c]);\n if(!this.error){this.newComment='';await this.selectPost(this.currentPost.id)}\n }finally{this.clearLock()}\n },\n goToList(){this.view='list';this.currentPost=null;this.error='';this.fetchPosts()},\n formatDate(s){if(!s)return'';try{return new Date(s.replace(' ','T')+'Z').toLocaleDateString(undefined,{month:'short',day:'numeric'})}catch{return s}},\n render(t){return this.md.render(t||'')},\n autoResize(el){el.style.height='auto';el.style.height=(el.scrollHeight+2)+'px'},\n enhanceAllMarkdown(){\n if(!window.enhanceCodeBlocks)return;\n const postEl=this.$root.querySelector('[data-post-content]');if(postEl)window.enhanceCodeBlocks(postEl,!0);\n this.$root.querySelectorAll('[data-comment-id]').forEach(el=>window.enhanceCodeBlocks(el,!0))\n }\n }\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,"json_output":false,"ignore_master_prompt":false,"json_schema":""},"storage":{}}] |