Add keyword search, draft
This commit is contained in:
parent
200dc6b352
commit
3f45a3db06
31468
package-lock.json
generated
Normal file
31468
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -27,6 +27,13 @@ export const DefaultRelays = new Map<string, RelaySettings>([
|
||||
["wss://relay.snort.social", { read: true, write: true }]
|
||||
]);
|
||||
|
||||
/**
|
||||
* Default search relays
|
||||
*/
|
||||
export const SearchRelays = new Map<string, RelaySettings>([
|
||||
["wss://relay.nostr.band", { read: true, write: false }],
|
||||
]);
|
||||
|
||||
/**
|
||||
* List of recommended follows for new users
|
||||
*/
|
||||
|
@ -13,7 +13,7 @@ export interface TimelineFeedOptions {
|
||||
}
|
||||
|
||||
export interface TimelineSubject {
|
||||
type: "pubkey" | "hashtag" | "global" | "ptag",
|
||||
type: "pubkey" | "hashtag" | "global" | "ptag" | "keyword",
|
||||
items: string[]
|
||||
}
|
||||
|
||||
@ -47,6 +47,10 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
||||
sub.PTags = new Set(subject.items);
|
||||
break;
|
||||
}
|
||||
case "keyword": {
|
||||
sub.Keywords = new Set(subject.items);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sub;
|
||||
}, [subject.type, subject.items]);
|
||||
@ -72,6 +76,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
||||
latestSub.Authors = sub.Authors;
|
||||
latestSub.HashTags = sub.HashTags;
|
||||
latestSub.Kinds = sub.Kinds;
|
||||
latestSub.Keywords = sub.Keywords;
|
||||
latestSub.Limit = 1;
|
||||
latestSub.Since = Math.floor(new Date().getTime() / 1000);
|
||||
sub.AddSubscription(latestSub);
|
||||
@ -165,4 +170,4 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
||||
latest.clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,11 @@ export class Subscriptions {
|
||||
*/
|
||||
HashTags?: Set<string>;
|
||||
|
||||
/**
|
||||
* A list of keywords to search
|
||||
*/
|
||||
Keywords?: Set<string>;
|
||||
|
||||
/**
|
||||
* a timestamp, events must be newer than this to pass
|
||||
*/
|
||||
@ -89,6 +94,7 @@ export class Subscriptions {
|
||||
this.Kinds = sub?.kinds ? new Set(sub.kinds) : undefined;
|
||||
this.ETags = sub?.["#e"] ? new Set(sub["#e"]) : undefined;
|
||||
this.PTags = sub?.["#p"] ? new Set(sub["#p"]) : undefined;
|
||||
this.Keywords = sub?.keywords ? new Set(sub.keywords) : undefined;
|
||||
this.Since = sub?.since ?? undefined;
|
||||
this.Until = sub?.until ?? undefined;
|
||||
this.Limit = sub?.limit ?? undefined;
|
||||
@ -133,6 +139,9 @@ export class Subscriptions {
|
||||
if(this.HashTags) {
|
||||
ret["#t"] = Array.from(this.HashTags);
|
||||
}
|
||||
if (this.Keywords) {
|
||||
ret.keywords = Array.from(this.Keywords);
|
||||
}
|
||||
if (this.Since !== null) {
|
||||
ret.since = this.Since;
|
||||
}
|
||||
@ -144,4 +153,4 @@ export class Subscriptions {
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { HexKey, TaggedRawEvent } from "Nostr";
|
||||
import { ProfileCacheExpire } from "Const";
|
||||
import { mapEventToProfile, MetadataCache, } from "State/Users";
|
||||
import { getDb } from "State/Users/Db";
|
||||
import { ProfileCacheExpire, SearchRelays } from "Const";
|
||||
import { mapEventToProfile, MetadataCache } from "Db/User";
|
||||
import Connection, { RelaySettings } from "Nostr/Connection";
|
||||
import Event from "Nostr/Event";
|
||||
import EventKind from "Nostr/EventKind";
|
||||
@ -48,7 +48,8 @@ export class NostrSystem {
|
||||
let c = new Connection(address, options);
|
||||
this.Sockets.set(address, c);
|
||||
for (let [_, s] of this.Subscriptions) {
|
||||
c.AddSubscription(s);
|
||||
if (!s.Keywords || SearchRelays.has(address))
|
||||
c.AddSubscription(s);
|
||||
}
|
||||
} else {
|
||||
// update settings if already connected
|
||||
@ -71,15 +72,17 @@ export class NostrSystem {
|
||||
}
|
||||
|
||||
AddSubscription(sub: Subscriptions) {
|
||||
for (let [_, s] of this.Sockets) {
|
||||
s.AddSubscription(sub);
|
||||
for (let [a, s] of this.Sockets) {
|
||||
if (!sub.Keywords || SearchRelays.has(a))
|
||||
s.AddSubscription(sub);
|
||||
}
|
||||
this.Subscriptions.set(sub.Id, sub);
|
||||
}
|
||||
|
||||
RemoveSubscription(subId: string) {
|
||||
for (let [_, s] of this.Sockets) {
|
||||
s.RemoveSubscription(subId);
|
||||
for (let [a, s] of this.Sockets) {
|
||||
if (!this.Subscriptions.get(subId)?.Keywords || SearchRelays.has(a))
|
||||
s.RemoveSubscription(subId);
|
||||
}
|
||||
this.Subscriptions.delete(subId);
|
||||
}
|
||||
@ -192,9 +195,9 @@ export class NostrSystem {
|
||||
let profile = mapEventToProfile(e);
|
||||
if (profile) {
|
||||
let existing = await db.find(profile.pubkey);
|
||||
if((existing?.created ?? 0) < profile.created) {
|
||||
if ((existing?.created ?? 0) < profile.created) {
|
||||
await db.put(profile);
|
||||
} else if(existing) {
|
||||
} else if (existing) {
|
||||
await db.update(profile.pubkey, { loaded: new Date().getTime() });
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ export type RawReqFilter = {
|
||||
"#e"?: u256[],
|
||||
"#p"?: u256[],
|
||||
"#t"?: string[],
|
||||
keywords?: u256[],
|
||||
since?: number,
|
||||
until?: number,
|
||||
limit?: number
|
||||
@ -53,4 +54,4 @@ export type UserMetadata = {
|
||||
nip05?: string,
|
||||
lud06?: string,
|
||||
lud16?: string
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,18 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search {
|
||||
margin: 0 10px 0 10px;
|
||||
}
|
||||
|
||||
.search input {
|
||||
margin: 0 5px 0 5px;
|
||||
}
|
||||
|
||||
.search .btn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.unread-count {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import "./Layout.css";
|
||||
import { useEffect } from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Outlet, useNavigate } from "react-router-dom";
|
||||
import { faBell, faMessage } from "@fortawesome/free-solid-svg-icons";
|
||||
@ -13,6 +13,7 @@ import { System } from "Nostr/System"
|
||||
import ProfileImage from "Element/ProfileImage";
|
||||
import useLoginFeed from "Feed/LoginFeed";
|
||||
import { totalUnread } from "Pages/MessagesPage";
|
||||
import { SearchRelays } from 'Const';
|
||||
|
||||
export default function Layout() {
|
||||
const dispatch = useDispatch();
|
||||
@ -24,6 +25,9 @@ export default function Layout() {
|
||||
const readNotifications = useSelector<RootState, number>(s => s.login.readNotifications);
|
||||
const dms = useSelector<RootState, RawEvent[]>(s => s.login.dms);
|
||||
const prefs = useSelector<RootState, UserPreferences>(s => s.login.preferences);
|
||||
|
||||
const [keyword, setKeyword] = useState<string>('');
|
||||
|
||||
useLoginFeed();
|
||||
|
||||
useEffect(() => {
|
||||
@ -32,11 +36,16 @@ export default function Layout() {
|
||||
System.ConnectToRelay(k, v);
|
||||
}
|
||||
for (let [k, v] of System.Sockets) {
|
||||
if (!relays[k]) {
|
||||
if (!relays[k] && !SearchRelays.has(k)) {
|
||||
System.DisconnectRelay(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let [k, v] of SearchRelays) {
|
||||
if (!System.Sockets.has(k)) {
|
||||
System.ConnectToRelay(k, v);
|
||||
}
|
||||
}
|
||||
}, [relays]);
|
||||
|
||||
function setTheme(theme: "light" | "dark") {
|
||||
@ -106,10 +115,19 @@ export default function Layout() {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function search() {
|
||||
if (keyword)
|
||||
navigate(`/search/${encodeURIComponent(keyword)}/`);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="header">
|
||||
<div className="logo" onClick={() => navigate("/")}>snort</div>
|
||||
<div className="search">
|
||||
<input type="text" placeholder="Search..." value={keyword} onChange={(e) => setKeyword(e.target.value)} onKeyPress={(e) => { if (e.key === 'Enter') search() } }/>
|
||||
<div className="btn" onClick={() => search()}>🔍</div>
|
||||
</div>
|
||||
<div>
|
||||
{key ? accountHeader() :
|
||||
<div className="btn" onClick={() => navigate("/login")}>Login</div>
|
||||
|
16
src/Pages/SearchPage.tsx
Normal file
16
src/Pages/SearchPage.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import Timeline from "Element/Timeline";
|
||||
|
||||
const SearchPage = () => {
|
||||
const params = useParams();
|
||||
const keyword = params.keyword!.toLowerCase();
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Search results for: {keyword}</h2>
|
||||
<Timeline key={keyword} subject={{ type: "keyword", items: [keyword] }} postsOnly={false} method={"LIMIT_UNTIL"} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default SearchPage;
|
@ -302,4 +302,4 @@ export const {
|
||||
markNotificationsRead,
|
||||
setPreferences
|
||||
} = LoginSlice.actions;
|
||||
export const reducer = LoginSlice.reducer;
|
||||
export const reducer = LoginSlice.reducer;
|
||||
|
@ -27,6 +27,7 @@ import MessagesPage from 'Pages/MessagesPage';
|
||||
import ChatPage from 'Pages/ChatPage';
|
||||
import DonatePage from 'Pages/DonatePage';
|
||||
import HashTagsPage from 'Pages/HashTagsPage';
|
||||
import SearchPage from 'Pages/SearchPage';
|
||||
|
||||
/**
|
||||
* HTTP query provider
|
||||
@ -88,6 +89,10 @@ const router = createBrowserRouter([
|
||||
{
|
||||
path: "/t/:tag",
|
||||
element: <HashTagsPage />
|
||||
},
|
||||
{
|
||||
path: "/search/:keyword",
|
||||
element: <SearchPage />
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user