This commit is contained in:
Shusui MOYATANI
2023-03-07 02:21:26 +09:00
parent f79365dbc4
commit f2b1360fd5
19 changed files with 137 additions and 71 deletions

View File

@@ -1,11 +1,7 @@
# nostRabbit 🐰
# Rabbit 🐰
A nostr client like TweetDeck powered by SolidJS.
## Supported features
- [ ] Posting a new text note. (NIP-01)
## 使い方
1. NIP-07に対応したブラウザ拡張機能のインストールが事前に必要です

58
package-lock.json generated
View File

@@ -19,7 +19,7 @@
"@types/lodash": "^4.14.191",
"heroicons": "^2.0.15",
"lodash": "^4.17.21",
"nostr-tools": "^1.3.2",
"nostr-tools": "^1.7.4",
"solid-js": "^1.6.9",
"tailwindcss": "^3.2.4",
"zod": "^3.20.6"
@@ -1187,9 +1187,9 @@
}
},
"node_modules/@noble/hashes": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-0.5.9.tgz",
"integrity": "sha512-7lN1Qh6d8DUGmfN36XRsbN/WcGIPNtTGhkw26vWId/DlCIGsYJJootTtPGghTLcn/AaXPx2Q0b3cacrwXa7OVw=="
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.0.0.tgz",
"integrity": "sha512-DZVbtY62kc3kkBtMHqwCOfXrT/hnoORy5BJ4+HU1IR59X0KWAOqsfzQPcUl/lQLlG7qXbe/fZ3r/emxtAl+sqg=="
},
"node_modules/@noble/secp256k1": {
"version": "1.7.1",
@@ -5889,15 +5889,16 @@
}
},
"node_modules/nostr-tools": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-1.3.2.tgz",
"integrity": "sha512-LKVfGDkTSzNFSIUHhCDKHogfFaToKrqzUAXJOJhnYKvBws1/XW52xOYZlfYnY9jc/7C+ylq8njFIL5c5e6ObjA==",
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-1.7.4.tgz",
"integrity": "sha512-YowDJ+S3UW9KCYPDZfZXXMITrJSMjiCmFOK5HohyKkg+w6EipFUTkFRBPRA2BTLXO/qw8gukKXfL0B7Dv3jtcQ==",
"dependencies": {
"@noble/hashes": "^0.5.7",
"@noble/secp256k1": "^1.7.0",
"@noble/hashes": "1.0.0",
"@noble/secp256k1": "^1.7.1",
"@scure/base": "^1.1.1",
"@scure/bip32": "^1.1.1",
"@scure/bip39": "^1.1.0"
"@scure/bip32": "^1.1.5",
"@scure/bip39": "^1.1.1",
"prettier": "^2.8.4"
}
},
"node_modules/npm-normalize-package-bin": {
@@ -6910,10 +6911,9 @@
}
},
"node_modules/prettier": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz",
"integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==",
"dev": true,
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz",
"integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==",
"bin": {
"prettier": "bin-prettier.js"
},
@@ -9337,9 +9337,9 @@
}
},
"@noble/hashes": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-0.5.9.tgz",
"integrity": "sha512-7lN1Qh6d8DUGmfN36XRsbN/WcGIPNtTGhkw26vWId/DlCIGsYJJootTtPGghTLcn/AaXPx2Q0b3cacrwXa7OVw=="
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.0.0.tgz",
"integrity": "sha512-DZVbtY62kc3kkBtMHqwCOfXrT/hnoORy5BJ4+HU1IR59X0KWAOqsfzQPcUl/lQLlG7qXbe/fZ3r/emxtAl+sqg=="
},
"@noble/secp256k1": {
"version": "1.7.1",
@@ -12732,15 +12732,16 @@
"dev": true
},
"nostr-tools": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-1.3.2.tgz",
"integrity": "sha512-LKVfGDkTSzNFSIUHhCDKHogfFaToKrqzUAXJOJhnYKvBws1/XW52xOYZlfYnY9jc/7C+ylq8njFIL5c5e6ObjA==",
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-1.7.4.tgz",
"integrity": "sha512-YowDJ+S3UW9KCYPDZfZXXMITrJSMjiCmFOK5HohyKkg+w6EipFUTkFRBPRA2BTLXO/qw8gukKXfL0B7Dv3jtcQ==",
"requires": {
"@noble/hashes": "^0.5.7",
"@noble/secp256k1": "^1.7.0",
"@noble/hashes": "1.0.0",
"@noble/secp256k1": "^1.7.1",
"@scure/base": "^1.1.1",
"@scure/bip32": "^1.1.1",
"@scure/bip39": "^1.1.0"
"@scure/bip32": "^1.1.5",
"@scure/bip39": "^1.1.1",
"prettier": "^2.8.4"
}
},
"npm-normalize-package-bin": {
@@ -13483,10 +13484,9 @@
"dev": true
},
"prettier": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz",
"integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==",
"dev": true
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz",
"integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw=="
},
"prettier-linter-helpers": {
"version": "1.0.0",

View File

@@ -57,7 +57,7 @@
"@types/lodash": "^4.14.191",
"heroicons": "^2.0.15",
"lodash": "^4.17.21",
"nostr-tools": "^1.3.2",
"nostr-tools": "^1.7.4",
"solid-js": "^1.6.9",
"tailwindcss": "^3.2.4",
"zod": "^3.20.6"

View File

@@ -6,6 +6,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/solid-query';
import Home from '@/pages/Home';
import NotFound from '@/pages/NotFound';
import Hello from '@/pages/Hello';
const queryClient = new QueryClient({});
@@ -20,6 +21,7 @@ const App: Component = () => (
<QueryClientProvider client={queryClient}>
<Routes>
<Route path="/" element={() => <Home />} />
<Route path="/hello" element={() => <Hello />} />
<Route path="/*" element={() => <NotFound />} />
</Routes>
</QueryClientProvider>

View File

@@ -1,6 +1,5 @@
import { createMemo } from 'solid-js';
import { type Event as NostrEvent } from 'nostr-tools/event';
import { type Filter } from 'nostr-tools/filter';
import { type Event as NostrEvent, type Filter } from 'nostr-tools';
import useConfig from '@/clients/useConfig';
import useBatch, { type Task } from '@/clients/useBatch';

View File

@@ -1,6 +1,5 @@
import { createSignal, createMemo, type Signal, type Accessor } from 'solid-js';
import { type Event as NostrEvent } from 'nostr-tools/event';
import { type Filter } from 'nostr-tools/filter';
import { type Event as NostrEvent, type Filter } from 'nostr-tools';
import useConfig from '@/clients/useConfig';
import useBatch, { type Task } from '@/clients/useBatch';

View File

@@ -1,9 +1,6 @@
import { createQuery } from '@tanstack/solid-query';
import { type UseSubscriptionProps } from '@/clients/useSubscription';
import type { Event as NostrEvent } from 'nostr-tools/event';
import type { Filter } from 'nostr-tools/filter';
import type { SimplePool } from 'nostr-tools/pool';
import type { SubscriptionOptions } from 'nostr-tools/relay';
import type { Event as NostrEvent, Filter, SimplePool, SubscriptionOptions } from 'nostr-tools';
import usePool from './usePool';
type GetEventsArgs = {

View File

@@ -1,6 +1,4 @@
import { getEventHash } from 'nostr-tools/event';
import type { Event as NostrEvent } from 'nostr-tools/event';
import type { Pub } from 'nostr-tools/relay';
import { getEventHash, type Event as NostrEvent, type Pub } from 'nostr-tools';
import '@/types/nostr.d';
import usePool from '@/clients/usePool';

View File

@@ -1,5 +1,5 @@
import { createMemo, type Accessor } from 'solid-js';
import { type Event as NostrEvent } from 'nostr-tools/event';
import { type Event as NostrEvent } from 'nostr-tools';
import { createQuery, useQueryClient, type CreateQueryResult } from '@tanstack/solid-query';
import useBatchedEvents, { type BatchedEvents } from '@/clients/useBatchedEvents';

View File

@@ -1,5 +1,5 @@
import { createMemo, type Accessor } from 'solid-js';
import { type Event as NostrEvent } from 'nostr-tools/event';
import { type Event as NostrEvent } from 'nostr-tools';
import { createQuery, type CreateQueryResult } from '@tanstack/solid-query';
import timeout from '@/utils/timeout';

View File

@@ -1,6 +1,5 @@
import { createMemo, type Accessor } from 'solid-js';
import { type Event as NostrEvent } from 'nostr-tools/event';
import { type Filter } from 'nostr-tools/filter';
import { type Event as NostrEvent, type Filter } from 'nostr-tools';
import { createQuery, type CreateQueryResult } from '@tanstack/solid-query';
import useBatchedEvent from '@/clients/useBatchedEvent';

View File

@@ -1,5 +1,5 @@
import { createMemo, type Accessor } from 'solid-js';
import { type Event as NostrEvent } from 'nostr-tools/event';
import { type Event as NostrEvent } from 'nostr-tools';
import { createQuery, useQueryClient, type CreateQueryResult } from '@tanstack/solid-query';
import useBatchedEvents, { type BatchedEvents } from '@/clients/useBatchedEvents';
@@ -19,6 +19,7 @@ export type UseReactions = {
};
const { exec } = useBatchedEvents<UseReactionsProps>(() => ({
interval: 5000,
generateKey: ({ eventId }) => eventId,
mergeFilters: (args) => {
const eventIds = args.map((arg) => arg.eventId);

View File

@@ -1,7 +1,5 @@
import { createSignal, createEffect, onCleanup } from 'solid-js';
import type { Event as NostrEvent } from 'nostr-tools/event';
import type { Filter } from 'nostr-tools/filter';
import type { SubscriptionOptions } from 'nostr-tools/relay';
import type { Event as NostrEvent, Filter, SubscriptionOptions } from 'nostr-tools';
import usePool from '@/clients/usePool';
export type UseSubscriptionProps = {

View File

@@ -1,15 +0,0 @@
// NORMAL (implicit AND)
// A filter 'hello world' should match 'hello world'
// A filter "Lorem amet" should match "Lorem ipsum dolor sit amet"
//
// AND
//
// A filter "Lorem AND amet" should match "Lorem ipsum dolor sit amet"
//
// CASE
// A filter 'hello world' should match 'HELLO WORLD'
// A filter 'HELLO WORLD' should match 'hello world'
//
// DOUBLEQUOTE
// A filter '"HELLO WORLD"' should match 'HELLO WORLD'
// A filter '"HELLO WORLD"' should not match 'hello world'

View File

@@ -29,3 +29,20 @@ const applyContentFilter = (contentFilter: ContentFilter): boolean => {
// TODO implement
throw new Error('NotImplemented');
};
// NORMAL (implicit AND)
// A filter 'hello world' should match 'hello world'
// A filter "Lorem amet" should match "Lorem ipsum dolor sit amet"
//
// AND
//
// A filter "Lorem AND amet" should match "Lorem ipsum dolor sit amet"
//
// CASE
// A filter 'hello world' should match 'HELLO WORLD'
// A filter 'HELLO WORLD' should match 'hello world'
//
// DOUBLEQUOTE
// A filter '"HELLO WORLD"' should match 'HELLO WORLD'
// A filter '"HELLO WORLD"' should not match 'hello world'

View File

@@ -41,7 +41,6 @@ const useShortcutKeys = ({ shortcuts = defaultShortcut, onShortcut }: UseShortcu
onMount(() => {
const handleKeydown: JSX.EventHandler<Window, KeyboardEvent> = (ev) => {
console.log(ev);
if (ev.type !== 'keydown') return;
if (ev.target instanceof HTMLTextAreaElement || ev.target instanceof HTMLInputElement) return;

64
src/pages/Hello.tsx Normal file
View File

@@ -0,0 +1,64 @@
import { createSignal, onMount, Switch, Match, type Component } from 'solid-js';
type SignerStatus = 'checking' | 'available' | 'unavailable';
const Hello: Component = () => {
const [signerStatus, setSignerStatus] = createSignal<SignerStatus>('checking');
const checkStatus = () => {
if (window.nostr != null) {
setSignerStatus('available');
} else {
setSignerStatus('unavailable');
}
};
onMount(() => {
let count = 0;
const intervalId = setInterval(() => {
checkStatus();
if (count >= 10) clearInterval(intervalId);
count += 1;
}, 1000);
});
return (
<div class="mx-auto flex max-w-[640px] flex-col items-center p-4 text-stone-600">
<div class="flex flex-col items-center gap-4 rounded bg-white p-4">
<div class="text-7xl">🐰</div>
<h1 class="text-5xl font-bold text-rose-300">Rabbit</h1>
<div>Rabbit is a Web client for Nostr.</div>
</div>
<div class="p-8 shadow-md">
<Switch>
<Match when={signerStatus() === 'checking'}>
...
</Match>
<Match when={signerStatus() === 'unavailable'}>
<div class="pb-1 text-lg font-bold"></div>
<p>
NIP-07
<br />
<a
class="text-blue-500 underline"
target="_blank"
rel="noopener noreferrer"
href="https://scrapbox.io/nostr/NIP-07#63e1c10c8b8fcb00000584fc"
>
</a>
</p>
</Match>
<Match when={signerStatus() === 'available'}>
<button class="rounded bg-rose-400 p-4 text-lg font-bold text-white hover:shadow-md">
NIP-07
</button>
</Match>
</Switch>
</div>
</div>
);
};
export default Hello;

View File

@@ -1,4 +1,4 @@
import { createSignal, Show, For } from 'solid-js';
import { createSignal, Show, For, createEffect } from 'solid-js';
import type { Component } from 'solid-js';
import Column from '@/components/Column';
@@ -7,6 +7,7 @@ import SideBar from '@/components/SideBar';
import Timeline from '@/components/Timeline';
import Notification from '@/components/Notification';
import TextNote from '@/components/TextNote';
import usePool from '@/clients/usePool';
import useCommands from '@/clients/useCommands';
import useConfig from '@/clients/useConfig';
import useSubscription from '@/clients/useSubscription';
@@ -20,9 +21,20 @@ useShortcutKeys({
});
const Home: Component = () => {
const pool = usePool();
const [config] = useConfig();
const pubkey = usePubkey();
const commands = useCommands();
createEffect(() => {
config().relayUrls.map(async (relayUrl) => {
const relay = await pool().ensureRelay(relayUrl);
relay.on('notice', (msg) => {
console.error(`NOTICE: ${relayUrl}: ${msg}`);
});
});
});
const { followings } = useFollowings(() => ({
relayUrls: config().relayUrls,
pubkey: pubkey(),