diff --git a/README.md b/README.md index ffd7801..54f68e5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ Snort is a nostr UI built with React, Snort intends to be fast and effecient -Snort supports the following NIP's +Snort supports the following NIP's: + - [x] NIP-01: Basic protocol flow description - [x] NIP-02: Contact List and Petnames (No petname support) - [ ] NIP-03: OpenTimestamps Attestations for Events @@ -26,4 +27,5 @@ Snort supports the following NIP's - [ ] NIP-36: Sensitive Content - [ ] NIP-40: Expiration Timestamp - [ ] NIP-42: Authentication of clients to relays -- [ ] NIP-51: Lists +- [x] NIP-50: Search +- [x] NIP-51: Lists diff --git a/package.json b/package.json index 8fbdd4b..918f900 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "react-textarea-autosize": "^8.4.0", "react-twitter-embed": "^4.0.4", "typescript": "^4.9.4", + "unist-util-visit": "^4.1.2", "uuid": "^9.0.0", "workbox-background-sync": "^6.4.2", "workbox-broadcast-update": "^6.4.2", diff --git a/public/index.html b/public/index.html index 6976940..b2e6424 100644 --- a/public/index.html +++ b/public/index.html @@ -4,11 +4,11 @@ - + + content="default-src 'self'; child-src 'none'; worker-src 'self'; frame-src youtube.com www.youtube.com https://platform.twitter.com https://embed.tidal.com https://w.soundcloud.com https://www.mixcloud.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; connect-src wss://* 'self' https://*; img-src * data:; font-src https://fonts.gstatic.com; media-src *; script-src 'self' https://static.cloudflareinsights.com https://platform.twitter.com https://embed.tidal.com;" /> diff --git a/src/Const.ts b/src/Const.ts index 3a1d20d..cd9878c 100644 --- a/src/Const.ts +++ b/src/Const.ts @@ -5,6 +5,11 @@ import { RelaySettings } from "Nostr/Connection"; */ export const ApiHost = "https://api.snort.social"; +/** + * Void.cat file upload service url + */ +export const VoidCatHost = "https://void.cat"; + /** * Websocket re-connect timeout */ @@ -19,9 +24,14 @@ export const ProfileCacheExpire = (1_000 * 60 * 5); * Default bootstrap relays */ export const DefaultRelays = new Map([ - ["wss://relay.snort.social", { read: true, write: true }], - ["wss://relay.damus.io", { read: true, write: true }], - ["wss://nostr-pub.wellorder.net", { read: true, write: true }], + ["wss://relay.snort.social", { read: true, write: true }] +]); + +/** + * Default search relays + */ +export const SearchRelays = new Map([ + ["wss://relay.nostr.band", { read: true, write: false }], ]); /** @@ -99,3 +109,9 @@ export const TidalRegex = /tidal\.com\/(?:browse\/)?(\w+)\/([a-z0-9-]+)/i; * SoundCloud regex */ export const SoundCloudRegex = /soundcloud\.com\/(?!live)([a-zA-Z0-9]+)\/([a-zA-Z0-9-]+)/ + +/** + * Mixcloud regex + */ + +export const MixCloudRegex = /mixcloud\.com\/(?!live)([a-zA-Z0-9]+)\/([a-zA-Z0-9-]+)/ \ No newline at end of file diff --git a/src/Db/User.ts b/src/Db/User.ts deleted file mode 100644 index c5f4947..0000000 --- a/src/Db/User.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { HexKey, TaggedRawEvent, UserMetadata } from "Nostr"; -import { hexToBech32 } from "../Util"; - -export interface MetadataCache extends UserMetadata { - /** - * When the object was saved in cache - */ - loaded: number, - - /** - * When the source metadata event was created - */ - created: number, - - /** - * The pubkey of the owner of this metadata - */ - pubkey: HexKey -}; - -export function mapEventToProfile(ev: TaggedRawEvent) { - try { - let data: UserMetadata = JSON.parse(ev.content); - return { - pubkey: ev.pubkey, - npub: hexToBech32("npub", ev.pubkey), - created: ev.created_at, - loaded: new Date().getTime(), - ...data - } as MetadataCache; - } catch (e) { - console.error("Failed to parse JSON", ev, e); - } -} \ No newline at end of file diff --git a/src/Db/index.ts b/src/Db/index.ts index b30881a..8834c96 100644 --- a/src/Db/index.ts +++ b/src/Db/index.ts @@ -1,16 +1,20 @@ import Dexie, { Table } from "dexie"; -import { MetadataCache } from "Db/User"; +import { MetadataCache } from "State/Users"; import { hexToBech32 } from "Util"; +export const NAME = 'snortDB' +export const VERSION = 2 + +const STORES = { + users: '++pubkey, name, display_name, picture, nip05, npub' +} export class SnortDB extends Dexie { users!: Table; constructor() { - super('snortDB'); - this.version(2).stores({ - users: '++pubkey, name, display_name, picture, nip05, npub' - }).upgrade(tx => { + super(NAME); + this.version(VERSION).stores(STORES).upgrade(tx => { return tx.table("users").toCollection().modify(user => { user.npub = hexToBech32("npub", user.pubkey) }) diff --git a/src/Element/FollowButton.tsx b/src/Element/FollowButton.tsx index 7609d5a..14c9500 100644 --- a/src/Element/FollowButton.tsx +++ b/src/Element/FollowButton.tsx @@ -4,13 +4,14 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faUserMinus, faUserPlus } from "@fortawesome/free-solid-svg-icons"; import { HexKey } from "Nostr"; import { RootState } from "State/Store"; +import { parseId } from "Util"; export interface FollowButtonProps { pubkey: HexKey, className?: string } export default function FollowButton(props: FollowButtonProps) { - const pubkey = props.pubkey; + const pubkey = parseId(props.pubkey); const publiser = useEventPublisher(); const isFollowing = useSelector(s => s.login.follows?.includes(pubkey) ?? false); diff --git a/src/Element/Mention.tsx b/src/Element/Mention.tsx index 20898cc..6e53ba2 100644 --- a/src/Element/Mention.tsx +++ b/src/Element/Mention.tsx @@ -1,11 +1,11 @@ import { useMemo } from "react"; import { Link } from "react-router-dom"; -import useProfile from "Feed/ProfileFeed"; +import { useUserProfile } from "Feed/ProfileFeed"; import { HexKey } from "Nostr"; import { hexToBech32, profileLink } from "Util"; export default function Mention({ pubkey }: { pubkey: HexKey }) { - const user = useProfile(pubkey)?.get(pubkey); + const user = useUserProfile(pubkey) const name = useMemo(() => { let name = hexToBech32("npub", pubkey).substring(0, 12); @@ -18,4 +18,4 @@ export default function Mention({ pubkey }: { pubkey: HexKey }) { }, [user, pubkey]); return e.stopPropagation()}>@{name} -} \ No newline at end of file +} diff --git a/src/Element/MixCloudEmbed.tsx b/src/Element/MixCloudEmbed.tsx new file mode 100644 index 0000000..8812b65 --- /dev/null +++ b/src/Element/MixCloudEmbed.tsx @@ -0,0 +1,27 @@ +import { MixCloudRegex } from "Const"; +import { useSelector } from "react-redux"; +import { RootState } from "State/Store"; + +const MixCloudEmbed = ({link}: {link: string}) => { + + const feedPath = (MixCloudRegex.test(link) && RegExp.$1) + "%2F" + ( MixCloudRegex.test(link) && RegExp.$2) + + const lightTheme = useSelector(s => s.login.preferences.theme === "light"); + + const lightParams = lightTheme ? "light=1" : "light=0"; + + return( + <> +
+