mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-11-13 12:00:44 +08:00
Overhaul app responsiveness
We introduce more breakpoints to cover mobile, small-desktop, medium-desktop and large-desktop viewports.
This commit is contained in:
+37
-4
@@ -97,17 +97,50 @@ pre {
|
||||
background-color: var(--color-fill-ghost);
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
/*
|
||||
Breakpoints
|
||||
===========
|
||||
mobile 0px - 719.98px
|
||||
small desktop 720px - 1010.98px
|
||||
medium desktop 1011px - 1349.98px
|
||||
desktop 1350px - ∞ px
|
||||
*/
|
||||
|
||||
@media (max-width: 719.98px) {
|
||||
body {
|
||||
min-width: 0;
|
||||
}
|
||||
.global-hide-on-mobile {
|
||||
.global-hide-on-mobile-down {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 721px) {
|
||||
.global-hide-on-desktop {
|
||||
@media (max-width: 1010.98px) {
|
||||
.global-hide-on-small-desktop-down {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1349.98px) {
|
||||
.global-hide-on-medium-desktop-down {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 720px) {
|
||||
.global-hide-on-small-desktop-up {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1011px) {
|
||||
.global-hide-on-medium-desktop-up {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1350px) {
|
||||
.global-hide-on-desktop-up {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
@media (max-width: 719.98px) {
|
||||
.app {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr auto;
|
||||
@@ -30,17 +30,17 @@
|
||||
</style>
|
||||
|
||||
<div class="app">
|
||||
<div class="global-hide-on-mobile header">
|
||||
<div class="global-hide-on-mobile-down header">
|
||||
<Header />
|
||||
</div>
|
||||
<div class="content">
|
||||
<slot />
|
||||
</div>
|
||||
<div style:margin-top="auto">
|
||||
<div class="global-hide-on-mobile">
|
||||
<div class="global-hide-on-mobile-down">
|
||||
<Footer />
|
||||
</div>
|
||||
<div class="global-hide-on-desktop">
|
||||
<div class="global-hide-on-small-desktop-up">
|
||||
<MobileFooter />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
<div class="footer">
|
||||
<div class="left">
|
||||
<Popover popoverPositionBottom="2rem" popoverPositionLeft="0">
|
||||
<Popover popoverPositionBottom="3rem" popoverPositionLeft="0">
|
||||
<IconButton slot="toggle" let:toggle on:click={toggle}>
|
||||
<IconSmall name="settings" />
|
||||
Settings
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</style>
|
||||
|
||||
{#if $httpdStore.state === "authenticated"}
|
||||
<Popover popoverPositionTop="3rem" popoverPositionRight="0">
|
||||
<Popover popoverPositionTop="2.5rem" popoverPositionRight="0">
|
||||
<Button slot="toggle" let:toggle on:click={toggle} variant="naked-toggle">
|
||||
<div class="peer-info">
|
||||
<div style:height="1.25rem" style:margin-right="0.5rem">
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
font-weight: var(--font-weight-semibold);
|
||||
font-size: var(--font-size-small);
|
||||
white-space: nowrap;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<Popover popoverPositionTop="3rem" popoverPositionRight="0">
|
||||
<Popover popoverPositionTop="2.5rem" popoverPositionRight="0">
|
||||
<Button slot="toggle" let:toggle on:click={toggle} variant={"naked-toggle"}>
|
||||
{#if node.state === "running"}
|
||||
<IconSmall name="online" />
|
||||
|
||||
+2
-2
@@ -7,9 +7,7 @@
|
||||
|
||||
<style>
|
||||
.help {
|
||||
width: 18.5rem;
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--color-foreground-dim);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
@@ -18,6 +16,8 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
gap: 2rem;
|
||||
}
|
||||
.divider {
|
||||
border-bottom: 1px solid var(--color-fill-separator);
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
<style>
|
||||
.settings {
|
||||
width: 24rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -28,6 +27,8 @@
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.right {
|
||||
@@ -78,7 +79,7 @@
|
||||
</Radio>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="item global-hide-on-mobile-down">
|
||||
<div
|
||||
style="display: flex; flex-direction: row; align-items: center; gap: 0.5rem;">
|
||||
Make changes on the web (experimental)
|
||||
|
||||
@@ -54,8 +54,10 @@
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
flex-wrap: wrap;
|
||||
padding: 0 0.75rem;
|
||||
height: 1.5rem;
|
||||
min-height: 1.5rem;
|
||||
gap: 0.5rem;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
@@ -143,19 +145,18 @@
|
||||
{utils.formatTimestamp(timestamp)}
|
||||
</span>
|
||||
{#if lastEdit}
|
||||
<div class="card-metadata">•</div>
|
||||
<div
|
||||
class="card-metadata"
|
||||
title={utils.formatEditedCaption(
|
||||
lastEdit.author,
|
||||
lastEdit.timestamp,
|
||||
)}>
|
||||
edited
|
||||
• edited
|
||||
</div>
|
||||
{/if}
|
||||
<div class="header-right">
|
||||
{#if id && editComment && state === "read"}
|
||||
<div class="edit-buttons">
|
||||
<div class="edit-buttons global-hide-on-mobile-down">
|
||||
<IconButton title="edit comment" on:click={() => (state = "edit")}>
|
||||
<IconSmall name={"edit"} />
|
||||
</IconButton>
|
||||
@@ -199,15 +200,17 @@
|
||||
<div class="actions">
|
||||
{#if id && reactOnComment}
|
||||
{@const reactOnComment_ = reactOnComment}
|
||||
<ReactionSelector
|
||||
{reactions}
|
||||
on:select={async ({ detail: { authors, emoji } }) => {
|
||||
try {
|
||||
await reactOnComment_(authors, emoji);
|
||||
} finally {
|
||||
closeFocused();
|
||||
}
|
||||
}} />
|
||||
<div class="global-hide-on-mobile-down">
|
||||
<ReactionSelector
|
||||
{reactions}
|
||||
on:select={async ({ detail: { authors, emoji } }) => {
|
||||
try {
|
||||
await reactOnComment_(authors, emoji);
|
||||
} finally {
|
||||
closeFocused();
|
||||
}
|
||||
}} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if id && reactions && reactions.length > 0}
|
||||
<Reactions handleReaction={reactOnComment} {reactions} />
|
||||
|
||||
@@ -42,6 +42,14 @@
|
||||
font-size: var(--font-size-small);
|
||||
text-align: center;
|
||||
}
|
||||
.command {
|
||||
max-width: 25rem;
|
||||
}
|
||||
@media (max-width: 719.98px) {
|
||||
.command {
|
||||
max-width: 20rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="error">
|
||||
@@ -54,13 +62,13 @@
|
||||
{#if error}
|
||||
<div class="help">
|
||||
If you need help resolving this issue, copy the error message
|
||||
<br />
|
||||
<br class="global-hide-on-mobile-down" />
|
||||
below and send it to us on
|
||||
<ExternalLink href={config.supportWebsite}>
|
||||
{config.supportWebsite}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
<div style:max-width="25rem">
|
||||
<div class="command">
|
||||
<Command
|
||||
command={JSON.stringify(error, Object.getOwnPropertyNames(error))}
|
||||
fullWidth
|
||||
|
||||
@@ -56,6 +56,19 @@
|
||||
border-bottom-left-radius: var(--border-radius-small);
|
||||
border-bottom-right-radius: var(--border-radius-small);
|
||||
}
|
||||
@media (max-width: 719.98px) {
|
||||
.header {
|
||||
border-radius: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
padding: 0 1rem 0 1rem;
|
||||
}
|
||||
.container {
|
||||
border-radius: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div bind:this={header} class="header" class:collapsed={!expanded} class:sticky>
|
||||
|
||||
@@ -42,4 +42,5 @@
|
||||
class:txt-small={fontSize === "small"}
|
||||
class:txt-tiny={fontSize === "tiny"}>
|
||||
{@html render(content)}
|
||||
<slot />
|
||||
</span>
|
||||
|
||||
@@ -215,6 +215,7 @@
|
||||
}
|
||||
.markdown {
|
||||
max-width: 1024px;
|
||||
word-break: break-word;
|
||||
}
|
||||
.front-matter {
|
||||
font-size: var(--font-size-tiny);
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
font-family: var(--font-family-monospace);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
font-size: var(--font-size-small);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.popover-container {
|
||||
display: flex;
|
||||
|
||||
@@ -67,6 +67,11 @@
|
||||
.reply {
|
||||
padding: 1rem;
|
||||
}
|
||||
@media (max-width: 719.98px) {
|
||||
.comments {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="comments">
|
||||
@@ -115,7 +120,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#if createReply}
|
||||
<div id={`reply-${root.id}`} class="reply">
|
||||
<div id={`reply-${root.id}`} class="reply global-hide-on-mobile-down">
|
||||
<CommentToggleInput
|
||||
{rawPath}
|
||||
focus
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
@media (max-width: 719.98px) {
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
@@ -156,9 +156,9 @@
|
||||
</style>
|
||||
|
||||
<AppLayout>
|
||||
<div class="wrapper">
|
||||
<div class="wrapper" style:padding-bottom="2.5rem">
|
||||
{#if nodeId}
|
||||
<div class="global-hide-on-mobile">
|
||||
<div class="global-hide-on-mobile-down">
|
||||
<HomepageSection
|
||||
loading={$httpdStore.state !== "stopped" &&
|
||||
localProjects === undefined}
|
||||
@@ -235,18 +235,15 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#if !nodeId}
|
||||
<div class="global-hide-on-mobile">
|
||||
<Popover
|
||||
popoverPositionTop="1.5rem"
|
||||
popoverPositionLeft="0"
|
||||
popoverPositionRight="-15rem">
|
||||
<div class="global-hide-on-mobile-down">
|
||||
<Popover popoverPositionTop="2.5rem" popoverPositionLeft="0">
|
||||
<IconButton slot="toggle" let:toggle on:click={toggle}>
|
||||
<span style:color="var(--color-fill-gray)">
|
||||
<IconSmall name="info" />
|
||||
</span>
|
||||
</IconButton>
|
||||
|
||||
<div slot="popover" class="popover txt-small">
|
||||
<div slot="popover" class="popover txt-small" style:width="15rem">
|
||||
<div style:padding-bottom="0.5rem">
|
||||
To browse your local projects, run:
|
||||
</div>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<Popover
|
||||
{popoverPositionRight}
|
||||
{popoverPositionLeft}
|
||||
popoverPositionBottom="1.5rem">
|
||||
popoverPositionBottom="2.5rem">
|
||||
<IconButton slot="toggle" let:toggle on:click={toggle}>
|
||||
<span style:color="var(--color-fill-gray)">
|
||||
<IconSmall name="info" />
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-regular);
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
@media (max-width: 719.98px) {
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
@@ -147,10 +147,10 @@
|
||||
<!-- else this is probably a local node -->
|
||||
<!-- So we show only the nid -->
|
||||
<CopyableId id={nid} style="oid">
|
||||
<div class="global-hide-on-desktop">
|
||||
<div class="global-hide-on-small-desktop-up">
|
||||
{truncateId(nid)}
|
||||
</div>
|
||||
<div class="global-hide-on-mobile">
|
||||
<div class="global-hide-on-mobile-down">
|
||||
{nid}
|
||||
</div>
|
||||
</CopyableId>
|
||||
@@ -166,19 +166,19 @@
|
||||
<div class="txt-semibold">
|
||||
{isLocal(baseUrl.hostname) ? "Seeded" : "Pinned"} projects
|
||||
</div>
|
||||
<div class="global-hide-on-mobile" style:margin-left="auto">
|
||||
<div class="global-hide-on-mobile-down" style:margin-left="auto">
|
||||
{#if policy && scope}
|
||||
<ScopePolicyPopover {scope} {policy} popoverPositionRight="0" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="subtitle global-hide-on-desktop">
|
||||
<div class="subtitle global-hide-on-small-desktop-up">
|
||||
{#if policy && scope}
|
||||
<ScopePolicyPopover {scope} {policy} popoverPositionLeft="-5.5rem" />
|
||||
<ScopePolicyPopover {scope} {policy} popoverPositionRight="-4.5rem" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div style:margin-top="1rem">
|
||||
<div style:margin-top="1rem" style:padding-bottom="2.5rem">
|
||||
{#await fetchProjectInfos( baseUrl, { show: isLocal(baseUrl.hostname) ? "all" : "pinned", perPage: stats.repos.total }, )}
|
||||
<div style:height="35vh">
|
||||
<Loading small center />
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
|
||||
import FileDiff from "@app/views/projects/Changeset/FileDiff.svelte";
|
||||
import FileLocationChange from "@app/views/projects/Changeset/FileLocationChange.svelte";
|
||||
import Observer, { intersection } from "@app/components/Observer.svelte";
|
||||
import IconButton from "@app/components/IconButton.svelte";
|
||||
import IconSmall from "@app/components/IconSmall.svelte";
|
||||
import Observer, { intersection } from "@app/components/Observer.svelte";
|
||||
|
||||
export let diff: Diff;
|
||||
export let files: Record<string, CommitBlob>;
|
||||
@@ -58,12 +59,21 @@
|
||||
.summary {
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
@media (max-width: 719.98px) {
|
||||
.diff-list {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
.header {
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="header">
|
||||
<div class="summary">
|
||||
<span>{diffDescription(diff.files)}</span>
|
||||
with
|
||||
<br class="global-hide-on-small-desktop-up" />
|
||||
<span class:additions={diff.stats.insertions > 0}>
|
||||
{diff.stats.insertions}
|
||||
{pluralize("insertion", diff.stats.insertions)}
|
||||
@@ -75,15 +85,15 @@
|
||||
</span>
|
||||
</div>
|
||||
{#if diff.stats.filesChanged > 1}
|
||||
<div style:display="flex" style:gap="1rem">
|
||||
<IconButton on:click={() => (expanded = !expanded)}>
|
||||
{#if expanded === true}
|
||||
Collapse all
|
||||
{:else}
|
||||
Expand all
|
||||
{/if}
|
||||
</IconButton>
|
||||
</div>
|
||||
<IconButton on:click={() => (expanded = !expanded)}>
|
||||
{#if expanded === true}
|
||||
<IconSmall name="collapse" />
|
||||
<span class="global-hide-on-mobile-down">Collapse all</span>
|
||||
{:else}
|
||||
<IconSmall name="expand" />
|
||||
<span class="global-hide-on-mobile-down">Expand all</span>
|
||||
{/if}
|
||||
</IconButton>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
.assignee {
|
||||
display: flex;
|
||||
@@ -96,9 +97,29 @@
|
||||
position: relative;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 1349.98px) {
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.header {
|
||||
margin-bottom: 0;
|
||||
height: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.no-assignees {
|
||||
height: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
<div class="wrapper">
|
||||
<div class="header">Assignees</div>
|
||||
<div class="body">
|
||||
{#if locallyAuthenticated}
|
||||
@@ -150,14 +171,16 @@
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<Badge
|
||||
variant="outline"
|
||||
size="small"
|
||||
title="add assignee"
|
||||
round
|
||||
on:click={() => (showInput = true)}>
|
||||
<IconSmall name="plus" />
|
||||
</Badge>
|
||||
<div class="global-hide-on-mobile-down">
|
||||
<Badge
|
||||
variant="outline"
|
||||
size="small"
|
||||
title="add assignee"
|
||||
round
|
||||
on:click={() => (showInput = true)}>
|
||||
<IconSmall name="plus" />
|
||||
</Badge>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
{#each updatedAssignees as assignee}
|
||||
@@ -168,7 +191,7 @@
|
||||
</div>
|
||||
</Badge>
|
||||
{:else}
|
||||
<div class="txt-missing">No assignees</div>
|
||||
<div class="txt-missing no-assignees">No assignees</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -50,6 +50,10 @@
|
||||
margin: 0.5rem 0;
|
||||
font-size: var(--font-size-tiny);
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="teaser" aria-label="commit-teaser">
|
||||
@@ -81,10 +85,15 @@
|
||||
<pre>{commit.description.trim()}</pre>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="global-hide-on-small-desktop-up">
|
||||
<CompactCommitAuthorship {commit} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div style="display: flex; gap: 0.5rem; height: 21px; align-items: center;">
|
||||
<CompactCommitAuthorship {commit} />
|
||||
<div class="global-hide-on-mobile-down">
|
||||
<CompactCommitAuthorship {commit} />
|
||||
</div>
|
||||
<IconButton title="Browse repo at this commit">
|
||||
<Link
|
||||
route={{
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
}
|
||||
.summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
@@ -36,8 +36,8 @@
|
||||
</div>
|
||||
<div class="subtitle">
|
||||
<slot name="state" />
|
||||
<slot name="author" />
|
||||
</div>
|
||||
<slot name="subtitle" />
|
||||
<div class="description">
|
||||
<slot name="description" />
|
||||
</div>
|
||||
|
||||
@@ -9,9 +9,6 @@
|
||||
|
||||
<style>
|
||||
.header {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
font-size: var(--font-size-small);
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
@@ -20,14 +17,26 @@
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1.25rem;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
||||
@media (max-width: 1349.98px) {
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.header {
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
<div class="header">
|
||||
<span>Attachments</span>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<div class="header">Attachments</div>
|
||||
<div class="body">
|
||||
{#each embeds as embed}
|
||||
<Badge variant="neutral">
|
||||
|
||||
@@ -57,16 +57,17 @@
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.metadata-section-header {
|
||||
.header {
|
||||
font-size: var(--font-size-small);
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.metadata-section-body {
|
||||
.body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
.validation-message {
|
||||
display: flex;
|
||||
@@ -76,11 +77,30 @@
|
||||
position: relative;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
@media (max-width: 1349.98px) {
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.header {
|
||||
margin-bottom: 0;
|
||||
height: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.no-labels {
|
||||
height: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
<div class="metadata-section-header">Labels</div>
|
||||
<div class="metadata-section-body">
|
||||
<div class="wrapper">
|
||||
<div class="header">Labels</div>
|
||||
<div class="body">
|
||||
{#if locallyAuthenticated}
|
||||
{#each updatedLabels as label}
|
||||
<Badge
|
||||
@@ -124,14 +144,16 @@
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<Badge
|
||||
variant="outline"
|
||||
size="small"
|
||||
title="add labels"
|
||||
round
|
||||
on:click={() => (showInput = true)}>
|
||||
<IconSmall name="plus"></IconSmall>
|
||||
</Badge>
|
||||
<div class="global-hide-on-mobile-down">
|
||||
<Badge
|
||||
variant="outline"
|
||||
size="small"
|
||||
title="add labels"
|
||||
round
|
||||
on:click={() => (showInput = true)}>
|
||||
<IconSmall name="plus"></IconSmall>
|
||||
</Badge>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
{#each updatedLabels as label}
|
||||
@@ -139,7 +161,7 @@
|
||||
{label}
|
||||
</Badge>
|
||||
{:else}
|
||||
<div class="txt-missing">No labels</div>
|
||||
<div class="txt-missing no-labels">No labels</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import Badge from "@app/components/Badge.svelte";
|
||||
|
||||
export let labels: string[];
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
|
||||
{#each labels.slice(0, 4) as label}
|
||||
<Badge style="max-width:7rem" variant="neutral">
|
||||
<span class="label">{label}</span>
|
||||
</Badge>
|
||||
{/each}
|
||||
{#if labels.length > 4}
|
||||
<Badge title={labels.slice(4, undefined).join(" ")} variant="neutral">
|
||||
<span class="label">
|
||||
+{labels.length - 4} more label{labels.length > 5 ? "s" : ""}
|
||||
</span>
|
||||
</Badge>
|
||||
{/if}
|
||||
@@ -0,0 +1,75 @@
|
||||
<script lang="ts">
|
||||
import type { PatchReviews } from "../Patch.svelte";
|
||||
|
||||
import IconSmall from "@app/components/IconSmall.svelte";
|
||||
import NodeId from "@app/components/NodeId.svelte";
|
||||
|
||||
export let reviews: PatchReviews;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.header {
|
||||
font-size: var(--font-size-small);
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
.review {
|
||||
color: var(--color-fill-gray);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.review-accept {
|
||||
color: var(--color-foreground-success);
|
||||
}
|
||||
.review-reject {
|
||||
color: var(--color-foreground-red);
|
||||
}
|
||||
@media (max-width: 1349.98px) {
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
.header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.no-reviews {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.body {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="wrapper">
|
||||
<div class="header">Reviews</div>
|
||||
<div class="body">
|
||||
{#each Object.values(reviews) as { latest, review }}
|
||||
<div class="review" class:txt-missing={!latest}>
|
||||
<span
|
||||
class:review-accept={review.verdict === "accept"}
|
||||
class:review-reject={review.verdict === "reject"}>
|
||||
{#if review.verdict === "accept"}
|
||||
<IconSmall name="checkmark" />
|
||||
{:else if review.verdict === "reject"}
|
||||
<IconSmall name="cross" />
|
||||
{:else}
|
||||
<IconSmall name="chat" />
|
||||
{/if}
|
||||
</span>
|
||||
<NodeId nodeId={review.author.id} alias={review.author.alias} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="txt-missing no-reviews">No reviews</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
@@ -201,6 +201,7 @@
|
||||
}
|
||||
.revision-description {
|
||||
margin-left: 2.75rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
.author-metadata {
|
||||
color: var(--color-fill-gray);
|
||||
@@ -216,16 +217,18 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
min-height: 2.5rem;
|
||||
padding: 0.5rem 0;
|
||||
font-size: var(--font-size-small);
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.authorship-header {
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
white-space: nowrap;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
padding: 0 0.5rem;
|
||||
height: 1.5rem;
|
||||
min-height: 1.5rem;
|
||||
gap: 0.5rem;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
@@ -276,6 +279,14 @@
|
||||
margin-left: 1.25rem;
|
||||
background-color: var(--color-fill-separator);
|
||||
}
|
||||
@media (max-width: 719.98px) {
|
||||
.revision-box {
|
||||
border-radius: 0;
|
||||
}
|
||||
.action {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="revision" style:margin-bottom={expanded ? "2rem" : "0.5rem"}>
|
||||
@@ -289,7 +300,9 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="revision-data">
|
||||
<span title={utils.absoluteTimestamp(revisionTimestamp)}>
|
||||
<span
|
||||
class="global-hide-on-mobile-down"
|
||||
title={utils.absoluteTimestamp(revisionTimestamp)}>
|
||||
{utils.formatTimestamp(revisionTimestamp)}
|
||||
</span>
|
||||
{#if loading}
|
||||
@@ -313,7 +326,7 @@
|
||||
{/if}
|
||||
<Popover
|
||||
popoverPadding="0"
|
||||
popoverPositionTop="2.5rem"
|
||||
popoverPositionTop={expanded ? "3rem" : "2.5rem"}
|
||||
popoverPositionRight="0"
|
||||
popoverBorderRadius="var(--border-radius-small)">
|
||||
<IconButton
|
||||
@@ -419,17 +432,18 @@
|
||||
{utils.formatTimestamp(revisionTimestamp)}
|
||||
</span>
|
||||
{#if revisionEdits.length > 1 && lastEdit}
|
||||
<div class="author-metadata">•</div>
|
||||
<div
|
||||
class="author-metadata"
|
||||
title={utils.formatEditedCaption(
|
||||
lastEdit.author,
|
||||
lastEdit.timestamp,
|
||||
)}>
|
||||
edited
|
||||
• edited
|
||||
</div>
|
||||
{/if}
|
||||
<div style="display: flex; gap: 0.5rem; margin-left: auto;">
|
||||
<div
|
||||
class="global-hide-on-mobile-down"
|
||||
style="display: flex; gap: 0.5rem; margin-left: auto;">
|
||||
{#if canEdit(revisionAuthor.id) && editRevision && revisionState === "read"}
|
||||
<IconButton
|
||||
title="edit revision"
|
||||
@@ -470,15 +484,17 @@
|
||||
<div class="actions">
|
||||
{#if reactOnRevision}
|
||||
{@const reactOnRevision_ = reactOnRevision}
|
||||
<ReactionSelector
|
||||
reactions={revisionReactions}
|
||||
on:select={async ({ detail: { emoji, authors } }) => {
|
||||
try {
|
||||
await reactOnRevision_(authors, emoji);
|
||||
} finally {
|
||||
closeFocused();
|
||||
}
|
||||
}} />
|
||||
<div class="global-hide-on-mobile-down">
|
||||
<ReactionSelector
|
||||
reactions={revisionReactions}
|
||||
on:select={async ({ detail: { emoji, authors } }) => {
|
||||
try {
|
||||
await reactOnRevision_(authors, emoji);
|
||||
} finally {
|
||||
closeFocused();
|
||||
}
|
||||
}} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if revisionReactions && revisionReactions.length > 0}
|
||||
<Reactions
|
||||
@@ -507,7 +523,7 @@
|
||||
{#if error}
|
||||
<div
|
||||
class="diff-error txt-monospace txt-small"
|
||||
style:border-radius="var(--border-radius-small">
|
||||
style:border-radius="var(--border-radius-small)">
|
||||
<ErrorMessage
|
||||
title="Failed to load diff for this revision"
|
||||
description="Make sure you are able to connect to the seed <code>${utils.baseUrlToString(
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import IconSmall from "@app/components/IconSmall.svelte";
|
||||
|
||||
export let commentCount: number;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.comments {
|
||||
color: var(--color-foreground-dim);
|
||||
font-size: var(--font-size-tiny);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="comments">
|
||||
<IconSmall name="chat" />
|
||||
<span>{commentCount}</span>
|
||||
</div>
|
||||
@@ -39,6 +39,11 @@
|
||||
white-space: pre-wrap;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.button-container {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<Layout {baseUrl} {project}>
|
||||
@@ -50,7 +55,7 @@
|
||||
stripEmphasizedStyling
|
||||
fontSize="large"
|
||||
content={header.summary} />
|
||||
<div style:margin-left="auto" style:display="flex" style:gap="0.5rem">
|
||||
<div class="button-container">
|
||||
<Link
|
||||
route={{
|
||||
resource: "project.source",
|
||||
|
||||
@@ -64,6 +64,10 @@
|
||||
margin: 0.5rem 0;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="teaser">
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import type { BaseUrl, Patch, Revision } from "@httpd-client";
|
||||
|
||||
import { HttpdClient } from "@httpd-client";
|
||||
import { formatCommit } from "@app/lib/utils";
|
||||
|
||||
import DiffStatBadge from "@app/components/DiffStatBadge.svelte";
|
||||
import Link from "@app/components/Link.svelte";
|
||||
import Loading from "@app/components/Loading.svelte";
|
||||
|
||||
export let projectId: string;
|
||||
export let baseUrl: BaseUrl;
|
||||
export let patch: Patch;
|
||||
export let latestRevision: Revision;
|
||||
|
||||
$: diffPromise = api.project.getDiff(
|
||||
projectId,
|
||||
latestRevision.base,
|
||||
latestRevision.oid,
|
||||
);
|
||||
|
||||
const api = new HttpdClient(baseUrl);
|
||||
</script>
|
||||
|
||||
{#await diffPromise}
|
||||
<Loading small />
|
||||
{:then { diff }}
|
||||
<Link
|
||||
title="Compare {formatCommit(latestRevision.base)}..{formatCommit(
|
||||
latestRevision.oid,
|
||||
)}"
|
||||
route={{
|
||||
resource: "project.patch",
|
||||
project: projectId,
|
||||
node: baseUrl,
|
||||
patch: patch.id,
|
||||
view: {
|
||||
name: "diff",
|
||||
fromCommit: latestRevision.base,
|
||||
toCommit: latestRevision.oid,
|
||||
},
|
||||
}}>
|
||||
<DiffStatBadge
|
||||
hoverable
|
||||
insertions={diff.stats.insertions}
|
||||
deletions={diff.stats.deletions} />
|
||||
</Link>
|
||||
{/await}
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
<Popover popoverPositionTop="2.5rem" popoverPositionRight="0">
|
||||
<Button slot="toggle" let:toggle on:click={toggle} variant="outline">
|
||||
Clone
|
||||
<span class="global-hide-on-small-desktop-down">Clone</span>
|
||||
<Icon name="download" />
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
|
||||
<style>
|
||||
.more {
|
||||
margin: 2rem 0;
|
||||
margin-top: 2rem;
|
||||
min-height: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
+128
-119
@@ -249,14 +249,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function saveLabels(sessionId: string, labels: string[]) {
|
||||
async function saveLabels(labels: string[]) {
|
||||
try {
|
||||
await api.project.updateIssue(
|
||||
project.id,
|
||||
issue.id,
|
||||
{ type: "label", labels },
|
||||
sessionId,
|
||||
);
|
||||
if (session) {
|
||||
labelState = "submit";
|
||||
await api.project.updateIssue(
|
||||
project.id,
|
||||
issue.id,
|
||||
{ type: "label", labels },
|
||||
session.id,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
modal.show({
|
||||
@@ -275,21 +278,22 @@
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
labelState = "read";
|
||||
await refreshIssue();
|
||||
}
|
||||
}
|
||||
|
||||
async function saveAssignees(
|
||||
sessionId: string,
|
||||
assignees: Reaction["authors"],
|
||||
) {
|
||||
async function saveAssignees(assignees: Reaction["authors"]) {
|
||||
try {
|
||||
await api.project.updateIssue(
|
||||
project.id,
|
||||
issue.id,
|
||||
{ type: "assign", assignees: assignees.map(({ id }) => id) },
|
||||
sessionId,
|
||||
);
|
||||
if (session) {
|
||||
assigneeState = "submit";
|
||||
await api.project.updateIssue(
|
||||
project.id,
|
||||
issue.id,
|
||||
{ type: "assign", assignees: assignees.map(({ id }) => id) },
|
||||
session.id,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
modal.show({
|
||||
@@ -308,6 +312,7 @@
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
assigneeState = "read";
|
||||
await refreshIssue();
|
||||
}
|
||||
}
|
||||
@@ -438,7 +443,6 @@
|
||||
.metadata {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: var(--font-size-small);
|
||||
padding: 1rem;
|
||||
border-left: 1px solid var(--color-border-hint);
|
||||
gap: 1.5rem;
|
||||
@@ -450,14 +454,6 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.author {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
gap: 0.5rem;
|
||||
font-family: var(--font-family-sans-serif);
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
.author-metadata {
|
||||
color: var(--color-fill-gray);
|
||||
font-size: var(--font-size-small);
|
||||
@@ -477,10 +473,9 @@
|
||||
align-items: center;
|
||||
margin-left: -0.25rem;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.issue {
|
||||
display: block;
|
||||
@media (max-width: 719.98px) {
|
||||
.bottom {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -509,24 +504,28 @@
|
||||
</div>
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
{#if session && role.isDelegateOrAuthor(session.publicKey, delegates, issue.author.id) && issueState === "read"}
|
||||
<Button
|
||||
variant="outline"
|
||||
title="edit issue"
|
||||
on:click={() => (issueState = "edit")}>
|
||||
<IconSmall name={"edit"} />
|
||||
Edit
|
||||
</Button>
|
||||
<div class="global-hide-on-mobile-down">
|
||||
<Button
|
||||
variant="outline"
|
||||
title="edit issue"
|
||||
on:click={() => (issueState = "edit")}>
|
||||
<IconSmall name={"edit"} />
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{#if issueState === "read"}
|
||||
<Share {baseUrl} />
|
||||
{#if session && role.isDelegateOrAuthor(session.publicKey, delegates, issue.author.id)}
|
||||
<CobStateButton
|
||||
items={items.filter(
|
||||
([, state]) => !isEqual(state, issue.state),
|
||||
)}
|
||||
{selectedItem}
|
||||
state={issue.state}
|
||||
save={partial(saveStatus, session.id)} />
|
||||
<div class="global-hide-on-small-desktop-down">
|
||||
<CobStateButton
|
||||
items={items.filter(
|
||||
([, state]) => !isEqual(state, issue.state),
|
||||
)}
|
||||
{selectedItem}
|
||||
state={issue.state}
|
||||
save={partial(saveStatus, session.id)} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
@@ -544,7 +543,55 @@
|
||||
{issue.state.reason}
|
||||
</Badge>
|
||||
{/if}
|
||||
<NodeId
|
||||
stylePopoverPositionLeft="-13px"
|
||||
nodeId={issue.author.id}
|
||||
alias={issue.author.alias} />
|
||||
opened
|
||||
<CopyableId id={issue.id} style="oid">
|
||||
{utils.formatObjectId(issue.id)}
|
||||
</CopyableId>
|
||||
<span title={utils.absoluteTimestamp(issue.discussion[0].timestamp)}>
|
||||
{utils.formatTimestamp(issue.discussion[0].timestamp)}
|
||||
</span>
|
||||
{#if lastDescriptionEdit}
|
||||
<div
|
||||
class="author-metadata"
|
||||
title={utils.formatEditedCaption(
|
||||
lastDescriptionEdit.author,
|
||||
lastDescriptionEdit.timestamp,
|
||||
)}>
|
||||
• edited
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<div slot="subtitle" class="global-hide-on-desktop-up">
|
||||
<div
|
||||
style:margin-top="2rem"
|
||||
style="display: flex; flex-direction: column; gap: 0.5rem;">
|
||||
<AssigneeInput
|
||||
locallyAuthenticated={role.isDelegate(
|
||||
session?.publicKey,
|
||||
delegates,
|
||||
)}
|
||||
assignees={issue.assignees}
|
||||
submitInProgress={assigneeState === "submit"}
|
||||
on:save={({ detail: newAssignees }) => {
|
||||
void saveAssignees(newAssignees);
|
||||
}} />
|
||||
<LabelInput
|
||||
locallyAuthenticated={role.isDelegate(
|
||||
session?.publicKey,
|
||||
delegates,
|
||||
)}
|
||||
labels={issue.labels}
|
||||
submitInProgress={labelState === "submit"}
|
||||
on:save={({ detail: newLabels }) => void saveLabels(newLabels)} />
|
||||
<div class="global-hide-on-mobile-down">
|
||||
<Embeds embeds={uniqueEmbeds} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<svelte:fragment slot="description">
|
||||
{#if $experimental && issueState !== "read"}
|
||||
<ExtendedTextarea
|
||||
@@ -589,17 +636,19 @@
|
||||
{/if}
|
||||
<div class="reactions">
|
||||
{#if $experimental && session}
|
||||
<ReactionSelector
|
||||
reactions={issue.discussion[0].reactions}
|
||||
on:select={async ({ detail: { authors, emoji } }) => {
|
||||
try {
|
||||
if (session) {
|
||||
await reactOnComment(session, issue.id, authors, emoji);
|
||||
<div class="global-hide-on-mobile-down">
|
||||
<ReactionSelector
|
||||
reactions={issue.discussion[0].reactions}
|
||||
on:select={async ({ detail: { authors, emoji } }) => {
|
||||
try {
|
||||
if (session) {
|
||||
await reactOnComment(session, issue.id, authors, emoji);
|
||||
}
|
||||
} finally {
|
||||
closeFocused();
|
||||
}
|
||||
} finally {
|
||||
closeFocused();
|
||||
}
|
||||
}} />
|
||||
}} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if issue.discussion[0].reactions.length > 0}
|
||||
<Reactions
|
||||
@@ -609,30 +658,6 @@
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<div class="author" slot="author">
|
||||
<NodeId
|
||||
stylePopoverPositionLeft="-13px"
|
||||
nodeId={issue.author.id}
|
||||
alias={issue.author.alias} />
|
||||
opened
|
||||
<CopyableId id={issue.id} style="oid">
|
||||
{utils.formatObjectId(issue.id)}
|
||||
</CopyableId>
|
||||
<span title={utils.absoluteTimestamp(issue.discussion[0].timestamp)}>
|
||||
{utils.formatTimestamp(issue.discussion[0].timestamp)}
|
||||
</span>
|
||||
{#if lastDescriptionEdit}
|
||||
<div class="author-metadata">•</div>
|
||||
<div
|
||||
class="author-metadata"
|
||||
title={utils.formatEditedCaption(
|
||||
lastDescriptionEdit.author,
|
||||
lastDescriptionEdit.timestamp,
|
||||
)}>
|
||||
edited
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</CobHeader>
|
||||
<div class="bottom">
|
||||
{#if threads.length > 0}
|
||||
@@ -664,46 +689,40 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#if $experimental && session}
|
||||
<div class="connector" />
|
||||
<CommentToggleInput
|
||||
focus
|
||||
rawPath={rawPath(project.head)}
|
||||
placeholder="Leave your comment"
|
||||
enableAttachments
|
||||
submit={partial(createComment, session.id)} />
|
||||
<div
|
||||
style="display:flex; flex-direction: column; align-items: flex-start;">
|
||||
{#if role.isDelegateOrAuthor(session.publicKey, delegates, issue.author.id)}
|
||||
<div class="connector" />
|
||||
<CobStateButton
|
||||
items={items.filter(
|
||||
([, state]) => !isEqual(state, issue.state),
|
||||
)}
|
||||
{selectedItem}
|
||||
state={issue.state}
|
||||
save={partial(saveStatus, session.id)} />
|
||||
{/if}
|
||||
<div class="global-hide-on-mobile-down">
|
||||
<div class="connector" />
|
||||
<CommentToggleInput
|
||||
focus
|
||||
rawPath={rawPath(project.head)}
|
||||
placeholder="Leave your comment"
|
||||
enableAttachments
|
||||
submit={partial(createComment, session.id)} />
|
||||
<div
|
||||
style="display:flex; flex-direction: column; align-items: flex-start;">
|
||||
{#if role.isDelegateOrAuthor(session.publicKey, delegates, issue.author.id)}
|
||||
<div class="connector" />
|
||||
<CobStateButton
|
||||
items={items.filter(
|
||||
([, state]) => !isEqual(state, issue.state),
|
||||
)}
|
||||
{selectedItem}
|
||||
state={issue.state}
|
||||
save={partial(saveStatus, session.id)} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="metadata global-hide-on-mobile">
|
||||
<div class="metadata global-hide-on-medium-desktop-down">
|
||||
<AssigneeInput
|
||||
locallyAuthenticated={Boolean(
|
||||
role.isDelegate(session?.publicKey, delegates),
|
||||
)}
|
||||
assignees={issue.assignees}
|
||||
submitInProgress={assigneeState === "submit"}
|
||||
on:save={async ({ detail: newAssignees }) => {
|
||||
if (session) {
|
||||
assigneeState = "submit";
|
||||
try {
|
||||
await saveAssignees(session.id, newAssignees);
|
||||
} finally {
|
||||
assigneeState = "read";
|
||||
}
|
||||
}
|
||||
await refreshIssue();
|
||||
on:save={({ detail: newAssignees }) => {
|
||||
void saveAssignees(newAssignees);
|
||||
}} />
|
||||
<LabelInput
|
||||
locallyAuthenticated={Boolean(
|
||||
@@ -711,17 +730,7 @@
|
||||
)}
|
||||
labels={issue.labels}
|
||||
submitInProgress={labelState === "submit"}
|
||||
on:save={async ({ detail: newLabels }) => {
|
||||
if (session) {
|
||||
labelState = "submit";
|
||||
try {
|
||||
await saveLabels(session.id, newLabels);
|
||||
} finally {
|
||||
labelState = "read";
|
||||
}
|
||||
}
|
||||
await refreshIssue();
|
||||
}} />
|
||||
on:save={({ detail: newLabels }) => void saveLabels(newLabels)} />
|
||||
<Embeds embeds={uniqueEmbeds} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
formatTimestamp,
|
||||
} from "@app/lib/utils";
|
||||
|
||||
import Badge from "@app/components/Badge.svelte";
|
||||
import IconSmall from "@app/components/IconSmall.svelte";
|
||||
import InlineMarkdown from "@app/components/InlineMarkdown.svelte";
|
||||
import Link from "@app/components/Link.svelte";
|
||||
import NodeId from "@app/components/NodeId.svelte";
|
||||
|
||||
import CommentCounter from "../CommentCounter.svelte";
|
||||
import Labels from "../Cob/Labels.svelte";
|
||||
|
||||
export let baseUrl: BaseUrl;
|
||||
export let issue: Issue;
|
||||
export let projectId: string;
|
||||
@@ -23,8 +25,6 @@
|
||||
}
|
||||
return acc;
|
||||
}, 0);
|
||||
|
||||
let hover = false;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -57,25 +57,11 @@
|
||||
.issue-title:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.comment-count {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
.labels {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.right {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-left: auto;
|
||||
color: var(--color-foreground-dim);
|
||||
font-size: var(--font-size-tiny);
|
||||
min-height: 1.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
.state {
|
||||
justify-self: center;
|
||||
@@ -91,12 +77,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
class="issue-teaser"
|
||||
on:mouseenter={() => (hover = true)}
|
||||
on:mouseleave={() => (hover = false)}>
|
||||
<div role="button" tabindex="0" class="issue-teaser">
|
||||
<div
|
||||
class="state"
|
||||
class:closed={issue.state.status === "closed"}
|
||||
@@ -116,32 +97,20 @@
|
||||
{#if !issue.title}
|
||||
<span class="txt-missing">No title</span>
|
||||
{:else}
|
||||
<InlineMarkdown fontSize="regular" content={issue.title} />
|
||||
<InlineMarkdown fontSize="regular" content={issue.title}>
|
||||
{#if issue.labels.length > 0}
|
||||
<span
|
||||
style="display: inline-flex; gap: 0.5rem; flex-wrap: wrap;">
|
||||
<Labels labels={issue.labels} />
|
||||
</span>
|
||||
{/if}
|
||||
</InlineMarkdown>
|
||||
{/if}
|
||||
</span>
|
||||
</Link>
|
||||
<span class="labels">
|
||||
{#each issue.labels.slice(0, 4) as label}
|
||||
<Badge variant={hover ? "background" : "neutral"}>
|
||||
{label}
|
||||
</Badge>
|
||||
{/each}
|
||||
{#if issue.labels.length > 4}
|
||||
<Badge
|
||||
title={issue.labels.slice(4, undefined).join(" ")}
|
||||
variant={hover ? "background" : "neutral"}>
|
||||
+{issue.labels.length - 4} more labels
|
||||
</Badge>
|
||||
{/if}
|
||||
</span>
|
||||
<div class="right">
|
||||
{#if commentCount > 0}
|
||||
<div style:display="flex" style:gap="1rem">
|
||||
<div class="comment-count">
|
||||
<IconSmall name="chat" />
|
||||
<span>{commentCount}</span>
|
||||
</div>
|
||||
</div>
|
||||
<CommentCounter {commentCount} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
|
||||
<style>
|
||||
.more {
|
||||
margin: 2rem 0;
|
||||
margin-top: 2rem;
|
||||
min-height: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -148,17 +148,19 @@
|
||||
<div style="margin-left: auto; display: flex; gap: 1rem;">
|
||||
<Share {baseUrl} />
|
||||
{#if $experimental && $httpdStore.state === "authenticated" && isLocal(baseUrl.hostname)}
|
||||
<Link
|
||||
route={{
|
||||
resource: "project.newIssue",
|
||||
project: project.id,
|
||||
node: baseUrl,
|
||||
}}>
|
||||
<Button variant="secondary">
|
||||
<IconSmall name="plus" />
|
||||
New Issue
|
||||
</Button>
|
||||
</Link>
|
||||
<div class="global-hide-on-mobile-down">
|
||||
<Link
|
||||
route={{
|
||||
resource: "project.newIssue",
|
||||
project: project.id,
|
||||
node: baseUrl,
|
||||
}}>
|
||||
<Button variant="secondary">
|
||||
<IconSmall name="plus" />
|
||||
New Issue
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
export let activeTab: ActiveTab | undefined = undefined;
|
||||
export let baseUrl: BaseUrl;
|
||||
export let project: Project;
|
||||
export let stylePaddingBottom: string = "2.5rem";
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -41,13 +42,10 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
@media (max-width: 719.98px) {
|
||||
.desktop-header {
|
||||
display: none;
|
||||
}
|
||||
.sidebar {
|
||||
display: none;
|
||||
}
|
||||
.content {
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
@@ -66,11 +64,15 @@
|
||||
<AppHeader />
|
||||
</div>
|
||||
|
||||
<div class="sidebar">
|
||||
<div class="sidebar global-hide-on-medium-desktop-down">
|
||||
<Sidebar {activeTab} {baseUrl} {project} />
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="sidebar global-hide-on-mobile-down global-hide-on-desktop-up">
|
||||
<Sidebar {activeTab} {baseUrl} {project} collapsedOnly />
|
||||
</div>
|
||||
|
||||
<div class="content" style:padding-bottom={stylePaddingBottom}>
|
||||
<slot name="header" />
|
||||
<slot name="subheader" />
|
||||
<slot />
|
||||
@@ -90,7 +92,7 @@
|
||||
<Button
|
||||
variant={activeTab === "source" ? "secondary" : "secondary-mobile"}
|
||||
styleWidth="100%">
|
||||
<IconSmall name="home" />
|
||||
<IconSmall name="chevron-left-right" />
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
+144
-208
@@ -34,6 +34,10 @@
|
||||
}
|
||||
|
||||
export type Timeline = TimelineMerge | TimelineReview | TimelineThread;
|
||||
export type PatchReviews = Record<
|
||||
string,
|
||||
{ latest: boolean; review: Review }
|
||||
>;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -64,8 +68,6 @@
|
||||
import CommentToggleInput from "@app/components/CommentToggleInput.svelte";
|
||||
import CopyableId from "@app/components/CopyableId.svelte";
|
||||
import DiffStatBadge from "@app/components/DiffStatBadge.svelte";
|
||||
import DropdownList from "@app/components/DropdownList.svelte";
|
||||
import DropdownListItem from "@app/components/DropdownList/DropdownListItem.svelte";
|
||||
import Embeds from "@app/views/projects/Cob/Embeds.svelte";
|
||||
import ErrorModal from "@app/modals/ErrorModal.svelte";
|
||||
import ExtendedTextarea from "@app/components/ExtendedTextarea.svelte";
|
||||
@@ -77,13 +79,15 @@
|
||||
import Markdown from "@app/components/Markdown.svelte";
|
||||
import NodeId from "@app/components/NodeId.svelte";
|
||||
import Placeholder from "@app/components/Placeholder.svelte";
|
||||
import Popover, { closeFocused } from "@app/components/Popover.svelte";
|
||||
import Radio from "@app/components/Radio.svelte";
|
||||
import ReactionSelector from "@app/components/ReactionSelector.svelte";
|
||||
import Reactions from "@app/components/Reactions.svelte";
|
||||
import Reviews from "@app/views/projects/Cob/Reviews.svelte";
|
||||
import RevisionComponent from "@app/views/projects/Cob/Revision.svelte";
|
||||
import RevisionSelector from "@app/views/projects/Patch/RevisionSelector.svelte";
|
||||
import Share from "@app/views/projects/Share.svelte";
|
||||
import TextInput from "@app/components/TextInput.svelte";
|
||||
import { closeFocused } from "@app/components/Popover.svelte";
|
||||
|
||||
export let baseUrl: BaseUrl;
|
||||
export let patch: Patch;
|
||||
@@ -411,14 +415,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function saveLabels(sessionId: string, labels: string[]) {
|
||||
async function saveLabels(labels: string[]) {
|
||||
try {
|
||||
await api.project.updatePatch(
|
||||
project.id,
|
||||
patch.id,
|
||||
{ type: "label", labels },
|
||||
sessionId,
|
||||
);
|
||||
if (session) {
|
||||
labelState = "submit";
|
||||
await api.project.updatePatch(
|
||||
project.id,
|
||||
patch.id,
|
||||
{ type: "label", labels },
|
||||
session.id,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
modal.show({
|
||||
@@ -437,6 +444,7 @@
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
labelState = "read";
|
||||
await refreshPatch();
|
||||
}
|
||||
}
|
||||
@@ -550,7 +558,7 @@
|
||||
$: description = patch.revisions[0].description;
|
||||
$: lastEdit = patch.revisions[0].edits.at(-1);
|
||||
$: newDescription = description;
|
||||
$: patchReviews = computeReviews(patch);
|
||||
$: reviews = computeReviews(patch);
|
||||
$: selectedItem = patch.state.status === "open" ? items[1] : items[0];
|
||||
$: timelineTuple = patch.revisions.map<
|
||||
[
|
||||
@@ -646,7 +654,7 @@
|
||||
}
|
||||
.bottom {
|
||||
background-color: var(--color-background-default);
|
||||
padding: 1rem;
|
||||
padding: 1rem 1rem 0 1rem;
|
||||
height: 100%;
|
||||
}
|
||||
.actions {
|
||||
@@ -669,39 +677,10 @@
|
||||
width: 1rem;
|
||||
height: 100%;
|
||||
}
|
||||
.author {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
font-family: var(--font-family-sans-serif);
|
||||
font-size: var(--font-size-small);
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.metadata-section-header {
|
||||
font-size: var(--font-size-small);
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.metadata-section-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.author-metadata {
|
||||
color: var(--color-fill-gray);
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
.review {
|
||||
color: var(--color-fill-gray);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.review-accept {
|
||||
color: var(--color-foreground-success);
|
||||
}
|
||||
.review-reject {
|
||||
color: var(--color-foreground-red);
|
||||
}
|
||||
.revision-description {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -718,10 +697,13 @@
|
||||
margin-left: 1.25rem;
|
||||
background-color: var(--color-fill-separator);
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
@media (max-width: 719.98px) {
|
||||
.patch {
|
||||
display: block;
|
||||
}
|
||||
.bottom {
|
||||
padding: 1rem 0 0 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -749,24 +731,28 @@
|
||||
{/if}
|
||||
</div>
|
||||
{#if session && role.isDelegateOrAuthor(session.publicKey, delegates, patch.author.id) && patchState === "read"}
|
||||
<Button
|
||||
variant="outline"
|
||||
title="edit patch"
|
||||
on:click={() => (patchState = "edit")}>
|
||||
<IconSmall name={"edit"} />
|
||||
Edit
|
||||
</Button>
|
||||
<div class="global-hide-on-mobile-down">
|
||||
<Button
|
||||
variant="outline"
|
||||
title="edit patch"
|
||||
on:click={() => (patchState = "edit")}>
|
||||
<IconSmall name={"edit"} />
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{#if patchState === "read"}
|
||||
<Share {baseUrl} />
|
||||
{#if session && role.isDelegateOrAuthor(session.publicKey, delegates, patch.author.id)}
|
||||
<CobStateButton
|
||||
items={items.filter(
|
||||
([, state]) => !isEqual(state, patch.state),
|
||||
)}
|
||||
{selectedItem}
|
||||
state={patch.state}
|
||||
save={partial(saveStatus, session.id)} />
|
||||
<div class="global-hide-on-small-desktop-down">
|
||||
<CobStateButton
|
||||
items={items.filter(
|
||||
([, state]) => !isEqual(state, patch.state),
|
||||
)}
|
||||
{selectedItem}
|
||||
state={patch.state}
|
||||
save={partial(saveStatus, session.id)} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
@@ -788,7 +774,46 @@
|
||||
insertions={stats.insertions}
|
||||
deletions={stats.deletions} />
|
||||
</Link>
|
||||
<NodeId
|
||||
stylePopoverPositionLeft="-13px"
|
||||
nodeId={patch.author.id}
|
||||
alias={patch.author.alias} />
|
||||
opened
|
||||
<CopyableId id={patch.id} style="oid">
|
||||
{utils.formatObjectId(patch.id)}
|
||||
</CopyableId>
|
||||
<span title={utils.absoluteTimestamp(patch.revisions[0].timestamp)}>
|
||||
{utils.formatTimestamp(patch.revisions[0].timestamp)}
|
||||
</span>
|
||||
{#if patch.revisions[0].edits.length > 1 && lastEdit}
|
||||
<div
|
||||
class="author-metadata"
|
||||
title={utils.formatEditedCaption(
|
||||
lastEdit.author,
|
||||
lastEdit.timestamp,
|
||||
)}>
|
||||
• edited
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<div slot="subtitle" class="global-hide-on-desktop-up">
|
||||
<div
|
||||
style:margin-top="2rem"
|
||||
style="display: flex; flex-direction: column; gap: 0.5rem;">
|
||||
<Reviews {reviews} />
|
||||
<LabelInput
|
||||
locallyAuthenticated={role.isDelegate(
|
||||
session?.publicKey,
|
||||
delegates,
|
||||
)}
|
||||
submitInProgress={labelState === "submit"}
|
||||
labels={patch.labels}
|
||||
on:save={({ detail: newLabels }) => {
|
||||
void saveLabels(newLabels);
|
||||
}} />
|
||||
<Embeds embeds={uniqueEmbeds} />
|
||||
</div>
|
||||
</div>
|
||||
<svelte:fragment slot="description">
|
||||
<div class="revision-description">
|
||||
{#if $experimental && session && patchState !== "read" && lastEdit}
|
||||
@@ -832,22 +857,24 @@
|
||||
{#if ($experimental && session) || (firstRevision.revisionReactions && firstRevision.revisionReactions.length > 0)}
|
||||
<div class="actions">
|
||||
{#if session}
|
||||
<ReactionSelector
|
||||
reactions={firstRevision.revisionReactions}
|
||||
on:select={async ({ detail: { emoji, authors } }) => {
|
||||
if (session) {
|
||||
try {
|
||||
await reactOnRevision(
|
||||
session,
|
||||
patch.id,
|
||||
authors,
|
||||
emoji,
|
||||
);
|
||||
} finally {
|
||||
closeFocused();
|
||||
<div class="global-hide-on-mobile-down">
|
||||
<ReactionSelector
|
||||
reactions={firstRevision.revisionReactions}
|
||||
on:select={async ({ detail: { emoji, authors } }) => {
|
||||
if (session) {
|
||||
try {
|
||||
await reactOnRevision(
|
||||
session,
|
||||
patch.id,
|
||||
authors,
|
||||
emoji,
|
||||
);
|
||||
} finally {
|
||||
closeFocused();
|
||||
}
|
||||
}
|
||||
}
|
||||
}} />
|
||||
}} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if firstRevision.revisionReactions.length > 0}
|
||||
<Reactions
|
||||
@@ -859,30 +886,6 @@
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<div class="author" slot="author">
|
||||
<NodeId
|
||||
stylePopoverPositionLeft="-13px"
|
||||
nodeId={patch.author.id}
|
||||
alias={patch.author.alias} />
|
||||
opened
|
||||
<CopyableId id={patch.id} style="oid">
|
||||
{utils.formatObjectId(patch.id)}
|
||||
</CopyableId>
|
||||
<span title={utils.absoluteTimestamp(patch.revisions[0].timestamp)}>
|
||||
{utils.formatTimestamp(patch.revisions[0].timestamp)}
|
||||
</span>
|
||||
{#if patch.revisions[0].edits.length > 1 && lastEdit}
|
||||
<div class="author-metadata">•</div>
|
||||
<div
|
||||
class="author-metadata"
|
||||
title={utils.formatEditedCaption(
|
||||
lastEdit.author,
|
||||
lastEdit.timestamp,
|
||||
)}>
|
||||
edited
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</CobHeader>
|
||||
|
||||
<div class="tabs">
|
||||
@@ -923,67 +926,27 @@
|
||||
{/if}
|
||||
</Radio>
|
||||
|
||||
<div style="margin-left: auto; margin-top: -0.5rem;">
|
||||
{#if view.name === "changes"}
|
||||
<div style="margin-left: auto; ">
|
||||
<Popover
|
||||
popoverPadding="0"
|
||||
popoverPositionTop="2.5rem"
|
||||
popoverBorderRadius="var(--border-radius-small)">
|
||||
<Button
|
||||
let:expanded
|
||||
slot="toggle"
|
||||
let:toggle
|
||||
on:click={toggle}
|
||||
size="regular"
|
||||
disabled={patch.revisions.length === 1}>
|
||||
<span style:color="var(--color-foreground-contrast)">
|
||||
Revision
|
||||
</span>
|
||||
<span
|
||||
style:color="var(--color-fill-secondary)"
|
||||
style:font-family="var(--font-family-monospace)">
|
||||
{utils.formatObjectId(view.revision)}
|
||||
</span>
|
||||
<IconSmall name={expanded ? "chevron-up" : "chevron-down"} />
|
||||
</Button>
|
||||
<DropdownList slot="popover" items={patch.revisions}>
|
||||
<svelte:fragment slot="item" let:item>
|
||||
<Link
|
||||
on:afterNavigate={closeFocused}
|
||||
route={{
|
||||
resource: "project.patch",
|
||||
project: project.id,
|
||||
node: baseUrl,
|
||||
patch: patch.id,
|
||||
view: {
|
||||
name: view.name,
|
||||
revision: item.id,
|
||||
},
|
||||
}}>
|
||||
<DropdownListItem selected={item.id === view.revision}>
|
||||
<span
|
||||
style:color={item.id === view.revision
|
||||
? "var(--color-foreground-contrast)"
|
||||
: "var(--color-fill-gray)"}>
|
||||
Revision
|
||||
</span>
|
||||
<span
|
||||
style:color="var(--color-fill-secondary)"
|
||||
style:font-family="var(--font-family-monospace)">
|
||||
{utils.formatObjectId(item.id)}
|
||||
</span>
|
||||
</DropdownListItem>
|
||||
</Link>
|
||||
</svelte:fragment>
|
||||
</DropdownList>
|
||||
</Popover>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if view.name === "changes"}
|
||||
<div
|
||||
class="global-hide-on-mobile-down"
|
||||
style="margin-left: auto; margin-top: -0.5rem;">
|
||||
<RevisionSelector {view} {baseUrl} {patch} {project} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="tabs-spacer" />
|
||||
</div>
|
||||
<div class="bottom">
|
||||
{#if view.name === "changes"}
|
||||
<div
|
||||
style:width="100%"
|
||||
style:padding="0 1rem"
|
||||
style:display="flex"
|
||||
class="global-hide-on-small-desktop-up">
|
||||
<div style:margin-left="auto">
|
||||
<RevisionSelector {view} {baseUrl} {patch} {project} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if view.name === "diff"}
|
||||
<Changeset
|
||||
{baseUrl}
|
||||
@@ -1030,29 +993,31 @@
|
||||
previousRevOid={previousRevision?.oid}>
|
||||
{#if index === patch.revisions.length - 1}
|
||||
{#if $experimental && session && view.name === "activity"}
|
||||
<div class="connector" />
|
||||
<CommentToggleInput
|
||||
rawPath={rawPath(patch.revisions[0].id)}
|
||||
focus
|
||||
enableAttachments
|
||||
placeholder="Leave your comment"
|
||||
submit={partial(
|
||||
createComment,
|
||||
session.id,
|
||||
revision.revisionId,
|
||||
)} />
|
||||
{#if role.isDelegateOrAuthor(session.publicKey, delegates, patch.author.id)}
|
||||
<div class="global-hide-on-mobile-down">
|
||||
<div class="connector" />
|
||||
<div style="display: flex;">
|
||||
<CobStateButton
|
||||
items={items.filter(
|
||||
([, state]) => !isEqual(state, patch.state),
|
||||
)}
|
||||
{selectedItem}
|
||||
state={patch.state}
|
||||
save={partial(saveStatus, session.id)} />
|
||||
</div>
|
||||
{/if}
|
||||
<CommentToggleInput
|
||||
rawPath={rawPath(patch.revisions[0].id)}
|
||||
focus
|
||||
enableAttachments
|
||||
placeholder="Leave your comment"
|
||||
submit={partial(
|
||||
createComment,
|
||||
session.id,
|
||||
revision.revisionId,
|
||||
)} />
|
||||
{#if role.isDelegateOrAuthor(session.publicKey, delegates, patch.author.id)}
|
||||
<div class="connector" />
|
||||
<div style="display: flex;">
|
||||
<CobStateButton
|
||||
items={items.filter(
|
||||
([, state]) => !isEqual(state, patch.state),
|
||||
)}
|
||||
{selectedItem}
|
||||
state={patch.state}
|
||||
save={partial(saveStatus, session.id)} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</RevisionComponent>
|
||||
@@ -1076,43 +1041,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metadata global-hide-on-mobile">
|
||||
<div>
|
||||
<div class="metadata-section-header">Reviews</div>
|
||||
<div class="metadata-section-body">
|
||||
{#each Object.values(patchReviews) as { latest, review }}
|
||||
<div class="review" class:txt-missing={!latest}>
|
||||
<span
|
||||
class:review-accept={review.verdict === "accept"}
|
||||
class:review-reject={review.verdict === "reject"}>
|
||||
{#if review.verdict === "accept"}
|
||||
<IconSmall name="checkmark" />
|
||||
{:else if review.verdict === "reject"}
|
||||
<IconSmall name="cross" />
|
||||
{:else}
|
||||
<IconSmall name="chat" />
|
||||
{/if}
|
||||
</span>
|
||||
<NodeId nodeId={review.author.id} alias={review.author.alias} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="txt-missing">No reviews</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="metadata global-hide-on-medium-desktop-down">
|
||||
<Reviews {reviews} />
|
||||
<LabelInput
|
||||
locallyAuthenticated={role.isDelegate(session?.publicKey, delegates)}
|
||||
submitInProgress={labelState === "submit"}
|
||||
labels={patch.labels}
|
||||
on:save={async ({ detail: newLabels }) => {
|
||||
if (session) {
|
||||
labelState = "submit";
|
||||
try {
|
||||
await saveLabels(session.id, newLabels);
|
||||
} finally {
|
||||
labelState = "read";
|
||||
}
|
||||
}
|
||||
on:save={({ detail: newLabels }) => {
|
||||
void saveLabels(newLabels);
|
||||
}} />
|
||||
<Embeds embeds={uniqueEmbeds} />
|
||||
</div>
|
||||
|
||||
@@ -2,42 +2,32 @@
|
||||
import type { BaseUrl } from "@httpd-client";
|
||||
import type { Patch } from "@httpd-client";
|
||||
|
||||
import { HttpdClient } from "@httpd-client";
|
||||
import {
|
||||
absoluteTimestamp,
|
||||
formatObjectId,
|
||||
formatTimestamp,
|
||||
formatCommit,
|
||||
} from "@app/lib/utils";
|
||||
|
||||
import Badge from "@app/components/Badge.svelte";
|
||||
import DiffStatBadge from "@app/components/DiffStatBadge.svelte";
|
||||
import IconSmall from "@app/components/IconSmall.svelte";
|
||||
import InlineMarkdown from "@app/components/InlineMarkdown.svelte";
|
||||
import Link from "@app/components/Link.svelte";
|
||||
import NodeId from "@app/components/NodeId.svelte";
|
||||
import Loading from "@app/components/Loading.svelte";
|
||||
|
||||
import Labels from "../Cob/Labels.svelte";
|
||||
import DiffStatBadgeLoader from "../DiffStatBadgeLoader.svelte";
|
||||
import CommentCounter from "../CommentCounter.svelte";
|
||||
|
||||
export let projectId: string;
|
||||
export let baseUrl: BaseUrl;
|
||||
export let patch: Patch;
|
||||
|
||||
const api = new HttpdClient(baseUrl);
|
||||
|
||||
const latestRevisionIndex = patch.revisions.length - 1;
|
||||
const latestRevision = patch.revisions[latestRevisionIndex];
|
||||
$: diffPromise = api.project.getDiff(
|
||||
projectId,
|
||||
latestRevision.base,
|
||||
latestRevision.oid,
|
||||
);
|
||||
|
||||
// Counts the amount of comments in all the discussions over all revisions.
|
||||
$: commentCount = patch.revisions.reduce(
|
||||
(acc, curr) => acc + curr.discussions.reduce(acc => acc + 1, 0),
|
||||
0,
|
||||
);
|
||||
let hover = false;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -71,10 +61,9 @@
|
||||
text-decoration: underline;
|
||||
}
|
||||
.right {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
.state {
|
||||
justify-self: center;
|
||||
@@ -82,22 +71,6 @@
|
||||
margin-right: 0.5rem;
|
||||
padding: 0.25rem 0;
|
||||
}
|
||||
.labels {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.comments {
|
||||
color: var(--color-foreground-dim);
|
||||
font-size: var(--font-size-tiny);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.draft {
|
||||
color: var(--color-foreground-dim);
|
||||
}
|
||||
@@ -110,14 +83,22 @@
|
||||
.merged {
|
||||
color: var(--color-fill-primary);
|
||||
}
|
||||
.diff-comment {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
margin-left: 0.5rem;
|
||||
min-height: 1.5rem;
|
||||
}
|
||||
@media (max-width: 719.98px) {
|
||||
.diff-comment {
|
||||
flex-direction: column-reverse;
|
||||
align-items: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
class="patch-teaser"
|
||||
on:mouseenter={() => (hover = true)}
|
||||
on:mouseleave={() => (hover = false)}>
|
||||
<div role="button" tabindex="0" class="patch-teaser">
|
||||
<div
|
||||
class="state"
|
||||
class:draft={patch.state.status === "draft"}
|
||||
@@ -136,25 +117,15 @@
|
||||
patch: patch.id,
|
||||
}}>
|
||||
<span class="patch-title">
|
||||
<InlineMarkdown fontSize="regular" content={patch.title} />
|
||||
<InlineMarkdown fontSize="regular" content={patch.title}>
|
||||
{#if patch.labels.length > 0}
|
||||
<span style="display: inline-flex; gap: 0.5rem; flex-wrap: wrap;">
|
||||
<Labels labels={patch.labels} />
|
||||
</span>
|
||||
{/if}
|
||||
</InlineMarkdown>
|
||||
</span>
|
||||
</Link>
|
||||
<span class="labels">
|
||||
{#each patch.labels.slice(0, 4) as label}
|
||||
<Badge
|
||||
style="max-width:7rem"
|
||||
variant={hover ? "background" : "neutral"}>
|
||||
<span class="label">{label}</span>
|
||||
</Badge>
|
||||
{/each}
|
||||
{#if patch.labels.length > 4}
|
||||
<Badge
|
||||
title={patch.labels.slice(4, undefined).join(" ")}
|
||||
variant={hover ? "background" : "neutral"}>
|
||||
<span class="label">+{patch.labels.length - 4} more labels</span>
|
||||
</Badge>
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
<div class="summary">
|
||||
<span class="subtitle">
|
||||
@@ -165,8 +136,10 @@
|
||||
{patch.revisions.length > 1 ? "updated" : "opened"}
|
||||
<span class="global-oid">{formatObjectId(patch.id)}</span>
|
||||
{#if patch.revisions.length > 1}
|
||||
to <span class="global-oid">
|
||||
{formatObjectId(patch.revisions[patch.revisions.length - 1].id)}
|
||||
<span class="global-hide-on-mobile-down">
|
||||
to <span class="global-oid">
|
||||
{formatObjectId(patch.revisions[patch.revisions.length - 1].id)}
|
||||
</span>
|
||||
</span>
|
||||
{/if}
|
||||
<span title={absoluteTimestamp(latestRevision.timestamp)}>
|
||||
@@ -176,37 +149,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div style:display="flex" style:gap="1rem">
|
||||
<div class="diff-comment">
|
||||
{#if commentCount > 0}
|
||||
<div class="comments">
|
||||
<IconSmall name="chat" />
|
||||
<span>{commentCount}</span>
|
||||
</div>
|
||||
<CommentCounter {commentCount} />
|
||||
{/if}
|
||||
{#await diffPromise}
|
||||
<Loading small />
|
||||
{:then { diff }}
|
||||
<Link
|
||||
title="Compare {formatCommit(latestRevision.base)}..{formatCommit(
|
||||
latestRevision.oid,
|
||||
)}"
|
||||
route={{
|
||||
resource: "project.patch",
|
||||
project: projectId,
|
||||
node: baseUrl,
|
||||
patch: patch.id,
|
||||
view: {
|
||||
name: "diff",
|
||||
fromCommit: latestRevision.base,
|
||||
toCommit: latestRevision.oid,
|
||||
},
|
||||
}}>
|
||||
<DiffStatBadge
|
||||
hoverable
|
||||
insertions={diff.stats.insertions}
|
||||
deletions={diff.stats.deletions} />
|
||||
</Link>
|
||||
{/await}
|
||||
<DiffStatBadgeLoader {projectId} {baseUrl} {patch} {latestRevision} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<script lang="ts">
|
||||
import type { PatchView } from "../router";
|
||||
import type { BaseUrl, Patch, Project } from "@httpd-client";
|
||||
import * as utils from "@app/lib/utils";
|
||||
|
||||
import Button from "@app/components/Button.svelte";
|
||||
import DropdownList from "@app/components/DropdownList.svelte";
|
||||
import DropdownListItem from "@app/components/DropdownList/DropdownListItem.svelte";
|
||||
import IconSmall from "@app/components/IconSmall.svelte";
|
||||
import Link from "@app/components/Link.svelte";
|
||||
import Popover from "@app/components/Popover.svelte";
|
||||
import { closeFocused } from "@app/components/Popover.svelte";
|
||||
|
||||
export let view: Extract<PatchView, { name: "changes" }>;
|
||||
export let baseUrl: BaseUrl;
|
||||
export let patch: Patch;
|
||||
export let project: Project;
|
||||
</script>
|
||||
|
||||
<Popover
|
||||
popoverPadding="0"
|
||||
popoverPositionTop="3rem"
|
||||
popoverBorderRadius="var(--border-radius-small)">
|
||||
<Button
|
||||
let:expanded
|
||||
slot="toggle"
|
||||
let:toggle
|
||||
on:click={toggle}
|
||||
size="regular"
|
||||
disabled={patch.revisions.length === 1}>
|
||||
<span style:color="var(--color-foreground-contrast)">Revision</span>
|
||||
<span
|
||||
style:color="var(--color-fill-secondary)"
|
||||
style:font-family="var(--font-family-monospace)">
|
||||
{utils.formatObjectId(view.revision)}
|
||||
</span>
|
||||
<IconSmall name={expanded ? "chevron-up" : "chevron-down"} />
|
||||
</Button>
|
||||
<DropdownList slot="popover" items={patch.revisions}>
|
||||
<svelte:fragment slot="item" let:item>
|
||||
<Link
|
||||
on:afterNavigate={closeFocused}
|
||||
route={{
|
||||
resource: "project.patch",
|
||||
project: project.id,
|
||||
node: baseUrl,
|
||||
patch: patch.id,
|
||||
view: {
|
||||
name: view.name,
|
||||
revision: item.id,
|
||||
},
|
||||
}}>
|
||||
<DropdownListItem selected={item.id === view.revision}>
|
||||
<span
|
||||
style:color={item.id === view.revision
|
||||
? "var(--color-foreground-contrast)"
|
||||
: "var(--color-fill-gray)"}>
|
||||
Revision
|
||||
</span>
|
||||
<span
|
||||
style:color="var(--color-fill-secondary)"
|
||||
style:font-family="var(--font-family-monospace)">
|
||||
{utils.formatObjectId(item.id)}
|
||||
</span>
|
||||
</DropdownListItem>
|
||||
</Link>
|
||||
</svelte:fragment>
|
||||
</DropdownList>
|
||||
</Popover>
|
||||
@@ -78,7 +78,7 @@
|
||||
|
||||
<style>
|
||||
.more {
|
||||
margin: 2rem 0;
|
||||
margin-top: 2rem;
|
||||
min-height: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -162,22 +162,24 @@
|
||||
<div style="margin-left: auto; display: flex; gap: 1rem;">
|
||||
<Share {baseUrl} />
|
||||
{#if $experimental && $httpdStore.state === "authenticated" && isLocal(baseUrl.hostname)}
|
||||
<Popover popoverPositionTop="2.5rem" popoverPositionRight="0">
|
||||
<Button
|
||||
slot="toggle"
|
||||
let:toggle
|
||||
on:click={toggle}
|
||||
variant="secondary">
|
||||
<IconSmall name="plus" />
|
||||
New Patch
|
||||
</Button>
|
||||
<div class="global-hide-on-mobile-down">
|
||||
<Popover popoverPositionTop="2.5rem" popoverPositionRight="0">
|
||||
<Button
|
||||
slot="toggle"
|
||||
let:toggle
|
||||
on:click={toggle}
|
||||
variant="secondary">
|
||||
<IconSmall name="plus" />
|
||||
New Patch
|
||||
</Button>
|
||||
|
||||
<div slot="popover" class="popover txt-small">
|
||||
To create a patch, first checkout a new branch and commit your
|
||||
changes, then run the following command.
|
||||
<Command command="git push rad HEAD:refs/patches" />
|
||||
</div>
|
||||
</Popover>
|
||||
<div slot="popover" class="popover txt-small">
|
||||
To create a patch, first checkout a new branch and commit your
|
||||
changes, then run the following command.
|
||||
<Command command="git push rad HEAD:refs/patches" />
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,13 +54,15 @@
|
||||
let:toggle
|
||||
on:click={toggle}>
|
||||
<IconSmall name="link" />
|
||||
{caption}
|
||||
<span class="global-hide-on-small-desktop-down">
|
||||
{caption}
|
||||
</span>
|
||||
</Button>
|
||||
<ShareButton slot="popover" />
|
||||
</Popover>
|
||||
{:else}
|
||||
<Button variant="outline" size="regular" on:click={copy}>
|
||||
<IconSmall name={shareIcon} />
|
||||
Copy link
|
||||
<span class="global-hide-on-small-desktop-down">Copy link</span>
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
@@ -23,8 +23,9 @@
|
||||
export let activeTab: ActiveTab | undefined = undefined;
|
||||
export let baseUrl: BaseUrl;
|
||||
export let project: Project;
|
||||
export let collapsedOnly = false;
|
||||
|
||||
let expanded = loadSidebarState();
|
||||
let expanded = collapsedOnly ? false : loadSidebarState();
|
||||
|
||||
export function storeSidebarState(expanded: boolean): void {
|
||||
window.localStorage.setItem(
|
||||
@@ -338,40 +339,45 @@
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="sidebar-footer" style:flex-direction="row">
|
||||
<Button title={"Collapse"} on:click={toggleSidebar} variant="background">
|
||||
<div class="icon" class:expanded>
|
||||
<IconSmall name="chevron-left" />
|
||||
</div>
|
||||
</Button>
|
||||
<div style:width="1.5rem" />
|
||||
<div class="horizontal-buttons" class:expanded>
|
||||
<Popover popoverPositionBottom="2.5rem" popoverPositionLeft="0">
|
||||
<Button
|
||||
variant="background"
|
||||
title="Settings"
|
||||
slot="toggle"
|
||||
let:toggle
|
||||
on:click={toggle}>
|
||||
<IconSmall name="settings" />
|
||||
Settings
|
||||
</Button>
|
||||
{#if !collapsedOnly}
|
||||
<div class="sidebar-footer" style:flex-direction="row">
|
||||
<Button
|
||||
title={"Collapse"}
|
||||
on:click={toggleSidebar}
|
||||
variant="background">
|
||||
<div class="icon" class:expanded>
|
||||
<IconSmall name="chevron-left" />
|
||||
</div>
|
||||
</Button>
|
||||
<div style:width="1.5rem" />
|
||||
<div class="horizontal-buttons" class:expanded>
|
||||
<Popover popoverPositionBottom="2.5rem" popoverPositionLeft="0">
|
||||
<Button
|
||||
variant="background"
|
||||
title="Settings"
|
||||
slot="toggle"
|
||||
let:toggle
|
||||
on:click={toggle}>
|
||||
<IconSmall name="settings" />
|
||||
Settings
|
||||
</Button>
|
||||
|
||||
<Settings slot="popover" />
|
||||
</Popover>
|
||||
<Popover popoverPositionBottom="2.5rem" popoverPositionLeft="0">
|
||||
<Button
|
||||
variant="background"
|
||||
title="Help"
|
||||
slot="toggle"
|
||||
let:toggle
|
||||
on:click={toggle}>
|
||||
<IconSmall name="help" />
|
||||
Help
|
||||
</Button>
|
||||
<Help slot="popover" />
|
||||
</Popover>
|
||||
<Settings slot="popover" />
|
||||
</Popover>
|
||||
<Popover popoverPositionBottom="2.5rem" popoverPositionLeft="0">
|
||||
<Button
|
||||
variant="background"
|
||||
title="Help"
|
||||
slot="toggle"
|
||||
let:toggle
|
||||
on:click={toggle}>
|
||||
<IconSmall name="help" />
|
||||
Help
|
||||
</Button>
|
||||
<Help slot="popover" />
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding-bottom: 2.5rem;
|
||||
/* To allow pre elements to shrink when overflowing */
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -119,9 +120,16 @@
|
||||
top: 0rem;
|
||||
max-height: calc(100vh - 5.5rem);
|
||||
}
|
||||
@media (max-width: 719.98px) {
|
||||
.container {
|
||||
display: flex;
|
||||
width: inherit;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<Layout {baseUrl} {project} activeTab="source">
|
||||
<Layout {baseUrl} {project} activeTab="source" stylePaddingBottom="0">
|
||||
<ProjectNameHeader {project} {baseUrl} {seeding} slot="header" />
|
||||
|
||||
<div style:margin="1rem 0 1rem 1rem" slot="subheader">
|
||||
@@ -136,7 +144,7 @@
|
||||
filesLinkActive={true}
|
||||
historyLinkActive={false} />
|
||||
</div>
|
||||
<div class="global-hide-on-desktop">
|
||||
<div class="global-hide-on-medium-desktop-up">
|
||||
{#if tree.entries.length > 0}
|
||||
<div style:margin="1rem">
|
||||
<Button
|
||||
@@ -170,7 +178,7 @@
|
||||
|
||||
<div class="container center-content">
|
||||
{#if tree.entries.length > 0}
|
||||
<div class="column-left global-hide-on-mobile">
|
||||
<div class="column-left global-hide-on-small-desktop-down">
|
||||
<div class="source-tree sticky">
|
||||
<TreeComponent
|
||||
projectId={project.id}
|
||||
|
||||
@@ -137,13 +137,21 @@
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.markdown-wrapper {
|
||||
padding: 2rem;
|
||||
}
|
||||
@media (max-width: 719.98px) {
|
||||
.markdown-wrapper {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<File sticky={false}>
|
||||
<FilePath slot="left-header" filenameWithPath={blob.path} />
|
||||
<svelte:fragment slot="right-header">
|
||||
<CommitButton styleRoundBorders {projectId} {baseUrl} commit={lastCommit} />
|
||||
<div class="global-hide-on-mobile teaser-buttons">
|
||||
<div class="global-hide-on-mobile-down teaser-buttons">
|
||||
{#if enablePreview}
|
||||
<Radio ariaLabel="Toggle render method">
|
||||
<Button
|
||||
@@ -188,7 +196,7 @@
|
||||
{/if}
|
||||
{:else if preview && blob.content}
|
||||
{#if isMarkdown}
|
||||
<div style:padding="2rem">
|
||||
<div class="markdown-wrapper">
|
||||
<Markdown content={blob.content} {rawPath} {path} />
|
||||
</div>
|
||||
{:else if isSvg}
|
||||
|
||||
@@ -75,15 +75,18 @@
|
||||
Private
|
||||
</Badge>
|
||||
{/if}
|
||||
<div
|
||||
class="global-hide-on-mobile"
|
||||
style="margin-left: auto; display: flex; gap: 0.5rem;">
|
||||
<div style="margin-left: auto; display: flex; gap: 0.5rem;">
|
||||
<Share {baseUrl} />
|
||||
<CloneButton {baseUrl} id={project.id} name={project.name} />
|
||||
<SeedButton
|
||||
{seeding}
|
||||
seedCount={project.seeding}
|
||||
projectId={project.id} />
|
||||
<div
|
||||
style:display="flex"
|
||||
style:gap="0.5rem"
|
||||
class="global-hide-on-mobile-down">
|
||||
<CloneButton {baseUrl} id={project.id} name={project.name} />
|
||||
<SeedButton
|
||||
{seeding}
|
||||
seedCount={project.seeding}
|
||||
projectId={project.id} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="id">
|
||||
|
||||
@@ -43,7 +43,9 @@
|
||||
<div class="identifier global-commit">
|
||||
{commitShortId}
|
||||
</div>
|
||||
<span>{commit.summary}</span>
|
||||
<span class="global-hide-on-small-desktop-down">
|
||||
{commit.summary}
|
||||
</span>
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@@ -397,7 +397,7 @@ test("only one modal can be open at a time", async ({ page }) => {
|
||||
await expect(page.getByText("bob")).not.toBeVisible();
|
||||
await expect(page.getByText("feature/branch")).not.toBeVisible();
|
||||
|
||||
await page.getByText("Settings").click();
|
||||
await page.getByRole("button", { name: "Settings" }).click();
|
||||
await expect(page.getByText("Code font")).toBeVisible();
|
||||
await expect(page.getByText("Use the Radicle CLI")).not.toBeVisible();
|
||||
await expect(page.getByText("bob")).not.toBeVisible();
|
||||
|
||||
@@ -180,7 +180,7 @@ test("handling embeds", async ({ page, authenticatedPeer }) => {
|
||||
`${scheme}://${hostname}:${port}/raw/rad:z2J7s48EbCBckcEmj2dm5eaFVoBsy/blobs/bae036309c2182c7304c97956969369823b5c6ad?mime=image/png`,
|
||||
);
|
||||
await expect(
|
||||
page.locator(".badge").filter({ hasText: "radicle-228x228.png" }),
|
||||
page.getByRole("button", { name: "radicle-228x228.png" }),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Edit" }).click();
|
||||
@@ -201,9 +201,9 @@ test("handling embeds", async ({ page, authenticatedPeer }) => {
|
||||
await page.getByRole("button", { name: "Save" }).click();
|
||||
await expect(page.getByRole("button", { name: "Submit" })).toBeHidden();
|
||||
await expect(
|
||||
page.locator(".badge").filter({ hasText: "apple-touch-icon.png" }),
|
||||
page.getByRole("button", { name: "apple-touch-icon.png" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.locator(".badge").filter({ hasText: "radicle-228x228.png" }),
|
||||
page.getByRole("button", { name: "radicle-228x228.png" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -88,12 +88,12 @@ test("create a new issue", async ({ page, authenticatedPeer }) => {
|
||||
await expect(page.getByText("This is a title")).toBeVisible();
|
||||
await expect(page.getByText("This is a description")).toBeVisible();
|
||||
await expect(
|
||||
page.getByText(
|
||||
`did:key:${authenticatedPeer.nodeId.substring(
|
||||
page.getByRole("button", {
|
||||
name: `did:key:${authenticatedPeer.nodeId.substring(
|
||||
0,
|
||||
6,
|
||||
)}…${authenticatedPeer.nodeId.slice(-6)}`,
|
||||
),
|
||||
}),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("button", { name: "documentation" }),
|
||||
|
||||
@@ -134,7 +134,7 @@ test("handling embeds", async ({ page, authenticatedPeer }) => {
|
||||
`${scheme}://${hostname}:${port}/raw/rad:z2J7s48EbCBckcEmj2dm5eaFVoBsy/blobs/bae036309c2182c7304c97956969369823b5c6ad?mime=image/png`,
|
||||
);
|
||||
await expect(
|
||||
page.locator(".badge").filter({ hasText: "radicle-228x228.png" }),
|
||||
page.getByRole("button", { name: "radicle-228x228.png" }),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "edit comment" }).click();
|
||||
@@ -155,9 +155,9 @@ test("handling embeds", async ({ page, authenticatedPeer }) => {
|
||||
await page.getByRole("button", { name: "Save" }).click();
|
||||
await expect(page.getByRole("button", { name: "Save" })).toBeHidden();
|
||||
await expect(
|
||||
page.locator(".badge").filter({ hasText: "apple-touch-icon.png" }),
|
||||
page.getByRole("button", { name: "apple-touch-icon.png" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.locator(".badge").filter({ hasText: "radicle-228x228.png" }),
|
||||
page.getByRole("button", { name: "radicle-228x228.png" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ test("theme persistance", async ({ page }) => {
|
||||
await expect(
|
||||
page.getByRole("banner").getByRole("link", { name: "source-browsing" }),
|
||||
).toBeVisible();
|
||||
await page.getByText("Settings").click();
|
||||
await page.getByRole("button", { name: "Settings" }).click();
|
||||
|
||||
await page.getByText("System").click();
|
||||
await page.getByRole("button", { name: "Light Mode" }).click();
|
||||
@@ -38,7 +38,7 @@ test("change theme", async ({ page }) => {
|
||||
await expect(
|
||||
page.getByRole("banner").getByRole("link", { name: "source-browsing" }),
|
||||
).toBeVisible();
|
||||
await page.getByText("Settings").click();
|
||||
await page.getByRole("button", { name: "Settings" }).click();
|
||||
|
||||
await page.getByRole("button", { name: "Light Mode" }).click();
|
||||
await expect(page.locator("html")).toHaveAttribute("data-theme", "light");
|
||||
@@ -65,7 +65,7 @@ test("change code font", async ({ page }) => {
|
||||
page.getByRole("banner").getByRole("link", { name: "source-browsing" }),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByText("Settings").click();
|
||||
await page.getByRole("button", { name: "Settings" }).click();
|
||||
|
||||
await page.getByText("System").click();
|
||||
await expect(page.getByText("System")).toHaveClass(/selected/);
|
||||
|
||||
Reference in New Issue
Block a user