Merge pull request #76 from v0l/autocomplete-query

feat: query for autocompletion using local db
This commit is contained in:
Alejandro 2023-01-17 11:22:28 +01:00 committed by GitHub
commit 5fceb969ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 40 additions and 25 deletions

View File

@ -15,7 +15,7 @@ export default function Mention({ pubkey }: { pubkey: HexKey }) {
name = user!.name!; name = user!.name!;
} }
return name; return name;
}, [user]); }, [user, pubkey]);
return <Link to={profileLink(pubkey)} onClick={(e) => e.stopPropagation()}>@{name}</Link> return <Link to={profileLink(pubkey)} onClick={(e) => e.stopPropagation()}>@{name}</Link>
} }

View File

@ -1,7 +1,7 @@
import { useQuery } from "react-query"; import { useQuery } from "react-query";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck, faSpinner, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons"; import { faSpinner, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
import './Nip05.css' import './Nip05.css'
import { HexKey } from "../nostr"; import { HexKey } from "../nostr";
@ -14,12 +14,16 @@ async function fetchNip05Pubkey(name: string, domain: string) {
if (!name || !domain) { if (!name || !domain) {
return undefined; return undefined;
} }
const res = await fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(name)}`); try {
const data: NostrJson = await res.json(); const res = await fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(name)}`);
const match = Object.keys(data.names).find(n => { const data: NostrJson = await res.json();
return n.toLowerCase() === name.toLowerCase(); const match = Object.keys(data.names).find(n => {
}); return n.toLowerCase() === name.toLowerCase();
return match ? data.names[match] : undefined; });
return match ? data.names[match] : undefined;
} catch (error) {
return undefined
}
} }
const VERIFICATION_CACHE_TIME = 24 * 60 * 60 * 1000 const VERIFICATION_CACHE_TIME = 24 * 60 * 60 * 1000

View File

@ -28,7 +28,7 @@ export default function ProfileImage({ pubkey, subHeader, showUsername = true, c
name = user!.name!; name = user!.name!;
} }
return name; return name;
}, [user]); }, [user, pubkey]);
return ( return (
<div className={`pfp${className ? ` ${className}` : ""}`}> <div className={`pfp${className ? ` ${className}` : ""}`}>

View File

@ -3,7 +3,7 @@ import ReactMarkdown from "react-markdown";
import { TwitterTweetEmbed } from "react-twitter-embed"; import { TwitterTweetEmbed } from "react-twitter-embed";
import { UrlRegex, FileExtensionRegex, MentionRegex, InvoiceRegex, YoutubeUrlRegex, TweetUrlRegex, HashtagRegex } from "../Const"; import { UrlRegex, FileExtensionRegex, MentionRegex, InvoiceRegex, YoutubeUrlRegex, TweetUrlRegex, HashtagRegex } from "../Const";
import { eventLink, hexToBech32, profileLink } from "../Util"; import { eventLink, hexToBech32 } from "../Util";
import Invoice from "./Invoice"; import Invoice from "./Invoice";
import LazyImage from "./LazyImage"; import LazyImage from "./LazyImage";
import Hashtag from "./Hashtag"; import Hashtag from "./Hashtag";

View File

@ -24,6 +24,12 @@
margin-right: 8px; margin-right: 8px;
} }
.user-picture .avatar {
border-width: 1px;
width: 40px;
height: 40px;
}
.user-details { .user-details {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -1,31 +1,23 @@
import "@webscopeio/react-textarea-autocomplete/style.css"; import "@webscopeio/react-textarea-autocomplete/style.css";
import "./Textarea.css"; import "./Textarea.css";
import Nostrich from "../nostrich.jpg";
import { useState } from "react";
import { useLiveQuery } from "dexie-react-hooks"; import { useLiveQuery } from "dexie-react-hooks";
import ReactTextareaAutocomplete from "@webscopeio/react-textarea-autocomplete"; import ReactTextareaAutocomplete from "@webscopeio/react-textarea-autocomplete";
import TextareaAutosize from "react-textarea-autosize"; import TextareaAutosize from "react-textarea-autosize";
import Avatar from "./Avatar";
import Nip05 from "./Nip05"; import Nip05 from "./Nip05";
import { hexToBech32 } from "../Util"; import { hexToBech32 } from "../Util";
import { db } from "../db"; import { db } from "../db";
import { MetadataCache } from "../db/User"; import { MetadataCache } from "../db/User";
function searchUsers(query: string, users: MetadataCache[]) { const UserItem = (metadata: MetadataCache) => {
const q = query.toLowerCase() const { pubkey, display_name, picture, nip05, ...rest } = metadata
return users.filter(({ name, display_name, about, nip05 }: MetadataCache) => {
return name?.toLowerCase().includes(q)
|| display_name?.toLowerCase().includes(q)
|| about?.toLowerCase().includes(q)
|| nip05?.toLowerCase().includes(q)
}).slice(0, 3)
}
const UserItem = ({ pubkey, display_name, picture, nip05, ...rest }: MetadataCache) => {
return ( return (
<div key={pubkey} className="user-item"> <div key={pubkey} className="user-item">
<div className="user-picture"> <div className="user-picture">
{picture && <img src={picture ? picture : Nostrich} className="picture" />} <Avatar user={metadata} />
</div> </div>
<div className="user-details"> <div className="user-details">
<strong>{display_name || rest.name}</strong> <strong>{display_name || rest.name}</strong>
@ -36,10 +28,23 @@ const UserItem = ({ pubkey, display_name, picture, nip05, ...rest }: MetadataCac
} }
const Textarea = ({ users, onChange, ...rest }: any) => { const Textarea = ({ users, onChange, ...rest }: any) => {
const [query, setQuery] = useState('')
const allUsers = useLiveQuery( const allUsers = useLiveQuery(
() => db.users.toArray() () => db.users
.where("name").startsWithIgnoreCase(query)
.or("display_name").startsWithIgnoreCase(query)
.or("nip05").startsWithIgnoreCase(query)
.limit(5)
.toArray(),
[query],
); );
const userDataProvider = (token: string) => {
setQuery(token)
return allUsers
}
return ( return (
<ReactTextareaAutocomplete <ReactTextareaAutocomplete
{...rest} {...rest}
@ -50,7 +55,7 @@ const Textarea = ({ users, onChange, ...rest }: any) => {
trigger={{ trigger={{
"@": { "@": {
afterWhitespace: true, afterWhitespace: true,
dataProvider: token => allUsers ? searchUsers(token, allUsers) : [], dataProvider: userDataProvider,
component: (props: any) => <UserItem {...props.entity} />, component: (props: any) => <UserItem {...props.entity} />,
output: (item: any) => `@${hexToBech32("npub", item.pubkey)}` output: (item: any) => `@${hexToBech32("npub", item.pubkey)}`
} }