diff --git a/src/components/IssueSecondColumn.svelte b/src/components/IssueSecondColumn.svelte index fd826fc43..9dccc34b0 100644 --- a/src/components/IssueSecondColumn.svelte +++ b/src/components/IssueSecondColumn.svelte @@ -4,9 +4,10 @@ import type { IssueStatus } from "@app/views/repo/router"; import capitalize from "lodash/capitalize"; + import fuzzysort from "fuzzysort"; import * as router from "@app/lib/router"; - import { issueStatusColor } from "@app/lib/utils"; + import { issueStatusColor, modifierKey } from "@app/lib/utils"; import Border from "./Border.svelte"; import DropdownList from "./DropdownList.svelte"; @@ -14,8 +15,10 @@ import Icon from "./Icon.svelte"; import IssueTeaser from "@app/components/IssueTeaser.svelte"; import Link from "./Link.svelte"; + import NakedButton from "./NakedButton.svelte"; import OutlineButton from "./OutlineButton.svelte"; import Popover, { closeFocused } from "./Popover.svelte"; + import TextInput from "./TextInput.svelte"; const activeRouteStore = router.activeRouteStore; @@ -34,12 +37,38 @@ /* eslint-enable prefer-const */ const project = $derived(repo.payloads["xyz.radicle.project"]!); + + let showFilters: boolean = $state(false); + let searchInput = $state(""); + + const searchableIssues = $derived( + issues + .flatMap(i => { + return { + issue: i, + labels: i.labels.join(" "), + assignees: i.assignees + .map(a => { + return a.alias ?? ""; + }) + .join(" "), + author: i.author.alias ?? "", + }; + }) + .filter((item): item is NonNullable => item !== undefined), + ); + + const searchResults = $derived( + fuzzysort.go(searchInput, searchableIssues, { + keys: ["issue.title", "labels", "assignees", "author"], + all: true, + }), + ); {#snippet issueSnippet()} -
-
- {#if issue.state.status === "open"} - - {:else} - - {/if} -
-
- -
- - opened - - {formatTimestamp(issue.timestamp)} -
-
-
- -
- {#if !compact} - {#each issue.labels as label} -
-{/snippet} - -{#if focussed} - - {@render issueSnippet()} - -{:else}
{ void push({ resource: "repo.issue", rid, issue: issue.id, status }); }}> - {@render issueSnippet()} +
+
+ {#if issue.state.status === "open"} + + {:else} + + {/if} +
+
+ +
+ + opened + + {formatTimestamp(issue.timestamp)} +
+
+
+ +
+ {#if !compact} + {#each issue.labels as label} +
+{/snippet} + +{#if focussed} + + {@render issueSnippet()} + +{:else} + {@render issueSnippet()} {/if} diff --git a/src/components/NakedButton.svelte b/src/components/NakedButton.svelte index 45a730b87..4dc73438a 100644 --- a/src/components/NakedButton.svelte +++ b/src/components/NakedButton.svelte @@ -10,6 +10,7 @@ styleHeight?: string; stylePadding?: string; active?: boolean; + keyShortcuts?: string; } const { @@ -21,6 +22,7 @@ styleHeight = "2rem", stylePadding = "0 8px", active = false, + keyShortcuts, }: Props = $props(); const style = $derived( @@ -239,6 +241,7 @@ class:active onclick={!disabled ? onclick : undefined} {title} + aria-keyshortcuts={keyShortcuts} role="button" tabindex="0" {style} diff --git a/src/components/PatchTeaser.svelte b/src/components/PatchTeaser.svelte index b23fa2942..d32ea7d60 100644 --- a/src/components/PatchTeaser.svelte +++ b/src/components/PatchTeaser.svelte @@ -52,6 +52,7 @@ cursor: pointer; font-size: var(--font-size-regular); word-break: break-word; + width: 100%; } .selected { background-color: var(--color-fill-float-hover); @@ -75,61 +76,6 @@ {#snippet patchSnippet()} -
-
- -
-
- -
- - opened - - {formatTimestamp(patch.timestamp)} -
-
-
- -
- {#if !compact} - {#await cachedDiffStats(rid, patch.base, patch.head) then stats} - - {/await} - - {#each patch.labels as label} -
-{/snippet} - -{#if focussed} - - {@render patchSnippet()} - -{:else}
{ if (loadPatch) { await loadPatch(patch.id); @@ -150,6 +97,57 @@ }); } }}> - {@render patchSnippet()} +
+
+ +
+
+ +
+ + opened + + {formatTimestamp(patch.timestamp)} +
+
+
+ +
+ {#if !compact} + {#await cachedDiffStats(rid, patch.base, patch.head) then stats} + + {/await} + + {#each patch.labels as label} +
+{/snippet} + +{#if focussed} + + {@render patchSnippet()} + +{:else} + {@render patchSnippet()} {/if} diff --git a/src/views/repo/Issues.svelte b/src/views/repo/Issues.svelte index a975e8cab..823e3fd8b 100644 --- a/src/views/repo/Issues.svelte +++ b/src/views/repo/Issues.svelte @@ -149,7 +149,7 @@ {status} /> {/each} - {#if issues.length === 0} + {#if searchResults.length === 0}
- {#if status === "all"} - No issues. + {#if issues.length > 0 && searchResults.length === 0} + No matching issues. {:else} - No {status} issues. + No {status === "all" ? "" : status} issues. {/if}
diff --git a/src/views/repo/Patch.svelte b/src/views/repo/Patch.svelte index 478acbc88..ed74bb444 100644 --- a/src/views/repo/Patch.svelte +++ b/src/views/repo/Patch.svelte @@ -11,8 +11,11 @@ import type { Revision } from "@bindings/cob/patch/Revision"; import capitalize from "lodash/capitalize"; + import fuzzysort from "fuzzysort"; import * as roles from "@app/lib/roles"; + import * as router from "@app/lib/router"; + import { DEFAULT_TAKE } from "./router"; import { announce } from "@app/components/AnnounceSwitch.svelte"; import { formatOid, @@ -20,6 +23,7 @@ patchStatusColor, } from "@app/lib/utils"; import { invoke } from "@app/lib/invoke"; + import { modifierKey } from "@app/lib/utils"; import { nodeRunning } from "@app/lib/events"; import AssigneeInput from "@app/components/AssigneeInput.svelte"; @@ -32,6 +36,7 @@ import LabelInput from "@app/components/LabelInput.svelte"; import Layout from "./Layout.svelte"; import Link from "@app/components/Link.svelte"; + import NakedButton from "@app/components/NakedButton.svelte"; import OutlineButton from "@app/components/OutlineButton.svelte"; import PatchStateBadge from "@app/components/PatchStateBadge.svelte"; import PatchStateButton from "@app/components/PatchStateButton.svelte"; @@ -190,18 +195,22 @@ } } - async function loadMoreTeasers() { + async function loadMoreTeasers(all: boolean = false) { if (more) { const p = await invoke>("list_patches", { rid: repo.rid, status, - skip: cursor + 20, - take: 20, + skip: cursor + DEFAULT_TAKE, + take: all ? undefined : DEFAULT_TAKE, }); cursor = p.cursor; more = p.more; - patchTeasers = [...patchTeasers, ...p.content]; + if (all) { + patchTeasers = p.content; + } else { + patchTeasers = [...patchTeasers, ...p.content]; + } } } @@ -222,6 +231,7 @@ invoke>("list_patches", { rid: repo.rid, status, + take: DEFAULT_TAKE, }), ]); } @@ -244,6 +254,7 @@ patches = await invoke>("list_patches", { rid: repo.rid, status: filter, + take: DEFAULT_TAKE, }); status = filter; } catch (error) { @@ -260,6 +271,34 @@ }); }) as Revision; } + + let showFilters: boolean = $state(false); + let loading: boolean = $state(false); + let searchInput = $state(""); + + const searchablePatches = $derived( + patches.content + .flatMap(i => { + return { + patch: i, + labels: i.labels.join(" "), + assignees: i.assignees + .map(a => { + return a.alias ?? ""; + }) + .join(" "), + author: i.author.alias ?? "", + }; + }) + .filter((item): item is NonNullable => item !== undefined), + ); + + const searchResults = $derived( + fuzzysort.go(searchInput, searchablePatches, { + keys: ["patch.title", "labels", "assignees", "author"], + all: true, + }), + );