Radish alpha
r
Radicle desktop app
Radicle
Git (anonymous pull)
Log in to clone via SSH
Single line selection only
Rūdolfs Ošiņš committed 1 year ago
commit f20df85d5eb85560c102f0e81254e17bf984c3e4
parent 5b8c21d7806a691e99e3baf526cdf32b87f2461c
5 files changed +152 -28
modified src/components/Changeset.svelte
@@ -2,9 +2,10 @@
  import type { CodeLocation } from "@bindings/cob/thread/CodeLocation";
  import type { Diff } from "@bindings/diff/Diff";
  import type { Embed } from "@bindings/cob/thread/Embed";
+
  import type { FileDiff } from "@bindings/diff/FileDiff";
  import type { Thread } from "@bindings/cob/thread/Thread";

-
  import FileDiff from "./FileDiff.svelte";
+
  import FileDiffComponent from "./FileDiff.svelte";

  interface Props {
    diff: Diff;
@@ -28,6 +29,18 @@
    head,
    codeCommentThreads,
  }: Props = $props();
+

+
  function filterCommentThreadsByPath(file: FileDiff) {
+
    if (
+
      file.status === "added" ||
+
      file.status === "deleted" ||
+
      file.status === "modified"
+
    ) {
+
      return codeCommentThreads?.filter(t => {
+
        return t.root.location?.path === file.path;
+
      });
+
    }
+
  }
</script>

<style>
@@ -44,13 +57,13 @@
<div class="diff-list">
  {#each diff.files as file}
    <div class="diff">
-
      <FileDiff
+
      <FileDiffComponent
        {file}
        {expanded}
        {rid}
        {createComment}
        {head}
-
        {codeCommentThreads} />
+
        codeCommentThreads={filterCommentThreadsByPath(file)} />
    </div>
  {/each}
</div>
modified src/components/Diff.svelte
@@ -10,6 +10,7 @@
    endHunkIdx: number;
    startLineIdx: number;
    endLineIdx: number;
+
    codeLocation: CodeLocation | undefined;
  }

  import type { CodeLocation } from "@bindings/cob/thread/CodeLocation";
@@ -19,7 +20,10 @@
  import type { Thread } from "@bindings/cob/thread/Thread";

  import escape from "lodash/escape";
+

  import CommentToggleInput from "./CommentToggleInput.svelte";
+
  import Icon from "@app/components/Icon.svelte";
+
  import ThreadComponent from "@app/components/Thread.svelte";

  interface Props {
    file: FileDiff;
@@ -37,7 +41,8 @@
  const { file, rid, createComment, head, codeCommentThreads }: Props =
    $props();

-
  console.log({ codeCommentThreads });
+
  // TODO: implement generating CodeLocation based on range selection.
+
  const enableLineRangeSelection = false;

  let dragging = $state(false);
  let selection: Selection | undefined = $state(undefined);
@@ -60,6 +65,22 @@
    }
  }

+
  console.log({ codeCommentThreads });
+
  function findLineComment(line: Modification) {
+
    return codeCommentThreads?.find(t => {
+
      if (line.type === "addition") {
+
        return t.root.location?.new?.range.end === line.lineNo + 1;
+
      } else if (line.type === "deletion") {
+
        return t.root.location?.old?.range.end === line.lineNo + 1;
+
      } else if (line.type === "context") {
+
        return (
+
          t.root.location?.new?.range.end === line.lineNoNew + 1 ||
+
          t.root.location?.old?.range.end === line.lineNoOld + 1
+
        );
+
      }
+
    });
+
  }
