mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-11-15 11:20:36 +08:00
292 lines
7.7 KiB
Svelte
292 lines
7.7 KiB
Svelte
<script lang="ts">
|
|
import type { CodeComments } from "@app/components/Diff.svelte";
|
|
import type { Author } from "@bindings/cob/Author";
|
|
import type { Revision } from "@bindings/cob/patch/Revision";
|
|
import type { CodeLocation } from "@bindings/cob/thread/CodeLocation";
|
|
import type { Embed } from "@bindings/cob/thread/Embed";
|
|
import type { Thread } from "@bindings/cob/thread/Thread";
|
|
import type { Config } from "@bindings/config/Config";
|
|
|
|
import { draftReviewStorage } from "@app/lib/draftReviewStorage";
|
|
import { nodeRunning } from "@app/lib/events";
|
|
import { invoke } from "@app/lib/invoke";
|
|
import * as roles from "@app/lib/roles";
|
|
import { didFromPublicKey, publicKeyFromDid } from "@app/lib/utils";
|
|
|
|
import { announce } from "@app/components/AnnounceSwitch.svelte";
|
|
import Changes from "@app/components/Changes.svelte";
|
|
import CommentComponent from "@app/components/Comment.svelte";
|
|
import Discussion from "@app/components/Discussion.svelte";
|
|
|
|
interface Props {
|
|
rid: string;
|
|
repoDelegates: Author[];
|
|
patchId: string;
|
|
revision: Revision;
|
|
config: Config;
|
|
loadPatch: () => Promise<void>;
|
|
hideDescription?: boolean;
|
|
}
|
|
|
|
const {
|
|
rid,
|
|
repoDelegates,
|
|
patchId,
|
|
revision,
|
|
config,
|
|
loadPatch,
|
|
hideDescription = false,
|
|
}: Props = $props();
|
|
|
|
const currentUserAuthor: Author = $derived({
|
|
did: didFromPublicKey(config.publicKey),
|
|
alias: config.alias ?? undefined,
|
|
});
|
|
|
|
const draftReview = $derived(
|
|
draftReviewStorage.getForRevision(revision.id, currentUserAuthor),
|
|
);
|
|
|
|
const hasPublishedReview = $derived(
|
|
revision.reviews?.some(r => r.author.did === currentUserAuthor.did) ??
|
|
false,
|
|
);
|
|
|
|
const codeCommentThreads: Thread<CodeLocation>[] = $derived(
|
|
draftReview
|
|
? (draftReview.comments
|
|
.filter(c => c.location && !c.replyTo)
|
|
.map(root => ({
|
|
root,
|
|
replies: draftReview.comments.filter(c => c.replyTo === root.id),
|
|
})) as Thread<CodeLocation>[])
|
|
: [],
|
|
);
|
|
|
|
async function createCodeComment(
|
|
body: string,
|
|
_embeds: Embed[],
|
|
_replyTo?: string,
|
|
location?: CodeLocation,
|
|
) {
|
|
if (!location) return;
|
|
try {
|
|
let draftId = draftReview?.id;
|
|
if (!draftId) {
|
|
draftId = draftReviewStorage.create(rid, revision.id);
|
|
}
|
|
draftReviewStorage.addComment(draftId, { body, location });
|
|
} catch (error) {
|
|
console.error("Creating code comment failed", error);
|
|
} finally {
|
|
await loadPatch();
|
|
}
|
|
}
|
|
|
|
async function editCodeComment(
|
|
commentId: string,
|
|
body: string,
|
|
_embeds: Embed[],
|
|
) {
|
|
if (!draftReview) return;
|
|
draftReviewStorage.updateComment(draftReview.id, commentId, {
|
|
body,
|
|
});
|
|
await loadPatch();
|
|
}
|
|
|
|
async function deleteCodeComment(commentId: string) {
|
|
if (!draftReview) return;
|
|
draftReviewStorage.deleteComment(draftReview.id, commentId);
|
|
await loadPatch();
|
|
}
|
|
|
|
const codeComments: CodeComments | undefined = $derived(
|
|
hasPublishedReview
|
|
? undefined
|
|
: {
|
|
config,
|
|
createComment: createCodeComment,
|
|
editComment: editCodeComment,
|
|
deleteComment: deleteCodeComment,
|
|
repoDelegates,
|
|
rid,
|
|
threads: codeCommentThreads,
|
|
canReply: true,
|
|
disableAttachments: "Publish your review to attach files",
|
|
},
|
|
);
|
|
|
|
const commentThreads = $derived(
|
|
((revision.discussion &&
|
|
revision.discussion
|
|
.filter(
|
|
comment =>
|
|
(comment.id !== revision.id && !comment.replyTo) ||
|
|
comment.replyTo === revision.id,
|
|
)
|
|
.map(thread => {
|
|
return {
|
|
root: thread,
|
|
replies:
|
|
revision.discussion &&
|
|
revision.discussion
|
|
.filter(comment => comment.replyTo === thread.id)
|
|
.sort((a, b) => a.edits[0].timestamp - b.edits[0].timestamp),
|
|
};
|
|
}, [])) as Thread[]) || [],
|
|
);
|
|
|
|
async function editRevision(description: string, embeds: Embed[]) {
|
|
try {
|
|
await invoke("edit_patch", {
|
|
rid: rid,
|
|
cobId: patchId,
|
|
action: {
|
|
type: "revision.edit",
|
|
revision: revision.id,
|
|
description,
|
|
embeds,
|
|
},
|
|
opts: { announce: $nodeRunning && $announce },
|
|
});
|
|
} catch (error) {
|
|
console.error("Editing revision failed: ", error);
|
|
} finally {
|
|
await loadPatch();
|
|
}
|
|
}
|
|
|
|
async function reactOnRevision(authors: Author[], reaction: string) {
|
|
try {
|
|
await invoke("edit_patch", {
|
|
rid: rid,
|
|
cobId: patchId,
|
|
action: {
|
|
type: "revision.react",
|
|
revision: revision.id,
|
|
reaction,
|
|
active: !authors.find(
|
|
({ did }) => publicKeyFromDid(did) === config.publicKey,
|
|
),
|
|
},
|
|
opts: { announce: $nodeRunning && $announce },
|
|
});
|
|
} catch (error) {
|
|
console.error("Editing reactions failed", error);
|
|
} finally {
|
|
await loadPatch();
|
|
}
|
|
}
|
|
|
|
async function createComment(
|
|
body: string,
|
|
embeds: Embed[],
|
|
replyTo?: string,
|
|
) {
|
|
try {
|
|
await invoke("create_patch_comment", {
|
|
rid: rid,
|
|
new: { id: patchId, body, embeds, replyTo, revision: revision.id },
|
|
opts: { announce: $nodeRunning && $announce },
|
|
});
|
|
} catch (error) {
|
|
console.error("Creating comment failed", error);
|
|
} finally {
|
|
await loadPatch();
|
|
}
|
|
}
|
|
|
|
async function editComment(commentId: string, body: string, embeds: Embed[]) {
|
|
try {
|
|
await invoke("edit_patch", {
|
|
rid: rid,
|
|
cobId: patchId,
|
|
action: {
|
|
type: "revision.comment.edit",
|
|
comment: commentId,
|
|
body,
|
|
revision: revision.id,
|
|
embeds,
|
|
},
|
|
opts: { announce: $nodeRunning && $announce },
|
|
});
|
|
} catch (error) {
|
|
console.error("Editing comment failed: ", error);
|
|
} finally {
|
|
await loadPatch();
|
|
}
|
|
}
|
|
|
|
async function reactOnComment(
|
|
commentId: string,
|
|
authors: Author[],
|
|
reaction: string,
|
|
) {
|
|
try {
|
|
await invoke("edit_patch", {
|
|
rid: rid,
|
|
cobId: patchId,
|
|
action: {
|
|
type: "revision.comment.react",
|
|
comment: commentId,
|
|
reaction,
|
|
revision: revision.id,
|
|
active: !authors.find(
|
|
({ did }) => publicKeyFromDid(did) === config.publicKey,
|
|
),
|
|
},
|
|
opts: { announce: $nodeRunning && $announce },
|
|
});
|
|
} catch (error) {
|
|
console.error("Editing comment reactions failed", error);
|
|
} finally {
|
|
await loadPatch();
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.patch-body {
|
|
margin-bottom: 1rem;
|
|
background-color: var(--color-surface-canvas);
|
|
border-radius: var(--border-radius-sm);
|
|
}
|
|
</style>
|
|
|
|
{#if !hideDescription}
|
|
<div class="txt-body-m-regular patch-body">
|
|
<CommentComponent
|
|
caption={revision.id === patchId ? "opened patch" : "created revision"}
|
|
{rid}
|
|
currentUserNid={config.publicKey}
|
|
id={revision.id}
|
|
lastEdit={revision.description.length > 1
|
|
? revision.description.at(-1)
|
|
: undefined}
|
|
author={revision.author}
|
|
reactions={revision.reactions}
|
|
timestamp={revision.timestamp}
|
|
body={revision.description.slice(-1)[0].body}
|
|
reactOnComment={reactOnRevision}
|
|
editComment={roles.isDelegateOrAuthor(
|
|
config.publicKey,
|
|
repoDelegates.map(delegate => delegate.did),
|
|
revision.author.did,
|
|
) && editRevision}>
|
|
</CommentComponent>
|
|
</div>
|
|
{/if}
|
|
|
|
<Discussion
|
|
cobId={patchId}
|
|
{commentThreads}
|
|
{config}
|
|
{createComment}
|
|
{editComment}
|
|
{reactOnComment}
|
|
{repoDelegates}
|
|
{rid} />
|
|
|
|
<Changes {rid} {patchId} {revision} {codeComments} />
|