diff --git a/packages/app/src/Components/Event/Create/NoteCreator.tsx b/packages/app/src/Components/Event/Create/NoteCreator.tsx
index cfe76b5a..ee0902b1 100644
--- a/packages/app/src/Components/Event/Create/NoteCreator.tsx
+++ b/packages/app/src/Components/Event/Create/NoteCreator.tsx
@@ -1,6 +1,16 @@
/* eslint-disable max-lines */
import { fetchNip05Pubkey, unixNow } from "@snort/shared";
-import { EventBuilder, EventKind, NostrLink, NostrPrefix, TaggedNostrEvent, tryParseNostrLink } from "@snort/system";
+import {
+ addExtensionToNip94Url,
+ EventBuilder,
+ EventKind,
+ nip94TagsToIMeta,
+ NostrLink,
+ NostrPrefix,
+ readNip94Tags,
+ TaggedNostrEvent,
+ tryParseNostrLink,
+} from "@snort/system";
import { useUserProfile } from "@snort/system-react";
import { ZapTarget } from "@snort/wallet";
import { Menu, MenuItem } from "@szhsin/react-menu";
@@ -28,7 +38,7 @@ import usePreferences from "@/Hooks/usePreferences";
import useRelays from "@/Hooks/useRelays";
import { useNoteCreator } from "@/State/NoteCreator";
import { openFile, trackEvent } from "@/Utils";
-import useFileUpload, { addExtensionToNip94Url, nip94TagsToIMeta, readNip94Tags } from "@/Utils/Upload";
+import useFileUpload from "@/Utils/Upload";
import { GetPowWorker } from "@/Utils/wasm";
import { OkResponseRow } from "./OkResponseRow";
diff --git a/packages/app/src/Components/Feed/RootTabItems.tsx b/packages/app/src/Components/Feed/RootTabItems.tsx
index 252406c7..e955132a 100644
--- a/packages/app/src/Components/Feed/RootTabItems.tsx
+++ b/packages/app/src/Components/Feed/RootTabItems.tsx
@@ -94,6 +94,17 @@ export function rootTabItems(base: string, pubKey: string | undefined, tags: Arr
>
),
},
+ {
+ tab: "media",
+ path: `${base}/media`,
+ show: true,
+ element: (
+ <>
+
+
+ >
+ ),
+ },
] as Array<{
tab: RootTabRoutePath;
path: string;
diff --git a/packages/app/src/Components/Feed/TimelineFollows.tsx b/packages/app/src/Components/Feed/TimelineFollows.tsx
index ac8fc0a9..d4fa0485 100644
--- a/packages/app/src/Components/Feed/TimelineFollows.tsx
+++ b/packages/app/src/Components/Feed/TimelineFollows.tsx
@@ -15,12 +15,16 @@ import { AutoLoadMore } from "../Event/LoadMore";
import TimelineChunk from "./TimelineChunk";
export interface TimelineFollowsProps {
+ id?: string;
postsOnly: boolean;
- liveStreams?: boolean;
noteFilter?: (ev: NostrEvent) => boolean;
noteRenderer?: (ev: NostrEvent) => ReactNode;
noteOnClick?: (ev: NostrEvent) => void;
displayAs?: DisplayAs;
+ kinds?: Array;
+ showDisplayAsSelector?: boolean;
+ firstChunkSize?: number;
+ windowSize?: number;
}
/**
@@ -38,12 +42,15 @@ const TimelineFollows = (props: TimelineFollowsProps) => {
const { isFollowing, followList } = useFollowsControls();
const { chunks, showMore } = useTimelineChunks({
now: openedAt,
- firstChunkSize: Hour * 2,
+ window: props.windowSize,
+ firstChunkSize: props.firstChunkSize ?? Hour * 2,
});
const builder = useCallback(
(rb: RequestBuilder) => {
- rb.withFilter().authors(followList).kinds([EventKind.TextNote, EventKind.Repost, EventKind.Polls]);
+ rb.withFilter()
+ .authors(followList)
+ .kinds(props.kinds ?? [EventKind.TextNote, EventKind.Repost, EventKind.Polls]);
},
[followList],
);
@@ -58,11 +65,13 @@ const TimelineFollows = (props: TimelineFollowsProps) => {
return (
<>
- setDisplayAs(displayAs)} />
+ {(props.showDisplayAsSelector ?? true) && (
+ setDisplayAs(displayAs)} />
+ )}
{chunks.map(c => (
void }) {
{
const parsed = transformTextCached(e.id, e.content, e.tags);
const images = parsed.filter(a => a.type === "media" && a.mimeType?.startsWith("image/"));
diff --git a/packages/app/src/Pages/Root/Media.tsx b/packages/app/src/Pages/Root/Media.tsx
new file mode 100644
index 00000000..6a9a8eeb
--- /dev/null
+++ b/packages/app/src/Pages/Root/Media.tsx
@@ -0,0 +1,19 @@
+import { EventKind } from "@snort/system";
+
+import TimelineFollows from "@/Components/Feed/TimelineFollows";
+import { Day } from "@/Utils/Const";
+
+export default function MediaPosts() {
+ return (
+
+
+
+ );
+}
diff --git a/packages/app/src/Pages/Root/Root.css b/packages/app/src/Pages/Root/Root.css
deleted file mode 100644
index e69de29b..00000000
diff --git a/packages/app/src/Pages/Root/RootRoutes.tsx b/packages/app/src/Pages/Root/RootRoutes.tsx
index 6e28c792..8e0d3404 100644
--- a/packages/app/src/Pages/Root/RootRoutes.tsx
+++ b/packages/app/src/Pages/Root/RootRoutes.tsx
@@ -1,5 +1,5 @@
import { lazy } from "react";
-import { Outlet, RouteObject } from "react-router-dom";
+import { Outlet, RouteObject, useLocation } from "react-router-dom";
import { LiveStreams } from "@/Components/LiveStream/LiveStreams";
import { RootTabRoutes } from "@/Pages/Root/RootTabRoutes";
@@ -8,9 +8,10 @@ import { getCurrentRefCode } from "@/Utils";
const InviteModal = lazy(() => import("@/Components/Invite"));
export default function RootPage() {
const code = getCurrentRefCode();
+ const location = useLocation();
return (
<>
-
+ {(location.pathname === "/" || location.pathname === "/following") && }
diff --git a/packages/app/src/Pages/Root/RootTabRoutes.tsx b/packages/app/src/Pages/Root/RootTabRoutes.tsx
index c61bc97c..0f8c63bd 100644
--- a/packages/app/src/Pages/Root/RootTabRoutes.tsx
+++ b/packages/app/src/Pages/Root/RootTabRoutes.tsx
@@ -7,6 +7,7 @@ import { ConversationsTab } from "@/Pages/Root/ConversationsTab";
import { DefaultTab } from "@/Pages/Root/DefaultTab";
import { FollowedByFriendsTab } from "@/Pages/Root/FollowedByFriendsTab";
import { ForYouTab } from "@/Pages/Root/ForYouTab";
+import MediaPosts from "@/Pages/Root/Media";
import { NotesTab } from "@/Pages/Root/NotesTab";
import { TagsTab } from "@/Pages/Root/TagsTab";
import { TopicsPage } from "@/Pages/TopicsPage";
@@ -23,7 +24,8 @@ export type RootTabRoutePath =
| "trending/hashtags"
| "suggested"
| "t/:tag"
- | "topics";
+ | "topics"
+ | "media";
export type RootTabRoute = {
path: RootTabRoutePath;
@@ -83,4 +85,8 @@ export const RootTabRoutes: RootTabRoute[] = [
path: "topics",
element: ,
},
+ {
+ path: "media",
+ element: ,
+ },
];
diff --git a/packages/app/src/Utils/Upload/Nip96.ts b/packages/app/src/Utils/Upload/Nip96.ts
index 724c10e3..6a32e2ed 100644
--- a/packages/app/src/Utils/Upload/Nip96.ts
+++ b/packages/app/src/Utils/Upload/Nip96.ts
@@ -1,8 +1,8 @@
import { base64 } from "@scure/base";
import { throwIfOffline } from "@snort/shared";
-import { EventKind, EventPublisher, NostrEvent } from "@snort/system";
+import { addExtensionToNip94Url, EventKind, EventPublisher, NostrEvent, readNip94Tags } from "@snort/system";
-import { addExtensionToNip94Url, readNip94Tags, UploadResult } from ".";
+import { UploadResult } from ".";
export class Nip96Uploader {
#info?: Nip96Info;
diff --git a/packages/app/src/Utils/Upload/index.ts b/packages/app/src/Utils/Upload/index.ts
index 9e7bd3a3..46500598 100644
--- a/packages/app/src/Utils/Upload/index.ts
+++ b/packages/app/src/Utils/Upload/index.ts
@@ -1,28 +1,12 @@
-import { EventPublisher, NostrEvent } from "@snort/system";
+import { EventPublisher, Nip94Tags, NostrEvent } from "@snort/system";
import useEventPublisher from "@/Hooks/useEventPublisher";
import { useMediaServerList } from "@/Hooks/useMediaServerList";
import { bech32ToHex, randomSample } from "@/Utils";
-import { FileExtensionRegex, KieranPubKey } from "@/Utils/Const";
+import { KieranPubKey } from "@/Utils/Const";
import { Nip96Uploader } from "./Nip96";
-export interface Nip94Tags {
- url?: string;
- mimeType?: string;
- hash?: string;
- originalHash?: string;
- size?: number;
- dimensions?: [number, number];
- magnet?: string;
- blurHash?: string;
- thumb?: string;
- image?: Array;
- summary?: string;
- alt?: string;
- fallback?: Array;
-}
-
export interface UploadResult {
url?: string;
error?: string;
@@ -86,124 +70,3 @@ export default function useFileUpload(privKey?: string) {
return new Nip96Uploader("https://nostr.build", pub);
}
}
-
-export function addExtensionToNip94Url(meta: Nip94Tags) {
- if (!meta.url?.match(FileExtensionRegex) && meta.mimeType) {
- switch (meta.mimeType) {
- case "image/webp": {
- return `${meta.url}.webp`;
- }
- case "image/jpeg":
- case "image/jpg": {
- return `${meta.url}.jpg`;
- }
- case "video/mp4": {
- return `${meta.url}.mp4`;
- }
- }
- }
- return meta.url;
-}
-
-/**
- * Read NIP-94 tags from `imeta` tag
- */
-export function readNip94TagsFromIMeta(tag: Array) {
- const asTags = tag.slice(1).map(a => a.split(" ", 2));
- return readNip94Tags(asTags);
-}
-
-/**
- * Read NIP-94 tags from event tags
- */
-export function readNip94Tags(tags: Array>) {
- const res: Nip94Tags = {};
- for (const tx of tags) {
- const [k, v] = tx;
- switch (k) {
- case "url": {
- res.url = v;
- break;
- }
- case "m": {
- res.mimeType = v;
- break;
- }
- case "x": {
- res.hash = v;
- break;
- }
- case "ox": {
- res.originalHash = v;
- break;
- }
- case "size": {
- res.size = Number(v);
- break;
- }
- case "dim": {
- res.dimensions = v.split("x").map(Number) as [number, number];
- break;
- }
- case "magnet": {
- res.magnet = v;
- break;
- }
- case "blurhash": {
- res.blurHash = v;
- break;
- }
- case "thumb": {
- res.thumb = v;
- break;
- }
- case "image": {
- res.image ??= [];
- res.image.push(v);
- break;
- }
- case "summary": {
- res.summary = v;
- break;
- }
- case "alt": {
- res.alt = v;
- break;
- }
- case "fallback": {
- res.fallback ??= [];
- res.fallback.push(v);
- break;
- }
- }
- }
- return res;
-}
-
-export function nip94TagsToIMeta(meta: Nip94Tags) {
- const ret: Array = ["imeta"];
- const ifPush = (key: string, value?: string | number) => {
- if (value) {
- ret.push(`${key} ${value}`);
- }
- };
- ifPush("url", meta.url);
- ifPush("m", meta.mimeType);
- ifPush("x", meta.hash);
- ifPush("ox", meta.originalHash);
- ifPush("size", meta.size);
- ifPush("dim", meta.dimensions?.join("x"));
- ifPush("magnet", meta.magnet);
- ifPush("blurhash", meta.blurHash);
- ifPush("thumb", meta.thumb);
- ifPush("summary", meta.summary);
- ifPush("alt", meta.alt);
- if (meta.image) {
- meta.image.forEach(a => ifPush("image", a));
- }
- if (meta.fallback) {
- meta.fallback.forEach(a => ifPush("fallback", a));
- }
-
- return ret;
-}
diff --git a/packages/app/src/Utils/getEventMedia.ts b/packages/app/src/Utils/getEventMedia.ts
index a2fa0bca..90a568dc 100644
--- a/packages/app/src/Utils/getEventMedia.ts
+++ b/packages/app/src/Utils/getEventMedia.ts
@@ -1,8 +1,21 @@
-import { TaggedNostrEvent } from "@snort/system";
+import { EventKind, ParsedFragment, readNip94TagsFromIMeta, TaggedNostrEvent } from "@snort/system";
import { transformTextCached } from "@/Hooks/useTextTransformCache";
export default function getEventMedia(event: TaggedNostrEvent) {
+ // emulate parsed media from imeta kinds
+ const mediaKinds = [EventKind.Photo, EventKind.Video, EventKind.ShortVideo];
+ if (mediaKinds.includes(event.kind)) {
+ const meta = event.tags.filter(a => a[0] === "imeta").map(readNip94TagsFromIMeta);
+ return meta.map(
+ a =>
+ ({
+ type: "media",
+ mimeType: a.mimeType,
+ content: a.url,
+ }) as ParsedFragment,
+ );
+ }
const parsed = transformTextCached(event.id, event.content, event.tags);
return parsed.filter(
a => a.type === "media" && (a.mimeType?.startsWith("image/") || a.mimeType?.startsWith("video/")),
diff --git a/packages/system/src/impl/nip92.ts b/packages/system/src/impl/nip92.ts
new file mode 100644
index 00000000..bc61f95d
--- /dev/null
+++ b/packages/system/src/impl/nip92.ts
@@ -0,0 +1,39 @@
+import { Nip94Tags, readNip94Tags } from "./nip94";
+
+/**
+ * Read NIP-94 tags from `imeta` tag
+ */
+export function readNip94TagsFromIMeta(tag: Array) {
+ const asTags = tag.slice(1).map(a => a.split(" ", 2));
+ return readNip94Tags(asTags);
+}
+
+export function nip94TagsToIMeta(meta: Nip94Tags) {
+ const ret: Array = ["imeta"];
+ const ifPush = (key: string, value?: string | number) => {
+ if (value) {
+ ret.push(`${key} ${value}`);
+ }
+ };
+ ifPush("url", meta.url);
+ ifPush("m", meta.mimeType);
+ ifPush("x", meta.hash);
+ ifPush("ox", meta.originalHash);
+ ifPush("size", meta.size);
+ ifPush("dim", meta.dimensions?.join("x"));
+ ifPush("magnet", meta.magnet);
+ ifPush("blurhash", meta.blurHash);
+ ifPush("thumb", meta.thumb);
+ ifPush("summary", meta.summary);
+ ifPush("alt", meta.alt);
+ ifPush("duration", meta.duration);
+ ifPush("bitrate", meta.bitrate);
+ if (meta.image) {
+ meta.image.forEach(a => ifPush("image", a));
+ }
+ if (meta.fallback) {
+ meta.fallback.forEach(a => ifPush("fallback", a));
+ }
+
+ return ret;
+}
diff --git a/packages/system/src/impl/nip94.ts b/packages/system/src/impl/nip94.ts
new file mode 100644
index 00000000..2a0c4d07
--- /dev/null
+++ b/packages/system/src/impl/nip94.ts
@@ -0,0 +1,112 @@
+import { FileExtensionRegex } from "../const";
+
+export interface Nip94Tags {
+ url?: string;
+ mimeType?: string;
+ hash?: string;
+ originalHash?: string;
+ size?: number;
+ dimensions?: [number, number];
+ magnet?: string;
+ blurHash?: string;
+ thumb?: string;
+ image?: Array;
+ summary?: string;
+ alt?: string;
+ fallback?: Array;
+ duration?: number;
+ bitrate?: number;
+}
+
+/**
+ * Read NIP-94 tags from event tags
+ */
+export function readNip94Tags(tags: Array>) {
+ const res: Nip94Tags = {};
+ for (const tx of tags) {
+ const [k, v] = tx;
+ switch (k) {
+ case "url": {
+ res.url = v;
+ break;
+ }
+ case "m": {
+ res.mimeType = v;
+ break;
+ }
+ case "x": {
+ res.hash = v;
+ break;
+ }
+ case "ox": {
+ res.originalHash = v;
+ break;
+ }
+ case "size": {
+ res.size = Number(v);
+ break;
+ }
+ case "dim": {
+ res.dimensions = v.split("x").map(Number) as [number, number];
+ break;
+ }
+ case "magnet": {
+ res.magnet = v;
+ break;
+ }
+ case "blurhash": {
+ res.blurHash = v;
+ break;
+ }
+ case "thumb": {
+ res.thumb = v;
+ break;
+ }
+ case "image": {
+ res.image ??= [];
+ res.image.push(v);
+ break;
+ }
+ case "summary": {
+ res.summary = v;
+ break;
+ }
+ case "alt": {
+ res.alt = v;
+ break;
+ }
+ case "fallback": {
+ res.fallback ??= [];
+ res.fallback.push(v);
+ break;
+ }
+ case "duration": {
+ res.duration = Number(v);
+ break;
+ }
+ case "bitrate": {
+ res.bitrate = Number(v);
+ break;
+ }
+ }
+ }
+ return res;
+}
+
+export function addExtensionToNip94Url(meta: Nip94Tags) {
+ if (!meta.url?.match(FileExtensionRegex) && meta.mimeType) {
+ switch (meta.mimeType) {
+ case "image/webp": {
+ return `${meta.url}.webp`;
+ }
+ case "image/jpeg":
+ case "image/jpg": {
+ return `${meta.url}.jpg`;
+ }
+ case "video/mp4": {
+ return `${meta.url}.mp4`;
+ }
+ }
+ }
+ return meta.url;
+}
diff --git a/packages/system/src/index.ts b/packages/system/src/index.ts
index 13394f20..e7f5a6c7 100644
--- a/packages/system/src/index.ts
+++ b/packages/system/src/index.ts
@@ -35,6 +35,8 @@ export * from "./impl/nip44";
export * from "./impl/nip46";
export * from "./impl/nip57";
export * from "./impl/nip55";
+export * from "./impl/nip94";
+export * from "./impl/nip92";
export * from "./cache/index";
export * from "./cache/user-relays";