+

  function determineSelectedAnchor(
    side: Side,
    line: Modification,
@@ -107,6 +128,12 @@
    e.preventDefault();
    e.stopPropagation();

+
    // Allow line selection only when createComment is
+
    // supplied (i.e. in reviews).
+
    if (!createComment) {
+
      return;
+
    }
+

    dragging = true;

    selection = {
@@ -117,6 +144,30 @@
      endHunkIdx: hunkIdx,
      startLineIdx: lineIdx,
      endLineIdx: lineIdx,
+
      codeLocation: {
+
        commit: head,
+
        path: filePath(file, side),
+
        old:
+
          side === "left"
+
            ? {
+
                type: "lines",
+
                range: {
+
                  start: lineNumber(line, "left") as number,
+
                  end: (lineNumber(line, "left") as number) + 1,
+
                },
+
              }
+
            : null,
+
        new:
+
          side === "right"
+
            ? {
+
                type: "lines",
+
                range: {
+
                  start: lineNumber(line, "right") as number,
+
                  end: (lineNumber(line, "right") as number) + 1,
+
                },
+
              }
+
            : null,
+
      },
    };
  }

@@ -131,6 +182,10 @@
    e.preventDefault();
    e.stopPropagation();

+
    if (!enableLineRangeSelection) {
+
      return;
+
    }
+

    if (
      selection &&
      // Prevent inter-hunk selection.
@@ -155,6 +210,7 @@
        endHunkIdx: hunkIdx,
        startLineIdx: selection.startLineIdx,
        endLineIdx: lineIdx,
+
        codeLocation: undefined,
      };
    }
  }
@@ -166,6 +222,10 @@
    hunkIdx: number,
    lineIdx: number,
  ) {
+
    if (!enableLineRangeSelection) {
+
      return;
+
    }
+

    if (
      dragging &&
      selection &&
@@ -192,6 +252,7 @@
        endHunkIdx: hunkIdx,
        startLineIdx: selection.startLineIdx,
        endLineIdx: lineIdx,
+
        codeLocation: undefined,
      };
    }
  }
@@ -254,6 +315,7 @@
  .context > .sign {
    color: var(--color-foreground-disabled);
  }
+
  /*
  .start-marker::before {
    content: "";
    position: absolute;
@@ -274,6 +336,7 @@
    background-color: cyan;
    z-index: 1;
  }
+
  */
  /* Workaround for faking hover color change on end marker while dragging. */
  .start-marker,
  .end-marker {
@@ -329,6 +392,17 @@
    user-select: text;
    width: 100%;
    word-break: break-word;
+
    display: flex;
+
    align-items: flex-start;
+
  }
+
  .comment-icon {
+
    margin-left: auto;
+
    margin-right: 1rem;
+
    margin-top: 3px;
+
  }
+
  .thread {
+
    background-color: var(--color-fill-float-hover);
+
    padding: 0.5rem;
  }
  .comment-form {
    background-color: var(--color-fill-float-hover);
@@ -361,6 +435,7 @@

      <div>
        {#each hunk.lines as line, lineIdx}
+
          {@const comment = findLineComment(line)}
          <div
            class="line"
            class:addition={line.type === "addition"}
@@ -427,16 +502,50 @@
                      `<span class="global-syntax ${paint.style}">${escape(paint.item)}</span>`,
                  )
                  .join("")}
