Add author auth, forking, tags, and stats tracking

Introduce token-based author authentication (register/verify API),
   skill forking with EditGate protection, tag metadata on skills,
   and download/push stats. Enhanced push scripts with token auth
   and per-skill filtering. Updated UI with stats, tags, and
   author info on skill cards.
This commit is contained in:
Alejandro Martinez
2026-02-12 14:37:40 +01:00
parent 39d8afb251
commit aa477a553b
80 changed files with 3618 additions and 660 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{_ as i}from"./_plugin-vue_export-helper.DlAUqK2U.js";import{c as d,a as l,b as c,t as u,e as m,f as p,o as f}from"./runtime-core.esm-bundler.D9KZBfyO.js";const _=m({__name:"DeleteButton",props:{slug:{}},setup(n,{expose:t}){t();const o=n,e=p(!1);async function a(){if(confirm(`Delete "${o.slug}"? This cannot be undone.`)){e.value=!0;try{const s=await fetch(`/api/skills/${o.slug}`,{method:"DELETE"});if(!s.ok&&s.status!==204)throw new Error("Failed to delete");window.location.href="/"}catch{alert("Failed to delete skill."),e.value=!1}}}const r={props:o,deleting:e,handleDelete:a};return Object.defineProperty(r,"__isScriptSetup",{enumerable:!1,value:!0}),r}}),b=["disabled"];function g(n,t,o,e,a,r){return f(),d("button",{onClick:e.handleDelete,disabled:e.deleting,class:"inline-flex items-center gap-1.5 rounded-lg border border-red-500/20 bg-red-500/5 px-3.5 py-2 text-sm font-medium text-red-400 hover:bg-red-500/10 hover:border-red-500/30 disabled:opacity-50 active:scale-[0.97] transition-all"},[t[0]||(t[0]=l("svg",{class:"h-3.5 w-3.5",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor","stroke-width":"2"},[l("path",{"stroke-linecap":"round","stroke-linejoin":"round",d:"m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"})],-1)),c(" "+u(e.deleting?"Deleting...":"Delete"),1)],8,b)}const v=i(_,[["render",g]]);export{v as default};

View File

@@ -0,0 +1 @@
import{c as d,a as o,d as u,l as b,g as p,t as f,w as C,v as _,b as m,T as S,F as T,h as E,i,m as M,o as c}from"./runtime-dom.esm-bundler.A7MyAQcw.js";import{_ as V}from"./_plugin-vue_export-helper.DlAUqK2U.js";const B=E({__name:"EditGate",props:{slug:{},authorEmail:{},authorName:{},authorHasToken:{type:Boolean}},setup(h,{expose:e}){e();const r=h,t=i(!1),s=i(""),a=i(""),n=i(!1),v=i();async function x(){if(!r.authorEmail||!r.authorHasToken){window.location.href=`/${r.slug}/edit`;return}const l=localStorage.getItem("skillshere-token")||"";if(l)try{if((await fetch("/api/auth/verify",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:r.authorEmail,token:l})})).ok){localStorage.setItem("skillshere-token",l),window.location.href=`/${r.slug}/edit`;return}}catch{}t.value=!0,a.value="",s.value="",M(()=>v.value?.focus())}async function g(){n.value=!0,a.value="";try{const l=await fetch("/api/auth/verify",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:r.authorEmail,token:s.value})});if(!l.ok){const y=await l.json();a.value=y.error||"Invalid token";return}localStorage.setItem("skillshere-token",s.value),window.location.href=`/${r.slug}/edit`}catch{a.value="Could not verify token"}finally{n.value=!1}}function w(){t.value=!1,window.location.href=`/new?from=${encodeURIComponent(r.slug)}`}const k={props:r,showModal:t,token:s,error:a,verifying:n,tokenInput:v,handleClick:x,verify:g,forkSkill:w};return Object.defineProperty(k,"__isScriptSetup",{enumerable:!1,value:!0}),k}}),I={class:"w-full max-w-md rounded-2xl border border-white/[0.08] bg-[var(--color-surface-200)] p-6 shadow-2xl"},N={class:"text-sm text-gray-500 mb-4"},j={class:"text-gray-300"},O={key:0,class:"mt-2 text-sm text-red-400"},A={class:"mt-4 flex items-center gap-3"},H=["disabled"],P={key:0,class:"h-4 w-4 animate-spin",fill:"none",viewBox:"0 0 24 24"};function F(h,e,r,t,s,a){return c(),d(T,null,[o("button",{onClick:t.handleClick,class:"inline-flex items-center gap-1.5 rounded-lg border border-white/[0.08] bg-surface-200 px-3.5 py-2 text-sm font-medium text-gray-300 hover:border-white/[0.15] hover:text-white transition-all"},[...e[3]||(e[3]=[o("svg",{class:"h-3.5 w-3.5",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor","stroke-width":"2"},[o("path",{"stroke-linecap":"round","stroke-linejoin":"round",d:"m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"})],-1),u(" Edit ",-1)])]),(c(),b(S,{to:"body"},[t.showModal?(c(),d("div",{key:0,class:"fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm",onClick:e[2]||(e[2]=p(n=>t.showModal=!1,["self"]))},[o("div",I,[e[7]||(e[7]=o("h3",{class:"text-lg font-semibold text-white mb-1"},"Author Verification",-1)),o("p",N,[e[4]||(e[4]=u(" This skill is owned by ",-1)),o("strong",j,f(r.authorName||r.authorEmail),1),e[5]||(e[5]=u(". Enter your token to edit. ",-1))]),o("form",{onSubmit:p(t.verify,["prevent"])},[C(o("input",{ref:"tokenInput","onUpdate:modelValue":e[0]||(e[0]=n=>t.token=n),type:"password",placeholder:"Paste your author token...",class:"w-full rounded-xl border border-white/[0.06] bg-[var(--color-surface-100)] px-4 py-2.5 text-sm text-white placeholder-gray-600 font-mono focus:border-[var(--color-accent-500)]/50 focus:outline-none focus:ring-1 focus:ring-[var(--color-accent-500)]/20 transition-all"},null,512),[[_,t.token]]),t.error?(c(),d("p",O,f(t.error),1)):m("",!0),o("div",A,[o("button",{type:"submit",disabled:t.verifying||!t.token,class:"inline-flex items-center gap-2 rounded-xl bg-[var(--color-accent-500)] px-5 py-2 text-sm font-semibold text-white shadow-lg shadow-[var(--color-accent-500)]/20 hover:bg-[var(--color-accent-600)] disabled:opacity-50 active:scale-[0.97] transition-all"},[t.verifying?(c(),d("svg",P,[...e[6]||(e[6]=[o("circle",{class:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor","stroke-width":"4"},null,-1),o("path",{class:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"},null,-1)])])):m("",!0),u(" "+f(t.verifying?"Verifying...":"Continue to Edit"),1)],8,H),o("button",{type:"button",onClick:t.forkSkill,class:"text-sm text-[var(--color-accent-400)] hover:text-[var(--color-accent-300)] transition-colors"}," Fork instead "),o("button",{type:"button",onClick:e[1]||(e[1]=n=>t.showModal=!1),class:"ml-auto text-sm text-gray-600 hover:text-gray-300 transition-colors"}," Cancel ")])],32)])])):m("",!0)]))],64)}const D=V(B,[["render",F]]);export{D as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{v as m}from"./runtime-dom.esm-bundler.ALO2-icn.js";import{_ as f}from"./_plugin-vue_export-helper.DlAUqK2U.js";import{c as _,a as r,w as h,e as v,g as x,f as y,o as w}from"./runtime-core.esm-bundler.D9KZBfyO.js";const k=v({__name:"SkillSearch",setup(c,{expose:e}){e();const n=y("");x(n,a=>{const o=a.toLowerCase().trim();document.querySelectorAll("[data-skill]").forEach(s=>{const i=s.dataset.name||"",d=s.dataset.description||"",u=s.dataset.tools||"",p=!o||i.includes(o)||d.includes(o)||u.includes(o);s.style.display=p?"":"none"})});const t={query:n};return Object.defineProperty(t,"__isScriptSetup",{enumerable:!1,value:!0}),t}}),b={class:"mb-6 max-w-md"},S={class:"relative"};function g(c,e,n,t,a,o){return w(),_("div",b,[r("div",S,[e[1]||(e[1]=r("svg",{class:"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-600",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor","stroke-width":"2"},[r("path",{"stroke-linecap":"round","stroke-linejoin":"round",d:"m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"})],-1)),h(r("input",{"onUpdate:modelValue":e[0]||(e[0]=l=>t.query=l),type:"text",placeholder:"Search skills...",class:"w-full rounded-xl border border-white/[0.06] bg-[var(--color-surface-100)] pl-10 pr-4 py-2.5 text-sm text-white placeholder-gray-600 focus:border-[var(--color-accent-500)]/50 focus:outline-none focus:ring-1 focus:ring-[var(--color-accent-500)]/20 transition-all"},null,512),[[m,t.query]])])])}const j=f(k,[["render",g]]);export{j as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{c as m,d as y}from"./runtime-dom.esm-bundler.ALO2-icn.js";import{e as v,i as r,S}from"./runtime-core.esm-bundler.D9KZBfyO.js";const g=()=>{},A=v({props:{value:String,name:String,hydrate:{type:Boolean,default:!0}},setup({name:t,value:e,hydrate:a}){if(!e)return()=>null;let c=a?"astro-slot":"astro-static-slot";return()=>r(c,{name:t,innerHTML:e})}});var h=A;let p=new WeakMap;var M=t=>async(e,a,c,{client:l})=>{if(!t.hasAttribute("ssr"))return;const f=e.name?`${e.name} Host`:void 0,i={};for(const[n,o]of Object.entries(c))i[n]=()=>r(h,{value:o,name:n==="default"?void 0:n});const u=l!=="only",d=u?m:y;let s=p.get(t);if(s)s.props=a,s.slots=i,s.component.$forceUpdate();else{s={props:a,slots:i};const n=d({name:f,render(){let o=r(e,s.props,s.slots);return s.component=this,b(e.setup)&&(o=r(S,null,o)),o}});n.config.idPrefix=t.getAttribute("prefix")??void 0,await g(),n.mount(t,u),p.set(t,s),t.addEventListener("astro:unmount",()=>n.unmount(),{once:!0})}};function b(t){const e=t?.constructor;return e&&e.name==="AsyncFunction"}export{M as default};

1
dist/client/_astro/client.BnTlSu1B.js vendored Normal file
View File

@@ -0,0 +1 @@
import{h as m,s as r,u as y,x as v,S}from"./runtime-dom.esm-bundler.A7MyAQcw.js";const g=()=>{},h=m({props:{value:String,name:String,hydrate:{type:Boolean,default:!0}},setup({name:t,value:s,hydrate:o}){if(!s)return()=>null;let c=o?"astro-slot":"astro-static-slot";return()=>r(c,{name:t,innerHTML:s})}});var A=h;let p=new WeakMap;var H=t=>async(s,o,c,{client:l})=>{if(!t.hasAttribute("ssr"))return;const f=s.name?`${s.name} Host`:void 0,u={};for(const[n,a]of Object.entries(c))u[n]=()=>r(A,{value:a,name:n==="default"?void 0:n});const i=l!=="only",d=i?y:v;let e=p.get(t);if(e)e.props=o,e.slots=u,e.component.$forceUpdate();else{e={props:o,slots:u};const n=d({name:f,render(){let a=r(s,e.props,e.slots);return e.component=this,b(s.setup)&&(a=r(S,null,a)),a}});n.config.idPrefix=t.getAttribute("prefix")??void 0,await g(),n.mount(t,i),p.set(t,e),t.addEventListener("astro:unmount",()=>n.unmount(),{once:!0})}};function b(t){const s=t?.constructor;return s&&s.name==="AsyncFunction"}export{H as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

21
dist/client/favicon.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB