mirror of
https://seed.radicle.dev/rad:z4D5UCArafTzTQpDZNQRuqswh3ury.git
synced 2026-10-18 19:10:45 +08:00
Add svelte runes
This commit is contained in:
+1
-1
@@ -59,7 +59,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
$: document.documentElement.setAttribute("data-theme", $theme);
|
||||
$effect(() => document.documentElement.setAttribute("data-theme", $theme));
|
||||
</script>
|
||||
|
||||
{#if $activeRouteStore.resource === "booting"}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { createIcon } from "@app/lib/blockies";
|
||||
|
||||
export let publicKey: string;
|
||||
const {
|
||||
publicKey,
|
||||
}: {
|
||||
publicKey: string;
|
||||
} = $props();
|
||||
|
||||
function createContainer(source: string) {
|
||||
const seed = source.toLowerCase();
|
||||
|
||||
@@ -1,23 +1,40 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
export let children: Snippet;
|
||||
export let variant: "primary" | "secondary" | "ghost" | "float" | "danger";
|
||||
export let hoverable: boolean = false;
|
||||
export let onclick: (() => void) | undefined = undefined;
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
variant: "primary" | "secondary" | "ghost" | "float" | "danger";
|
||||
hoverable?: boolean;
|
||||
onclick?: () => void;
|
||||
stylePadding?: string;
|
||||
styleHeight?: string;
|
||||
styleMinHeight?: string;
|
||||
styleWidth?: string;
|
||||
styleCursor?: "default" | "pointer" | "text";
|
||||
styleGap?: string;
|
||||
styleOverflow?: string;
|
||||
flatTop?: boolean;
|
||||
}
|
||||
|
||||
export let stylePadding: string | undefined = undefined;
|
||||
export let styleHeight: string | undefined = undefined;
|
||||
export let styleMinHeight: string | undefined = undefined;
|
||||
export let styleWidth: string | undefined = undefined;
|
||||
export let styleCursor: "default" | "pointer" | "text" = "default";
|
||||
export let styleGap: string = "0.5rem";
|
||||
export let styleOverflow: string | undefined = undefined;
|
||||
export let flatTop: boolean = false;
|
||||
const {
|
||||
children,
|
||||
variant,
|
||||
hoverable = false,
|
||||
onclick,
|
||||
stylePadding,
|
||||
styleHeight,
|
||||
styleMinHeight,
|
||||
styleWidth,
|
||||
styleCursor = "default",
|
||||
styleGap = "0.5rem",
|
||||
styleOverflow,
|
||||
flatTop = false,
|
||||
}: Props = $props();
|
||||
|
||||
$: style =
|
||||
const style = $derived(
|
||||
`--local-button-color-1: var(--color-fill-${variant});` +
|
||||
`--local-hover-background-color: ${hoverable ? "var(--color-background-float)" : "var(--color-background-default)"}`;
|
||||
`--local-hover-background-color: ${hoverable ? "var(--color-background-float)" : "var(--color-background-default)"}`,
|
||||
);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -170,7 +187,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
style:width={styleWidth}
|
||||
style:cursor={styleCursor}
|
||||
|
||||
@@ -1,22 +1,33 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
export let children: Snippet;
|
||||
export let variant: "primary" | "secondary" | "ghost";
|
||||
export let onclick: (() => void) | undefined = undefined;
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
variant: "primary" | "secondary" | "ghost";
|
||||
onclick?: () => void;
|
||||
disabled?: boolean;
|
||||
active?: boolean;
|
||||
flatLeft?: boolean;
|
||||
flatRight?: boolean;
|
||||
}
|
||||
|
||||
export let disabled: boolean = false;
|
||||
export let active: boolean = false;
|
||||
const {
|
||||
children,
|
||||
variant,
|
||||
onclick = undefined,
|
||||
disabled = false,
|
||||
active = false,
|
||||
flatLeft = false,
|
||||
flatRight = false,
|
||||
}: Props = $props();
|
||||
|
||||
export let flatLeft: boolean = false;
|
||||
export let flatRight: boolean = false;
|
||||
|
||||
$: style =
|
||||
const style = $derived(
|
||||
`--button-color-1: var(--color-fill-${variant});` +
|
||||
`--button-color-2: var(--color-fill-${variant}-hover);` +
|
||||
`--button-color-3: var(--color-fill-${variant}-shade);` +
|
||||
// The ghost colors are called --color-fill-counter and --color-fill-counter-emphasized.
|
||||
`--button-color-4: var(--color-fill${variant === "ghost" ? "" : `-${variant}`}-counter)`;
|
||||
`--button-color-2: var(--color-fill-${variant}-hover);` +
|
||||
`--button-color-3: var(--color-fill-${variant}-shade);` +
|
||||
// The ghost colors are called --color-fill-counter and --color-fill-counter-emphasized.
|
||||
`--button-color-4: var(--color-fill${variant === "ghost" ? "" : `-${variant}`}-counter)`,
|
||||
);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -347,7 +358,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="container active"
|
||||
style:cursor={!disabled ? "pointer" : "default"}
|
||||
|
||||
@@ -18,26 +18,41 @@
|
||||
import ReactionSelector from "@app/components/ReactionSelector.svelte";
|
||||
import Reactions from "@app/components/Reactions.svelte";
|
||||
|
||||
export let actions: Snippet | undefined = undefined;
|
||||
export let id: string | undefined = undefined;
|
||||
export let rid: string;
|
||||
export let author: Author;
|
||||
export let body: string;
|
||||
export let reactions: Reaction[] | undefined = undefined;
|
||||
export let embeds: Map<string, Embed> | undefined = undefined;
|
||||
export let caption = "commented";
|
||||
export let timestamp: number;
|
||||
export let lastEdit: Edit | undefined = undefined;
|
||||
export let disallowEmptyBody: boolean = false;
|
||||
interface Props {
|
||||
actions?: Snippet;
|
||||
id?: string;
|
||||
rid: string;
|
||||
author: Author;
|
||||
body: string;
|
||||
reactions?: Reaction[];
|
||||
embeds?: Map<string, Embed>;
|
||||
caption?: string;
|
||||
timestamp: number;
|
||||
lastEdit?: Edit;
|
||||
disallowEmptyBody?: boolean;
|
||||
editComment?: (body: string, embeds: Embed[]) => Promise<void>;
|
||||
reactOnComment?: (authors: Author[], reaction: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export let editComment:
|
||||
| ((body: string, embeds: Embed[]) => Promise<void>)
|
||||
| undefined = undefined;
|
||||
export let reactOnComment:
|
||||
| ((authors: Author[], reaction: string) => Promise<void>)
|
||||
| undefined = undefined;
|
||||
/* eslint-disable prefer-const */
|
||||
let {
|
||||
actions,
|
||||
id,
|
||||
rid,
|
||||
author,
|
||||
body = $bindable(),
|
||||
reactions,
|
||||
embeds,
|
||||
caption = "commented",
|
||||
timestamp,
|
||||
lastEdit,
|
||||
disallowEmptyBody = false,
|
||||
editComment,
|
||||
reactOnComment,
|
||||
}: Props = $props();
|
||||
/* eslint-enable prefer-const */
|
||||
|
||||
let state: "read" | "edit" | "submit" = "read";
|
||||
let state: "read" | "edit" | "submit" = $state("read");
|
||||
|
||||
async function toggleEdit() {
|
||||
if (state === "read") {
|
||||
@@ -136,7 +151,7 @@
|
||||
popoverPositionRight="0"
|
||||
popoverPositionBottom="1.5rem"
|
||||
{reactions}
|
||||
on:select={async ({ detail: { authors, emoji } }) => {
|
||||
select={async ({ authors, emoji }) => {
|
||||
try {
|
||||
await reactOnComment(authors, emoji);
|
||||
} finally {
|
||||
@@ -169,7 +184,7 @@
|
||||
submitInProgress={state === "submit"}
|
||||
submitCaption="Save"
|
||||
placeholder="Leave a comment"
|
||||
on:submit={async ({ detail: { comment, embeds } }) => {
|
||||
submit={async ({ comment, embeds }) => {
|
||||
state = "submit";
|
||||
try {
|
||||
await editComment(comment, Array.from(embeds.values()));
|
||||
@@ -177,7 +192,7 @@
|
||||
state = "read";
|
||||
}
|
||||
}}
|
||||
on:close={async () => {
|
||||
close={async () => {
|
||||
body = body;
|
||||
await tick();
|
||||
state = "read";
|
||||
@@ -199,7 +214,7 @@
|
||||
popoverPositionLeft="0"
|
||||
popoverPositionBottom="1.5rem"
|
||||
{reactions}
|
||||
on:select={async ({ detail: { authors, emoji } }) => {
|
||||
select={async ({ authors, emoji }) => {
|
||||
try {
|
||||
await reactOnComment(authors, emoji);
|
||||
} finally {
|
||||
|
||||
@@ -4,17 +4,33 @@
|
||||
import ExtendedTextarea from "@app/components/ExtendedTextarea.svelte";
|
||||
import Border from "./Border.svelte";
|
||||
|
||||
export let rid: string;
|
||||
export let placeholder: string | undefined = undefined;
|
||||
export let focus: boolean = false;
|
||||
export let submit: (comment: string, embeds: Embed[]) => Promise<void>;
|
||||
export let onclose: (() => void) | undefined = undefined;
|
||||
export let onexpand: (() => void) | undefined = undefined;
|
||||
export let disallowEmptyBody: boolean = false;
|
||||
interface Props {
|
||||
rid: string;
|
||||
placeholder?: string;
|
||||
focus?: boolean;
|
||||
submit: (comment: string, embeds: Embed[]) => Promise<void>;
|
||||
onclose?: () => void;
|
||||
onexpand?: () => void;
|
||||
disallowEmptyBody?: boolean;
|
||||
}
|
||||
|
||||
let state: "collapsed" | "expanded" | "submit";
|
||||
/* eslint-disable prefer-const */
|
||||
let {
|
||||
rid,
|
||||
placeholder,
|
||||
focus = false,
|
||||
submit,
|
||||
onclose,
|
||||
onexpand,
|
||||
disallowEmptyBody = false,
|
||||
}: Props = $props();
|
||||
/* eslint-enable prefer-const */
|
||||
|
||||
$: state = onclose !== undefined ? "expanded" : "collapsed";
|
||||
let state: "collapsed" | "expanded" | "submit" | undefined = $state();
|
||||
|
||||
$effect(() => {
|
||||
state = onclose !== undefined ? "expanded" : "collapsed";
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -33,14 +49,14 @@
|
||||
submitInProgress={state === "submit"}
|
||||
{focus}
|
||||
stylePadding="0.5rem 0.75rem"
|
||||
on:close={() => {
|
||||
close={() => {
|
||||
if (onclose !== undefined) {
|
||||
onclose();
|
||||
} else {
|
||||
state = "collapsed";
|
||||
}
|
||||
}}
|
||||
on:submit={async ({ detail: { comment, embeds } }) => {
|
||||
submit={async ({ comment, embeds }) => {
|
||||
try {
|
||||
state = "submit";
|
||||
await submit(comment, Array.from(embeds.values()));
|
||||
|
||||
@@ -6,9 +6,13 @@
|
||||
|
||||
import Icon from "./Icon.svelte";
|
||||
|
||||
export let id: string;
|
||||
const {
|
||||
id,
|
||||
}: {
|
||||
id: string;
|
||||
} = $props();
|
||||
|
||||
let icon: ComponentProps<Icon>["name"] = "copy";
|
||||
let icon: ComponentProps<typeof Icon>["name"] = $state("copy");
|
||||
|
||||
const restoreIcon = debounce(() => {
|
||||
icon = "copy";
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script lang="ts">
|
||||
import type { Stats } from "@bindings/cob/Stats";
|
||||
|
||||
export let stats: Stats;
|
||||
interface Props {
|
||||
stats: Stats;
|
||||
}
|
||||
|
||||
const { stats }: Props = $props();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<script lang="ts" generics="T">
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
export let item: Snippet<[T]>;
|
||||
export let empty: Snippet | undefined = undefined;
|
||||
export let items: T[];
|
||||
export let styleDropdownMinWidth: string | undefined = undefined;
|
||||
interface Props {
|
||||
item: Snippet<[T]>;
|
||||
empty?: Snippet;
|
||||
items: T[];
|
||||
styleDropdownMinWidth?: string;
|
||||
}
|
||||
|
||||
const { item, empty, items, styleDropdownMinWidth }: Props = $props();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
export let children: Snippet;
|
||||
export let selected: boolean;
|
||||
export let disabled: boolean = false;
|
||||
export let title: string | undefined = undefined;
|
||||
export let style: string | undefined = undefined;
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
selected: boolean;
|
||||
onclick: () => void;
|
||||
disabled?: boolean;
|
||||
title?: string;
|
||||
style?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
onclick,
|
||||
children,
|
||||
selected,
|
||||
disabled = false,
|
||||
title,
|
||||
style,
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -46,7 +58,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@@ -55,6 +67,6 @@
|
||||
class:disabled
|
||||
{style}
|
||||
{title}
|
||||
on:click>
|
||||
{onclick}>
|
||||
{@render children()}
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
import type { ComponentProps } from "svelte";
|
||||
import type { Embed } from "@bindings/cob/thread/Embed";
|
||||
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
import * as utils from "@app/lib/utils";
|
||||
|
||||
import Button from "./Button.svelte";
|
||||
@@ -12,39 +10,58 @@
|
||||
import Textarea from "./Textarea.svelte";
|
||||
import OutlineButton from "./OutlineButton.svelte";
|
||||
|
||||
export let rid: string;
|
||||
export let placeholder: string = "Leave your comment";
|
||||
export let submitCaption: string = "Comment";
|
||||
export let focus: boolean = false;
|
||||
export let inline: boolean = false;
|
||||
export let body: string = "";
|
||||
export let embeds: Map<string, Embed> = new Map();
|
||||
export let submitInProgress: boolean = false;
|
||||
export let disallowEmptyBody: boolean = false;
|
||||
export let isValid: () => boolean = () => {
|
||||
return true;
|
||||
};
|
||||
export let stylePadding: string | undefined = undefined;
|
||||
export let borderVariant: ComponentProps<Textarea>["borderVariant"] = "float";
|
||||
interface Props {
|
||||
rid: string;
|
||||
placeholder?: string;
|
||||
submitCaption?: string;
|
||||
focus?: boolean;
|
||||
inline?: boolean;
|
||||
body?: string;
|
||||
embeds?: Map<string, Embed>;
|
||||
submitInProgress?: boolean;
|
||||
disallowEmptyBody?: boolean;
|
||||
isValid?: () => boolean;
|
||||
stylePadding?: string;
|
||||
borderVariant?: ComponentProps<typeof Textarea>["borderVariant"];
|
||||
submit: (opts: {
|
||||
comment: string;
|
||||
embeds: Map<string, Embed>;
|
||||
}) => Promise<void>;
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
let preview: boolean = false;
|
||||
let selectionStart = 0;
|
||||
let selectionEnd = 0;
|
||||
let inputFiles: FileList | undefined = undefined;
|
||||
/* eslint-disable prefer-const */
|
||||
let {
|
||||
rid,
|
||||
placeholder = "Leave your comment",
|
||||
submitCaption = "Comment",
|
||||
focus = false,
|
||||
inline = false,
|
||||
body = $bindable(""),
|
||||
embeds = new Map(),
|
||||
submitInProgress = false,
|
||||
disallowEmptyBody = false,
|
||||
isValid = () => true,
|
||||
stylePadding,
|
||||
borderVariant = "float",
|
||||
submit,
|
||||
close,
|
||||
}: Props = $props();
|
||||
/* eslint-enable prefer-const */
|
||||
|
||||
let preview: boolean = $state(false);
|
||||
let selectionStart = $state(0);
|
||||
let selectionEnd = $state(0);
|
||||
let inputFiles: FileList | undefined = $state(undefined);
|
||||
|
||||
const inputId = `input-label-${crypto.randomUUID()}`;
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
submit: {
|
||||
comment: string;
|
||||
embeds: Map<string, Embed>;
|
||||
};
|
||||
close: null;
|
||||
}>();
|
||||
|
||||
function submit() {
|
||||
dispatch("submit", { comment: body, embeds });
|
||||
preview = false;
|
||||
function submitFn() {
|
||||
void submit({ comment: body, embeds })
|
||||
.then(() => (preview = false))
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -106,7 +123,7 @@
|
||||
bind:selectionEnd
|
||||
bind:selectionStart
|
||||
{focus}
|
||||
on:submit={submit}
|
||||
submit={() => submit({ comment: body, embeds })}
|
||||
bind:value={body}
|
||||
{placeholder} />
|
||||
{/if}
|
||||
@@ -116,7 +133,7 @@
|
||||
variant="ghost"
|
||||
onclick={() => {
|
||||
preview = false;
|
||||
dispatch("close");
|
||||
close();
|
||||
}}>
|
||||
<Icon name="cross" />Discard
|
||||
</OutlineButton>
|
||||
@@ -138,7 +155,7 @@
|
||||
disabled={!isValid() ||
|
||||
submitInProgress ||
|
||||
(disallowEmptyBody && body.trim() === "")}
|
||||
onclick={submit}>
|
||||
onclick={submitFn}>
|
||||
<Icon name="checkmark" />
|
||||
{#if submitInProgress}
|
||||
Saving…
|
||||
|
||||
@@ -11,9 +11,13 @@
|
||||
import Popover from "./Popover.svelte";
|
||||
import ThemeSwitch from "./ThemeSwitch.svelte";
|
||||
|
||||
export let breadcrumbs: Snippet;
|
||||
export let iconLeft: Snippet | undefined = undefined;
|
||||
export let center: Snippet | undefined = undefined;
|
||||
interface Props {
|
||||
breadcrumbs: Snippet;
|
||||
iconLeft?: Snippet;
|
||||
center?: Snippet;
|
||||
}
|
||||
|
||||
const { breadcrumbs, iconLeft, center }: Props = $props();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
+48
-40
@@ -1,45 +1,53 @@
|
||||
<script lang="ts">
|
||||
import { unreachable } from "@app/lib/utils";
|
||||
|
||||
export let size: "16" | "32" = "16";
|
||||
export let onclick: (() => void) | undefined = undefined;
|
||||
export let styleCursor: "default" | "pointer" = "default";
|
||||
interface Props {
|
||||
size?: "16" | "32";
|
||||
onclick?: () => void;
|
||||
styleCursor?: "default" | "pointer";
|
||||
name:
|
||||
| "arrow-left"
|
||||
| "arrow-right"
|
||||
| "checkmark"
|
||||
| "chevron-down"
|
||||
| "chevron-right"
|
||||
| "comment"
|
||||
| "copy"
|
||||
| "cross"
|
||||
| "dashboard"
|
||||
| "delegate"
|
||||
| "diff"
|
||||
| "eye"
|
||||
| "face"
|
||||
| "file"
|
||||
| "inbox"
|
||||
| "issue"
|
||||
| "lock"
|
||||
| "markdown"
|
||||
| "moon"
|
||||
| "more-vertical"
|
||||
| "offline"
|
||||
| "online"
|
||||
| "patch"
|
||||
| "pen"
|
||||
| "plus"
|
||||
| "reply"
|
||||
| "repo"
|
||||
| "revision"
|
||||
| "seedling"
|
||||
| "seedling-filled"
|
||||
| "settings"
|
||||
| "sidebar"
|
||||
| "sun"
|
||||
| "warning";
|
||||
}
|
||||
|
||||
export let name:
|
||||
| "arrow-left"
|
||||
| "arrow-right"
|
||||
| "checkmark"
|
||||
| "chevron-down"
|
||||
| "chevron-right"
|
||||
| "comment"
|
||||
| "copy"
|
||||
| "cross"
|
||||
| "dashboard"
|
||||
| "delegate"
|
||||
| "diff"
|
||||
| "eye"
|
||||
| "face"
|
||||
| "file"
|
||||
| "inbox"
|
||||
| "issue"
|
||||
| "lock"
|
||||
| "markdown"
|
||||
| "moon"
|
||||
| "more-vertical"
|
||||
| "offline"
|
||||
| "online"
|
||||
| "patch"
|
||||
| "pen"
|
||||
| "plus"
|
||||
| "reply"
|
||||
| "repo"
|
||||
| "revision"
|
||||
| "seedling"
|
||||
| "seedling-filled"
|
||||
| "settings"
|
||||
| "sidebar"
|
||||
| "sun"
|
||||
| "warning";
|
||||
const {
|
||||
size = "16",
|
||||
onclick = undefined,
|
||||
styleCursor = "default",
|
||||
name,
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -52,8 +60,8 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<svg
|
||||
style:cursor={styleCursor}
|
||||
role="img"
|
||||
|
||||
+23
-12
@@ -8,16 +8,9 @@
|
||||
|
||||
import Icon from "./Icon.svelte";
|
||||
|
||||
export let children: Snippet | undefined = undefined;
|
||||
export let id: string;
|
||||
export let clipboard: string = id;
|
||||
export let shorten: boolean = true;
|
||||
export let variant: "oid" | "commit" | "none";
|
||||
export let ariaLabel: string | undefined = undefined;
|
||||
|
||||
let icon: ComponentProps<Icon>["name"] = "copy";
|
||||
let icon: ComponentProps<typeof Icon>["name"] = $state("copy");
|
||||
const text = "Click to copy";
|
||||
let tooltip = text;
|
||||
let tooltip = $state(text);
|
||||
|
||||
const restoreIcon = debounce(() => {
|
||||
icon = "copy";
|
||||
@@ -31,8 +24,26 @@
|
||||
restoreIcon();
|
||||
}
|
||||
|
||||
let visible: boolean = false;
|
||||
export let debounceTimeout = 50;
|
||||
let visible: boolean = $state(false);
|
||||
interface Props {
|
||||
children?: Snippet;
|
||||
id: string;
|
||||
clipboard?: string;
|
||||
shorten?: boolean;
|
||||
variant: "oid" | "commit" | "none";
|
||||
ariaLabel?: string;
|
||||
debounceTimeout?: number;
|
||||
}
|
||||
|
||||
const {
|
||||
children,
|
||||
id,
|
||||
clipboard = id,
|
||||
shorten = true,
|
||||
variant,
|
||||
ariaLabel,
|
||||
debounceTimeout = 50,
|
||||
}: Props = $props();
|
||||
|
||||
const setVisible = debounce((value: boolean) => {
|
||||
visible = value;
|
||||
@@ -73,7 +84,7 @@
|
||||
</style>
|
||||
|
||||
<div class="container">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
onmouseenter={() => {
|
||||
setVisible(true);
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
import dompurify from "dompurify";
|
||||
import escape from "lodash/escape";
|
||||
|
||||
export let content: string;
|
||||
export let fontSize: "tiny" | "small" | "regular" | "medium" | "large" =
|
||||
"small";
|
||||
interface Props {
|
||||
content: string;
|
||||
fontSize?: "tiny" | "small" | "regular" | "medium" | "large";
|
||||
}
|
||||
|
||||
const { content, fontSize = "small" }: Props = $props();
|
||||
|
||||
function formatInlineTitle(input: string): string {
|
||||
return input.replaceAll(/`([^`]+)`/g, "<code>$1</code>");
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
import IssueStateBadge from "@app/components/IssueStateBadge.svelte";
|
||||
import NodeId from "@app/components/NodeId.svelte";
|
||||
|
||||
export let issue: Issue;
|
||||
interface Props {
|
||||
issue: Issue;
|
||||
}
|
||||
|
||||
const { issue }: Props = $props();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
import capitalize from "lodash/capitalize";
|
||||
import { issueStatusColor } from "@app/lib/utils";
|
||||
|
||||
export let state: Issue["state"];
|
||||
interface Props {
|
||||
state: Issue["state"];
|
||||
}
|
||||
|
||||
const { state }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
||||
@@ -12,8 +12,13 @@
|
||||
import Icon from "@app/components/Icon.svelte";
|
||||
import Popover from "@app/components/Popover.svelte";
|
||||
|
||||
export let state: State;
|
||||
export let save: (state: State) => Promise<void>;
|
||||
const {
|
||||
save,
|
||||
...rest
|
||||
}: {
|
||||
state: State;
|
||||
save: (state: State) => Promise<void>;
|
||||
} = $props();
|
||||
|
||||
const actions: { caption: string; state: State }[] = [
|
||||
{ caption: "Reopen", state: { status: "open" } },
|
||||
@@ -25,7 +30,9 @@
|
||||
];
|
||||
|
||||
// Pick a default for the action button when the issue state changes.
|
||||
$: selectedAction = state.status === "open" ? actions[1] : actions[0];
|
||||
let selectedAction = $state(
|
||||
rest.state.status === "open" ? actions[1] : actions[0],
|
||||
);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -40,7 +47,7 @@
|
||||
<Button
|
||||
variant="secondary"
|
||||
flatRight
|
||||
onclick={() => void save(selectedAction["state"])}>
|
||||
onclick={() => void save($state.snapshot(selectedAction["state"]))}>
|
||||
{selectedAction["caption"]}
|
||||
</Button>
|
||||
|
||||
@@ -57,11 +64,12 @@
|
||||
{/snippet}
|
||||
{#snippet popover()}
|
||||
<Border variant="ghost">
|
||||
<DropdownList items={actions.filter(a => !isEqual(a.state, state))}>
|
||||
<DropdownList
|
||||
items={actions.filter(a => !isEqual(a.state, rest.state))}>
|
||||
{#snippet item(action)}
|
||||
<DropdownListItem
|
||||
selected={isEqual(selectedAction, action)}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
selectedAction = action;
|
||||
closeFocused();
|
||||
}}>
|
||||
|
||||
@@ -14,8 +14,12 @@
|
||||
import InlineTitle from "./InlineTitle.svelte";
|
||||
import NodeId from "./NodeId.svelte";
|
||||
|
||||
export let issue: Issue;
|
||||
export let rid: string;
|
||||
interface Props {
|
||||
issue: Issue;
|
||||
rid: string;
|
||||
}
|
||||
|
||||
const { issue, rid }: Props = $props();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
import IssueStateBadge from "@app/components/IssueStateBadge.svelte";
|
||||
import NodeId from "@app/components/NodeId.svelte";
|
||||
|
||||
export let operation: Extract<Operation, { type: "lifecycle" }>;
|
||||
interface Props {
|
||||
operation: Extract<Operation, { type: "lifecycle" }>;
|
||||
}
|
||||
|
||||
const { operation }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Border variant="float" stylePadding="1rem">
|
||||
|
||||
@@ -4,10 +4,19 @@
|
||||
|
||||
import { push, routeToPath } from "@app/lib/router";
|
||||
|
||||
export let children: Snippet;
|
||||
export let route: Route;
|
||||
export let disabled: boolean = false;
|
||||
export let variant: "active" | "regular" | "tab" = "regular";
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
route: Route;
|
||||
disabled?: boolean;
|
||||
variant?: "active" | "regular" | "tab";
|
||||
}
|
||||
|
||||
const {
|
||||
children,
|
||||
route,
|
||||
disabled = false,
|
||||
variant = "regular",
|
||||
}: Props = $props();
|
||||
|
||||
function navigateToRoute(event: MouseEvent): void {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
<script lang="ts">
|
||||
import dompurify from "dompurify";
|
||||
import matter from "@radicle/gray-matter";
|
||||
import { afterUpdate } from "svelte";
|
||||
import { toDom } from "hast-util-to-dom";
|
||||
|
||||
import { Renderer, markdownWithExtensions } from "@app/lib/markdown";
|
||||
import { highlight } from "@app/lib/syntax";
|
||||
import { twemoji, scrollIntoView, isCommit } from "@app/lib/utils";
|
||||
import { invoke } from "@app/lib/invoke";
|
||||
import { tick } from "svelte";
|
||||
|
||||
export let rid: string;
|
||||
export let content: string;
|
||||
// If true, add <br> on a single line break
|
||||
export let breaks: boolean = false;
|
||||
interface Props {
|
||||
rid: string;
|
||||
content: string;
|
||||
// If true, add <br> on a single line break
|
||||
breaks?: boolean;
|
||||
}
|
||||
|
||||
const { rid, content, breaks = false }: Props = $props();
|
||||
|
||||
let container: HTMLElement;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let frontMatter: [string, any][] | undefined = undefined;
|
||||
|
||||
$: {
|
||||
const doc = $derived(matter(content));
|
||||
const frontMatter = $derived.by(() => {
|
||||
try {
|
||||
const doc = matter(content);
|
||||
content = doc.content;
|
||||
frontMatter = Object.entries(doc.data).filter(
|
||||
return Object.entries(doc.data).filter(
|
||||
([, val]) => typeof val === "string" || typeof val === "number",
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Not able to parse frontmatter: ", error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function render(content: string): string {
|
||||
return dompurify.sanitize(
|
||||
@@ -39,85 +40,86 @@
|
||||
);
|
||||
}
|
||||
|
||||
afterUpdate(async () => {
|
||||
for (const e of container.querySelectorAll("a")) {
|
||||
try {
|
||||
const url = new URL(e.href);
|
||||
if (url.origin !== window.origin) {
|
||||
e.target = "_blank";
|
||||
$effect(() => {
|
||||
void tick().then(() => {
|
||||
for (const e of container.querySelectorAll("a")) {
|
||||
try {
|
||||
const url = new URL(e.href);
|
||||
if (url.origin !== window.origin) {
|
||||
e.target = "_blank";
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Not able to parse url", e);
|
||||
}
|
||||
// Don't underline <a> tags that contain images.
|
||||
// Make an exception for emojis.
|
||||
if (
|
||||
e.firstElementChild instanceof HTMLImageElement &&
|
||||
!e.firstElementChild.classList.contains("txt-emoji")
|
||||
) {
|
||||
e.classList.add("no-underline");
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Not able to parse url", e);
|
||||
}
|
||||
// Don't underline <a> tags that contain images.
|
||||
// Make an exception for emojis.
|
||||
if (
|
||||
e.firstElementChild instanceof HTMLImageElement &&
|
||||
!e.firstElementChild.classList.contains("txt-emoji")
|
||||
) {
|
||||
e.classList.add("no-underline");
|
||||
|
||||
// Replace standard HTML checkboxes with our custom radicle-icon-small element
|
||||
for (const i of container.querySelectorAll('input[type="checkbox"]')) {
|
||||
i.parentElement?.classList.add("task-item");
|
||||
|
||||
const checkbox = document.createElement("radicle-icon-small");
|
||||
const checked = i.getAttribute("checked");
|
||||
checkbox.setAttribute(
|
||||
"name",
|
||||
checked === null ? "checkbox-unchecked" : "checkbox-checked",
|
||||
);
|
||||
i.insertAdjacentElement("beforebegin", checkbox);
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Replace standard HTML checkboxes with our custom radicle-icon-small element
|
||||
for (const i of container.querySelectorAll('input[type="checkbox"]')) {
|
||||
i.parentElement?.classList.add("task-item");
|
||||
// Iterate over all images, and replace the source with a canonicalized URL
|
||||
// pointing at the repos /raw endpoint.
|
||||
for (const i of container.querySelectorAll("img")) {
|
||||
const imagePath = i.getAttribute("src");
|
||||
|
||||
const checkbox = document.createElement("radicle-icon-small");
|
||||
const checked = i.getAttribute("checked");
|
||||
checkbox.setAttribute(
|
||||
"name",
|
||||
checked === null ? "checkbox-unchecked" : "checkbox-checked",
|
||||
);
|
||||
i.insertAdjacentElement("beforebegin", checkbox);
|
||||
i.remove();
|
||||
}
|
||||
|
||||
// Iterate over all images, and replace the source with a canonicalized URL
|
||||
// pointing at the repos /raw endpoint.
|
||||
for (const i of container.querySelectorAll("img")) {
|
||||
const imagePath = i.getAttribute("src");
|
||||
|
||||
// If the image is an oid embed
|
||||
if (imagePath && isCommit(imagePath)) {
|
||||
const base64Content = await invoke<string>("get_file_by_oid", {
|
||||
rid,
|
||||
oid: imagePath,
|
||||
});
|
||||
|
||||
i.setAttribute("src", `data:image/jpeg;base64,${base64Content}`);
|
||||
continue;
|
||||
// If the image is an oid embed
|
||||
if (imagePath && isCommit(imagePath)) {
|
||||
void invoke<string>("get_file_by_oid", {
|
||||
rid,
|
||||
oid: imagePath,
|
||||
}).then(base64Content =>
|
||||
i.setAttribute("src", `data:image/jpeg;base64,${base64Content}`),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces code blocks in the background with highlighted code.
|
||||
const prefix = "language-";
|
||||
const nodes = Array.from(document.body.querySelectorAll("pre code"));
|
||||
// Replaces code blocks in the background with highlighted code.
|
||||
const prefix = "language-";
|
||||
const nodes = Array.from(document.body.querySelectorAll("pre code"));
|
||||
|
||||
const treeChanges: Promise<void>[] = [];
|
||||
const treeChanges: Promise<void>[] = [];
|
||||
|
||||
for (const node of nodes) {
|
||||
const className = Array.from(node.classList).find(name =>
|
||||
name.startsWith(prefix),
|
||||
);
|
||||
if (!className) continue;
|
||||
for (const node of nodes) {
|
||||
const className = Array.from(node.classList).find(name =>
|
||||
name.startsWith(prefix),
|
||||
);
|
||||
if (!className) continue;
|
||||
|
||||
treeChanges.push(
|
||||
highlight(node.textContent ?? "", className.slice(prefix.length))
|
||||
.then(tree => {
|
||||
if (tree) {
|
||||
node.replaceChildren(toDom(tree, { fragment: true }));
|
||||
}
|
||||
})
|
||||
.catch(e => console.warn("Not able to highlight code block", e)),
|
||||
);
|
||||
}
|
||||
treeChanges.push(
|
||||
highlight(node.textContent ?? "", className.slice(prefix.length))
|
||||
.then(tree => {
|
||||
if (tree) {
|
||||
node.replaceChildren(toDom(tree, { fragment: true }));
|
||||
}
|
||||
})
|
||||
.catch(e => console.warn("Not able to highlight code block", e)),
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.allSettled(treeChanges);
|
||||
void Promise.allSettled(treeChanges);
|
||||
|
||||
if (window.location.hash) {
|
||||
scrollIntoView(window.location.hash.substring(1));
|
||||
}
|
||||
if (window.location.hash) {
|
||||
scrollIntoView(window.location.hash.substring(1));
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -385,17 +387,18 @@
|
||||
{#if frontMatter && frontMatter.length > 0}
|
||||
<div class="front-matter">
|
||||
<table>
|
||||
{#each frontMatter as [key, val]}
|
||||
<!-- svelte-ignore node_invalid_placement_ssr -->
|
||||
<tr>
|
||||
<td><span class="txt-bold">{key}</span></td>
|
||||
<td>{val}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
<tbody>
|
||||
{#each frontMatter as [key, val]}
|
||||
<tr>
|
||||
<td><span class="txt-bold">{key}</span></td>
|
||||
<td>{val}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="markdown" bind:this={container} use:twemoji={{ exclude: ["21a9"] }}>
|
||||
{@html render(content)}
|
||||
{@html render(doc.content)}
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
export let children: Snippet;
|
||||
export let variant: "primary" | "secondary" | "ghost";
|
||||
export let onclick: (() => void) | undefined = undefined;
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
variant: "primary" | "secondary" | "ghost";
|
||||
onclick?: () => void;
|
||||
}
|
||||
|
||||
$: style =
|
||||
const { children, variant, onclick }: Props = $props();
|
||||
|
||||
const style = $derived(
|
||||
`--button-color-1: var(--color-fill-${variant});` +
|
||||
`--button-color-2: var(--color-fill-${variant}-hover);` +
|
||||
`--button-color-3: var(--color-fill-${variant}-shade);` +
|
||||
// The ghost colors are called --color-fill-counter and --color-fill-counter-emphasized.
|
||||
`--button-color-4: var(--color-fill${variant === "ghost" ? "" : `-${variant}`}-counter)`;
|
||||
`--button-color-2: var(--color-fill-${variant}-hover);` +
|
||||
`--button-color-3: var(--color-fill-${variant}-shade);` +
|
||||
// The ghost colors are called --color-fill-counter and --color-fill-counter-emphasized.
|
||||
`--button-color-4: var(--color-fill${variant === "ghost" ? "" : `-${variant}`}-counter)`,
|
||||
);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -197,7 +202,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div class="container" {onclick} role="button" tabindex="0" {style}>
|
||||
<div class="pixel p1-1"></div>
|
||||
<div class="pixel p1-2"></div>
|
||||
|
||||
@@ -3,12 +3,19 @@
|
||||
|
||||
import Avatar from "./Avatar.svelte";
|
||||
|
||||
export let publicKey: string;
|
||||
export let alias: string | undefined = undefined;
|
||||
interface Props {
|
||||
publicKey: string;
|
||||
alias?: string;
|
||||
styleFontSize?: string;
|
||||
styleFontFamily?: string;
|
||||
}
|
||||
|
||||
export let styleFontSize: string | undefined = "var(--font-size-small)";
|
||||
export let styleFontFamily: string | undefined =
|
||||
"var(--font-family-monospace)";
|
||||
const {
|
||||
publicKey,
|
||||
alias,
|
||||
styleFontSize = "var(--font-size-small)",
|
||||
styleFontFamily = "var(--font-family-monospace)",
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
export let children: Snippet;
|
||||
export let variant: "primary" | "secondary" | "ghost";
|
||||
export let onclick: (() => void) | undefined = undefined;
|
||||
export let disabled: boolean = false;
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
variant: "primary" | "secondary" | "ghost";
|
||||
onclick?: () => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
$: style =
|
||||
const { children, variant, onclick, disabled = false }: Props = $props();
|
||||
|
||||
const style = $derived(
|
||||
`--button-color-1: var(--color-fill-${variant});` +
|
||||
`--button-color-2: var(--color-fill-${variant}-hover);` +
|
||||
`--button-color-3: var(--color-fill-${variant}-shade);` +
|
||||
// The ghost colors are called --color-fill-counter and --color-fill-counter-emphasized.
|
||||
`--button-color-4: var(--color-fill${variant === "ghost" ? "" : `-${variant}`}-counter)`;
|
||||
`--button-color-2: var(--color-fill-${variant}-hover);` +
|
||||
`--button-color-3: var(--color-fill-${variant}-shade);` +
|
||||
// The ghost colors are called --color-fill-counter and --color-fill-counter-emphasized.
|
||||
`--button-color-4: var(--color-fill${variant === "ghost" ? "" : `-${variant}`}-counter)`,
|
||||
);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -241,7 +246,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="container"
|
||||
style:cursor={!disabled ? "pointer" : "default"}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
import NodeId from "./NodeId.svelte";
|
||||
import Id from "./Id.svelte";
|
||||
|
||||
let stats: Stats | undefined = undefined;
|
||||
let stats: Stats | undefined = $state(undefined);
|
||||
|
||||
onMount(async () => {
|
||||
stats = await invoke<Stats>("diff_stats", {
|
||||
@@ -28,8 +28,12 @@
|
||||
});
|
||||
});
|
||||
|
||||
export let patch: Patch;
|
||||
export let rid: string;
|
||||
interface Props {
|
||||
patch: Patch;
|
||||
rid: string;
|
||||
}
|
||||
|
||||
const { patch, rid }: Props = $props();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -10,17 +10,33 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
export let toggle: Snippet<[() => void]>;
|
||||
export let popover: Snippet;
|
||||
export let popoverContainerMinWidth: string | undefined = undefined;
|
||||
export let popoverPadding: string | undefined = undefined;
|
||||
export let popoverPositionBottom: string | undefined = undefined;
|
||||
export let popoverPositionLeft: string | undefined = undefined;
|
||||
export let popoverPositionRight: string | undefined = undefined;
|
||||
export let popoverPositionTop: string | undefined = undefined;
|
||||
interface Props {
|
||||
toggle: Snippet<[() => void]>;
|
||||
popover: Snippet;
|
||||
popoverContainerMinWidth?: string;
|
||||
popoverPadding?: string;
|
||||
popoverPositionBottom?: string;
|
||||
popoverPositionLeft?: string;
|
||||
popoverPositionRight?: string;
|
||||
popoverPositionTop?: string;
|
||||
expanded?: boolean;
|
||||
}
|
||||
|
||||
export let expanded = false;
|
||||
let thisComponent: HTMLDivElement;
|
||||
/* eslint-disable prefer-const */
|
||||
let {
|
||||
toggle,
|
||||
popover,
|
||||
popoverContainerMinWidth,
|
||||
popoverPadding,
|
||||
popoverPositionBottom,
|
||||
popoverPositionLeft,
|
||||
popoverPositionRight,
|
||||
popoverPositionTop,
|
||||
expanded = $bindable(false),
|
||||
}: Props = $props();
|
||||
/* eslint-enable prefer-const */
|
||||
|
||||
let thisComponent: HTMLDivElement | undefined = $state();
|
||||
|
||||
function clickOutside(ev: MouseEvent | TouchEvent) {
|
||||
if ($focused && !ev.composedPath().includes($focused)) {
|
||||
@@ -37,7 +53,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: expanded = $focused === thisComponent;
|
||||
$effect(() => {
|
||||
expanded = $focused === thisComponent;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
<script lang="ts">
|
||||
import type { Reaction } from "@bindings/cob/Reaction";
|
||||
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
import Border from "./Border.svelte";
|
||||
import Icon from "./Icon.svelte";
|
||||
import Popover from "./Popover.svelte";
|
||||
|
||||
export let reactions: Reaction[] | undefined = undefined;
|
||||
export let popoverPositionBottom: string | undefined = undefined;
|
||||
export let popoverPositionRight: string | undefined = undefined;
|
||||
export let popoverPositionLeft: string | undefined = undefined;
|
||||
interface Props {
|
||||
reactions?: Reaction[];
|
||||
popoverPositionBottom?: string;
|
||||
popoverPositionRight?: string;
|
||||
popoverPositionLeft?: string;
|
||||
select: (reaction: Reaction) => Promise<void>;
|
||||
}
|
||||
|
||||
const {
|
||||
reactions,
|
||||
popoverPositionBottom,
|
||||
popoverPositionRight,
|
||||
popoverPositionLeft,
|
||||
select,
|
||||
}: Props = $props();
|
||||
|
||||
const availableReactions = ["👍", "👎", "😄", "🎉", "🙁", "🚀", "👀"];
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
select: Reaction;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -58,12 +63,8 @@
|
||||
)}
|
||||
<button
|
||||
class:active={Boolean(lookedUpReaction)}
|
||||
onclick={() => {
|
||||
dispatch(
|
||||
"select",
|
||||
lookedUpReaction || { emoji: reaction, authors: [] },
|
||||
);
|
||||
}}>
|
||||
onclick={() =>
|
||||
select(lookedUpReaction || { emoji: reaction, authors: [] })}>
|
||||
{reaction}
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
import type { Author } from "@bindings/cob/Author";
|
||||
import type { Reaction } from "@bindings/cob/Reaction";
|
||||
|
||||
export let reactions: Reaction[];
|
||||
export let handleReaction:
|
||||
| ((authors: Author[], reaction: string) => Promise<void>)
|
||||
| undefined;
|
||||
interface Props {
|
||||
reactions: Reaction[];
|
||||
handleReaction?: (authors: Author[], reaction: string) => Promise<void>;
|
||||
}
|
||||
|
||||
const { reactions, handleReaction }: Props = $props();
|
||||
|
||||
function authorsToTooltip(authors: Author[]) {
|
||||
return authors.map(a => a.alias ?? a.did).join("\n");
|
||||
|
||||
@@ -8,11 +8,15 @@
|
||||
import RepoHeader from "./RepoHeader.svelte";
|
||||
import Id from "./Id.svelte";
|
||||
|
||||
export let repo: RepoInfo;
|
||||
export let selfDid: string;
|
||||
export let onclick: (() => void) | undefined = undefined;
|
||||
interface Props {
|
||||
repo: RepoInfo;
|
||||
selfDid: string;
|
||||
onclick?: () => void;
|
||||
}
|
||||
|
||||
$: project = repo.payloads["xyz.radicle.project"]!;
|
||||
const { repo, selfDid, onclick }: Props = $props();
|
||||
|
||||
const project = $derived(repo.payloads["xyz.radicle.project"]!);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -3,12 +3,21 @@
|
||||
|
||||
import Icon from "./Icon.svelte";
|
||||
|
||||
export let repo: RepoInfo;
|
||||
export let selfDid: string;
|
||||
export let emphasizedTitle: boolean = true;
|
||||
export let showLabels: boolean = true;
|
||||
interface Props {
|
||||
repo: RepoInfo;
|
||||
selfDid: string;
|
||||
emphasizedTitle?: boolean;
|
||||
showLabels?: boolean;
|
||||
}
|
||||
|
||||
$: project = repo.payloads["xyz.radicle.project"]!;
|
||||
const {
|
||||
repo,
|
||||
selfDid,
|
||||
emphasizedTitle = true,
|
||||
showLabels = true,
|
||||
}: Props = $props();
|
||||
|
||||
const project = $derived(repo.payloads["xyz.radicle.project"]!);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -2,20 +2,38 @@
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import Border from "./Border.svelte";
|
||||
import type { FormEventHandler } from "svelte/elements";
|
||||
|
||||
export let name: string | undefined = undefined;
|
||||
export let placeholder: string | undefined = undefined;
|
||||
export let value: string | undefined = undefined;
|
||||
interface Props {
|
||||
name?: string;
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
autofocus?: boolean;
|
||||
autoselect?: boolean;
|
||||
disabled?: boolean;
|
||||
onSubmit?: () => void;
|
||||
onDismiss?: () => void;
|
||||
valid?: boolean;
|
||||
oninput?: FormEventHandler<HTMLInputElement>;
|
||||
}
|
||||
|
||||
export let autofocus: boolean = false;
|
||||
export let autoselect: boolean = false;
|
||||
export let disabled: boolean = false;
|
||||
export let onSubmit: (() => void) | undefined = undefined;
|
||||
export let onDismiss: (() => void) | undefined = undefined;
|
||||
export let valid: boolean = true;
|
||||
/* eslint-disable prefer-const */
|
||||
let {
|
||||
name,
|
||||
placeholder,
|
||||
value = $bindable(undefined),
|
||||
autofocus = false,
|
||||
autoselect = false,
|
||||
disabled = false,
|
||||
onSubmit,
|
||||
onDismiss,
|
||||
valid = true,
|
||||
oninput,
|
||||
}: Props = $props();
|
||||
/* eslint-enable prefer-const */
|
||||
|
||||
let inputElement: HTMLInputElement | undefined = undefined;
|
||||
let focussed = false;
|
||||
let inputElement: HTMLInputElement | undefined = $state(undefined);
|
||||
let focussed = $state(false);
|
||||
|
||||
onMount(() => {
|
||||
if (inputElement === undefined) {
|
||||
@@ -31,6 +49,7 @@
|
||||
});
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
event.stopPropagation();
|
||||
if (event.key === "Enter" && valid && onSubmit) {
|
||||
onSubmit();
|
||||
}
|
||||
@@ -72,10 +91,10 @@
|
||||
variant={valid ? (focussed ? "secondary" : "ghost") : "danger"}
|
||||
styleWidth="100%">
|
||||
<input
|
||||
on:focus={() => {
|
||||
onfocus={() => {
|
||||
focussed = true;
|
||||
}}
|
||||
on:blur={() => {
|
||||
onblur={() => {
|
||||
focussed = false;
|
||||
}}
|
||||
bind:this={inputElement}
|
||||
@@ -86,6 +105,6 @@
|
||||
bind:value
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
on:keydown|stopPropagation={handleKeydown}
|
||||
on:input />
|
||||
onkeydown={handleKeydown}
|
||||
{oninput} />
|
||||
</Border>
|
||||
|
||||
@@ -1,67 +1,89 @@
|
||||
<script lang="ts">
|
||||
import type { FormEventHandler } from "svelte/elements";
|
||||
import type { ComponentProps } from "svelte";
|
||||
|
||||
import { afterUpdate, beforeUpdate, createEventDispatcher } from "svelte";
|
||||
import { tick } from "svelte";
|
||||
|
||||
import * as utils from "@app/lib/utils";
|
||||
|
||||
import Border from "./Border.svelte";
|
||||
|
||||
export let value: string | undefined = undefined;
|
||||
export let placeholder: string | undefined = undefined;
|
||||
export let focus: boolean = false;
|
||||
export let size: "grow" | "resizable" | "fixed-height" = "grow";
|
||||
export let styleMinHeight: string | undefined = undefined;
|
||||
export let stylePadding: string = "0.75rem";
|
||||
export let borderVariant: ComponentProps<Border>["variant"] = "float";
|
||||
interface Props {
|
||||
borderVariant?: ComponentProps<typeof Border>["variant"];
|
||||
focus?: boolean;
|
||||
oninput?: FormEventHandler<HTMLTextAreaElement>;
|
||||
onkeypress?: FormEventHandler<HTMLTextAreaElement>;
|
||||
placeholder?: string;
|
||||
selectionEnd?: number;
|
||||
selectionStart?: number;
|
||||
size?: "grow" | "resizable" | "fixed-height";
|
||||
styleMinHeight?: string;
|
||||
stylePadding?: string;
|
||||
submit: () => Promise<void>;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
// Defaulting selectionStart and selectionEnd to 0, since no full support yet.
|
||||
export let selectionStart: number = 0;
|
||||
export let selectionEnd: number = 0;
|
||||
/* eslint-disable prefer-const */
|
||||
let {
|
||||
borderVariant = "float",
|
||||
focus = false,
|
||||
oninput,
|
||||
onkeypress,
|
||||
placeholder = undefined,
|
||||
// Defaulting selectionStart and selectionEnd to 0, since no full support yet.
|
||||
selectionEnd = $bindable(0),
|
||||
selectionStart = $bindable(0),
|
||||
size = "grow",
|
||||
styleMinHeight = undefined,
|
||||
stylePadding = "0.75rem",
|
||||
submit,
|
||||
value = $bindable(undefined),
|
||||
}: Props = $props();
|
||||
/* eslint-enable prefer-const */
|
||||
|
||||
let textareaElement: HTMLTextAreaElement | undefined = undefined;
|
||||
let focussed = false;
|
||||
let textareaElement: HTMLTextAreaElement | undefined = $state(undefined);
|
||||
let focussed = $state(false);
|
||||
|
||||
// We either auto-grow the textarea, or allow the user to resize it. These
|
||||
// options are mutually exclusive because a user resized textarea would
|
||||
// automatically shrink upon text input otherwise.
|
||||
$: if (textareaElement && size === "grow") {
|
||||
// React to changes to the textarea content.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||
value;
|
||||
$effect(() => {
|
||||
if (textareaElement && size === "grow") {
|
||||
// React to changes to the textarea content.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||
value;
|
||||
|
||||
// Reset height to 0px on every value change so that the textarea
|
||||
// immediately shrinks when all text is deleted.
|
||||
textareaElement.style.height = `0px`;
|
||||
textareaElement.style.height = `${textareaElement.scrollHeight}px`;
|
||||
}
|
||||
// Reset height to 0px on every value change so that the textarea
|
||||
// immediately shrinks when all text is deleted.
|
||||
textareaElement.style.height = `0px`;
|
||||
textareaElement.style.height = `${textareaElement.scrollHeight}px`;
|
||||
}
|
||||
if (textareaElement && focus) {
|
||||
textareaElement.focus();
|
||||
focussed = false;
|
||||
}
|
||||
});
|
||||
|
||||
$: if (textareaElement && focus) {
|
||||
textareaElement.focus();
|
||||
focus = false;
|
||||
}
|
||||
|
||||
beforeUpdate(() => {
|
||||
$effect.pre(() => {
|
||||
if (textareaElement) {
|
||||
({ selectionStart, selectionEnd } = textareaElement);
|
||||
}
|
||||
});
|
||||
|
||||
afterUpdate(() => {
|
||||
if (textareaElement && focus) {
|
||||
textareaElement.setSelectionRange(selectionStart, selectionEnd);
|
||||
textareaElement.focus();
|
||||
}
|
||||
$effect(() => {
|
||||
void tick().then(() => {
|
||||
if (textareaElement && focus) {
|
||||
textareaElement.setSelectionRange(selectionStart, selectionEnd);
|
||||
textareaElement.focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
submit: null;
|
||||
}>();
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
event.stopPropagation();
|
||||
const auxiliarKey = utils.isMac() ? event.metaKey : event.ctrlKey;
|
||||
if (auxiliarKey && event.key === "Enter") {
|
||||
dispatch("submit");
|
||||
void submit();
|
||||
}
|
||||
if (event.key === "Escape") {
|
||||
textareaElement?.blur();
|
||||
@@ -117,10 +139,10 @@
|
||||
? "scroll"
|
||||
: undefined}
|
||||
{placeholder}
|
||||
on:input
|
||||
on:focus={() => (focussed = true)}
|
||||
on:blur={() => (focussed = false)}
|
||||
on:keydown|stopPropagation={handleKeydown}
|
||||
on:keypress>
|
||||
{oninput}
|
||||
{onkeypress}
|
||||
onfocus={() => (focussed = true)}
|
||||
onblur={() => (focussed = false)}
|
||||
onkeydown={handleKeydown}>
|
||||
</textarea>
|
||||
</Border>
|
||||
|
||||
@@ -13,25 +13,38 @@
|
||||
import ExtendedTextarea from "./ExtendedTextarea.svelte";
|
||||
import Icon from "@app/components/Icon.svelte";
|
||||
|
||||
export let thread: {
|
||||
root: Comment;
|
||||
replies: Comment[];
|
||||
};
|
||||
export let rid: string;
|
||||
export let canEditComment: (author: string) => true | undefined;
|
||||
export let editComment:
|
||||
| ((commentId: string, body: string, embeds: Embed[]) => Promise<void>)
|
||||
| undefined;
|
||||
export let createReply:
|
||||
| ((commentId: string, comment: string, embeds: Embed[]) => Promise<void>)
|
||||
| undefined;
|
||||
export let reactOnComment:
|
||||
| ((
|
||||
commentId: string,
|
||||
authors: Author[],
|
||||
reaction: string,
|
||||
) => Promise<void>)
|
||||
| undefined;
|
||||
interface Props {
|
||||
thread: {
|
||||
root: Comment;
|
||||
replies: Comment[];
|
||||
};
|
||||
rid: string;
|
||||
canEditComment: (author: string) => true | undefined;
|
||||
editComment?: (
|
||||
commentId: string,
|
||||
body: string,
|
||||
embeds: Embed[],
|
||||
) => Promise<void>;
|
||||
createReply?: (
|
||||
commentId: string,
|
||||
comment: string,
|
||||
embeds: Embed[],
|
||||
) => Promise<void>;
|
||||
reactOnComment?: (
|
||||
commentId: string,
|
||||
authors: Author[],
|
||||
reaction: string,
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
const {
|
||||
thread,
|
||||
rid,
|
||||
canEditComment,
|
||||
editComment,
|
||||
createReply,
|
||||
reactOnComment,
|
||||
}: Props = $props();
|
||||
|
||||
async function toggleReply() {
|
||||
showReplyForm = !showReplyForm;
|
||||
@@ -46,16 +59,16 @@
|
||||
});
|
||||
}
|
||||
|
||||
let showReplyForm = false;
|
||||
let submitInProgress = false;
|
||||
let showReplyForm = $state(false);
|
||||
let submitInProgress = $state(false);
|
||||
|
||||
$: root = thread.root;
|
||||
$: replies = thread.replies;
|
||||
|
||||
$: style =
|
||||
const root = $derived(thread.root);
|
||||
const replies = $derived(thread.replies);
|
||||
const style = $derived(
|
||||
replies.length > 0
|
||||
? "--local-clip-path: var(--2px-top-corner-fill)"
|
||||
: "--local-clip-path: var(--2px-corner-fill)";
|
||||
: "--local-clip-path: var(--2px-corner-fill)",
|
||||
);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -135,8 +148,8 @@
|
||||
submitCaption="Reply"
|
||||
focus
|
||||
stylePadding="0.5rem 0.75rem"
|
||||
on:close={() => (showReplyForm = false)}
|
||||
on:submit={async ({ detail: { comment, embeds } }) => {
|
||||
close={() => (showReplyForm = false)}
|
||||
submit={async ({ comment, embeds }) => {
|
||||
try {
|
||||
submitInProgress = true;
|
||||
await createReply(
|
||||
|
||||
+1
-1
@@ -153,7 +153,7 @@ export const patchStatusBackgroundColor: Record<
|
||||
merged: "var(--color-fill-delegate)",
|
||||
};
|
||||
|
||||
export function authorForNodeId(author: Author): ComponentProps<NodeId> {
|
||||
export function authorForNodeId(author: Author): ComponentProps<typeof NodeId> {
|
||||
return { publicKey: publicKeyFromDid(author.did), alias: author.alias };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<script lang="ts">
|
||||
import Icon from "@app/components/Icon.svelte";
|
||||
|
||||
export let error: string;
|
||||
export let hint: string | undefined = undefined;
|
||||
interface Props {
|
||||
error: string;
|
||||
hint?: string;
|
||||
}
|
||||
|
||||
const { error, hint }: Props = $props();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -9,8 +9,12 @@
|
||||
import RepoCard from "@app/components/RepoCard.svelte";
|
||||
import NodeId from "@app/components/NodeId.svelte";
|
||||
|
||||
export let repos: RepoInfo[];
|
||||
export let config: Config;
|
||||
interface Props {
|
||||
repos: RepoInfo[];
|
||||
config: Config;
|
||||
}
|
||||
|
||||
const { repos, config }: Props = $props();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -21,13 +21,17 @@
|
||||
import Textarea from "@app/components/Textarea.svelte";
|
||||
import Markdown from "@app/components/Markdown.svelte";
|
||||
|
||||
export let repo: RepoInfo;
|
||||
export let issues: Issue[];
|
||||
export let config: Config;
|
||||
interface Props {
|
||||
repo: RepoInfo;
|
||||
issues: Issue[];
|
||||
config: Config;
|
||||
}
|
||||
|
||||
let title: string = "";
|
||||
let description: string = "";
|
||||
let preview: boolean = false;
|
||||
const { repo, issues, config }: Props = $props();
|
||||
|
||||
let title: string = $state("");
|
||||
let description: string = $state("");
|
||||
let preview: boolean = $state(false);
|
||||
const announce = false;
|
||||
|
||||
const labels: string[] = [];
|
||||
@@ -47,7 +51,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
$: project = repo.payloads["xyz.radicle.project"]!;
|
||||
const project = $derived(repo.payloads["xyz.radicle.project"]!);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -176,6 +180,7 @@
|
||||
placeholder="Description"
|
||||
bind:value={description}
|
||||
size="fixed-height"
|
||||
submit={createIssue}
|
||||
styleMinHeight="100%" />
|
||||
{/if}
|
||||
<div
|
||||
|
||||
+42
-33
@@ -35,48 +35,57 @@
|
||||
|
||||
import Layout from "./Layout.svelte";
|
||||
|
||||
export let repo: RepoInfo;
|
||||
export let issue: Issue;
|
||||
export let issues: Issue[];
|
||||
export let config: Config;
|
||||
interface Props {
|
||||
repo: RepoInfo;
|
||||
issue: Issue;
|
||||
issues: Issue[];
|
||||
config: Config;
|
||||
}
|
||||
|
||||
let topLevelReplyOpen = false;
|
||||
let editingTitle = false;
|
||||
let updatedTitle: string = issue.title;
|
||||
/* eslint-disable prefer-const */
|
||||
let { repo, issue, issues: initialIssues, config }: Props = $props();
|
||||
/* eslint-enable prefer-const */
|
||||
|
||||
const issues = $state(initialIssues);
|
||||
let topLevelReplyOpen = $state(false);
|
||||
let editingTitle = $state(false);
|
||||
let updatedTitle = $state(issue.title);
|
||||
|
||||
// The view doesn't get destroyed when we switch between different issues in
|
||||
// the sidebar and because of that the top-level state gets retained when the
|
||||
// issue changes. This reactive statement makes sure we always load the new
|
||||
// issue and reset the state to defaults.
|
||||
let issueId = issue.id;
|
||||
$: if (issueId !== issue.id) {
|
||||
issueId = issue.id;
|
||||
topLevelReplyOpen = false;
|
||||
editingTitle = false;
|
||||
updatedTitle = issue.title;
|
||||
void loadActivity();
|
||||
}
|
||||
$effect(() => {
|
||||
if (issueId !== issue.id) {
|
||||
issueId = issue.id;
|
||||
topLevelReplyOpen = false;
|
||||
editingTitle = false;
|
||||
updatedTitle = issue.title;
|
||||
void loadActivity();
|
||||
}
|
||||
});
|
||||
|
||||
$: project = repo.payloads["xyz.radicle.project"]!;
|
||||
const project = $derived(repo.payloads["xyz.radicle.project"]!);
|
||||
const issueBody = $derived(issue.discussion[0]);
|
||||
const threads = $derived(
|
||||
issue.discussion
|
||||
.filter(
|
||||
comment =>
|
||||
(comment.id !== issueBody.id && !comment.replyTo) ||
|
||||
comment.replyTo === issueBody.id,
|
||||
)
|
||||
.map(thread => {
|
||||
return {
|
||||
root: thread,
|
||||
replies: issue.discussion
|
||||
.filter(comment => comment.replyTo === thread.id)
|
||||
.sort((a, b) => a.edits[0].timestamp - b.edits[0].timestamp),
|
||||
};
|
||||
}, []),
|
||||
);
|
||||
|
||||
$: issueBody = issue.discussion[0];
|
||||
|
||||
$: threads = issue.discussion
|
||||
.filter(
|
||||
comment =>
|
||||
(comment.id !== issueBody.id && !comment.replyTo) ||
|
||||
comment.replyTo === issueBody.id,
|
||||
)
|
||||
.map(thread => {
|
||||
return {
|
||||
root: thread,
|
||||
replies: issue.discussion
|
||||
.filter(comment => comment.replyTo === thread.id)
|
||||
.sort((a, b) => a.edits[0].timestamp - b.edits[0].timestamp),
|
||||
};
|
||||
}, []);
|
||||
|
||||
let activity: Operation[];
|
||||
let activity = $state<Operation[]>([]);
|
||||
|
||||
async function loadActivity() {
|
||||
activity = await invoke("activity_by_id", {
|
||||
|
||||
@@ -17,12 +17,16 @@
|
||||
import RepoHeader from "@app/components/RepoHeader.svelte";
|
||||
import Button from "@app/components/Button.svelte";
|
||||
|
||||
export let repo: RepoInfo;
|
||||
export let issues: Issue[];
|
||||
export let config: Config;
|
||||
export let status: IssueStatus;
|
||||
interface Props {
|
||||
repo: RepoInfo;
|
||||
issues: Issue[];
|
||||
config: Config;
|
||||
status: IssueStatus;
|
||||
}
|
||||
|
||||
$: project = repo.payloads["xyz.radicle.project"]!;
|
||||
const { repo, issues, config, status }: Props = $props();
|
||||
|
||||
const project = $derived(repo.payloads["xyz.radicle.project"]!);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -7,20 +7,31 @@
|
||||
import Icon from "@app/components/Icon.svelte";
|
||||
import NakedButton from "@app/components/NakedButton.svelte";
|
||||
|
||||
export let children: Snippet;
|
||||
export let breadcrumbs: Snippet;
|
||||
export let headerCenter: Snippet | undefined = undefined;
|
||||
export let sidebar: Snippet;
|
||||
export let loadMore: (() => Promise<void>) | undefined = undefined;
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
breadcrumbs: Snippet;
|
||||
headerCenter?: Snippet;
|
||||
sidebar: Snippet;
|
||||
loadMore?: () => Promise<void>;
|
||||
}
|
||||
|
||||
let hidden = false;
|
||||
let listElement: HTMLElement;
|
||||
const {
|
||||
children,
|
||||
breadcrumbs,
|
||||
headerCenter = undefined,
|
||||
sidebar,
|
||||
loadMore = undefined,
|
||||
}: Props = $props();
|
||||
|
||||
let hidden = $state(false);
|
||||
let listElement: HTMLElement | undefined = $state();
|
||||
let loading = false;
|
||||
|
||||
onMount(() => {
|
||||
if (listElement && loadMore) {
|
||||
listElement.addEventListener("scroll", async () => {
|
||||
if (
|
||||
listElement &&
|
||||
listElement.scrollTop + listElement.clientHeight >=
|
||||
listElement.scrollHeight - 600 &&
|
||||
loading === false
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
formatTimestamp,
|
||||
patchStatusColor,
|
||||
} from "@app/lib/utils";
|
||||
import { invoke } from "@app/lib/invoke";
|
||||
|
||||
import Border from "@app/components/Border.svelte";
|
||||
import CopyableId from "@app/components/CopyableId.svelte";
|
||||
@@ -21,22 +20,17 @@
|
||||
import Markdown from "@app/components/Markdown.svelte";
|
||||
import Id from "@app/components/Id.svelte";
|
||||
|
||||
export let repo: RepoInfo;
|
||||
export let patch: Patch;
|
||||
export let patches: Patch[];
|
||||
export let revisions: Revision[];
|
||||
export let config: Config;
|
||||
interface Props {
|
||||
repo: RepoInfo;
|
||||
patch: Patch;
|
||||
patches: Patch[];
|
||||
revisions: Revision[];
|
||||
config: Config;
|
||||
}
|
||||
|
||||
$: void invoke("get_diff", {
|
||||
rid: repo.rid,
|
||||
options: {
|
||||
base: revisions[0].base,
|
||||
head: revisions[0].head,
|
||||
unified: 10,
|
||||
},
|
||||
}).then(console.log);
|
||||
const { repo, patch, patches, revisions, config }: Props = $props();
|
||||
|
||||
$: project = repo.payloads["xyz.radicle.project"]!;
|
||||
const project = $derived(repo.payloads["xyz.radicle.project"]!);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -16,14 +16,24 @@
|
||||
import PatchTeaser from "@app/components/PatchTeaser.svelte";
|
||||
import RepoHeader from "@app/components/RepoHeader.svelte";
|
||||
|
||||
export let repo: RepoInfo;
|
||||
export let patches: PaginatedQuery<Patch[]>;
|
||||
export let config: Config;
|
||||
export let status: PatchStatus | undefined = undefined;
|
||||
interface Props {
|
||||
repo: RepoInfo;
|
||||
patches: PaginatedQuery<Patch[]>;
|
||||
config: Config;
|
||||
status?: PatchStatus;
|
||||
}
|
||||
|
||||
$: items = patches.content;
|
||||
$: more = patches.more;
|
||||
$: cursor = patches.cursor;
|
||||
const { repo, patches, config, status }: Props = $props();
|
||||
|
||||
let items = $state(patches.content);
|
||||
let cursor = patches.cursor;
|
||||
let more = patches.more;
|
||||
|
||||
$effect(() => {
|
||||
items = patches.content;
|
||||
cursor = patches.cursor;
|
||||
more = patches.more;
|
||||
});
|
||||
|
||||
async function loadMore() {
|
||||
if (more) {
|
||||
@@ -40,7 +50,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: project = repo.payloads["xyz.radicle.project"]!;
|
||||
const project = $derived(repo.payloads["xyz.radicle.project"]!);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user