+
                {#if comment}
+
                  <div class="global-flex comment-icon">
+
                    <Icon name="comment" />
+
                  </div>
+
                {/if}
              </div>
            {:else if line.line !== ""}
              <div class="code">
                {line.line}
+
                {#if comment}
+
                  <div class="global-flex comment-icon">
+
                    <Icon name="comment" />
+
                  </div>
+
                {/if}
              </div>
            {:else}
              <div class="code"><br /></div>
            {/if}
          </div>
-
          {#if !dragging && selection?.endHunkIdx === hunkIdx && selection.endLineIdx === lineIdx}
+

+
          {#if comment}
+
            <div class="thread">
+
              <ThreadComponent
+
                inline
+
                {rid}
+
                thread={comment}
+
                createReply={async (body, embeds) => {
+
                  if (!createComment) {
+
                    return;
+
                  }
+
                  await createComment(
+
                    body,
+
                    embeds,
+
                    comment.root.id,
+
                    comment.root.location,
+
                  );
+
                }}
+
                canEditComment={() => {
+
                  return undefined;
+
                }} />
+
            </div>
+
          {/if}
+

+
          {#if !dragging && selection?.endHunkIdx === hunkIdx && selection.endLineIdx === lineIdx && selection.codeLocation && !comment && createComment}
            <div
              class="comment-form"
              onpointerdown={e => {
@@ -461,21 +570,14 @@
                focus
                placeholder="Leave a comment"
                submit={async (body, embeds) => {
-
                  if (selection === undefined || createComment === undefined) {
-
                    return;
+
                  if (selection?.codeLocation) {
+
                    await createComment(
+
                      body,
+
                      embeds,
+
                      undefined,
+
                      selection.codeLocation,
+
                    );
                  }
-
                  await createComment(body, embeds, undefined, {
-
                    commit: head,
-
                    path: selection.file,
-
                    old: {
-
                      type: "lines",
-
                      range: { start: 0, end: 0 },
-
                    },
-
                    new: {
-
                      type: "lines",
-
                      range: { start: 0, end: 0 },
-
                    },
-
                  });
                }} />
            </div>
          {/if}
modified src/components/File.svelte
@@ -99,7 +99,11 @@
    {@render leftHeader()}
  </div>
  {#if rightHeader}
-
    <div style:margin-left="auto" style:margin-right="1rem">
+
    <div
+
      class="global-flex"
+
      style:gap="1rem"
+
      style:margin-left="auto"
+
      style:margin-right="1rem">
      {@render rightHeader()}
    </div>
  {/if}
modified src/components/FileDiff.svelte
@@ -94,6 +94,9 @@
        </span>
      </div>
    {/if}
+
    {#if codeCommentThreads && codeCommentThreads.length > 0}
+
      <Icon name="comment" />
+
    {/if}
  {/snippet}

  {#snippet children()}
modified src/components/Thread.svelte
@@ -1,7 +1,8 @@
<script lang="ts">
  import type { Author } from "@bindings/cob/Author";
-
  import type { Comment } from "@bindings/cob/thread/Comment";
+
  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 { tick } from "svelte";
  import partial from "lodash/partial";
@@ -14,10 +15,7 @@
  import Icon from "@app/components/Icon.svelte";

  interface Props {
-
    thread: {
-
      root: Comment;
-
      replies: Comment[];
-
    };
+
    thread: Thread<CodeLocation>;
    rid: string;
    canEditComment: (author: string) => true | undefined;
    editComment?: (
@@ -35,6 +33,7 @@
      authors: Author[],
      reaction: string,
    ) => Promise<void>;
+
    inline?: boolean;
  }

  const {
@@ -44,6 +43,7 @@
    editComment,
    createReply,
    reactOnComment,
+
    inline = false,
  }: Props = $props();

  async function toggleReply() {
@@ -97,7 +97,7 @@
</style>

<div class="comments" {style}>
-
  <div class="top-level-comment">
+
  <div class:top-level-comment={!inline}>
    <CommentComponent
      disallowEmptyBody
      {rid}
@@ -112,12 +112,14 @@
        partial(editComment, root.id)}
      reactOnComment={reactOnComment && partial(reactOnComment, root.id)}>
      {#snippet actions()}
-
        <Icon name="reply" onclick={toggleReply} />
+
        {#if createReply}
+
          <Icon name="reply" onclick={toggleReply} />
+
        {/if}
      {/snippet}
    </CommentComponent>
  </div>
  {#if replies.length > 0 || (createReply && showReplyForm)}
-
    <Border variant="float" styleOverflow="hidden" flatTop>
+
    <Border variant="float" styleOverflow="hidden" flatTop={!inline}>
      <div style:width="100%">
        {#if replies.length > 0}
          {#each replies as reply}