mirror of
https://seed.radicle.dev/rad:z4D5UCArafTzTQpDZNQRuqswh3ury.git
synced 2026-10-18 19:10:45 +08:00
Add repo fuzzy finder to home screen
This commit is contained in:
Generated
+25
@@ -38,9 +38,11 @@
|
|||||||
"eslint-config-prettier": "^10.1.1",
|
"eslint-config-prettier": "^10.1.1",
|
||||||
"eslint-plugin-svelte": "^3.3.2",
|
"eslint-plugin-svelte": "^3.3.2",
|
||||||
"execa": "^9.5.2",
|
"execa": "^9.5.2",
|
||||||
|
"fuzzysort": "^3.1.0",
|
||||||
"get-port": "^7.1.0",
|
"get-port": "^7.1.0",
|
||||||
"happy-dom": "^17.4.4",
|
"happy-dom": "^17.4.4",
|
||||||
"hast-util-to-dom": "^4.0.1",
|
"hast-util-to-dom": "^4.0.1",
|
||||||
|
"keyux": "^0.11.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"marked": "^15.0.7",
|
"marked": "^15.0.7",
|
||||||
"marked-emoji": "^2.0.0",
|
"marked-emoji": "^2.0.0",
|
||||||
@@ -2830,6 +2832,13 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fuzzysort": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/get-intrinsic": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
@@ -3260,6 +3269,22 @@
|
|||||||
"katex": "cli.js"
|
"katex": "cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/keyux": {
|
||||||
|
"version": "0.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/keyux/-/keyux-0.11.1.tgz",
|
||||||
|
"integrity": "sha512-FIPQNnbtYuGdVU/vL0X9fcJwtkuzOwhzceUezMZMgTwGDNA2tmO4A/uA4tt1hdb0Cu3S3o+dNm5ifA+/reNX2A==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.0.0 || >=20.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||||
|
|||||||
@@ -53,9 +53,11 @@
|
|||||||
"eslint-config-prettier": "^10.1.1",
|
"eslint-config-prettier": "^10.1.1",
|
||||||
"eslint-plugin-svelte": "^3.3.2",
|
"eslint-plugin-svelte": "^3.3.2",
|
||||||
"execa": "^9.5.2",
|
"execa": "^9.5.2",
|
||||||
|
"fuzzysort": "^3.1.0",
|
||||||
"get-port": "^7.1.0",
|
"get-port": "^7.1.0",
|
||||||
"happy-dom": "^17.4.4",
|
"happy-dom": "^17.4.4",
|
||||||
"hast-util-to-dom": "^4.0.1",
|
"hast-util-to-dom": "^4.0.1",
|
||||||
|
"keyux": "^0.11.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"marked": "^15.0.7",
|
"marked": "^15.0.7",
|
||||||
"marked-emoji": "^2.0.0",
|
"marked-emoji": "^2.0.0",
|
||||||
|
|||||||
@@ -12,9 +12,10 @@
|
|||||||
repo: RepoInfo;
|
repo: RepoInfo;
|
||||||
selfDid: string;
|
selfDid: string;
|
||||||
onclick?: () => void;
|
onclick?: () => void;
|
||||||
|
focussed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { repo, selfDid, onclick }: Props = $props();
|
const { repo, selfDid, onclick, focussed }: Props = $props();
|
||||||
|
|
||||||
const project = $derived(repo.payloads["xyz.radicle.project"]!);
|
const project = $derived(repo.payloads["xyz.radicle.project"]!);
|
||||||
</script>
|
</script>
|
||||||
@@ -34,7 +35,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<Border
|
<Border
|
||||||
variant="ghost"
|
variant={focussed ? "secondary" : "ghost"}
|
||||||
styleCursor="pointer"
|
styleCursor="pointer"
|
||||||
styleWidth="100%"
|
styleWidth="100%"
|
||||||
stylePadding="8px 12px"
|
stylePadding="8px 12px"
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
onDismiss?: () => void;
|
onDismiss?: () => void;
|
||||||
valid?: boolean;
|
valid?: boolean;
|
||||||
oninput?: FormEventHandler<HTMLInputElement>;
|
oninput?: FormEventHandler<HTMLInputElement>;
|
||||||
|
keyShortcuts?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable prefer-const */
|
/* eslint-disable prefer-const */
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
onDismiss,
|
onDismiss,
|
||||||
valid = true,
|
valid = true,
|
||||||
oninput,
|
oninput,
|
||||||
|
keyShortcuts,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
/* eslint-enable prefer-const */
|
/* eslint-enable prefer-const */
|
||||||
|
|
||||||
@@ -93,6 +95,7 @@
|
|||||||
variant={valid ? (focussed ? "secondary" : "ghost") : "danger"}
|
variant={valid ? (focussed ? "secondary" : "ghost") : "danger"}
|
||||||
styleWidth="100%">
|
styleWidth="100%">
|
||||||
<input
|
<input
|
||||||
|
aria-keyshortcuts={keyShortcuts}
|
||||||
onfocus={() => {
|
onfocus={() => {
|
||||||
focussed = true;
|
focussed = true;
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { mount } from "svelte";
|
import { mount } from "svelte";
|
||||||
|
import { hotkeyKeyUX, hotkeyMacCompat, startKeyUX } from "keyux";
|
||||||
import App from "./App.svelte";
|
import App from "./App.svelte";
|
||||||
|
|
||||||
const app = mount(App, { target: document.body });
|
const app = mount(App, { target: document.body });
|
||||||
|
|
||||||
|
const mac = hotkeyMacCompat();
|
||||||
|
startKeyUX(window, [hotkeyKeyUX([mac])]);
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|||||||
+67
-11
@@ -6,17 +6,22 @@
|
|||||||
import type { RepoCount } from "@bindings/repo/RepoCount";
|
import type { RepoCount } from "@bindings/repo/RepoCount";
|
||||||
import type { RepoInfo } from "@bindings/repo/RepoInfo";
|
import type { RepoInfo } from "@bindings/repo/RepoInfo";
|
||||||
|
|
||||||
|
import fuzzysort from "fuzzysort";
|
||||||
|
|
||||||
import * as router from "@app/lib/router";
|
import * as router from "@app/lib/router";
|
||||||
import { didFromPublicKey } from "@app/lib/utils";
|
import { didFromPublicKey, modifierKey } from "@app/lib/utils";
|
||||||
import { dynamicInterval } from "@app/lib/interval";
|
import { dynamicInterval } from "@app/lib/interval";
|
||||||
import { invoke } from "@app/lib/invoke";
|
import { invoke } from "@app/lib/invoke";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
import Border from "@app/components/Border.svelte";
|
||||||
import CopyableId from "@app/components/CopyableId.svelte";
|
import CopyableId from "@app/components/CopyableId.svelte";
|
||||||
import HomeSidebar from "@app/components/HomeSidebar.svelte";
|
import HomeSidebar from "@app/components/HomeSidebar.svelte";
|
||||||
|
import Icon from "@app/components/Icon.svelte";
|
||||||
import Layout from "@app/views/repo/Layout.svelte";
|
import Layout from "@app/views/repo/Layout.svelte";
|
||||||
import Onboarding from "@app/views/home/Onboarding.svelte";
|
import Onboarding from "@app/views/home/Onboarding.svelte";
|
||||||
import RepoCard from "@app/components/RepoCard.svelte";
|
import RepoCard from "@app/components/RepoCard.svelte";
|
||||||
|
import TextInput from "@app/components/TextInput.svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
activeTab?: HomeReposTab;
|
activeTab?: HomeReposTab;
|
||||||
@@ -63,6 +68,22 @@
|
|||||||
invoke<Config>("config"),
|
invoke<Config>("config"),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let searchInput = $state("");
|
||||||
|
|
||||||
|
const searchableRepos = $derived(
|
||||||
|
repos
|
||||||
|
.flatMap(r => {
|
||||||
|
if (r.payloads["xyz.radicle.project"]) {
|
||||||
|
return { repo: r, name: r.payloads["xyz.radicle.project"].data.name };
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((item): item is NonNullable<typeof item> => item !== undefined),
|
||||||
|
);
|
||||||
|
|
||||||
|
const searchResults = $derived(
|
||||||
|
fuzzysort.go(searchInput, searchableRepos, { key: "name", all: true }),
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -82,7 +103,6 @@
|
|||||||
padding-right: 1.5rem;
|
padding-right: 1.5rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -100,24 +120,60 @@
|
|||||||
{notificationCount} />
|
{notificationCount} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="header">Repositories</div>
|
<div class="global-flex" style:margin-bottom="0.5rem">
|
||||||
|
<div class="header">Repositories</div>
|
||||||
|
<div style:margin-left="auto">
|
||||||
|
<TextInput
|
||||||
|
onSubmit={async () => {
|
||||||
|
if (searchResults.length === 1) {
|
||||||
|
await router.push({
|
||||||
|
resource: "repo.issues",
|
||||||
|
rid: searchResults[0].obj.repo.rid,
|
||||||
|
status: "open",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onDismiss={() => {
|
||||||
|
searchInput = "";
|
||||||
|
}}
|
||||||
|
placeholder={`Filter repositories ${modifierKey()} + f`}
|
||||||
|
keyShortcuts="ctrl+f"
|
||||||
|
bind:value={searchInput} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{#if repos.length > 0}
|
{#if repos.length > 0}
|
||||||
<div class="repo-grid">
|
{#if searchResults.length > 0}
|
||||||
{#each repos as repo}
|
<div class="repo-grid">
|
||||||
{#if repo.payloads["xyz.radicle.project"]}
|
{#each searchResults as result}
|
||||||
<RepoCard
|
<RepoCard
|
||||||
{repo}
|
focussed={searchResults.length === 1}
|
||||||
|
repo={result.obj.repo}
|
||||||
selfDid={didFromPublicKey(config.publicKey)}
|
selfDid={didFromPublicKey(config.publicKey)}
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
void router.push({
|
void router.push({
|
||||||
resource: "repo.issues",
|
resource: "repo.issues",
|
||||||
rid: repo.rid,
|
rid: result.obj.repo.rid,
|
||||||
status: "open",
|
status: "open",
|
||||||
});
|
});
|
||||||
}} />
|
}} />
|
||||||
{/if}
|
{/each}
|
||||||
{/each}
|
</div>
|
||||||
</div>
|
{:else}
|
||||||
|
<Border
|
||||||
|
variant="ghost"
|
||||||
|
styleAlignItems="center"
|
||||||
|
styleJustifyContent="center">
|
||||||
|
<div
|
||||||
|
class="global-flex"
|
||||||
|
style:height="74px"
|
||||||
|
style:justify-content="center">
|
||||||
|
<div class="txt-missing txt-small global-flex" style:gap="0.25rem">
|
||||||
|
<Icon name="none" />
|
||||||
|
No matching repositories.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Border>
|
||||||
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<Onboarding {reload} />
|
<Onboarding {reload} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
Reference in New Issue
Block a user