Allow opening links in a new tab

Signed-off-by: Rūdolfs Ošiņš <rudolfs@osins.org>
This commit is contained in:
Rūdolfs Ošiņš
2023-04-27 15:54:53 +02:00
parent 518b821250
commit 1f09d8a2a8
45 changed files with 1009 additions and 984 deletions
-8
View File
@@ -144,14 +144,6 @@ p {
.txt-faded {
color: var(--color-foreground-5);
}
.txt-title {
font-size: var(--font-size-large);
font-weight: var(--font-weight-normal);
color: var(--color-secondary);
text-align: left;
text-overflow: ellipsis;
overflow-x: hidden;
}
.txt-link {
color: var(--color-foreground-6);
text-decoration: none;
+1 -1
View File
@@ -35,7 +35,7 @@
{#each results as result}
<li>
<Link
on:click={modal.hide}
on:afterNavigate={modal.hide}
route={{
resource: "projects",
params: {
+2 -2
View File
@@ -96,7 +96,7 @@
class="item selector"
on:click|stopPropagation={() => (showFonts = !showFonts)}>
<div>Code font</div>
<Icon name={`chevron-${showFonts ? "up" : "down"}`} />
<Icon name={`chevron-${showFonts ? "down" : "right"}`} />
</div>
{#if showFonts}
<div
@@ -112,7 +112,7 @@
style:font-family={font.fontFamily}>
{font.displayName}
{#if isSelectedFont}
<Icon name="checkmark" />
<Icon name="checkmark-small" />
{/if}
</div>
{/each}
+1 -1
View File
@@ -17,6 +17,7 @@
align-items: center;
color: var(--color-foreground-6);
padding: 0.125rem 0;
gap: 0.25rem;
}
.id {
overflow: hidden;
@@ -24,7 +25,6 @@
white-space: nowrap;
}
.body {
margin: 0 0.4rem;
white-space: nowrap;
}
</style>
-1
View File
@@ -32,7 +32,6 @@
display: inline-block !important;
width: 1rem;
height: 1rem;
margin-right: 0.5rem;
}
</style>
+1 -1
View File
@@ -17,7 +17,7 @@
.section {
display: flex;
align-items: center;
max-width: 13rem;
max-width: 13.5rem;
padding: 0.2rem 0.5rem;
}
.text {
+20 -12
View File
@@ -1,19 +1,30 @@
<script lang="ts" strictEvents>
import debounce from "lodash/debounce";
import { createEventDispatcher } from "svelte";
import Icon from "@app/components/Icon.svelte";
import { toClipboard } from "@app/lib/utils";
const dispatch = createEventDispatcher<{ copied: never }>();
const copy = () => {
toClipboard(text);
dispatch("copied");
};
import Icon from "@app/components/Icon.svelte";
export let text: string;
export let small = false;
export let tooltip: string | undefined = undefined;
const dispatch = createEventDispatcher<{ copied: never }>();
let icon: "clipboard-small" | "checkmark-small" | "clipboard" | "checkmark" =
small ? "clipboard-small" : "clipboard";
const restoreIcon = debounce(() => {
icon = small ? "clipboard-small" : "clipboard";
}, 800);
const copy = () => {
toClipboard(text);
dispatch("copied");
icon = small ? "checkmark-small" : "checkmark";
restoreIcon();
};
</script>
<style>
@@ -24,6 +35,7 @@
display: inline-flex;
justify-content: center;
align-items: center;
user-select: none;
}
.clipboard.small {
width: 1.5rem;
@@ -46,9 +58,5 @@
class="clipboard"
class:small
on:click|stopPropagation={copy}>
{#if small}
<Icon name="clipboard-small" />
{:else}
<Icon name="clipboard" />
{/if}
<Icon name={icon} />
</span>
+1
View File
@@ -25,6 +25,7 @@
{#if stackTrace}
<div class="stack-trace">
<Clipboard
small
tooltip="Copy error to clipboard"
text={JSON.stringify({ errorMessage: message, stackTrace }, null, 2)} />
</div>
+218 -189
View File
@@ -6,6 +6,8 @@
| "checkmark"
| "checkmark-small"
| "chevron-down"
| "chevron-left"
| "chevron-right"
| "chevron-up"
| "clipboard"
| "clipboard-small"
@@ -36,41 +38,44 @@
viewBox="0 0 24 24">
{#if name === "browse"}
<path
d="M8.46934 7.23871C8.61151 7.10623 8.79956 7.03411 8.99386
7.03753C9.18816 7.04096 9.37355 7.11967 9.51096 7.25709C9.64838 7.3945
9.72709 7.57988 9.73052 7.77419C9.73394 7.96849 9.66182 8.15653 9.52934
8.29871L5.80934 12.0187L9.52934 15.7387C9.60303 15.8074 9.66213 15.8902
9.70312 15.9822C9.74411 16.0742 9.76615 16.1735 9.76793 16.2742C9.76971
16.3749 9.75118 16.4749 9.71346 16.5683C9.67574 16.6617 9.6196 16.7465
9.54838 16.8177C9.47716 16.889 9.39233 16.9451 9.29894 16.9828C9.20555
17.0206 9.10552 17.0391 9.00482 17.0373C8.90411 17.0355 8.8048 17.0135
8.7128 16.9725C8.6208 16.9315 8.538 16.8724 8.46934 16.7987L4.21934
12.5487C4.07889 12.4081 4 12.2175 4 12.0187C4 11.82 4.07889 11.6293 4.21934
11.4887L8.46934 7.23871V7.23871ZM15.0293 7.23871C14.9607 7.16502 14.8779
7.10592 14.7859 7.06493C14.6939 7.02394 14.5946 7.00189 14.4939
7.00012C14.3932 6.99834 14.2931 7.01686 14.1997 7.05459C14.1064 7.09231
14.0215 7.14845 13.9503 7.21967C13.8791 7.29089 13.8229 7.37572 13.7852
7.46911C13.7475 7.5625 13.729 7.66253 13.7307 7.76323C13.7325 7.86393
13.7546 7.96325 13.7956 8.05525C13.8366 8.14725 13.8957 8.23005 13.9693
8.29871L17.6893 12.0187L13.9693 15.7387C13.8957 15.8074 13.8366 15.8902
13.7956 15.9822C13.7546 16.0742 13.7325 16.1735 13.7307 16.2742C13.729
16.3749 13.7475 16.4749 13.7852 16.5683C13.8229 16.6617 13.8791 16.7465
13.9503 16.8177C14.0215 16.889 14.1064 16.9451 14.1997 16.9828C14.2931
17.0206 14.3932 17.0391 14.4939 17.0373C14.5946 17.0355 14.6939 17.0135
14.7859 16.9725C14.8779 16.9315 14.9607 16.8724 15.0293 16.7987L19.2793
12.5487C19.4198 12.4081 19.4987 12.2175 19.4987 12.0187C19.4987 11.82
19.4198 11.6293 19.2793 11.4887L15.0293 7.23871V7.23871Z" />
fill-rule="evenodd"
clip-rule="evenodd"
d="M9.35356 6.64645C9.54882 6.84171 9.54882 7.15829 9.35356
7.35355L5.06066 11.6464C4.8654 11.8417 4.8654 12.1583 5.06066
12.3536L9.35356 16.6464C9.54882 16.8417 9.54882 17.1583 9.35356
17.3536C9.15829 17.5488 8.84171 17.5488 8.64645 17.3536L4.35355
13.0607C3.76777 12.4749 3.76777 11.5251 4.35356 10.9393L8.64645
6.64645C8.84171 6.45118 9.15829 6.45118 9.35356 6.64645ZM14.6464
6.64645C14.8417 6.45118 15.1583 6.45118 15.3536 6.64645L19.6464
10.9393C20.2322 11.5251 20.2322 12.4749 19.6464 13.0607L15.3536
17.3536C15.1583 17.5488 14.8417 17.5488 14.6464 17.3536C14.4512 17.1583
14.4512 16.8417 14.6464 16.6464L18.9393 12.3536C19.1346 12.1583 19.1346
11.8417 18.9393 11.6464L14.6464 7.35355C14.4512 7.15829 14.4512 6.84171
14.6464 6.64645Z" />
{:else if name === "clipboard"}
<path
d="M9 5H14.7071L18 8.29289V17H9V5ZM10 6V16H17V9H14V6H10ZM15
6.70711L16.2929 8H15V6.70711ZM7 8H8V18H15V19H7V8Z" />
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.5 6C8.5 5.17157 9.17157 4.5 10 4.5H18C18.8284 4.5 19.5 5.17157 19.5
6V14C19.5 14.8284 18.8284 15.5 18 15.5H15.5V18C15.5 18.8284 14.8284 19.5
14 19.5H6C5.17157 19.5 4.5 18.8284 4.5 18V10C4.5 9.17157 5.17157 8.5 6
8.5H8.5V6ZM8.5 9.5H6C5.72386 9.5 5.5 9.72386 5.5 10V18C5.5 18.2761
5.72386 18.5 6 18.5H14C14.2761 18.5 14.5 18.2761 14.5 18V15.5H10C9.17157
15.5 8.5 14.8284 8.5 14V9.5ZM10 5.5C9.72386 5.5 9.5 5.72386 9.5 6V14C9.5
14.2761 9.72386 14.5 10 14.5H18C18.2761 14.5 18.5 14.2761 18.5 14V6C18.5
5.72386 18.2761 5.5 18 5.5H10Z" />
{:else if name === "clipboard-small"}
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14 7L17 10V16H10V7H14ZM13
8H11V15H16V11H13V8ZM14 8.41421V10H15.5858L14 8.41421ZM8
10H9V17H14V18H8V10Z" />
d="M10 6.5C10 5.67157 10.6716 5 11.5 5H17.5C18.3284 5 19 5.67157 19
6.5V12.5C19 13.3284 18.3284 14 17.5 14H15V16.5C15 17.3284 14.3284 18 13.5
18H7.5C6.67157 18 6 17.3284 6 16.5V10.5C6 9.67157 6.67157 9 7.5
9H10V6.5ZM10 10H7.5C7.22386 10 7 10.2239 7 10.5V16.5C7 16.7761 7.22386 17
7.5 17H13.5C13.7761 17 14 16.7761 14 16.5V14H11.5C10.6716 14 10 13.3284
10 12.5V10ZM11.5 13C11.2239 13 11 12.7761 11 12.5V6.5C11 6.22386 11.2239
6 11.5 6H17.5C17.7761 6 18 6.22386 18 6.5V12.5C18 12.7761 17.7761 13 17.5
13H11.5Z" />
{:else if name === "chat"}
<path
fill-rule="evenodd"
@@ -88,161 +93,161 @@
13.5H8C7.72386 13.5 7.5 13.2761 7.5 13Z" />
{:else if name === "ellipsis"}
<path
d="M7 12a2 2 0 1 1-4.001-.001A2 2 0 0 1 7 12zm12-2a2 2 0 1 0 .001
4.001A2 2 0 0 0 19 10zm-7 0a2 2 0 1 0 .001 4.001A2 2 0 0 0 12 10z" />
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.5 13C6.32843 13 7 12.3284 7 11.5C7 10.6716 6.32843 10 5.5
10C4.67157 10 4 10.6716 4 11.5C4 12.3284 4.67157 13 5.5 13ZM13.5
11.5C13.5 12.3284 12.8284 13 12 13C11.1716 13 10.5 12.3284 10.5 11.5C10.5
10.6716 11.1716 10 12 10C12.8284 10 13.5 10.6716 13.5 11.5ZM20 11.5C20
12.3284 19.3284 13 18.5 13C17.6716 13 17 12.3284 17 11.5C17 10.6716
17.6716 10 18.5 10C19.3284 10 20 10.6716 20 11.5Z" />
{:else if name === "chevron-down"}
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 16.353549 9.8535548 c 0.19527 0.1952622 0.19527 0.5118432 0
0.7071032 l -3.29289 3.2929 c -0.58579 0.58578 -1.53553 0.58578
-2.12132 0 l -3.292894 -3.2929 c -0.195262 -0.19526 -0.195262
-0.511841 0 -0.7071042 0.195263 -0.195262 0.511844 -0.195262
0.707104 0 l 3.2929 3.2928942 c 0.19526 0.19526 0.51184 0.19526
0.7071 0 l 3.2929 -3.2928932 c 0.19526 -0.195262 0.51184
-0.195262 0.7071 0 z" />
d="M16.3536 9.64645C16.5488 9.84171 16.5488 10.1583 16.3536
10.3536L13.0607 13.6464C12.4749 14.2322 11.5251 14.2322 10.9393
13.6464L7.64645 10.3536C7.45118 10.1583 7.45118 9.84171 7.64645
9.64645C7.84171 9.45118 8.15829 9.45118 8.35355 9.64645L11.6464
12.9393C11.8417 13.1346 12.1583 13.1346 12.3536 12.9393L15.6464
9.64645C15.8417 9.45118 16.1583 9.45118 16.3536 9.64645Z" />
{:else if name === "chevron-up"}
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.64645 14.3536C7.45118 14.1583 7.45118 13.8417 7.64645
13.6464L10.9393 10.3536C11.5251 9.76777 12.4749 9.76777 13.0607
10.3536L16.3536 13.6464C16.5488 13.8417 16.5488 14.1583 16.3536
14.3536C16.1583 14.5488 15.8417 14.5488 15.6464 14.3536L12.3536
11.0607C12.1583 10.8654 11.8417 10.8654 11.6464 11.0607L8.35355
14.3536C8.15829 14.5488 7.84171 14.5488 7.64645 14.3536Z" />
13.6464L10.9393 10.3536C11.5251 9.76777 12.4749 9.76777 13.0607
10.3536L16.3536 13.6464C16.5488 13.8417 16.5488 14.1583 16.3536
14.3536C16.1583 14.5488 15.8417 14.5488 15.6464 14.3536L12.3536
11.0607C12.1583 10.8654 11.8417 10.8654 11.6464 11.0607L8.35355
14.3536C8.15829 14.5488 7.84171 14.5488 7.64645 14.3536Z" />
{:else if name === "chevron-left"}
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14.3536 16.3536C14.1583 16.5488 13.8417 16.5488 13.6464
16.3536L10.3536 13.0607C9.76777 12.4749 9.76777 11.5251 10.3536
10.9393L13.6464 7.64645C13.8417 7.45119 14.1583 7.45119 14.3536
7.64645C14.5488 7.84171 14.5488 8.15829 14.3536 8.35355L11.0607
11.6464C10.8654 11.8417 10.8654 12.1583 11.0607 12.3536L14.3536
15.6464C14.5488 15.8417 14.5488 16.1583 14.3536 16.3536Z" />
{:else if name === "chevron-right"}
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M9.64645 7.64645C9.84171 7.45118 10.1583 7.45118 10.3536
7.64645L13.6464 10.9393C14.2322 11.5251 14.2322 12.4749 13.6464
13.0607L10.3536 16.3536C10.1583 16.5488 9.84171 16.5488 9.64645
16.3536C9.45118 16.1583 9.45118 15.8417 9.64645 15.6464L12.9393
12.3536C13.1346 12.1583 13.1346 11.8417 12.9393 11.6464L9.64645
8.35355C9.45118 8.15829 9.45118 7.84171 9.64645 7.64645Z" />
{:else if name === "gear"}
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M9.23219 2.01728C9.38084
1.97716 9.53969 2.00775 9.66279 2.10022L11.5852 3.5442C11.8032 3.53214
12.0218 3.53214 12.2398 3.5442L14.1622 2.10022C14.2851 2.00788 14.4437
1.97724 14.5922 2.01712C15.4415 2.24523 16.2573 2.58349 17.0188
3.02327C17.1517 3.10003 17.242 3.23359 17.2637 3.38554L17.5851
5.63541L20.5219 6.58028C20.6402 6.61834 20.7401 6.69907 20.8021
6.80675C21.2406 7.56841 21.5787 8.38367 21.8077 9.23219C21.8479 9.38084
21.8173 9.53969 21.7248 9.66279L20.2747 11.5933C20.2763 11.6489 20.278
11.7141 20.2792 11.7842C20.2816 11.9194 20.2827 12.0847 20.2779
12.236L21.7248 14.1622C21.8171 14.2851 21.8478 14.4437 21.8079
14.5922C21.5798 15.4415 21.2415 16.2573 20.8017 17.0188C20.7397 17.1262
20.64 17.2067 20.5219 17.2447L17.5851 18.1896L17.2637 20.4395C17.242 20.5916
17.1515 20.7253 17.0183 20.8021C16.2566 21.2406 15.4413 21.5787 14.5928
21.8077C14.4442 21.8479 14.2853 21.8173 14.1622 21.7248L12.2398
20.2808C12.0255 20.2927 11.8107 20.2929 11.5964 20.2814L9.72748
21.8005C9.60295 21.9017 9.4374 21.937 9.28241 21.8954C8.41326 21.6619
7.54437 21.228 6.8062 20.8017C6.67328 20.725 6.58299 20.5914 6.56128
20.4395L6.22012 18.0514C6.06864 17.9052 5.91981 17.7564 5.77364
17.6049L3.38554 17.2637C3.23337 17.242 3.09966 17.1515 3.02295
17.0183C2.58438 16.2566 2.24635 15.4413 2.01728 14.5928C1.97716 14.4442
2.00775 14.2853 2.10022 14.1622L3.55032 12.2317C3.54868 12.1761 3.54702
12.1109 3.54579 12.0409C3.5434 11.9056 3.54229 11.7403 3.5471
11.5891L2.10022 9.66279C2.00788 9.53986 1.97724 9.38129 2.01712
9.23281C2.24523 8.38352 2.58349 7.56772 3.02327 6.8062C3.10003 6.67328
3.23359 6.58299 3.38554 6.56128L5.77364 6.22012C5.91981 6.06864 6.06864
5.91981 6.22012 5.77364L6.56128 3.38554C6.58302 3.23337 6.67354 3.09966
6.80675 3.02295C7.56841 2.58438 8.38367 2.24635 9.23219 2.01728ZM4.55577
12.3686C4.56087 12.4844 4.52562 12.5983 4.45604 12.6909L3.04873
14.5645C3.22919 15.1693 3.47157 15.7538 3.77202 16.3088L6.08634
16.6394C6.19815 16.6554 6.30125 16.7087 6.37886 16.7908C6.59125 17.0153
6.8097 17.2338 7.03423 17.4461C7.11628 17.5238 7.16963 17.6269 7.1856
17.7387L7.51653 20.0552C8.09055 20.3755 8.70317 20.6699 9.30047
20.8589L11.119 19.3808C11.2199 19.2987 11.3489 19.2592 11.4784
19.2707C11.7672 19.2962 12.0578 19.2962 12.3466 19.2707C12.4696 19.2598
12.5922 19.2948 12.6909 19.369L14.5645 20.7763C15.1693 20.5958 15.7538
20.3534 16.3088 20.053L16.6394 17.7387C16.6665 17.5493 16.7992 17.392
16.9812 17.3334L20.0293 16.3527C20.3415 15.7851 20.5919 15.1857 20.7765
14.5648L19.369 12.6909C19.2948 12.5922 19.2598 12.4696 19.2707
12.3467C19.2822 12.2167 19.2829 12.0017 19.2794 11.8018C19.2777 11.7056
19.2751 11.6187 19.273 11.5559L19.2703 11.4822L19.2695 11.4626L19.2693
11.4577L19.2692 11.4566C19.2641 11.3409 19.2994 11.2267 19.369
11.1341L20.7763 9.26051C20.591 8.63973 20.3405 8.04027 20.029
7.47218L16.9812 6.4916C16.7992 6.43302 16.6665 6.27569 16.6394
6.08634L16.3087 3.77166C15.7541 3.47068 15.1696 3.22832 14.5648
3.04851L12.6909 4.45604C12.5922 4.53018 12.4696 4.56518 12.3466
4.55431C12.0578 4.52877 11.7672 4.52877 11.4784 4.55431C11.3554 4.56518
11.2328 4.53018 11.1341 4.45604L9.26051 3.04873C8.65575 3.22919 8.07123
3.47157 7.51622 3.77202L7.1856 6.08634C7.16963 6.19815 7.11628 6.30125
7.03423 6.37886C6.8097 6.59125 6.59125 6.8097 6.37886 7.03423C6.30125
7.11628 6.19815 7.16963 6.08634 7.1856L3.77166 7.51627C3.47068 8.07088
3.22832 8.65536 3.04851 9.26022L4.45604 11.1341C4.53016 11.2328 4.56517
11.3554 4.55432 11.4783C4.54285 11.6083 4.5421 11.8233 4.54563
12.0232C4.54733 12.1194 4.54988 12.2063 4.55201 12.2691L4.55471
12.3429L4.55551 12.3624L4.55577 12.3686ZM11.9121 8.41248C9.97912 8.41248
8.41211 9.97948 8.41211 11.9125C8.41211 13.8455 9.97912 15.4125 11.9121
15.4125C13.8451 15.4125 15.4121 13.8455 15.4121 11.9125C15.4121 9.97948
13.8451 8.41248 11.9121 8.41248ZM7.41211 11.9125C7.41211 9.4272 9.42683
7.41248 11.9121 7.41248C14.3974 7.41248 16.4121 9.4272 16.4121
11.9125C16.4121 14.3978 14.3974 16.4125 11.9121 16.4125C9.42683 16.4125
7.41211 14.3978 7.41211 11.9125Z" />
d="M12 7.5C9.51472 7.5 7.5 9.51472 7.5 12C7.5 14.4853 9.51472 16.5 12
16.5C14.4853 16.5 16.5 14.4853 16.5 12C16.5 9.51472 14.4853 7.5 12
7.5ZM8.5 12C8.5 10.067 10.067 8.5 12 8.5C13.933 8.5 15.5 10.067 15.5
12C15.5 13.933 13.933 15.5 12 15.5C10.067 15.5 8.5 13.933 8.5 12Z" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M9.31972 2.10481C9.46836 2.06468 9.62721 2.09528 9.75032
2.18775L11.6727 3.63172C11.8908 3.61967 12.1093 3.61967 12.3273
3.63172L14.2497 2.18775C14.3727 2.09541 14.5312 2.06476 14.6797
2.10464C15.529 2.33276 16.3448 2.67101 17.1063 3.1108C17.2392 3.18756
17.3295 3.32112 17.3513 3.47307L17.692 5.85812C17.8472 6.00529 17.9972
6.15528 18.1421 6.30811L20.527 6.6488C20.6792 6.67054 20.8129 6.76107
20.8896 6.89428C21.1184 7.29169 21.3199 7.7037 21.4928 8.12741C21.6512
8.51576 21.7857 8.91393 21.8952 9.31972C21.9354 9.46836 21.9048 9.62721
21.8123 9.75032L20.2861 11.7822V12.2617L21.8123 14.2935C21.9047 14.4164
21.9353 14.575 21.8954 14.7235C21.6673 15.5728 21.3291 16.3886 20.8893
17.1501C20.8125 17.283 20.6789 17.3733 20.527 17.395L18.1389
17.7362C17.9927 17.8877 17.8439 18.0365 17.6924 18.1827L17.3513
20.5708C17.3295 20.7229 17.239 20.8566 17.1058 20.9333C16.3441 21.3719
15.5289 21.7099 14.6803 21.939C14.5317 21.9791 14.3729 21.9485 14.2497
21.8561L12.3273 20.4121C12.1093 20.4242 11.8908 20.4242 11.6727
20.4121L9.75032 21.8561C9.62739 21.9484 9.46882 21.9791 9.32033
21.9392C8.47105 21.7111 7.65525 21.3728 6.89373 20.933C6.76081 20.8563
6.67052 20.7227 6.64881 20.5708L6.30809 18.1857C6.15289 18.0385 6.00284
17.8885 5.85795 17.7357L3.47307 17.395C3.3209 17.3733 3.18719 17.2828
3.11048 17.1495C2.88165 16.7521 2.68018 16.3401 2.50729 15.9164C2.34882
15.5281 2.21436 15.1299 2.10481 14.7241C2.06469 14.5755 2.09528 14.4166
2.18775 14.2935L3.71344 12.2623V11.7815L2.18775 9.75032C2.09541 9.62739
2.06476 9.46881 2.10464 9.32033C2.33276 8.47104 2.67101 7.65525 3.1108
6.89373C3.18756 6.76081 3.32112 6.67051 3.47307 6.6488L5.86116
6.30765C6.00733 6.15616 6.15616 6.00733 6.30765 5.86116L6.6488
3.47307C6.67054 3.3209 6.76107 3.18718 6.89428 3.11048C7.65593 2.67191
8.4712 2.33387 9.31972 2.10481ZM19.4565 11.2216C19.3906 11.3094 19.3579
11.4117 19.3563 11.5137H19.351V12.5316L19.3563 12.5301C19.3579 12.632
19.3906 12.7344 19.4565 12.8222L20.864 14.6961C20.6842 15.3009 20.4419
15.8854 20.1409 16.44L17.8262 16.7707C17.7144 16.7867 17.6113 16.84
17.5337 16.9221C17.3213 17.1466 17.1028 17.365 16.8783 17.5774C16.7963
17.655 16.7429 17.7582 16.7269 17.87L16.3963 20.1843C15.8413 20.4847
15.2568 20.7271 14.652 20.9076L12.7784 19.5003C12.6797 19.4261 12.5571
19.3911 12.4341 19.402C12.1453 19.4275 11.8548 19.4275 11.5659
19.402C11.443 19.3911 11.3203 19.4261 11.2216 19.5003L9.34775
20.9078C8.74289 20.728 8.15841 20.4856 7.6038 20.1846L7.27313
17.87C7.25684 17.7559 7.20165 17.651 7.1169 17.5729C6.88932 17.3633
6.67417 17.1481 6.47132 16.9274C6.39323 16.8424 6.28813 16.787 6.17387
16.7707L3.85955 16.4401C3.70108 16.1473 3.55877 15.8464 3.43317
15.5386C3.32064 15.2628 3.22154 14.9816 3.13626 14.6958L4.54357
12.8222C4.60946 12.7345 4.64214 12.6321 4.64375 12.5302L4.64855
12.5316V11.5137H4.64375C4.64215 11.4117 4.60947 11.3094 4.54356
11.2216L3.13604 9.34774C3.31584 8.74288 3.5582 8.15841 3.85919
7.6038L6.17386 7.27313C6.28567 7.25716 6.38877 7.2038 6.46639
7.12175C6.67878 6.89723 6.89723 6.67878 7.12175 6.46639C7.2038 6.38877
7.25716 6.28567 7.27313 6.17386L7.60374 3.85954C8.15875 3.5591 8.74328
3.31672 9.34803 3.13625L11.2216 4.54356C11.3203 4.6177 11.443 4.65271
11.5659 4.64184C11.8548 4.6163 12.1453 4.6163 12.4341 4.64184C12.5571
4.65271 12.6797 4.6177 12.7784 4.54356L14.6523 3.13604C15.2572 3.31584
15.8416 3.5582 16.3963 3.85919L16.7269 6.17386C16.7432 6.28793 16.7984
6.39287 16.8832 6.47093C17.1107 6.68053 17.3259 6.89572 17.5287
7.11647C17.6068 7.20145 17.7119 7.25681 17.8262 7.27313L20.1405
7.60374C20.299 7.89648 20.4413 8.19742 20.5669 8.50522C20.6794 8.78098
20.7785 9.06225 20.8638 9.34803L19.4565 11.2216Z" />
{:else if name === "fork"}
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.34375 6.9375C7.5671
6.9375 6.9375 7.5671 6.9375 8.34375C6.9375 9.1204 7.5671 9.75 8.34375
9.75C9.1204 9.75 9.75 9.1204 9.75 8.34375C9.75 7.5671 9.1204 6.9375 8.34375
6.9375ZM8.8125 10.6406C9.8823 10.4235 10.6875 9.47764 10.6875
8.34375C10.6875 7.04933 9.63817 6 8.34375 6C7.04933 6 6 7.04933 6 8.34375C6
9.47764 6.8052 10.4235 7.875 10.6406V11.1562C7.875 11.8999 8.18601 12.4028
8.62101 12.7834C8.82767 12.9643 9.06032 13.1164 9.28665 13.2541C9.39277
13.3187 9.48974 13.3756 9.5849 13.4316C9.71089 13.5056 9.83369 13.5777
9.97031 13.6631C10.4167 13.9421 10.8179 14.2499 11.1146 14.7072C11.3641
15.0919 11.5582 15.6119 11.6108 16.3623C10.5481 16.5849 9.75 17.5274 9.75
18.6562C9.75 19.9507 10.7993 21 12.0938 21C13.3882 21 14.4375 19.9507
14.4375 18.6562C14.4375 17.5274 13.6394 16.5849 12.5766 16.3623C12.6293
15.6119 12.8234 15.0919 13.073 14.7072C13.3696 14.2499 13.7709 13.9421
14.2172 13.6631C14.3538 13.5777 14.4766 13.5056 14.6026 13.4316C14.6978
13.3756 14.7947 13.3187 14.9008 13.2541C15.1272 13.1164 15.3598 12.9643
15.5665 12.7834C16.0015 12.4028 16.3125 11.8999 16.3125
11.1562V10.6406C17.3823 10.4235 18.1875 9.47764 18.1875 8.34375C18.1875
7.04933 17.1382 6 15.8438 6C14.5493 6 13.5 7.04933 13.5 8.34375C13.5
9.47764 14.3052 10.4235 15.375 10.6406V11.1562C15.375 11.5845 15.2173
11.8433 14.9491 12.0779C14.8042 12.2047 14.6267 12.3235 14.4136
12.4532C14.3363 12.5002 14.2463 12.5532 14.1515 12.6091C14.0098 12.6926
13.8574 12.7824 13.7203 12.8681C13.2292 13.1751 12.6929 13.5705 12.2864
14.1971C12.2176 14.3032 12.1532 14.4147 12.0938 14.5323C12.0343 14.4147
11.9699 14.3032 11.9011 14.1971C11.4946 13.5705 10.9583 13.1751 10.4672
12.8681C10.3301 12.7824 10.1776 12.6926 10.036 12.6091C9.94121 12.5532
9.85123 12.5002 9.77389 12.4532C9.56077 12.3235 9.38326 12.2047 9.23836
12.0779C8.97024 11.8433 8.8125 11.5845 8.8125 11.1562V10.6406ZM15.8438
9.75C16.6204 9.75 17.25 9.1204 17.25 8.34375C17.25 7.5671 16.6204 6.9375
15.8438 6.9375C15.0671 6.9375 14.4375 7.5671 14.4375 8.34375C14.4375 9.1204
15.0671 9.75 15.8438 9.75ZM12.0938 17.25C11.3171 17.25 10.6875 17.8796
10.6875 18.6562C10.6875 19.4329 11.3171 20.0625 12.0938 20.0625C12.8704
20.0625 13.5 19.4329 13.5 18.6562C13.5 17.8796 12.8704 17.25 12.0938
17.25Z" />
d="M3.5 6C3.5 4.61929 4.61929 3.5 6 3.5C7.38071 3.5 8.5 4.61929 8.5 6C8.5
7.20948 7.64112 8.21836 6.5 8.44999V11C6.5 11.2761 6.72386 11.5 7
11.5H17C17.2761 11.5 17.5 11.2761 17.5 11V8.44999C16.3589 8.21836 15.5
7.20948 15.5 6C15.5 4.61929 16.6193 3.5 18 3.5C19.3807 3.5 20.5 4.61929
20.5 6C20.5 7.20948 19.6411 8.21836 18.5 8.44999V11C18.5 11.8284 17.8284
12.5 17 12.5H12.5V15.55C13.6411 15.7816 14.5 16.7905 14.5 18C14.5 19.3807
13.3807 20.5 12 20.5C10.6193 20.5 9.5 19.3807 9.5 18C9.5 16.7905 10.3589
15.7816 11.5 15.55V12.5H7C6.17157 12.5 5.5 11.8284 5.5 11V8.44999C4.35888
8.21836 3.5 7.20948 3.5 6ZM6 4.5C5.17157 4.5 4.5 5.17157 4.5 6C4.5
6.82843 5.17157 7.5 6 7.5C6.82843 7.5 7.5 6.82843 7.5 6C7.5 5.17157
6.82843 4.5 6 4.5ZM18 4.5C17.1716 4.5 16.5 5.17157 16.5 6C16.5 6.82843
17.1716 7.5 18 7.5C18.8284 7.5 19.5 6.82843 19.5 6C19.5 5.17157 18.8284
4.5 18 4.5ZM12 16.5C11.1716 16.5 10.5 17.1716 10.5 18C10.5 18.8284
11.1716 19.5 12 19.5C12.8284 19.5 13.5 18.8284 13.5 18C13.5 17.1716
12.8284 16.5 12 16.5Z" />
{:else if name === "moon"}
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M15.75 1.5C16.1642 1.5
16.5 1.83579 16.5 2.25V3H17.25C17.6642 3 18 3.33579 18 3.75C18 4.16421
17.6642 4.5 17.25 4.5H16.5V5.25C16.5 5.66421 16.1642 6 15.75 6C15.3358 6 15
5.66421 15 5.25V4.5H14.25C13.8358 4.5 13.5 4.16421 13.5 3.75C13.5 3.33579
13.8358 3 14.25 3H15V2.25C15 1.83579 15.3358 1.5 15.75 1.5ZM10.2246
3.15456C10.4159 3.34623 10.489 3.62613 10.4159 3.88688C9.65641 6.59529
10.4385 9.58344 12.4276 11.5724C14.4166 13.5615 17.4047 14.3436 20.1131
13.5841C20.3739 13.511 20.6538 13.5841 20.8454 13.7754C21.0371 13.9667
21.1108 14.2465 21.0382 14.5074C19.1394 21.3293 10.2649 23.5102 5.37735
18.6226C0.393755 13.639 2.70287 4.85168 9.49264 2.96184C9.75353 2.88923
10.0333 2.96289 10.2246 3.15456ZM8.69851 4.84862C3.95753 7.06376 2.57424
13.6982 6.43801 17.562C10.2273 21.3512 16.9232 20.041 19.1499
15.3017C16.3194 15.6466 13.4079 14.6741 11.3669 12.6331C9.32556 10.5918
8.35305 7.67963 8.69851 4.84862ZM20.25 5.25C20.6642 5.25 21 5.58579 21
6V7.5H22.5C22.9142 7.5 23.25 7.83579 23.25 8.25C23.25 8.66421 22.9142 9
22.5 9H21V10.5C21 10.9142 20.6642 11.25 20.25 11.25C19.8358 11.25 19.5
10.9142 19.5 10.5V9H18C17.5858 9 17.25 8.66421 17.25 8.25C17.25 7.83579
17.5858 7.5 18 7.5H19.5V6C19.5 5.58579 19.8358 5.25 20.25 5.25Z" />
d="M13.5 4.5C9.35786 4.5 6 7.85786 6 12C6 16.1421 9.35786 19.5 13.5
19.5C14.8679 19.5 16.149 19.1343 17.2524 18.4954C13.7773 18.3652 11
15.5069 11 12C11 8.49307 13.7773 5.6348 17.2524 5.50463C16.149 4.86574
14.8679 4.5 13.5 4.5ZM5 12C5 7.30558 8.80558 3.5 13.5 3.5C15.1842 3.5
16.7554 3.99035 18.0768 4.83625C18.5001 5.10719 18.5345 5.58051 18.3915
5.90969C18.2548 6.22425 17.9285 6.5 17.5 6.5C14.4624 6.5 12 8.96243 12
12C12 15.0376 14.4624 17.5 17.5 17.5C17.9285 17.5 18.2548 17.7758 18.3915
18.0903C18.5345 18.4195 18.5001 18.8928 18.0768 19.1638C16.7554 20.0097
15.1842 20.5 13.5 20.5C8.80558 20.5 5 16.6944 5 12Z" />
{:else if name === "checkmark"}
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M18.2941 6.59564C18.5174 6.75806 18.5668 7.07076 18.4044 7.29409L11.781
16.4012C10.8775 17.6436 9.07768 17.7848 7.99142 16.6985L5.64645 14.3536C5.45118
14.1583 5.45118 13.8417 5.64645 13.6465C5.84171 13.4512 6.15829 13.4512 6.35355
13.6465L8.69852 15.9914C9.35028 16.6432 10.4302 16.5584 10.9723 15.813L17.5956
6.70592C17.7581 6.48259 18.0708 6.43322 18.2941 6.59564Z" />
d="M18.2941 6.59564C18.5174 6.75806 18.5668 7.07076 18.4044
7.29409L11.781 16.4012C10.8775 17.6436 9.07768 17.7848 7.99142
16.6985L5.64645 14.3536C5.45118 14.1583 5.45118 13.8417 5.64645
13.6465C5.84171 13.4512 6.15829 13.4512 6.35355 13.6465L8.69852
15.9914C9.35028 16.6432 10.4302 16.5584 10.9723 15.813L17.5956
6.70592C17.7581 6.48259 18.0708 6.43322 18.2941 6.59564Z" />
{:else if name === "checkmark-small"}
<path
fill-rule="evenodd"
@@ -257,36 +262,60 @@
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12 0.75C12.4142 0.75
12.75 1.08579 12.75 1.5V3.375C12.75 3.78921 12.4142 4.125 12 4.125C11.5858
4.125 11.25 3.78921 11.25 3.375V1.5C11.25 1.08579 11.5858 0.75 12
0.75ZM4.04467 4.04467C4.33756 3.75178 4.81244 3.75178 5.10533
4.04467L6.42721 5.36655C6.7201 5.65944 6.7201 6.13431 6.42721
6.42721C6.13431 6.7201 5.65944 6.7201 5.36655 6.42721L4.04467
5.10533C3.75178 4.81244 3.75178 4.33756 4.04467 4.04467ZM19.9553
4.04467C20.2482 4.33756 20.2482 4.81244 19.9553 5.10533L18.6335
6.42721C18.3406 6.7201 17.8657 6.7201 17.5728 6.42721C17.2799 6.13431
17.2799 5.65944 17.5728 5.36655L18.8947 4.04467C19.1876 3.75178 19.6624
3.75178 19.9553 4.04467ZM12 7.125C9.30761 7.125 7.125 9.30761 7.125
12C7.125 14.6924 9.30761 16.875 12 16.875C14.6924 16.875 16.875 14.6924
16.875 12C16.875 9.30761 14.6924 7.125 12 7.125ZM5.625 12C5.625 8.47918
8.47918 5.625 12 5.625C15.5208 5.625 18.375 8.47918 18.375 12C18.375
15.5208 15.5208 18.375 12 18.375C8.47918 18.375 5.625 15.5208 5.625
12ZM0.75 12C0.75 11.5858 1.08579 11.25 1.5 11.25H3.375C3.78921 11.25 4.125
11.5858 4.125 12C4.125 12.4142 3.78921 12.75 3.375 12.75H1.5C1.08579 12.75
0.75 12.4142 0.75 12ZM19.875 12C19.875 11.5858 20.2108 11.25 20.625
11.25H22.5C22.9142 11.25 23.25 11.5858 23.25 12C23.25 12.4142 22.9142 12.75
22.5 12.75H20.625C20.2108 12.75 19.875 12.4142 19.875 12ZM6.42721
17.5728C6.7201 17.8657 6.7201 18.3406 6.42721 18.6335L5.10533
19.9553C4.81244 20.2482 4.33756 20.2482 4.04467 19.9553C3.75178 19.6624
3.75178 19.1876 4.04467 18.8947L5.36655 17.5728C5.65944 17.2799 6.13431
17.2799 6.42721 17.5728ZM17.5728 17.5728C17.8657 17.2799 18.3406 17.2799
18.6335 17.5728L19.9553 18.8947C20.2482 19.1876 20.2482 19.6624 19.9553
19.9553C19.6624 20.2482 19.1876 20.2482 18.8947 19.9553L17.5728
18.6335C17.2799 18.3406 17.2799 17.8657 17.5728 17.5728ZM12 19.875C12.4142
19.875 12.75 20.2108 12.75 20.625V22.5C12.75 22.9142 12.4142 23.25 12
23.25C11.5858 23.25 11.25 22.9142 11.25 22.5V20.625C11.25 20.2108 11.5858
19.875 12 19.875Z" />
d="M12 8.5C10.067 8.5 8.5 10.067 8.5 12C8.5 13.933 10.067 15.5 12
15.5C13.933 15.5 15.5 13.933 15.5 12C15.5 10.067 13.933 8.5 12 8.5ZM7.5
12C7.5 9.51472 9.51472 7.5 12 7.5C14.4853 7.5 16.5 9.51472 16.5 12C16.5
14.4853 14.4853 16.5 12 16.5C9.51472 16.5 7.5 14.4853 7.5 12Z" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12 18.5C12.2761 18.5 12.5 18.7239 12.5 19V21C12.5 21.2761 12.2761
21.5 12 21.5C11.7239 21.5 11.5 21.2761 11.5 21V19C11.5 18.7239 11.7239
18.5 12 18.5Z" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.40381 16.5962C7.59907 16.7915 7.59907 17.108 7.40381
17.3033L5.98959 18.7175C5.79433 18.9128 5.47775 18.9128 5.28249
18.7175C5.08722 18.5223 5.08722 18.2057 5.28249 18.0104L6.6967
16.5962C6.89196 16.4009 7.20854 16.4009 7.40381 16.5962Z" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.5 12C5.5 12.2761 5.27614 12.5 5 12.5H3C2.72386 12.5 2.5 12.2761 2.5
12C2.5 11.7239 2.72386 11.5 3 11.5H5C5.27614 11.5 5.5 11.7239 5.5 12Z" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.40381 7.40381C7.20854 7.59907 6.89196 7.59907 6.6967
7.40381L5.28249 5.98959C5.08722 5.79433 5.08722 5.47775 5.28249
5.28249C5.47775 5.08722 5.79433 5.08722 5.98959 5.28249L7.40381
6.6967C7.59907 6.89196 7.59907 7.20854 7.40381 7.40381Z" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12 2.5C12.2761 2.5 12.5 2.72386 12.5 3V5C12.5 5.27614 12.2761 5.5 12
5.5C11.7239 5.5 11.5 5.27614 11.5 5V3C11.5 2.72386 11.7239 2.5 12 2.5Z" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M18.7175 5.28249C18.9128 5.47775 18.9128 5.79433 18.7175
5.98959L17.3033 7.40381C17.108 7.59907 16.7915 7.59907 16.5962
7.40381C16.4009 7.20854 16.4009 6.89196 16.5962 6.6967L18.0104
5.28249C18.2057 5.08722 18.5223 5.08722 18.7175 5.28249Z" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M21.5 12C21.5 12.2761 21.2761 12.5 21 12.5H19C18.7239 12.5 18.5
12.2761 18.5 12C18.5 11.7239 18.7239 11.5 19 11.5H21C21.2761 11.5 21.5
11.7239 21.5 12Z" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M18.7175 18.7175C18.5223 18.9128 18.2057 18.9128 18.0104
18.7175L16.5962 17.3033C16.4009 17.108 16.4009 16.7915 16.5962
16.5962C16.7915 16.4009 17.108 16.4009 17.3033 16.5962L18.7175
18.0104C18.9128 18.2057 18.9128 18.5223 18.7175 18.7175Z" />
{:else if name === "twitter"}
<path
clip-rule="evenodd"
+10 -7
View File
@@ -2,22 +2,25 @@
import type { Route } from "@app/lib/router/definitions";
import { createEventDispatcher } from "svelte";
import { push, routeToPath } from "@app/lib/router";
import { push, routeToPath, useDefaultNavigation } from "@app/lib/router";
export let route: Route;
export let title: string | null = null;
export let id: string | null = null;
const dispatch = createEventDispatcher<{
click: never;
afterNavigate: never;
}>();
function onClick(): void {
function navigateToRoute(event: MouseEvent): void {
if (useDefaultNavigation(event)) {
return;
}
event.preventDefault();
push(route);
dispatch("click");
dispatch("afterNavigate");
}
</script>
<a on:click|preventDefault={onClick} {title} {id} href={routeToPath(route)}>
<a on:click={navigateToRoute} href={routeToPath(route)}>
<slot />
</a>
+1 -2
View File
@@ -97,8 +97,7 @@
}
</style>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="project" on:click class:compact>
<div class="project" class:compact>
<div class="left">
<div class="id">
<span class="name">{name}</span>
+22 -9
View File
@@ -1,16 +1,29 @@
<script lang="ts">
<script lang="ts" strictEvents>
import type { ProjectsParams } from "@app/lib/router/definitions";
import { updateProjectRoute, projectLinkHref } from "@app/lib/router";
import { createEventDispatcher } from "svelte";
import {
projectLinkHref,
updateProjectRoute,
useDefaultNavigation,
} from "@app/lib/router";
export let projectParams: Partial<ProjectsParams>;
export let id: string | undefined = undefined;
export let title: string | undefined = undefined;
const dispatch = createEventDispatcher<{ click: never }>();
function navigateToRoute(event: MouseEvent): void {
if (useDefaultNavigation(event)) {
return;
}
event.preventDefault();
updateProjectRoute(projectParams);
dispatch("click");
}
</script>
<a
{id}
on:click|preventDefault={() => {
updateProjectRoute(projectParams);
}}
href={projectLinkHref(projectParams)}>
<a {title} on:click={navigateToRoute} href={projectLinkHref(projectParams)}>
<slot />
</a>
+74
View File
@@ -0,0 +1,74 @@
<script lang="ts" strictEvents>
export let active: boolean = false;
export let hoverable: boolean = true;
export let clickable: boolean = false;
export let disabled: boolean = false;
export let size: "small" | "regular" = "regular";
</script>
<style>
.square-button {
display: flex;
align-items: center;
height: 2rem;
padding: 0 0.75rem;
background: var(--color-foreground-1);
border: none;
border-radius: var(--border-radius-small);
color: var(--color-foreground);
font-family: var(--font-family-monospace);
font-size: var(--font-size-tiny);
}
.small {
height: var(--button-tiny-height);
}
.clickable {
cursor: pointer;
user-select: none;
}
.active {
color: var(--color-background);
background: var(--color-foreground);
background-color: var(--color-foreground);
}
.active:hover {
background-color: var(--color-foreground);
}
.hoverable:hover {
background-color: var(--color-foreground-2);
}
.active.hoverable:hover {
background-color: var(--color-foreground);
}
.disabled {
cursor: not-allowed;
color: var(--color-foreground-5);
}
.disabled.active {
color: var(--color-background);
}
.disabled:hover {
background: var(--color-foreground-1);
}
</style>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
on:click
class="square-button"
class:active
class:hoverable
class:disabled
class:small={size === "small"}
class:clickable>
<span>
<slot />
</span>
</div>
-68
View File
@@ -1,68 +0,0 @@
<script lang="ts" context="module">
export interface Tab<T> {
title?: string;
disabled: boolean;
value: T;
}
</script>
<script lang="ts" strictEvents>
type T = $$Generic;
import capitalize from "lodash/capitalize";
import { createEventDispatcher } from "svelte";
export let options: Tab<T>[];
export let active: T;
const dispatch = createEventDispatcher<{ select: T }>();
function onSelect(option: Tab<T>) {
if (!option.disabled) {
dispatch("select", option.value);
}
}
</script>
<style>
.wrapper {
display: flex;
gap: 1rem;
user-select: none;
}
button {
border-radius: var(--border-radius-small);
color: var(--color-foreground-6);
cursor: pointer;
font-family: var(--font-family-monospace);
font-size: var(--font-size-tiny);
height: var(--button-tiny-height);
padding: 0.25rem 0.5rem;
border: none;
min-width: 0;
background-color: var(--color-background);
}
button:hover,
button.active {
cursor: pointer;
color: var(--color-foreground);
background-color: var(--color-foreground-1);
}
button[disabled],
button[disabled]:hover {
cursor: not-allowed;
color: var(--color-foreground-6);
}
</style>
<div class="wrapper">
{#each options as option}
<button
class="state-toggle"
on:click={() => onSelect(option)}
disabled={option.disabled}
class:active={active === option.value}>
{option.title ?? capitalize(`${option.value}`)}
</button>
{/each}
</div>
+10
View File
@@ -16,6 +16,16 @@ export const activeRouteStore: Readable<Route> = derived(
},
);
export function useDefaultNavigation(event: MouseEvent) {
return (
event.button !== 0 ||
event.altKey ||
event.ctrlKey ||
event.metaKey ||
event.shiftKey
);
}
export const base = import.meta.env.VITE_HASH_ROUTING ? "./" : "/";
// Gets triggered when clicking on an anchor hash tag e.g. <a href="#header"/>
+20 -24
View File
@@ -1,28 +1,13 @@
<script lang="ts">
import type { BaseUrl, Project } from "@httpd-client";
import * as router from "@app/lib/router";
import { config } from "@app/lib/config";
import { getProjectsFromSeeds } from "@app/lib/search";
import { loadProjectActivity } from "@app/lib/commit";
import { twemoji } from "@app/lib/utils";
import Link from "@app/components/Link.svelte";
import Loading from "@app/components/Loading.svelte";
import ErrorMessage from "@app/components/ErrorMessage.svelte";
import ProjectCard from "@app/components/ProjectCard.svelte";
function goToProject(project: Project, baseUrl: BaseUrl) {
router.push({
resource: "projects",
params: {
view: { resource: "tree" },
id: project.id,
hostnamePort: baseUrl.hostname,
peer: undefined,
revision: undefined,
},
});
}
</script>
<style>
@@ -97,14 +82,25 @@
{#each results as result}
{#await loadProjectActivity(result.project.id, result.baseUrl) then activity}
<div class="project">
<ProjectCard
compact
description={result.project.description}
head={result.project.head}
id={result.project.id}
name={result.project.name}
{activity}
on:click={() => goToProject(result.project, result.baseUrl)} />
<Link
route={{
resource: "projects",
params: {
view: { resource: "tree" },
id: result.project.id,
hostnamePort: result.baseUrl.hostname,
peer: undefined,
revision: undefined,
},
}}>
<ProjectCard
compact
description={result.project.description}
head={result.project.head}
id={result.project.id}
name={result.project.name}
{activity} />
</Link>
</div>
{/await}
{/each}
+4 -4
View File
@@ -11,8 +11,8 @@
import { lineNumbersGutter } from "@app/lib/syntax";
import { updateProjectRoute } from "@app/lib/router";
import HeaderToggleLabel from "@app/views/projects/HeaderToggleLabel.svelte";
import Readme from "@app/views/projects/Readme.svelte";
import SquareButton from "@app/components/SquareButton.svelte";
export let activeRoute: ProjectRoute;
export let blob: Blob;
@@ -103,7 +103,7 @@
}
.last-commit {
padding: 0.5rem;
padding: 0.5rem 0.75rem;
color: var(--color-secondary);
background-color: var(--color-secondary-2);
font-size: var(--font-size-tiny);
@@ -229,12 +229,12 @@
<div class="right">
{#if isMarkdown}
<div class="markdown-toggle">
<HeaderToggleLabel
<SquareButton
active={!showMarkdown}
clickable
on:click={toggleMarkdown}>
Raw
</HeaderToggleLabel>
</SquareButton>
</div>
{/if}
<div class="last-commit" title={lastCommit.author.name} use:twemoji>
+9 -11
View File
@@ -1,20 +1,15 @@
<script lang="ts" strictEvents>
import { createEventDispatcher } from "svelte";
import * as utils from "@app/lib/utils";
import Dropdown from "@app/components/Dropdown.svelte";
import Floating from "@app/components/Floating.svelte";
import ProjectLink from "@app/components/ProjectLink.svelte";
export let branches: Record<string, string>;
export let projectDefaultBranch: string;
export let projectHead: string | undefined = undefined;
export let revision: string;
const dispatch = createEventDispatcher<{ branchChanged: string }>();
const switchBranch = (name: string) => {
dispatch("branchChanged", name);
};
let branchLabel: string | null = null;
$: branchList = Object.keys(branches)
@@ -85,10 +80,13 @@
{branchLabel}
</div>
<svelte:fragment slot="modal">
<Dropdown
items={branchList}
selected={branchLabel}
on:select={e => switchBranch(e.detail.value)} />
<Dropdown items={branchList} selected={branchLabel}>
<div class="branch-item" slot="item" let:item>
<ProjectLink projectParams={{ revision: item.value }} on:click>
{item.value}
</ProjectLink>
</div>
</Dropdown>
</svelte:fragment>
</Floating>
<div class="hash layout-desktop">
+26 -34
View File
@@ -12,7 +12,6 @@
import { onMount } from "svelte";
import * as router from "@app/lib/router";
import * as utils from "@app/lib/utils";
import { HttpdClient } from "@httpd-client";
@@ -48,6 +47,11 @@
const api = new HttpdClient(baseUrl);
// When a user clicks between multiple files, we want to retain the previous
// file contents and show them while the new file is loading to prevent the
// UI from flickering or showing a loading indicator.
let previousBlob: Blob;
const loadBlob = async (path: string) => {
if (state.status === Status.Loaded && state.path === path) {
return state.blob;
@@ -63,6 +67,7 @@
}
state = { status: Status.Loaded, path, blob };
previousBlob = blob;
return blob;
};
@@ -70,31 +75,6 @@
browserErrorStore.set(undefined);
});
const onSelect = async (newPath: string) => {
browserErrorStore.set(undefined);
// Ensure we don't spend any time in a "loading" state. This means
// the loading spinner won't be shown, and instead the blob will be
// displayed once loaded.
const blob = await loadBlob(newPath).catch(() => {
browserErrorStore.set({
message: "Not able to load selected file",
path: newPath,
});
return undefined;
});
if (blob) {
getBlob = new Promise(resolve => resolve(blob));
}
// Close mobile tree if user navigates to other file
mobileFileTree = false;
router.updateProjectRoute({
view: { resource: "tree" },
path: newPath,
});
};
const fetchTree = async (path: string) => {
return api.project.getTree(project.id, commit, path).catch(() => {
browserErrorStore.set({
@@ -105,10 +85,6 @@
});
};
const toggleMobileFileTree = () => {
mobileFileTree = !mobileFileTree;
};
$: getBlob = loadBlob(path).catch(() => {
browserErrorStore.set({ message: "Not able to load file", path });
return undefined;
@@ -203,7 +179,9 @@
<Button
style="width: 100%;"
variant="secondary"
on:click={toggleMobileFileTree}>
on:click={() => {
mobileFileTree = !mobileFileTree;
}}>
Browse
</Button>
</nav>
@@ -218,8 +196,9 @@
{path}
{fetchTree}
{loadingPath}
on:select={e => {
onSelect(e.detail);
on:select={() => {
// Close mobile tree if user navigates to other file
mobileFileTree = false;
}} />
</div>
</div>
@@ -241,7 +220,20 @@
</Placeholder>
{:else}
{#await getBlob}
<Loading small center />
{#if previousBlob}
<div class="layout-desktop">
<BlobComponent
{line}
blob={previousBlob}
{activeRoute}
rawPath={utils.getRawBasePath(project.id, baseUrl, commit)} />
</div>
<div class="layout-mobile">
<Loading small center />
</div>
{:else}
<Loading small center />
{/if}
{:then blob}
{#if blob}
<BlobComponent
+17 -8
View File
@@ -48,7 +48,7 @@
.metadata-section {
margin-bottom: 4rem;
}
.metadata-section-header {
.header {
display: flex;
gap: 1rem;
align-items: center;
@@ -56,20 +56,27 @@
margin-bottom: 0.75rem;
color: var(--color-foreground-6);
}
.metadata-section-body {
.body {
display: flex;
flex-wrap: wrap;
flex-direction: row;
gap: 0.5rem;
margin-bottom: 1.25rem;
}
.metadata-section-empty {
.empty {
color: var(--color-foreground-5);
}
.chip-content {
display: flex;
align-items: center;
gap: 0.5rem;
white-space: nowrap;
}
</style>
<div class="metadata-section">
<div class="metadata-section-header">
<div class="header">
<span>Assignees</span>
{#if action === "edit"}
{#if !edit}
@@ -94,17 +101,19 @@
{/if}
{/if}
</div>
<div class="metadata-section-body">
<div class="body">
{#each updatedAssignees as assignee, key (assignee)}
<Chip
on:remove={removeAssignee}
removeable={edit || action === "create"}
{key}>
<Avatar inline nodeId={assignee} />
<span>{formatNodeId(assignee)}</span>
<div class="chip-content">
<Avatar inline nodeId={assignee} />
<span>{formatNodeId(assignee)}</span>
</div>
</Chip>
{:else}
<div class="metadata-section-empty">No assignees</div>
<div class="empty">No assignees</div>
{/each}
</div>
{#if edit || action === "create"}
+1
View File
@@ -66,6 +66,7 @@
.tag {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
+5 -5
View File
@@ -6,8 +6,8 @@
import * as router from "@app/lib/router";
import Changeset from "@app/views/projects/SourceBrowser/Changeset.svelte";
import CommitAuthorship from "@app/views/projects/Commit/CommitAuthorship.svelte";
import Clipboard from "@app/components/Clipboard.svelte";
import CommitAuthorship from "@app/views/projects/Commit/CommitAuthorship.svelte";
export let commit: Commit;
@@ -24,7 +24,7 @@
.commit {
padding: 0 2rem 0 8rem;
}
header {
.header {
padding: 1rem;
background: var(--color-background-1);
border-radius: var(--border-radius);
@@ -54,7 +54,7 @@
</style>
<div class="commit">
<header>
<div class="header">
<div class="summary">
<div class="txt-medium txt-bold" use:twemoji>{header.summary}</div>
<div class="layout-desktop-flex txt-monospace sha1">
@@ -68,6 +68,6 @@
</div>
<pre class="description txt-small">{header.description}</pre>
<CommitAuthorship {header} />
</header>
<Changeset diff={commit.diff} on:browse={onBrowse} />
</div>
<Changeset diff={commit.diff} revision={commit.commit.id} />
</div>
@@ -14,7 +14,6 @@
align-items: center;
gap: 0.25rem;
color: var(--color-foreground-6);
padding: 0.125rem 0;
}
.authorship .author,
.authorship .committer {
+38 -50
View File
@@ -1,67 +1,48 @@
<script lang="ts" strictEvents>
<script lang="ts">
import type { CommitHeader } from "@httpd-client";
import { createEventDispatcher } from "svelte";
import { formatCommit, twemoji } from "@app/lib/utils";
import CommitAuthorship from "./CommitAuthorship.svelte";
import Icon from "@app/components/Icon.svelte";
import ProjectLink from "@app/components/ProjectLink.svelte";
export let commit: CommitHeader;
const dispatch = createEventDispatcher<{ browseCommit: string }>();
function browseCommit(commit: string) {
dispatch("browseCommit", commit);
}
</script>
<style>
.teaser {
background-color: var(--color-background-1);
padding: 0.75rem 0rem;
display: flex;
align-items: center;
}
.teaser:hover {
background-color: var(--color-foreground-2);
}
.hash {
font-family: var(--font-family-monospace);
font-size: var(--font-size-tiny);
padding: 0 1.5rem;
}
.commit-teaser {
background-color: var(--color-background-1);
padding: 0.75rem 0rem;
}
.commit-teaser:hover {
background-color: var(--color-foreground-2);
cursor: pointer;
}
.commit-teaser:first-child {
border-top-left-radius: var(--border-radius-small);
border-top-right-radius: var(--border-radius-small);
}
.commit-teaser:last-child {
border-bottom-left-radius: var(--border-radius-small);
border-bottom-right-radius: var(--border-radius-small);
}
.commit-teaser:not(:last-child) {
border-bottom: 1px solid var(--color-background);
}
.commit-teaser {
display: flex;
align-items: center;
justify-content: space-between;
}
.column-left {
.left {
padding-left: 1rem;
flex: min-content;
}
.commit-teaser .column-right {
.right {
display: flex;
align-items: center;
padding-right: 1.5rem;
margin-left: auto;
}
.summary {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
padding-right: 1rem;
margin-bottom: 0.25rem;
font-size: var(--font-size-small);
}
.summary:hover {
color: var(--color-secondary);
}
.browse {
display: flex;
@@ -74,7 +55,7 @@
.hash {
padding-right: 0;
}
.column-left {
.left {
overflow: hidden;
}
.browse {
@@ -84,29 +65,36 @@
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
padding-right: 1rem;
}
}
</style>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="commit-teaser" on:click>
<div class="column-left">
<div class="header">
<div class="teaser">
<div class="left">
<ProjectLink
projectParams={{
view: { resource: "commits" },
revision: commit.id,
search: undefined,
}}>
<div class="summary" use:twemoji>
{commit.summary}
</div>
</div>
</ProjectLink>
<CommitAuthorship header={commit} />
</div>
<div class="column-right">
<div class="right">
<span class="hash txt-highlight">{formatCommit(commit.id)}</span>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="browse"
title="Browse the repository at this point in the history"
on:click|stopPropagation={() => browseCommit(commit.id)}>
<Icon name="browse" />
title="Browse the repository at this point in the history">
<ProjectLink
projectParams={{
view: { resource: "tree" },
revision: commit.id,
}}>
<Icon name="browse" />
</ProjectLink>
</div>
</div>
</div>
+77 -90
View File
@@ -2,16 +2,16 @@
import type { BaseUrl, Project, Remote, Tree } from "@httpd-client";
import type { ProjectRoute } from "@app/lib/router/definitions";
import * as router from "@app/lib/router";
import { closeFocused } from "@app/components/Floating.svelte";
import { config } from "@app/lib/config";
import { pluralize } from "@app/lib/pluralize";
import BranchSelector from "@app/views/projects/BranchSelector.svelte";
import CloneButton from "@app/views/projects/CloneButton.svelte";
import HeaderToggleLabel from "@app/views/projects/HeaderToggleLabel.svelte";
import Link from "@app/components/Link.svelte";
import PeerSelector from "@app/views/projects/PeerSelector.svelte";
import ProjectLink from "@app/components/ProjectLink.svelte";
import SquareButton from "@app/components/SquareButton.svelte";
export let project: Project;
export let activeRoute: ProjectRoute;
@@ -22,55 +22,10 @@
export let baseUrl: BaseUrl;
$: revision = activeRoute.params.revision ?? commit;
// Switches between project views.
const toggleContent = (
input: "issues" | "patches" | "history",
keepSourceInPath: boolean,
) => {
router.updateProjectRoute({
view: {
resource: activeRoute.params.view.resource === input ? "tree" : input,
},
id: project.id,
revision: revision,
search: undefined,
...(keepSourceInPath ? null : { revision: undefined, path: undefined }),
});
};
const updatePeer = (peer: string) => {
router.updateProjectRoute({
peer,
revision: undefined,
});
closeFocused();
};
const updateRevision = (revision: string) => {
router.updateProjectRoute({
revision,
});
closeFocused();
};
function goToSeed() {
if (baseUrl.port !== config.seeds.defaultHttpdPort) {
router.push({
resource: "seeds",
params: { hostnamePort: `${baseUrl.hostname}:${baseUrl.port}` },
});
} else {
router.push({
resource: "seeds",
params: { hostnamePort: baseUrl.hostname },
});
}
}
</script>
<style>
header {
.header {
font-size: var(--font-size-tiny);
padding: 0 2rem 0 8rem;
margin-bottom: 2rem;
@@ -82,21 +37,21 @@
}
@media (max-width: 960px) {
header {
.header {
padding-left: 2rem;
}
header {
.header {
margin-bottom: 1.5rem;
}
}
</style>
<header>
<div class="header">
{#if peers.length > 0}
<PeerSelector
{peers}
peer={activeRoute.params.peer}
on:peerChanged={event => updatePeer(event.detail)} />
on:click={() => closeFocused()} />
{/if}
<BranchSelector
@@ -104,45 +59,77 @@
projectHead={project.head}
{branches}
{revision}
on:branchChanged={event => updateRevision(event.detail)} />
on:click={() => closeFocused()} />
<CloneButton {baseUrl} id={project.id} name={project.name} />
<span>
<HeaderToggleLabel
clickable
ariaLabel="Seed"
title="Project data is fetched from this seed"
on:click={goToSeed}>
<span>{baseUrl.hostname}</span>
</HeaderToggleLabel>
</span>
<HeaderToggleLabel
ariaLabel="Commit count"
clickable
active={activeRoute.params.view.resource === "history"}
on:click={() => toggleContent("history", true)}>
<span class="txt-bold">{tree.stats.commits}</span>
{pluralize("commit", tree.stats.commits)}
</HeaderToggleLabel>
<HeaderToggleLabel
ariaLabel="Issue count"
active={activeRoute.params.view.resource === "issues"}
clickable
on:click={() => toggleContent("issues", false)}>
<span class="txt-bold">{project.issues.open}</span>
{pluralize("issue", project.issues.open)}
</HeaderToggleLabel>
<HeaderToggleLabel
ariaLabel="Patch count"
active={activeRoute.params.view.resource === "patches"}
clickable
on:click={() => toggleContent("patches", false)}>
<span class="txt-bold">{project.patches.open}</span>
{pluralize("patch", project.patches.open)}
</HeaderToggleLabel>
<HeaderToggleLabel ariaLabel="Contributor count">
<Link
route={{
resource: "seeds",
params: {
hostnamePort:
baseUrl.port === config.seeds.defaultHttpdPort
? baseUrl.hostname
: `${baseUrl.hostname}:${baseUrl.port}`,
},
}}>
<SquareButton>
{baseUrl.hostname}
</SquareButton>
</Link>
<ProjectLink
projectParams={{
id: project.id,
view: {
resource:
activeRoute.params.view.resource === "history" ? "tree" : "history",
},
revision: revision,
search: undefined,
}}>
<SquareButton active={activeRoute.params.view.resource === "history"}>
<span class="txt-bold">{tree.stats.commits}</span>
{pluralize("commit", tree.stats.commits)}
</SquareButton>
</ProjectLink>
<ProjectLink
projectParams={{
id: project.id,
view: {
resource:
activeRoute.params.view.resource === "issues" ? "tree" : "issues",
},
search: undefined,
revision: undefined,
path: undefined,
}}>
<SquareButton active={activeRoute.params.view.resource === "issues"}>
<span class="txt-bold">{project.issues.open}</span>
{pluralize("issue", project.issues.open)}
</SquareButton>
</ProjectLink>
<ProjectLink
projectParams={{
id: project.id,
view: {
resource:
activeRoute.params.view.resource === "patches" ? "tree" : "patches",
},
search: undefined,
revision: undefined,
path: undefined,
}}>
<SquareButton active={activeRoute.params.view.resource === "patches"}>
<span class="txt-bold">{project.patches.open}</span>
{pluralize("patch", project.patches.open)}
</SquareButton>
</ProjectLink>
<SquareButton hoverable={false}>
<span class="txt-bold">{tree.stats.contributors}</span>
{pluralize("contributor", tree.stats.contributors)}
</HeaderToggleLabel>
</header>
</SquareButton>
</div>
@@ -1,49 +0,0 @@
<script lang="ts">
export let title: string | undefined = undefined;
export let ariaLabel: string | undefined = undefined;
export let active = false;
export let clickable = false;
export let disabled = false;
</script>
<style>
.stat {
font-family: var(--font-family-monospace);
font-size: var(--font-size-tiny);
padding: 0.5rem 0.75rem;
padding-bottom: 1rem; /* moving the content a tad higher to match the previous span usage */
height: 2rem;
background: var(--color-foreground-1);
border: none;
color: var(--color-foreground);
border-radius: var(--border-radius-small);
min-width: max-content;
}
.active {
color: var(--color-background);
background: var(--color-foreground) !important;
background-color: var(--color-foreground);
}
.clickable {
cursor: pointer;
}
.clickable:hover {
background-color: var(--color-foreground-2);
}
.not-allowed {
cursor: not-allowed;
color: var(--color-foreground-5);
}
</style>
<button
{title}
{disabled}
class:active
class:clickable
class:not-allowed={disabled}
class="stat"
aria-label={ariaLabel}
on:click>
<slot />
</button>
+13 -31
View File
@@ -1,7 +1,6 @@
<script lang="ts">
import type { BaseUrl, CommitHeader } from "@httpd-client";
import * as router from "@app/lib/router";
import { HttpdClient } from "@httpd-client";
import { groupCommits } from "@app/lib/commit";
@@ -42,20 +41,6 @@
}
}
function goToSourceTreeAtCommit(event: { detail: string }) {
router.updateProjectRoute({
view: { resource: "tree" },
revision: event.detail,
});
}
function goToCommit(revision: string) {
router.updateProjectRoute({
view: { resource: "commits" },
revision,
});
}
$: showMoreButton =
!loading && !error && totalCommitCount && history.length < totalCommitCount;
@@ -67,11 +52,13 @@
padding: 0 2rem 0 8rem;
font-size: var(--font-size-small);
}
.commit-group header {
color: var(--color-foreground-6);
}
.commit-group-headers {
.group {
margin-bottom: 2rem;
border-radius: var(--border-radius);
overflow: hidden;
}
.teaser-wrapper:not(:last-child) {
border-bottom: 1px solid var(--color-background);
}
.more {
margin-top: 2rem;
@@ -88,18 +75,13 @@
{#if history}
<div class="history">
{#each groupCommits(history) as group (group.time)}
<div class="commit-group">
<header class="commit-date">
<p>{group.date}</p>
</header>
<div class="commit-group-headers">
{#each group.commits as commit (commit.id)}
<CommitTeaser
{commit}
on:click={() => goToCommit(commit.id)}
on:browseCommit={goToSourceTreeAtCommit} />
{/each}
</div>
<p style:color="var(--color-foreground-6)">{group.date}</p>
<div class="group">
{#each group.commits as commit (commit.id)}
<div class="teaser-wrapper">
<CommitTeaser {commit} />
</div>
{/each}
</div>
{/each}
<div class="more">
+20 -8
View File
@@ -6,6 +6,7 @@
import Authorship from "@app/components/Authorship.svelte";
import Badge from "@app/components/Badge.svelte";
import Icon from "@app/components/Icon.svelte";
import ProjectLink from "@app/components/ProjectLink.svelte";
export let issue: Issue;
@@ -25,14 +26,12 @@
<style>
.issue-teaser {
display: grid;
grid-template-columns: 3rem minmax(0, 1fr) auto;
display: flex;
padding: 0.75rem 0;
background-color: var(--color-foreground-1);
}
.issue-teaser:hover {
background-color: var(--color-foreground-2);
cursor: pointer;
}
.subtitle {
color: var(--color-foreground-6);
@@ -51,6 +50,10 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
}
.issue-title:hover {
color: var(--color-secondary);
}
.comment-count {
display: flex;
@@ -70,14 +73,15 @@
text-overflow: ellipsis;
}
.column-right {
.right {
align-self: center;
justify-self: center;
margin-left: auto;
}
.state {
justify-self: center;
align-self: center;
margin: 0 1.25rem;
}
.state-icon {
width: 0.5rem;
@@ -105,9 +109,17 @@
class:closed={issue.state.status === "closed"}
class:open={issue.state.status === "open"} />
</div>
<div class="column-left">
<div>
<div class="summary">
<span class="issue-title">{issue.title}</span>
<ProjectLink
projectParams={{
view: {
resource: "issue",
params: { issue: issue.id },
},
}}>
<span class="issue-title">{issue.title}</span>
</ProjectLink>
<span class="tags">
{#each issue.tags.slice(0, 4) as tag}
<Badge style="max-width:7rem" variant="secondary">
@@ -129,7 +141,7 @@
</div>
</div>
{#if commentCount > 0}
<div class="column-right">
<div class="right">
<div class="comment-count">
<Icon name="chat" />
<span>{commentCount}</span>
+69 -71
View File
@@ -6,10 +6,8 @@
<script lang="ts">
import type { Issue } from "@httpd-client";
import type { Tab } from "@app/components/TabBar.svelte";
import type { BaseUrl } from "@httpd-client";
import * as router from "@app/lib/router";
import * as utils from "@app/lib/utils";
import capitalize from "lodash/capitalize";
import { HttpdClient } from "@httpd-client";
@@ -17,11 +15,11 @@
import Button from "@app/components/Button.svelte";
import ErrorMessage from "@app/components/ErrorMessage.svelte";
import HeaderToggleLabel from "@app/views/projects/HeaderToggleLabel.svelte";
import IssueTeaser from "@app/views/projects/Issue/IssueTeaser.svelte";
import Loading from "@app/components/Loading.svelte";
import Placeholder from "@app/components/Placeholder.svelte";
import TabBar from "@app/components/TabBar.svelte";
import ProjectLink from "@app/components/ProjectLink.svelte";
import SquareButton from "@app/components/SquareButton.svelte";
export let projectId: string;
export let state: IssueStatus;
@@ -30,9 +28,6 @@
const perPage = 10;
// Keeping it true, to avoid an initial flash
// of EmptyState Placeholder
let refresh = true;
let loading = false;
let page = 0;
let error: any;
@@ -40,7 +35,7 @@
const api = new HttpdClient(baseUrl);
async function loadIssues(): Promise<void> {
async function loadIssues(state: IssueStatus): Promise<void> {
loading = true;
try {
const response = await api.project.getAllIssues(projectId, {
@@ -54,26 +49,17 @@
error = e;
} finally {
loading = false;
refresh = false;
}
}
function switchState(e: CustomEvent<IssueStatus>): void {
refresh = true;
// Update state to be used in the query
state = e.detail;
// Reset page to 0 to load the first page for a new state
page = 0;
// Remove all existing patches with old state
issues = [];
loadIssues();
router.updateProjectRoute({
search: `state=${state}`,
});
interface Tab {
value: IssueStatus;
title: string;
disabled: boolean;
}
const stateOptions: IssueStatus[] = ["open", "closed"];
const options = stateOptions.map<Tab<IssueStatus>>(s => ({
const options = stateOptions.map<Tab>(s => ({
value: s,
title: `${issueCounters[s]} ${s}`,
disabled: issueCounters[s] === 0,
@@ -85,7 +71,11 @@
issueCounters[state] &&
issues.length < issueCounters[state];
loadIssues();
$: {
page = 0;
issues = [];
loadIssues(state);
}
</script>
<style>
@@ -98,7 +88,7 @@
overflow: hidden;
}
.teaser:not(:last-child) {
border-bottom: 1px dashed var(--color-background);
border-bottom: 1px solid var(--color-background);
}
.section-header {
display: flex;
@@ -106,9 +96,6 @@
justify-content: space-between;
width: 100%;
}
.loader {
margin-top: 8rem;
}
.more {
margin-top: 2rem;
text-align: center;
@@ -125,61 +112,72 @@
<div class="issues">
<div class="section-header">
<div style="margin-bottom: 1rem;">
<TabBar {options} on:select={switchState} active={state} />
<div style="display: flex; gap: 0.5rem;">
{#each options as option}
{#if !option.disabled}
<ProjectLink
projectParams={{
search: `state=${option.value}`,
}}>
<SquareButton
size="small"
clickable={option.disabled}
active={option.value === state}
disabled={option.disabled}>
{option.title}
</SquareButton>
</ProjectLink>
{:else}
<SquareButton
size="small"
clickable={option.disabled}
active={option.value === state}
disabled={option.disabled}>
{option.title}
</SquareButton>
{/if}
{/each}
</div>
</div>
<HeaderToggleLabel
disabled={!$sessionStore || !utils.isLocal(baseUrl.hostname)}
on:click={() => {
router.updateProjectRoute({
{#if $sessionStore && utils.isLocal(baseUrl.hostname)}
<ProjectLink
projectParams={{
view: {
resource: "issues",
params: { view: { resource: "new" } },
},
});
}}
clickable>
New issue
</HeaderToggleLabel>
}}>
<SquareButton size="small">New issue</SquareButton>
</ProjectLink>
{/if}
</div>
<div class="issues-list">
{#if refresh}
<div class="loader">
<Loading center />
{#each issues as issue}
<div class="teaser">
<IssueTeaser {issue} />
</div>
{:else}
{#each issues as issue}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="teaser"
on:click={() => {
router.updateProjectRoute({
view: {
resource: "issue",
params: { issue: issue.id },
},
});
}}>
<IssueTeaser {issue} />
</div>
{#if error}
<ErrorMessage message="Couldn't load issues." stackTrace={error} />
{:else if loading}
<!-- We already show a loader below. -->
{:else}
{#if error}
<ErrorMessage message="Couldn't load issues." stackTrace={error} />
{:else}
<Placeholder emoji="🍂">
<div slot="title">{capitalize(state)} issues</div>
<div slot="body">No issues matched the current filter</div>
</Placeholder>
{/if}
{/each}
<div class="more">
{#if loading}
<Loading small={page !== 0} center />
{/if}
<Placeholder emoji="🍂">
<div slot="title">{capitalize(state)} issues</div>
<div slot="body">No issues matched the current filter</div>
</Placeholder>
{/if}
{/each}
</div>
<div class="more">
{#if loading}
<Loading small={page !== 0} center />
{/if}
{#if showMoreButton}
<Button variant="foreground" on:click={loadIssues}>More</Button>
{/if}
</div>
{#if showMoreButton}
<Button variant="foreground" on:click={() => loadIssues(state)}>
More
</Button>
{/if}
</div>
</div>
+40 -43
View File
@@ -40,8 +40,8 @@
import CommitTeaser from "./Commit/CommitTeaser.svelte";
import Dropdown from "@app/components/Dropdown.svelte";
import Floating from "@app/components/Floating.svelte";
import HeaderToggleLabel from "./HeaderToggleLabel.svelte";
import TabBar from "@app/components/TabBar.svelte";
import ProjectLink from "@app/components/ProjectLink.svelte";
import SquareButton from "@app/components/SquareButton.svelte";
import TagInput from "./Cob/TagInput.svelte";
import ThreadComponent from "@app/components/Thread.svelte";
@@ -54,14 +54,6 @@
const api = new HttpdClient(baseUrl);
const browseCommit = (event: { detail: string }) => {
router.updateProjectRoute({
view: { resource: "tree" },
search: undefined,
revision: event.detail,
});
};
async function createReply({
detail: reply,
}: CustomEvent<{ id: string; body: string }>) {
@@ -193,6 +185,11 @@
margin: 1rem;
color: var(--color-foreground-5);
}
.commit-list {
border-radius: var(--border-radius);
overflow: hidden;
margin-top: 1rem;
}
@media (max-width: 1092px) {
.patch {
@@ -214,14 +211,13 @@
<div>
<CobHeader id={patch.id} title={patch.title}>
<span slot="revision" class="txt-monospace txt-tiny">
<Floating>
<Floating disabled={patch.revisions.length === 1}>
<svelte:fragment slot="toggle">
<HeaderToggleLabel
<SquareButton
clickable={patch.revisions.length > 1}
disabled={patch.revisions.length === 1}
title="Toggle revision">
disabled={patch.revisions.length === 1}>
Revision {currentRevisionIndex}
</HeaderToggleLabel>
</SquareButton>
</svelte:fragment>
<svelte:fragment slot="modal">
<Dropdown
@@ -263,13 +259,32 @@
</div>
</svelte:fragment>
</CobHeader>
<TabBar
{options}
active={currentTab}
on:select={({ detail: tab }) =>
router.updateProjectRoute({
search: `tab=${tab}`,
})} />
<div style="display: flex; gap: 0.5rem;">
{#each options as option}
{#if !option.disabled}
<ProjectLink
projectParams={{
search: `tab=${option.value}`,
}}>
<SquareButton
size="small"
clickable={option.disabled}
active={option.value === currentTab}
disabled={option.disabled}>
{option.title}
</SquareButton>
</ProjectLink>
{:else}
<SquareButton
size="small"
clickable={option.disabled}
active={option.value === currentTab}
disabled={option.disabled}>
{option.title}
</SquareButton>
{/if}
{/each}
</div>
{#if currentTab === "activity"}
<div style:margin-top="1rem">
<div class="txt-tiny">
@@ -332,34 +347,16 @@
</div>
{:else if currentTab === "commits"}
{#await diffPromise then diff}
<div style:margin-top="1rem">
<div class="commit-list">
{#each diff.commits as commit}
<CommitTeaser
{commit}
on:click={() => {
router.updateProjectRoute({
view: { resource: "commits" },
revision: commit.id,
search: undefined,
});
}}
on:browseCommit={browseCommit} />
<CommitTeaser {commit} />
{/each}
</div>
{/await}
{:else if currentTab === "files"}
{#await diffPromise then diff}
<div style:margin-top="1rem">
<Changeset
diff={diff.diff}
on:browse={({ detail: path }) => {
router.updateProjectRoute({
view: { resource: "tree" },
search: undefined,
revision: currentRevision.oid,
path,
});
}} />
<Changeset revision={currentRevision.oid} diff={diff.diff} />
</div>
{/await}
{/if}
+19 -10
View File
@@ -9,6 +9,7 @@
import Badge from "@app/components/Badge.svelte";
import DiffStatBadge from "@app/components/DiffStatBadge.svelte";
import Icon from "@app/components/Icon.svelte";
import ProjectLink from "@app/components/ProjectLink.svelte";
export let projectId: string;
export let baseUrl: BaseUrl;
@@ -27,14 +28,12 @@
<style>
.patch-teaser {
display: grid;
grid-template-columns: 3rem minmax(0, 1fr) auto;
display: flex;
padding: 0.75rem 0;
background-color: var(--color-foreground-1);
}
.patch-teaser:hover {
background-color: var(--color-foreground-2);
cursor: pointer;
}
.meta {
display: flex;
@@ -57,23 +56,25 @@
text-overflow: ellipsis;
white-space: nowrap;
}
.patch-title:hover {
color: var(--color-secondary);
}
.comment-count {
display: flex;
flex-direction: row;
align-items: center;
padding-right: 1rem;
gap: 0.5rem;
color: var(--color-foreground-5);
}
.column-right {
.right {
align-self: center;
justify-self: center;
padding-right: 1rem;
margin-left: auto;
}
.state {
justify-self: center;
align-self: center;
margin: 0 1.25rem;
}
.tags {
display: flex;
@@ -117,9 +118,17 @@
class:merged={patch.state.status === "merged"}
class:archived={patch.state.status === "archived"} />
</div>
<div class="column-left">
<div>
<div class="summary">
<span class="patch-title">{patch.title}</span>
<ProjectLink
projectParams={{
view: {
resource: "patch",
params: { patch: patch.id },
},
}}>
<span class="patch-title">{patch.title}</span>
</ProjectLink>
<span class="tags">
{#each patch.tags.slice(0, 4) as tag}
<Badge style="max-width:7rem" variant="secondary">
@@ -142,7 +151,7 @@
</span>
</div>
</div>
<div class="column-right">
<div class="right">
<div class="comment-count">
{#await diffPromise then { diff }}
<DiffStatBadge
+66 -62
View File
@@ -5,10 +5,8 @@
</script>
<script lang="ts">
import type { Tab } from "@app/components/TabBar.svelte";
import type { BaseUrl } from "@httpd-client";
import * as router from "@app/lib/router";
import capitalize from "lodash/capitalize";
import { HttpdClient } from "@httpd-client";
@@ -17,7 +15,8 @@
import Loading from "@app/components/Loading.svelte";
import PatchTeaser from "./Patch/PatchTeaser.svelte";
import Placeholder from "@app/components/Placeholder.svelte";
import TabBar from "@app/components/TabBar.svelte";
import ProjectLink from "@app/components/ProjectLink.svelte";
import SquareButton from "@app/components/SquareButton.svelte";
export let projectId: string;
export let state: PatchStatus;
@@ -31,9 +30,6 @@
const perPage = 10;
// Keeping it true, to avoid an initial flash
// of EmptyState Placeholder
let refresh = true;
let loading = false;
let page = 0;
let error: any;
@@ -41,7 +37,7 @@
const api = new HttpdClient(baseUrl);
async function loadPatches(): Promise<void> {
async function loadPatches(state: PatchStatus): Promise<void> {
loading = true;
try {
const response = await api.project.getAllPatches(projectId, {
@@ -55,37 +51,33 @@
error = e;
} finally {
loading = false;
refresh = false;
}
}
function switchState(e: CustomEvent<PatchStatus>): void {
refresh = true;
// Update state to be used in the query
state = e.detail;
// Reset page to 0 to load the first page for a new state
page = 0;
// Remove all existing patches with old state
patches = [];
loadPatches();
router.updateProjectRoute({
search: `state=${state}`,
});
interface Tab {
value: PatchStatus;
title: string;
disabled: boolean;
}
const stateOptions: PatchStatus[] = ["draft", "open", "archived", "merged"];
const options = stateOptions.map<Tab<PatchStatus>>(s => ({
const options = stateOptions.map<Tab>(s => ({
value: s,
title: `${patchCounters[s]} ${s}`,
disabled: patchCounters[s] === 0,
}));
$: showMoreButton =
!loading &&
!error &&
patchCounters[state] &&
patches.length < patchCounters[state];
loadPatches();
$: {
page = 0;
patches = [];
loadPatches(state);
}
</script>
<style>
@@ -97,9 +89,6 @@
border-radius: var(--border-radius);
overflow: hidden;
}
.loader {
margin-top: 8rem;
}
.more {
margin-top: 2rem;
text-align: center;
@@ -118,47 +107,62 @@
<div class="patches">
<div style="margin-bottom: 1rem;">
<TabBar {options} on:select={switchState} active={state} />
</div>
<div class="patches-list">
{#if refresh}
<div class="loader">
<Loading center />
</div>
{:else}
{#each patches as patch}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="teaser"
on:click={() => {
router.updateProjectRoute({
view: {
resource: "patch",
params: { patch: patch.id },
},
});
}}>
<PatchTeaser {baseUrl} {projectId} {patch} />
</div>
{:else}
{#if error}
<ErrorMessage message="Couldn't load patches." stackTrace={error} />
<div style="display: flex; gap: 0.5rem;">
{#each options as option}
{#if option.disabled}
<SquareButton
size="small"
clickable={option.disabled}
active={option.value === state}
disabled={option.disabled}>
{option.title}
</SquareButton>
{:else}
<Placeholder emoji="🍂">
<div slot="title">{capitalize(state)} patches</div>
<div slot="body">No patches matched the current filter</div>
</Placeholder>
<ProjectLink
projectParams={{
search: `state=${option.value}`,
}}>
<SquareButton
size="small"
clickable={option.disabled}
active={option.value === state}
disabled={option.disabled}>
{option.title}
</SquareButton>
</ProjectLink>
{/if}
{/each}
<div class="more">
{#if loading}
<Loading small={page !== 0} center />
{/if}
{#if showMoreButton}
<Button variant="foreground" on:click={loadPatches}>More</Button>
{/if}
</div>
</div>
<div class="patches-list">
{#each patches as patch}
<div class="teaser">
<PatchTeaser {baseUrl} {projectId} {patch} />
</div>
{:else}
{#if error}
<ErrorMessage message="Couldn't load patches." stackTrace={error} />
{:else if loading}
<!-- We already show a loader below. -->
{:else}
<Placeholder emoji="🍂">
<div slot="title">{capitalize(state)} patches</div>
<div slot="body">No patches matched the current filter</div>
</Placeholder>
{/if}
{/each}
</div>
<div class="more">
{#if loading}
<div style:margin-top={page === 0 ? "8rem" : ""}>
<Loading small={page !== 0} center />
</div>
{/if}
{#if showMoreButton}
<Button variant="foreground" on:click={() => loadPatches(state)}>
More
</Button>
{/if}
</div>
</div>
+44 -28
View File
@@ -2,7 +2,7 @@
import type { Item } from "@app/components/Dropdown.svelte";
import type { Remote } from "@httpd-client";
import { createEventDispatcher, onMount } from "svelte";
import { onMount } from "svelte";
import { formatNodeId, truncateId } from "@app/lib/utils";
@@ -11,8 +11,9 @@
import Dropdown from "@app/components/Dropdown.svelte";
import Floating from "@app/components/Floating.svelte";
import Icon from "@app/components/Icon.svelte";
import ProjectLink from "@app/components/ProjectLink.svelte";
export let peer: string | null = null;
export let peer: string | undefined = undefined;
export let peers: Remote[];
let meta: Remote | undefined;
@@ -36,11 +37,6 @@
};
});
});
const dispatch = createEventDispatcher<{ peerChanged: string }>();
const switchPeer = (peer: string) => {
dispatch("peerChanged", peer);
};
</script>
<style>
@@ -69,7 +65,7 @@
display: flex;
flex-direction: row;
align-items: center;
gap: 0;
gap: 0.5rem;
}
.prefix {
display: inline-block;
@@ -83,6 +79,12 @@
height: 2rem;
line-height: initial;
background: var(--color-foreground-1);
gap: 0.5rem;
}
.avatar-id {
display: flex;
gap: 0.25rem;
}
</style>
@@ -93,39 +95,53 @@
<Icon name="fork" />
{/if}
{#if meta}
<span style:display="flex">
<span class="avatar-id">
<Avatar nodeId={meta.id} inline />
<span style:color="var(--color-secondary-5)">did:key:</span>
{truncateId(meta.id)}
<!-- Ignore prettier to avoid getting a whitespace between
did:key: and the nid due to a newline. -->
<!-- prettier-ignore -->
<span><span style:color="var(--color-secondary-5)">did:key:</span>{truncateId(meta.id)}</span>
</span>
{#if meta.delegate}
<Badge variant="primary">delegate</Badge>
{/if}
{:else if peer}
<span style:display="flex">
<span class="avatar-id">
<Avatar nodeId={peer} inline />
<span style:color="var(--color-secondary-5)">did:key:</span>
{truncateId(peer)}
<!-- prettier-ignore -->
<span><span style:color="var(--color-secondary-5)">did:key:</span>{truncateId(peer)}</span>
</span>
{/if}
</div>
</div>
<svelte:fragment slot="modal">
<Dropdown
{items}
selected={peer}
on:select={e => switchPeer(e.detail.value)}>
<div class="peer-item" slot="item" let:item>
<Avatar nodeId={item.value} inline />
<!-- We ignore prettier here for the following line
to avoid getting a whitespace between did:key: and the nid due to a newline -->
<!-- prettier-ignore -->
<span><span class="prefix">did:key:</span>{item.value}</span>
{#if item.badge}
<Badge variant="primary">{item.badge}</Badge>
{/if}
</div>
<Dropdown {items} selected={peer}>
<svelte:fragment slot="item" let:item>
<ProjectLink
on:click
projectParams={{
peer: item.value,
revision: undefined,
}}>
<div class="peer-item">
<span class="avatar-id">
<Avatar nodeId={item.value} inline />
<div class="layout-desktop">
<!-- prettier-ignore -->
<span><span class="prefix">did:key:</span>{item.value}</span>
</div>
<div class="layout-mobile">
<!-- prettier-ignore -->
<span><span class="prefix">did:key:</span>{truncateId(item.value)}</span>
</div>
</span>
{#if item.badge}
<Badge variant="primary">{item.badge}</Badge>
{/if}
</div>
</ProjectLink>
</svelte:fragment>
</Dropdown>
</svelte:fragment>
</Floating>
+15 -5
View File
@@ -16,22 +16,28 @@
<style>
.title {
display: flex;
align-items: center;
justify-content: left;
color: var(--color-secondary);
display: flex;
font-size: var(--font-size-x-large);
font-weight: var(--font-weight-bold);
justify-content: left;
margin-bottom: 0.5rem;
overflow-x: hidden;
text-align: left;
text-overflow: ellipsis;
}
.title .divider {
.divider {
color: var(--color-foreground-4);
margin: 0 0.5rem;
font-weight: var(--font-weight-normal);
}
.title .node-id {
.node-id {
color: var(--color-foreground-5);
font-weight: var(--font-weight-normal);
display: flex;
align-items: center;
white-space: nowrap;
}
.project-name:hover {
color: inherit;
@@ -68,11 +74,15 @@
.content {
padding-left: 2rem;
}
.title {
font-size: var(--font-size-medium);
font-weight: var(--font-weight-bold);
}
}
</style>
<header class="content">
<div class="title txt-bold txt-title">
<div class="title">
<span class="truncate">
<ProjectLink
projectParams={{
@@ -6,6 +6,7 @@
import FileDiff from "@app/views/projects/SourceBrowser/FileDiff.svelte";
export let diff: Diff;
export let revision: string;
const diffDescription = ({ modified, added, deleted }: Diff): string => {
const s = [];
@@ -53,12 +54,12 @@
</div>
<div class="diff-listing">
{#each diff.added as file}
<FileDiff on:browse {file} mode="added" />
<FileDiff on:browse {file} {revision} mode="added" />
{/each}
{#each diff.deleted as file}
<FileDiff on:browse {file} mode="deleted" />
<FileDiff on:browse {file} {revision} mode="deleted" />
{/each}
{#each diff.modified as file}
<FileDiff on:browse {file} />
<FileDiff on:browse {file} {revision} />
{/each}
</div>
@@ -1,23 +1,17 @@
<script lang="ts" strictEvents>
<script lang="ts">
import type {
DiffAddedDeletedModifiedChangeset,
HunkLine,
} from "@httpd-client";
import { createEventDispatcher } from "svelte";
import Badge from "@app/components/Badge.svelte";
import Icon from "@app/components/Icon.svelte";
const dispatch = createEventDispatcher<{ browse: string }>();
import ProjectLink from "@app/components/ProjectLink.svelte";
export let file: DiffAddedDeletedModifiedChangeset;
export let revision: string;
export let mode: string | null = null;
function collapse() {
collapsed = !collapsed;
}
let collapsed = false;
function lineNumberR(line: HunkLine): string | number {
@@ -64,19 +58,19 @@
</script>
<style>
.changeset-file {
.wrapper {
border: 1px solid var(--color-foreground-4);
border-radius: var(--border-radius-small);
margin-bottom: 2rem;
line-height: 1.5rem;
}
.changeset-file header {
cursor: pointer;
height: 3rem;
display: flex;
.header {
align-items: center;
background: none;
border-radius: 0;
display: flex;
flex-direction: row;
height: 3rem;
padding: 1rem;
}
main {
@@ -84,15 +78,8 @@
border-top: 1px dashed var(--color-foreground-4);
background: var(--color-background-1);
border-radius: 0 0 var(--border-radius-small) var(--border-radius-small);
}
.changeset-file main {
overflow-x: auto;
}
header {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.actions {
display: flex;
flex-direction: row;
@@ -105,69 +92,82 @@
text-align: center;
background-color: var(--color-foreground-2);
}
table.diff {
.browse {
margin-left: auto;
cursor: pointer;
}
.expand-button {
cursor: pointer;
user-select: none;
margin-right: 0.5rem;
}
.diff {
font-family: var(--font-family-monospace);
table-layout: fixed;
border-collapse: collapse;
margin: 0.5rem 0;
}
tr.diff-line {
.diff-line {
vertical-align: top;
}
tr.diff-line[data-type="+"] > * {
.diff-line[data-type="+"] > * {
color: var(--color-positive);
}
tr.diff-line[data-type="-"] > * {
.diff-line[data-type="-"] > * {
color: var(--color-negative);
}
td.diff-line-number {
.diff-line-number {
text-align: right;
user-select: none;
line-height: 1.5rem;
min-width: 3rem;
}
td.diff-line-number[data-type="+"],
td.diff-line-type[data-type="+"] {
.diff-line-number[data-type="+"],
.diff-line-type[data-type="+"] {
background-color: var(--color-positive-2);
color: var(--color-positive-6);
}
td.diff-line-number[data-type="-"],
td.diff-line-type[data-type="-"] {
.diff-line-number[data-type="-"],
.diff-line-type[data-type="-"] {
background-color: var(--color-negative-2);
color: var(--color-negative-6);
}
td.diff-line-number.left {
.diff-line-number.left {
padding: 0 0.5rem 0 0.75rem;
}
td.diff-line-number.right {
.diff-line-number.right {
padding: 0 0.75rem 0 0.5rem;
}
td.diff-line-content {
.diff-line-content {
white-space: pre-wrap;
overflow-wrap: anywhere;
width: 100%;
padding-right: 0.5rem;
}
td.diff-line-type {
.diff-line-type {
text-align: center;
padding-left: 0.75rem;
padding-right: 0.75rem;
}
td.diff-expand-header {
.diff-expand-header {
padding-left: 0.5rem;
color: var(--color-foreground-5);
}
td.diff-line-number {
.diff-line-number {
color: var(--color-foreground-5);
}
.browse {
display: flex;
}
</style>
<article id={file.path} class="changeset-file">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<header class="file-header" on:click={collapse}>
<div id={file.path} class="wrapper">
<header class="header">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="expand-button" on:click={() => (collapsed = !collapsed)}>
{#if collapsed}
<Icon name="chevron-right" />
{:else}
<Icon name="chevron-down" />
{/if}
</div>
<div class="actions">
<p class="txt-regular">{file.path}</p>
{#if mode === "added"}
@@ -176,12 +176,16 @@
<Badge variant="negative">deleted</Badge>
{/if}
</div>
<div
class="browse clickable"
on:click|stopPropagation={() => dispatch("browse", file.path)}>
<span title="View file" style="transform: scale(1.25);">
<div class="browse" title="View file">
<ProjectLink
projectParams={{
view: { resource: "tree" },
path: file.path,
revision,
search: undefined,
}}>
<Icon name="browse" />
</span>
</ProjectLink>
</div>
</header>
{#if !collapsed}
@@ -216,4 +220,4 @@
{/if}
</main>
{/if}
</article>
</div>
+12 -5
View File
@@ -5,6 +5,7 @@
import File from "./Tree/File.svelte";
import Folder from "./Tree/Folder.svelte";
import ProjectLink from "@app/components/ProjectLink.svelte";
export let fetchTree: (path: string) => Promise<Tree | undefined>;
export let path: string;
@@ -27,10 +28,16 @@
currentPath={path}
on:select={onSelect} />
{:else}
<File
active={entry.path === path}
loading={entry.path === loadingPath}
name={entry.name}
on:click={() => onSelect({ detail: entry.path })} />
<ProjectLink
projectParams={{
view: { resource: "tree" },
path: entry.path,
}}
on:click={() => onSelect({ detail: entry.path })}>
<File
active={entry.path === path}
loading={entry.path === loadingPath}
name={entry.name} />
</ProjectLink>
{/if}
{/each}
+1 -2
View File
@@ -46,8 +46,7 @@
}
</style>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="file" class:active on:click>
<div class="file" class:active>
<span class="name">{name}</span>
<div class="spinner">
{#if loading}
+12 -7
View File
@@ -4,6 +4,7 @@
import { createEventDispatcher } from "svelte";
import Loading from "@app/components/Loading.svelte";
import ProjectLink from "@app/components/ProjectLink.svelte";
import File from "./File.svelte";
@@ -84,13 +85,17 @@
{loadingPath}
{currentPath} />
{:else}
<File
active={entry.path === currentPath}
loading={entry.path === loadingPath}
name={entry.name}
on:click={() => {
onSelectFile({ detail: entry.path });
}} />
<ProjectLink
projectParams={{
view: { resource: "tree" },
path: entry.path,
}}
on:click={() => onSelectFile({ detail: entry.path })}>
<File
active={entry.path === currentPath}
loading={entry.path === loadingPath}
name={entry.name} />
</ProjectLink>
{/if}
{/each}
{/if}
+33 -31
View File
@@ -2,7 +2,6 @@
import type { Project, NodeStats } from "@httpd-client";
import type { WeeklyActivity } from "@app/lib/commit";
import * as router from "@app/lib/router";
import { HttpdClient } from "@httpd-client";
import { config } from "@app/lib/config";
import { extractBaseUrl, isLocal, truncateId } from "@app/lib/utils";
@@ -11,6 +10,7 @@
import Button from "@app/components/Button.svelte";
import Clipboard from "@app/components/Clipboard.svelte";
import ErrorMessage from "@app/components/ErrorMessage.svelte";
import Link from "@app/components/Link.svelte";
import Loading from "@app/components/Loading.svelte";
import ProjectCard from "@app/components/ProjectCard.svelte";
@@ -58,23 +58,6 @@
}
}
function goToProject(project: Project) {
router.push({
resource: "projects",
params: {
view: { resource: "tree" },
id: project.id,
hostnamePort:
baseUrl.port === config.seeds.defaultHttpdPort
? baseUrl.hostname
: `${baseUrl.hostname}:${baseUrl.port}`,
revision: undefined,
hash: undefined,
search: undefined,
},
});
}
$: showMoreButton =
!loadingProjects &&
!error &&
@@ -90,12 +73,18 @@
margin: 5rem 0;
}
.header {
display: flex;
width: 100%;
flex-direction: row;
align-items: center;
color: var(--color-secondary);
display: flex;
flex-direction: row;
font-size: var(--font-size-large);
font-weight: var(--font-weight-bold);
justify-content: space-between;
margin-bottom: 2rem;
overflow-x: hidden;
text-align: left;
text-overflow: ellipsis;
width: 100%;
}
table {
border-collapse: collapse;
@@ -128,9 +117,7 @@
<div class="wrapper">
<div class="header">
<span class="txt-title txt-bold">
{hostName}
</span>
{hostName}
</div>
{#await api.getRoot()}
@@ -168,13 +155,28 @@
<div style:margin-top="1rem">
{#each projectsWithActivity as { project, activity }}
<div style:margin-bottom="0.5rem">
<ProjectCard
{activity}
id={project.id}
name={project.name}
description={project.description}
head={project.head}
on:click={() => goToProject(project)} />
<Link
route={{
resource: "projects",
params: {
view: { resource: "tree" },
id: project.id,
hostnamePort:
baseUrl.port === config.seeds.defaultHttpdPort
? baseUrl.hostname
: `${baseUrl.hostname}:${baseUrl.port}`,
revision: undefined,
hash: undefined,
search: undefined,
},
}}>
<ProjectCard
{activity}
id={project.id}
name={project.name}
description={project.description}
head={project.head} />
</Link>
</div>
{/each}
</div>
+2 -2
View File
@@ -34,7 +34,7 @@ test("navigation between seed and project pages", async ({ page }) => {
await expectBackAndForwardNavigationWorks("/#/seeds/radicle.local", page);
await expectUrlPersistsReload(page);
await page.locator('role=button[name="Seed"]').click();
await page.locator('role=link[name="127.0.0.1"]').click();
await expect(page).toHaveURL("/#/seeds/127.0.0.1");
});
@@ -60,7 +60,7 @@ test.describe("project page navigation", () => {
await page.goto(projectTreeURL);
await expect(page).toHaveURL(projectTreeURL);
await page.locator('role=button[name="Commit count"]').click();
await page.locator('role=link[name="8 commits"]').click();
await expect(page).toHaveURL(`/#${projectFixtureUrl}/history/main`);
await expectBackAndForwardNavigationWorks(projectTreeURL, page);
+2 -2
View File
@@ -34,7 +34,7 @@ test("navigation between seed and project pages", async ({ page }) => {
await expectBackAndForwardNavigationWorks("/seeds/radicle.local", page);
await expectUrlPersistsReload(page);
await page.locator('role=button[name="Seed"]').click();
await page.locator('role=link[name="127.0.0.1"]').click();
await expect(page).toHaveURL("/seeds/127.0.0.1");
});
@@ -60,7 +60,7 @@ test.describe("project page navigation", () => {
await page.goto(projectTreeURL);
await expect(page).toHaveURL(projectTreeURL);
await page.locator('role=button[name="Commit count"]').click();
await page.locator('role=link[name="8 commits"]').click();
await expect(page).toHaveURL(
`${projectFixtureUrl}/history/${aliceMainHead}`,
);
+22 -17
View File
@@ -14,16 +14,21 @@ async function expectCounts(
params: { commits: number; contributors: number },
page: Page,
) {
await expect(page.locator('role=button[name="Commit count"]')).toContainText(
`${params.commits} ${params.commits === 1 ? "commit" : "commits"}`,
);
await expect(
page.locator('role=button[name="Contributor count"]'),
).toContainText(
`${params.contributors} ${
params.contributors === 1 ? "contributor" : "contributors"
}`,
);
page.locator(
`role=link[name="${params.commits} ${
params.commits === 1 ? "commit" : "commits"
}"]`,
),
).toBeVisible();
await expect(
page.locator(
`text=${params.contributors} ${
params.contributors === 1 ? "contributor" : "contributors"
}`,
),
).toBeVisible();
}
test("navigate to project", async ({ page }) => {
@@ -62,10 +67,10 @@ test("navigate to project", async ({ page }) => {
test("show source tree at specific revision", async ({ page }) => {
await page.goto(projectFixtureUrl);
await page.locator('role=button[name="Commit count"]').click();
await page.locator('role=link[name="8 commits"]').click();
await page
.locator(".commit-teaser", { hasText: "335dd6d" })
.locator(".teaser", { hasText: "335dd6d" })
.getByTitle("Browse the repository at this point in the history")
.click();
@@ -90,7 +95,7 @@ test("source file highlighting", async ({ page }) => {
test("navigate line numbers", async ({ page }) => {
await page.goto(`${projectFixtureUrl}/tree/main/markdown/cheatsheet.md`);
await page.locator('role=button[name="Raw"]').click();
await page.locator(".markdown-toggle").click();
await page.locator('[href="#L5"]').click();
await expect(page.locator("#L5")).toHaveClass("line highlight");
@@ -225,7 +230,7 @@ test("markdown files", async ({ page }) => {
// Switch between raw and rendered modes.
{
const rawButton = page.locator('role=button[name="Raw"]');
const rawButton = page.locator(".markdown-toggle .square-button");
await rawButton.click();
await expect(rawButton).toHaveClass(/active/);
@@ -262,9 +267,9 @@ test("peer and branch switching", async ({ page }) => {
await page.getByTitle("Change peer").click();
await page.locator(`text=${aliceRemote}`).click();
await expect(page.getByTitle("Change peer")).toHaveText(
` did:key: ${aliceRemote
.substring(8)
.substring(0, 6)}…${aliceRemote.slice(-6)} `,
` did:key:${aliceRemote.substring(8).substring(0, 6)}…${aliceRemote.slice(
-6,
)} `,
);
await expect(
page.locator(
@@ -327,7 +332,7 @@ test("peer and branch switching", async ({ page }) => {
await page.getByTitle("Change peer").click();
await page.locator(`text=${bobRemote}`).click();
await expect(page.getByTitle("Change peer")).toHaveText(
` did:key: ${bobRemote.substring(8).substring(0, 6)}…${bobRemote.slice(
` did:key:${bobRemote.substring(8).substring(0, 6)}…${bobRemote.slice(
-6,
)} `,
);
+3 -3
View File
@@ -13,7 +13,7 @@ test("navigation from commit list", async ({ page }) => {
await page.goto(projectFixtureUrl);
await page.getByTitle("Change peer").click();
await page.locator(`text=${bobRemote}`).click();
await page.locator('role=button[name="Commit count"]').click();
await page.locator('role=link[name="9 commits"]').click();
await page.locator("text=Update readme").click();
await expect(page).toHaveURL(modifiedFileFixture);
@@ -31,7 +31,7 @@ test("relative timestamps", async ({ page }) => {
});
await page.goto(modifiedFileFixture);
await expect(
page.locator(`.commit header >> text=${"Bob Belcher committed now"}`),
page.locator(`.commit .header >> text=${"Bob Belcher committed now"}`),
).toBeVisible();
});
@@ -40,7 +40,7 @@ test("modified file", async ({ page }) => {
// Commit header.
{
const header = page.locator(".commit header");
const header = page.locator(".commit .header");
await expect(header.locator("text=Update readme")).toBeVisible();
await expect(
header.locator("text=1e0bb83a89b63da815f2fc24e7ae3c5ceb30e0eb"),
+22 -29
View File
@@ -8,30 +8,28 @@ import {
test("peer and branch switching", async ({ page }) => {
await page.goto(projectFixtureUrl);
await page.locator('role=button[name="Commit count"]').click();
await page.locator('role=link[name="8 commits"]').click();
// Alice's peer.
{
await page.getByTitle("Change peer").click();
await page.locator(`text=${aliceRemote}`).click();
await expect(page.getByTitle("Change peer")).toHaveText(
` did:key: ${aliceRemote
.substring(8)
.substring(0, 6)}…${aliceRemote.slice(-6)} `,
` did:key:${aliceRemote.substring(8).substring(0, 6)}…${aliceRemote.slice(
-6,
)} `,
);
await expect(page.getByText("Thursday, November 17, 2022")).toBeVisible();
await expect(
page.locator(".commit-group-headers .commit-teaser"),
).toHaveCount(8);
await expect(page.locator(".history .teaser")).toHaveCount(8);
const latestCommit = page.locator(".commit-teaser").first();
const latestCommit = page.locator(".teaser").first();
await expect(latestCommit).toContainText(
"Adds a new markdown file with an image with a relative path",
);
await expect(latestCommit).toContainText("fcc9294");
const earliestCommit = page.locator(".commit-teaser").last();
const earliestCommit = page.locator(".teaser").last();
await expect(earliestCommit).toContainText(
"Initialize an empty git repository",
);
@@ -44,9 +42,7 @@ test("peer and branch switching", async ({ page }) => {
"feature/branch d6318f7",
);
await expect(page.getByText("Thursday, November 17, 2022")).toBeVisible();
await expect(
page.locator(".commit-group-headers .commit-teaser"),
).toHaveCount(10);
await expect(page.locator(".history .teaser")).toHaveCount(10);
await page.getByTitle("Change branch").click();
await page.locator("text=orphaned-branch").click();
@@ -55,9 +51,7 @@ test("peer and branch switching", async ({ page }) => {
"orphaned-branch af3641c",
);
await expect(page.getByText("Thursday, November 17, 2022")).toBeVisible();
await expect(
page.locator(".commit-group-headers .commit-teaser"),
).toHaveCount(1);
await expect(page.locator(".group .teaser")).toHaveCount(1);
}
// Bob's peer.
@@ -65,27 +59,26 @@ test("peer and branch switching", async ({ page }) => {
await page.getByTitle("Change peer").click();
await page.locator(`text=${bobRemote}`).click();
await expect(page.getByTitle("Change peer")).toContainText(
` did:key: ${bobRemote.substring(8).substring(0, 6)}…${bobRemote.slice(
` did:key:${bobRemote.substring(8).substring(0, 6)}…${bobRemote.slice(
-6,
)} `,
);
await expect(page.getByText("Tuesday, December 20, 2022")).toBeVisible();
await expect(
page.locator(".commit-group-headers").first().locator(".commit-teaser"),
).toHaveCount(1);
await expect(page.locator(".group").first().locator(".teaser")).toHaveCount(
1,
);
await expect(page.getByText("Thursday, November 17, 2022")).toBeVisible();
await expect(
page.locator(".commit-group-headers").last().locator(".commit-teaser"),
).toHaveCount(6);
await expect(page.locator(".group").last().locator(".teaser")).toHaveCount(
6,
);
await page.pause();
const latestCommit = page.locator(".commit-teaser").first();
const latestCommit = page.locator(".teaser").first();
await expect(latestCommit).toContainText("Update readme");
await expect(latestCommit).toContainText("1e0bb83");
const earliestCommit = page.locator(".commit-teaser").last();
const earliestCommit = page.locator(".teaser").last();
await expect(earliestCommit).toContainText(
"Initialize an empty git repository",
);
@@ -105,19 +98,19 @@ test("relative timestamps", async ({ page }) => {
});
await page.goto(projectFixtureUrl);
await page.locator('role=button[name="Commit count"]').click();
await page.locator('role=link[name="8 commits"]').click();
await page.getByTitle("Change peer").click();
await page.locator(`text=${bobRemote}`).click();
await expect(page.getByTitle("Change peer")).toHaveText(
` did:key: ${bobRemote.substring(8).substring(0, 6)}…${bobRemote.slice(
` did:key:${bobRemote.substring(8).substring(0, 6)}…${bobRemote.slice(
-6,
)} `,
);
const latestCommit = page.locator(".commit-teaser").first();
const latestCommit = page.locator(".teaser").first();
await expect(latestCommit).toContainText("Bob Belcher committed now");
await expect(latestCommit).toContainText("1e0bb83");
const earliestCommit = page.locator(".commit-teaser").last();
const earliestCommit = page.locator(".teaser").last();
await expect(earliestCommit).toContainText(
"Alice Liddell committed last month",
);