feat: add delete button for dev preview targets in miniapp UI

Add × button to each dev target card and expose the unregister action
through the miniapp API, allowing users to remove registered servers
directly from the Dev Preview tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dj-oyu 2026-02-24 03:05:58 +09:00
parent 36b4863ce0
commit bd7799f031
2 changed files with 52 additions and 0 deletions

View file

@ -575,6 +575,15 @@ func (h *Handler) apiDev(w http.ResponseWriter, r *http.Request) {
writeJSON(w, map[string]any{"error": err.Error()}) writeJSON(w, map[string]any{"error": err.Error()})
return return
} }
case "unregister":
if req.ID == "" {
writeJSON(w, map[string]any{"error": "id is required"})
return
}
if err := h.UnregisterDevTarget(req.ID); err != nil {
writeJSON(w, map[string]any{"error": err.Error()})
return
}
default: default:
writeJSON(w, map[string]any{"error": "unknown action"}) writeJSON(w, map[string]any{"error": "unknown action"})
return return

View file

@ -684,6 +684,26 @@
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.dev-target-delete {
flex-shrink: 0;
width: 24px;
height: 24px;
padding: 6px;
margin: -6px;
margin-left: 8px;
border-radius: 50%;
color: var(--hint);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: color 0.15s, background 0.15s;
-webkit-tap-highlight-color: transparent;
}
.dev-target-delete:active {
color: #ff3b30;
background: rgba(255, 59, 48, 0.1);
}
</style> </style>
</head> </head>
@ -1346,12 +1366,23 @@ function renderDevFromData(data) {
'<span class="dev-target-dot' + dotClass + '"></span>' + '<span class="dev-target-dot' + dotClass + '"></span>' +
'<span class="dev-target-name">' + escapeHtml(t.name) + '</span>' + '<span class="dev-target-name">' + escapeHtml(t.name) + '</span>' +
'<span class="dev-target-url">' + escapeHtml(displayUrl) + '</span>' + '<span class="dev-target-url">' + escapeHtml(displayUrl) + '</span>' +
'<span class="dev-target-delete" data-del-id="' + escapeAttr(t.id) + '" data-del-name="' + escapeAttr(t.name) + '">&times;</span>' +
'</div>'; '</div>';
}).join(''); }).join('');
} }
// Delegate click on target cards // Delegate click on target cards
document.getElementById('dev-targets-list').addEventListener('click', function(e) { document.getElementById('dev-targets-list').addEventListener('click', function(e) {
var delBtn = e.target.closest('.dev-target-delete');
if (delBtn) {
e.stopPropagation();
var id = delBtn.dataset.delId;
var name = delBtn.dataset.delName;
if (confirm('Remove "' + name + '"?')) {
postDevUnregister(id);
}
return;
}
var card = e.target.closest('[data-dev-id]'); var card = e.target.closest('[data-dev-id]');
if (!card) return; if (!card) return;
postDevAction(card.dataset.devId); postDevAction(card.dataset.devId);
@ -1372,6 +1403,18 @@ async function postDevAction(id) {
} catch(e) {} } catch(e) {}
} }
async function postDevUnregister(id) {
try {
var res = await fetch(API_BASE + '/miniapp/api/dev?initData=' + encodeURIComponent(initData), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'unregister', id: id }),
});
var data = await res.json();
if (!data.error) renderDevFromData(data);
} catch(e) {}
}
function loadDev() { function loadDev() {
apiFetch('/miniapp/api/dev').then(renderDevFromData).catch(function() {}); apiFetch('/miniapp/api/dev').then(renderDevFromData).catch(function() {});
} }