diff --git a/public/index.css b/public/index.css index 914975f80..e724a60df 100644 --- a/public/index.css +++ b/public/index.css @@ -22,6 +22,11 @@ body { background-color: var(--color-background-default); } +::selection { + background: var(--color-fill-yellow); + color: var(--color-foreground-black); +} + .global-oid { color: var(--color-foreground-emphasized); font-size: var(--font-size-small); diff --git a/public/syntax.css b/public/syntax.css index 0743ef7fc..5d7380dee 100644 --- a/public/syntax.css +++ b/public/syntax.css @@ -1,67 +1,67 @@ -.syntax.operator, -.syntax.keyword\.repeat, -.syntax.keyword { +.global-syntax.operator, +.global-syntax.keyword\.repeat, +.global-syntax.keyword { color: var(--color-prettylights-syntax-keyword); } -.syntax.type { +.global-syntax.type { color: var(--color-prettylights-syntax-entity); } -.syntax.property, -.syntax.variable\.parameter { +.global-syntax.property, +.global-syntax.variable\.parameter { color: var(--color-prettylights-syntax-variable); } -.syntax.punctuation\.delimiter { +.global-syntax.punctuation\.delimiter { } -.syntax.punctuation\.bracket { +.global-syntax.punctuation\.bracket { } -.syntax.attribute { +.global-syntax.attribute { } -.syntax.number, -.syntax.constant, -.syntax.type\.builtin, -.syntax.constant\.builtin, -.syntax.variable\.builtin, -.syntax.function { +.global-syntax.number, +.global-syntax.constant, +.global-syntax.type\.builtin, +.global-syntax.constant\.builtin, +.global-syntax.variable\.builtin, +.global-syntax.function { color: var(--color-prettylights-syntax-constant); } -.syntax.comment, -.syntax.comment\.documentation { +.global-syntax.comment, +.global-syntax.comment\.documentation { color: var(--color-prettylights-syntax-comment); } -.syntax.string { +.global-syntax.string { color: var(--color-prettylights-syntax-string); } -.syntax.string.special { +.global-syntax.string.special { } -.syntax.function\.method { +.global-syntax.function\.method { color: var(--color-prettylights-syntax-entity); } -.syntax.type.builtin { +.global-syntax.type.builtin { } -.syntax.punctuation.bracket { +.global-syntax.punctuation.bracket { } -.syntax.punctuation.delimiter { +.global-syntax.punctuation.delimiter { } -.syntax.punctuation.special { +.global-syntax.punctuation.special { } -.syntax.text.literal { +.global-syntax.text.literal { } -.syntax.text.title { +.global-syntax.text.title { } -.syntax.variable { +.global-syntax.variable { color: var(--color-prettylights-syntax-storage-modifier-import); } -.syntax.attribute { +.global-syntax.attribute { } -.syntax.label { +.global-syntax.label { } -.syntax.type { +.global-syntax.type { } -.syntax.variable.parameter { +.global-syntax.variable.parameter { } -.syntax.constructor { +.global-syntax.constructor { color: var(--color-prettylights-syntax-entity); } -.syntax.tag\.delimiter { +.global-syntax.tag\.delimiter { color: var(--color-prettylights-syntax-keyword); } diff --git a/src/components/Border.svelte b/src/components/Border.svelte index a2a1b99ae..fc969a179 100644 --- a/src/components/Border.svelte +++ b/src/components/Border.svelte @@ -5,7 +5,7 @@ children: Snippet; variant: "primary" | "secondary" | "ghost" | "float" | "danger" | "success"; hoverable?: boolean; - onclick?: () => void; + onclick?: (e: MouseEvent) => void; stylePosition?: string; stylePadding?: string; styleHeight?: string; diff --git a/src/components/Changes.svelte b/src/components/Changes.svelte index 4383ad8f8..91c3ad761 100644 --- a/src/components/Changes.svelte +++ b/src/components/Changes.svelte @@ -1,6 +1,7 @@ - -{#snippet styledPath(fullPath: string)} - - {fullPath.match(/^.*\/|/)?.values().next().value}{fullPath.split("/").slice(-1)} -{/snippet} - -{#snippet emptyPlaceholder()} -
- Empty file -
-{/snippet} - - - {#snippet leftHeader()} - {#if (headerBadgeCaption === "moved" || headerBadgeCaption === "copied") && oldFilePath} - - {@render styledPath(filePath)} - → - {@render styledPath(filePath)} - - {:else} - {@render styledPath(filePath)} - {/if} - - {#if headerBadgeCaption === "added"} - added - {:else if headerBadgeCaption === "deleted"} - deleted - {:else if headerBadgeCaption === "moved"} - moved - {:else if headerBadgeCaption === "copied"} - copied - {/if} - {/snippet} - - {#snippet children()} -
- {#if fileDiff.type === "plain"} - {#if fileDiff.hunks.length > 0} - - {#each fileDiff.hunks as hunk, hunkIdx} - - - - - - {#each hunk.lines as line, lineIdx} - - - - - - - - - {/each} - {/each} -
-
-
- {hunk.header} -
-
-
- {lineNumberL(line)} -
- {lineNumberR(line)} - - {lineSign(line)} - - {#if line.highlight?.items.length === 0 || line.line === ""} -
- {:else if line.highlight} - {@html line.highlight.items - .map( - s => - `${escape(s.item)}`, - ) - .join("")} - {:else} - {line.line} - {/if} -
- {:else} - {@render emptyPlaceholder()} - {/if} - {:else} - {@render emptyPlaceholder()} - {/if} -
- {/snippet} -
diff --git a/src/components/CommentToggleInput.svelte b/src/components/CommentToggleInput.svelte index b32b217ce..48c9c96d9 100644 --- a/src/components/CommentToggleInput.svelte +++ b/src/components/CommentToggleInput.svelte @@ -38,6 +38,7 @@ padding: 0 0.75rem; font-size: var(--font-size-small); color: var(--color-fill-gray); + font-family: var(--font-family-sans-serif); } @@ -71,7 +72,10 @@ variant="float" styleHeight="40px" styleWidth="100%" - onclick={() => { + onclick={e => { + e.preventDefault(); + e.stopPropagation(); + state = "expanded"; if (onexpand !== undefined) { onexpand(); diff --git a/src/components/Diff.svelte b/src/components/Diff.svelte new file mode 100644 index 000000000..ac282bf7e --- /dev/null +++ b/src/components/Diff.svelte @@ -0,0 +1,465 @@ + + + + + + +{#snippet commentHeader(filePath?: string, selectionRange?: SelectionRange)} + {#if filePath && selectionRange} +
+ {filePath.split("/").length > 1 ? "…/" : ""}{filePath + .split("/") + .slice(-1)}:{selectionRange.start.side === "left" + ? "L" + : "R"}{selectionRange.start.lineNumber} + {#if selectionRange.end} + -> + {selectionRange.end.side === "left" ? "L" : "R"}{selectionRange.end + .lineNumber} + {/if} +
+ {/if} +{/snippet} + +
+ {#if file.diff.type === "plain"} + {#each file.diff.hunks as hunk, hunkIdx} +
+
+
+
+
{hunk.header}
+
+ +
+ {#each hunk.lines as line, lineIdx} + {@const thread = findLineThread(line)} +
+
{ + if (codeComments?.createComment && !thread) { + selectLine(e, file, "left", line, hunkIdx, lineIdx); + } + }}> + {lineNumber(line, "left")} +
+ +
{ + if (codeComments?.createComment && !thread) { + selectLine(e, file, "right", line, hunkIdx, lineIdx); + } + }}> + {lineNumber(line, "right")} +
+ +
+ {#if line.type === "addition"} + + + {:else if line.type === "deletion"} + - + {/if} +
+ + {#if line.highlight && line.highlight.items.length > 0} +
+ {@html line.highlight.items + .map( + paint => + `${escape(paint.item)}`, + ) + .join("")} +
+ {:else if line.line !== ""} +
{line.line}
+ {:else} +

+ {/if} + +
+ {#if thread} + {#if thread.root.resolved && thread.replies.every(r => r.resolved)} + + {:else} + + {/if} + {/if} +
+
+ + {#if codeComments && thread} +
+
+ {@render commentHeader( + thread.root.location?.path, + rangeAnchorsFromCodeLocation(thread.root.location), + )} +
+ { + await codeComments.createComment( + body, + embeds, + thread.root.id, + ); + }} + editComment={codeComments.editComment} + canEditComment={partial( + roles.isDelegateOrAuthor, + codeComments.config.publicKey, + codeComments.repoDelegates.map(delegate => delegate.did), + )} /> +
+ {/if} + + {#if codeComments && selection && selection.hunkIdx === hunkIdx && selection.lineIdx === lineIdx && selection.codeLocation} +
{ + e.stopPropagation(); + }}> +
+ {@render commentHeader(selection.file, { + start: selection.start, + })} +
+ { + selection = undefined; + }} + focus + placeholder="Leave a comment" + submit={async (body, embeds) => { + if (selection?.codeLocation) { + try { + await codeComments.createComment( + body, + embeds, + undefined, + selection.codeLocation, + ); + } catch (e) { + console.error("Comment creation failed", e); + } finally { + selection = undefined; + } + } + }} /> +
+ {/if} + {/each} +
+ {/each} + {/if} +
diff --git a/src/components/ExtendedTextarea.svelte b/src/components/ExtendedTextarea.svelte index 17e9bde3e..dcf418ff4 100644 --- a/src/components/ExtendedTextarea.svelte +++ b/src/components/ExtendedTextarea.svelte @@ -207,6 +207,7 @@ gap: 1rem; width: 100%; flex: 1; + font-family: var(--font-family-sans-serif); } .inline { border: 0; diff --git a/src/components/File.svelte b/src/components/File.svelte index 6d673324f..766265b9e 100644 --- a/src/components/File.svelte +++ b/src/components/File.svelte @@ -7,14 +7,21 @@ import NakedButton from "./NakedButton.svelte"; interface Props { - sticky?: boolean; - leftHeader: Snippet; children: Snippet; expanded: boolean; + leftHeader: Snippet; + rightHeader?: Snippet; + sticky?: boolean; } /* eslint-disable prefer-const */ - let { sticky = true, leftHeader, children, expanded }: Props = $props(); + let { + children, + expanded, + leftHeader, + rightHeader, + sticky = true, + }: Props = $props(); /* eslint-enable prefer-const */ let header: HTMLElement | undefined = $state(); @@ -91,6 +98,15 @@ {@render leftHeader()} + {#if rightHeader} +
+ {@render rightHeader()} +
+ {/if} {#if expanded} diff --git a/src/components/FileDiff.svelte b/src/components/FileDiff.svelte new file mode 100644 index 000000000..a4db45808 --- /dev/null +++ b/src/components/FileDiff.svelte @@ -0,0 +1,127 @@ + + + + +{#snippet emptyPlaceholder()} +
+ Empty file +
+{/snippet} + + + {#snippet leftHeader()} + {#if file.status === "moved" || file.status === "copied"} + + + → + + + {:else} + + {/if} + + {#if file.status === "added"} + added + {:else if file.status === "deleted"} + deleted + {:else if file.status === "moved"} + moved + {:else if file.status === "copied"} + copied + {/if} + {/snippet} + + {#snippet rightHeader()} + {#if file.diff.type === "plain" && file.diff.hunks.length > 0} +
+ + +{file.diff.stats.additions} + + + -{file.diff.stats.deletions} + +
+ {/if} + {#if commentsOfThisFile && commentsOfThisFile.threads.length > 0} + + {/if} + {/snippet} + + {#if file.diff.type === "plain"} + {#if file.diff.hunks.length > 0} + + {:else} + {@render emptyPlaceholder()} + {/if} + {:else if file.diff.type === "binary"} +
+ Binary file +
+ {:else} + {@render emptyPlaceholder()} + {/if} +
diff --git a/src/components/Icon.svelte b/src/components/Icon.svelte index 89d24ecbb..072c7494e 100644 --- a/src/components/Icon.svelte +++ b/src/components/Icon.svelte @@ -12,6 +12,7 @@ | "arrow-right" | "arrow-right-hollow" | "attachment" + | "binary" | "branch" | "broom" | "broom-double" @@ -162,6 +163,26 @@ + {:else if name === "binary"} + + + + + + + + + + + + + + + + + + + {:else if name === "branch"} + interface Props { + fullPath: string; + } + + const { fullPath }: Props = $props(); + + + + + + + + {fullPath + .match(/^.*\/|/) + ?.values() + .next().value} + + {fullPath.split("/").slice(-1)} + + diff --git a/src/components/Review.svelte b/src/components/Review.svelte index 51d516cf9..015ee84fb 100644 --- a/src/components/Review.svelte +++ b/src/components/Review.svelte @@ -1,5 +1,6 @@