| |
);
|
| |
}
|
| |
|
| + |
// Hide the Attest button for the artifact's own author — attestation
|
| + |
// is a vouching signal from someone other than the author, and a
|
| + |
// self-attestation is a no-op in the COB. Delegates who happen to also
|
| + |
// be the author follow the same rule.
|
| + |
function isOwnArtifact(artifact: Artifact): boolean {
|
| + |
return publicKeyFromDid(artifact.author.did) === config.publicKey;
|
| + |
}
|
| + |
|
| |
type PeerRole = "author" | "delegate" | "other";
|
| |
|
| |
// Classify a peer DID relative to a specific artifact so the UI can
|
| |
// away by default but can be expanded per artifact.
|
| |
const revealLocations = $state<Record<string, boolean>>({});
|
| |
const revealAttestations = $state<Record<string, boolean>>({});
|
| + |
// Per-artifact redact form: { reason } when open, undefined when closed.
|
| + |
// Inline because window.prompt is a no-op in Tauri's WebKit webview, so
|
| + |
// the dialog-based flow silently did nothing.
|
| + |
const redactDraft = $state<Record<string, { reason: string } | undefined>>(
|
| + |
{},
|
| + |
);
|
| |
|
| |
const draft = $state<Record<string, { key: string; value: string }>>({});
|
| |
$effect(() => {
|
| |
}
|
| |
}
|
| |
|
| - |
async function redact(artifact: Artifact) {
|
| - |
const reason = window.prompt(
|
| - |
`Redact artifact "${artifact.name}"?\n\n` +
|
| - |
"This adds a redaction signed by you to the release COB. The CID stays\n" +
|
| - |
"on the COB so other peers can see why you redacted it. Type a reason:",
|
| - |
);
|
| - |
if (reason === null) return;
|
| + |
function openRedact(artifact: Artifact) {
|
| + |
redactDraft[artifact.cid] = { reason: "" };
|
| + |
}
|
| + |
|
| + |
function cancelRedact(artifact: Artifact) {
|
| + |
redactDraft[artifact.cid] = undefined;
|
| + |
}
|
| + |
|
| + |
async function confirmRedact(artifact: Artifact) {
|
| + |
const draft = redactDraft[artifact.cid];
|
| + |
if (!draft) return;
|
| + |
const reason = draft.reason.trim();
|
| + |
if (!reason) {
|
| + |
actionErrors[artifact.cid] = "Please provide a reason for the redaction.";
|
| + |
return;
|
| + |
}
|
| |
busy[artifact.cid] = true;
|
| |
actionErrors[artifact.cid] = undefined;
|
| |
try {
|
| |
font-size: 1rem;
|
| |
padding: 0;
|
| |
}
|
| + |
.redact-form {
|
| + |
margin-top: 0.5rem;
|
| + |
padding: 0.75rem;
|
| + |
border: 1px solid var(--color-feedback-error-border);
|
| + |
border-radius: var(--border-radius-sm);
|
| + |
background-color: var(--color-feedback-error-bg);
|
| + |
display: flex;
|
| + |
flex-direction: column;
|
| + |
gap: 0.5rem;
|
| + |
}
|
| + |
.redact-form-title {
|
| + |
font: var(--txt-body-s-semibold);
|
| + |
color: var(--color-feedback-error-text);
|
| + |
}
|
| + |
.redact-form textarea {
|
| + |
resize: vertical;
|
| + |
min-height: 3rem;
|
| + |
padding: 0.4rem 0.5rem;
|
| + |
border: 1px solid var(--color-border-subtle);
|
| + |
border-radius: var(--border-radius-sm);
|
| + |
background-color: var(--color-surface-canvas);
|
| + |
font: var(--txt-body-s-regular);
|
| + |
}
|
| + |
.redact-form .row {
|
| + |
display: flex;
|
| + |
gap: 0.5rem;
|
| + |
justify-content: flex-end;
|
| + |
}
|
| + |
.redact-form button {
|
| + |
padding: 0.25rem 0.75rem;
|
| + |
border: 1px solid var(--color-border-subtle);
|
| + |
border-radius: var(--border-radius-sm);
|
| + |
background-color: var(--color-surface-subtle);
|
| + |
cursor: pointer;
|
| + |
font: var(--txt-body-s-regular);
|
| + |
}
|
| + |
.redact-form button.confirm {
|
| + |
background-color: var(--color-feedback-error-fill);
|
| + |
color: var(--color-text-inverse);
|
| + |
border-color: var(--color-feedback-error-fill);
|
| + |
}
|
| + |
.redact-form button:disabled {
|
| + |
opacity: 0.5;
|
| + |
cursor: default;
|
| + |
}
|
| |
.empty {
|
| |
padding: 2rem;
|
| |
color: var(--color-text-secondary);
|
| |
Seed
|
| |
</button>
|
| |
{/if}
|
| - |
<button
|
| - |
onclick={() => attest(artifact)}
|
| - |
disabled={busy[artifact.cid]}>
|
| - |
Attest
|
| - |
</button>
|
| + |
{#if !isOwnArtifact(artifact)}
|
| + |
<button
|
| + |
onclick={() => attest(artifact)}
|
| + |
disabled={busy[artifact.cid]}>
|
| + |
Attest
|
| + |
</button>
|
| + |
{/if}
|
| |
{#if canEditMetadata(artifact)}
|
| |
<button
|
| |
class="danger"
|
| - |
onclick={() => redact(artifact)}
|
| - |
disabled={busy[artifact.cid]}>
|
| + |
onclick={() => openRedact(artifact)}
|
| + |
disabled={busy[artifact.cid] ||
|
| + |
redactDraft[artifact.cid] !== undefined}>
|
| |
Redact
|
| |
</button>
|
| |
{/if}
|
| |
{/each}
|
| |
</div>
|
| |
{/if}
|
| + |
{#if redactDraft[artifact.cid]}
|
| + |
<form
|
| + |
class="redact-form"
|
| + |
onsubmit={e => {
|
| + |
e.preventDefault();
|
| + |
void confirmRedact(artifact);
|
| + |
}}>
|
| + |
<div class="redact-form-title">
|
| + |
Redact "{artifact.name}"?
|
| + |
</div>
|
| + |
<div style:font="var(--txt-body-s-regular)">
|
| + |
This records a signed redaction on the release COB. Other
|
| + |
peers will see it and the artifact will appear blurred for
|
| + |
delegates and authors. The CID and existing locations stay on
|
| + |
the COB.
|
| + |
</div>
|
| + |
<textarea
|
| + |
placeholder="Reason (required)"
|
| + |
bind:value={redactDraft[artifact.cid]!.reason}
|
| + |
disabled={busy[artifact.cid]}>
|
| + |
</textarea>
|
| + |
<div class="row">
|
| + |
<button
|
| + |
type="button"
|
| + |
onclick={() => cancelRedact(artifact)}
|
| + |
disabled={busy[artifact.cid]}>
|
| + |
Cancel
|
| + |
</button>
|
| + |
<button
|
| + |
type="submit"
|
| + |
class="confirm"
|
| + |
disabled={busy[artifact.cid]}>
|
| + |
{busy[artifact.cid] ? "Redacting…" : "Redact"}
|
| + |
</button>
|
| + |
</div>
|
| + |
</form>
|
| + |
{/if}
|
| |
{#if artifact.metadata && Object.keys(artifact.metadata).length > 0}
|
| |
<dl class="metadata">
|
| |
{#each Object.entries(artifact.metadata) as [key, value] (key)}
|