Merge pull request #76 from v0l/autocomplete-query
feat: query for autocompletion using local db
This commit is contained in:
commit
5fceb969ce
@ -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>
|
||||||
}
|
}
|
@ -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
|
||||||
|
@ -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}` : ""}`}>
|
||||||
|
@ -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";
|
||||||
|
@ -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;
|
||||||
|
@ -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)}`
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user