Skip to content

Commit 3cb8a62

Browse files
forge: Delete button on issue/PR entries (author-only)
Surfaces the delete that already existed at the backend (pod-WAC on a body resource, or the hosted DELETE endpoint — both author-enforced, both tested) as a per-entry Delete button in the thread UI. The client does authFetch DELETE on the entry's resourceUrl (xlogin proof or bearer token); a non-author gets 403, the author's delete reloads to the existing "content removed by its author" tombstone. Shown to any signed-in user like the close/reopen button. Test asserts the button renders with its resource URL. 131 green.
1 parent c2b9963 commit 3cb8a62

2 files changed

Lines changed: 21 additions & 2 deletions

File tree

forge/plugin.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2348,7 +2348,8 @@ function el(tag,props){const e=document.createElement(tag);Object.assign(e,props
23482348
function setMsg(text){const m=document.getElementById('form-msg');if(m)m.textContent=text}
23492349
function shortId(id){return id.length>28?id.slice(0,16)+'…'+id.slice(-6):id}
23502350
function wireForms(){for(const id of ['submit-issue','submit-comment','toggle-state','submit-pull','do-merge','do-enable','submit-txo']){
2351-
const b=document.getElementById(id);if(b)b.disabled=!(T()||X())}}
2351+
const b=document.getElementById(id);if(b)b.disabled=!(T()||X())}
2352+
document.querySelectorAll('.del-entry').forEach(function(b){b.disabled=!(T()||X())})}
23522353
function renderAuth(){
23532354
const box=document.getElementById('forge-auth');if(!box)return;
23542355
box.textContent='';
@@ -2499,6 +2500,18 @@ if(tx)tx.onclick=async function(){
24992500
location.reload();
25002501
}catch(e){setMsg(String(e.message||e))}
25012502
};
2503+
document.querySelectorAll('.del-entry').forEach(function(b){b.onclick=async function(){
2504+
const url=b.getAttribute('data-del');if(!url)return;
2505+
if(!confirm('Delete this content? It will show as “content removed by its author.”'))return;
2506+
const was=b.disabled;b.disabled=true;setMsg('Deleting…');
2507+
try{
2508+
let res;
2509+
if(X()){res=await window.xlogin.authFetch(url,{method:'DELETE'})}
2510+
else{res=await fetch(url,{method:'DELETE',headers:{authorization:'Bearer '+T()}})}
2511+
if(!(res.ok||res.status===204||res.status===404))throw new Error(res.status===403?'only the author can delete this':'HTTP '+res.status);
2512+
location.reload();
2513+
}catch(e){setMsg(String(e&&e.message||e));b.disabled=was}
2514+
}});
25022515
document.addEventListener('xlogin',function(){renderAuth();wireForms()});
25032516
document.addEventListener('xlogout',function(){renderAuth();wireForms()});
25042517
if(window.xlogin&&window.xlogin.ready)window.xlogin.ready.then(function(){renderAuth();wireForms()});
@@ -2558,7 +2571,12 @@ ${pager}
25582571
const ownerBadge = ownerFromAgent(e.author) === owner ? ' <span class="badge">owner</span>' : '';
25592572
// Podless authors: their words live in pluginDir, not a pod — say so.
25602573
const hostedTag = e.hosted ? ' <span class="badge">hosted by the forge</span>' : '';
2561-
const head = `${identicon(who)} <a href="${esc(authorHref(e.author))}"><b>${esc(who)}</b></a>${ownerBadge}${hostedTag}
2574+
// Author-only Delete (server-enforced: pod WAC on the body, or the hosted
2575+
// DELETE endpoint — a non-author's request 403s). Shown to any signed-in
2576+
// user like the close/reopen button; deleting shows the existing tombstone.
2577+
const del = (e.removed || !e.resourceUrl) ? ''
2578+
: `<button class="btn del-entry" type="button" data-del="${esc(e.resourceUrl)}" disabled title="Delete (author only)" style="float:right;margin:6px;padding:2px 8px;font-size:12px">Delete</button>`;
2579+
const head = `${del}${identicon(who)} <a href="${esc(authorHref(e.author))}"><b>${esc(who)}</b></a>${ownerBadge}${hostedTag}
25622580
<span class="muted">${i === 0 ? openVerb : 'commented'} ${relTime(e.at)}</span>`;
25632581
const slot = e.removed
25642582
? '<div class="removed">content removed by its author</div>'

forge/test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,7 @@ describe('forge plugin', () => {
10191019
assert.ok(!html.includes(`>${pkA}<`), 'raw hex never rendered as a display name');
10201020
assert.ok(html.includes(`/.well-known/did/nostr/${pkA}`), "author links to core's DID-document route");
10211021
assert.ok(html.includes('>owner</span>'), 'hex-namespace owner badge still works');
1022+
assert.match(html, /class="btn del-entry"[^>]*data-del="[^"]*\/api\/hosted\//, 'per-entry Delete button renders pointing at the resource URL');
10221023
});
10231024

10241025
it("another agent cannot delete someone else's hosted content (403)", async () => {

0 commit comments

Comments
 (0)