diff --git a/src/element/profile.tsx b/src/element/profile.tsx
index a36fba2..774b6ee 100644
--- a/src/element/profile.tsx
+++ b/src/element/profile.tsx
@@ -4,7 +4,9 @@ import { Link } from "react-router-dom";
import { useUserProfile } from "@snort/system-react";
import { UserMetadata } from "@snort/system";
import { hexToBech32 } from "@snort/shared";
+
import { Icon } from "element/icon";
+import usePlaceholder from "hooks/placeholders";
import { System } from "index";
import { useInView } from "react-intersection-observer";
@@ -47,6 +49,7 @@ export function Profile({
useUserProfile(System, inView && !profile ? pubkey : undefined) || profile;
const showAvatar = options?.showAvatar ?? true;
const showName = options?.showName ?? true;
+ const placeholder = usePlaceholder(pubkey);
const content = (
<>
@@ -57,7 +60,7 @@ export function Profile({
))}
{icon}
diff --git a/src/element/stream-cards.css b/src/element/stream-cards.css
index f7f5eed..ad7d5a9 100644
--- a/src/element/stream-cards.css
+++ b/src/element/stream-cards.css
@@ -1,8 +1,12 @@
-.stream-cards {
+.stream-cards,
+.edit-container {
display: none;
}
@media (min-width: 1020px) {
+ .edit-container {
+ display: block;
+ }
.stream-cards {
display: grid;
align-items: flex-start;
@@ -86,6 +90,7 @@
.new-card h3 {
margin: 0;
margin-bottom: 12px;
+ font-weight: 500;
}
.new-card input[type="text"] {
diff --git a/src/element/text.css b/src/element/text.css
deleted file mode 100644
index d8364ab..0000000
--- a/src/element/text.css
+++ /dev/null
@@ -1,5 +0,0 @@
-.custom-emoji {
- width: 21px;
- height: 21px;
- display: inline-block;
-}
diff --git a/src/element/text.tsx b/src/element/text.tsx
index db43864..4aece15 100644
--- a/src/element/text.tsx
+++ b/src/element/text.tsx
@@ -1,13 +1,17 @@
-import { useMemo, type ReactNode } from "react";
+import { useMemo, type ReactNode, type FunctionComponent } from "react";
-import { parseNostrLink, validateNostrLink } from "@snort/system";
+import {
+ type NostrLink,
+ parseNostrLink,
+ validateNostrLink,
+} from "@snort/system";
-import { Address } from "element/address";
import { Event } from "element/Event";
import { Mention } from "element/mention";
import { Emoji } from "element/emoji";
import { HyperText } from "element/hypertext";
import { splitByUrl } from "utils";
+import type { Tags } from "types";
export type Fragment = string | ReactNode;
@@ -31,25 +35,11 @@ function extractLinks(fragments: Fragment[]) {
return (
normalizedStr.startsWith("http:") ||
- normalizedStr.startsWith("https:") ||
- normalizedStr.startsWith("magnet:")
+ normalizedStr.startsWith("https:")
);
};
if (validateLink()) {
- if (!a.startsWith("nostr:")) {
- return (
-
e.stopPropagation()}
- target="_blank"
- rel="noreferrer"
- className="ext"
- >
- {a}
-
- );
- }
return
{a};
}
return a;
@@ -122,7 +112,7 @@ function extractNpubs(fragments: Fragment[]) {
.flat();
}
-function extractNevents(fragments: Fragment[]) {
+function extractNevents(fragments: Fragment[], Event: NostrComponent) {
return fragments
.map((f) => {
if (typeof f === "string") {
@@ -144,7 +134,7 @@ function extractNevents(fragments: Fragment[]) {
.flat();
}
-function extractNaddrs(fragments: Fragment[]) {
+function extractNaddrs(fragments: Fragment[], Address: NostrComponent) {
return fragments
.map((f) => {
if (typeof f === "string") {
@@ -167,7 +157,7 @@ function extractNaddrs(fragments: Fragment[]) {
.flat();
}
-function extractNoteIds(fragments: Fragment[]) {
+function extractNoteIds(fragments: Fragment[], Event: NostrComponent) {
return fragments
.map((f) => {
if (typeof f === "string") {
@@ -189,22 +179,46 @@ function extractNoteIds(fragments: Fragment[]) {
.flat();
}
-export function transformText(ps: Fragment[], tags: Array
) {
+export type NostrComponent = FunctionComponent<{ link: NostrLink }>;
+
+export interface NostrComponents {
+ Event: NostrComponent;
+}
+
+const components: NostrComponents = {
+ Event,
+};
+
+export function transformText(
+ ps: Fragment[],
+ tags: Array,
+ customComponents = components
+) {
let fragments = extractEmoji(ps, tags);
fragments = extractNprofiles(fragments);
- fragments = extractNevents(fragments);
- fragments = extractNaddrs(fragments);
- fragments = extractNoteIds(fragments);
+ fragments = extractNevents(fragments, customComponents.Event);
+ fragments = extractNaddrs(fragments, customComponents.Event);
+ fragments = extractNoteIds(fragments, customComponents.Event);
fragments = extractNpubs(fragments);
fragments = extractLinks(fragments);
return fragments;
}
-export function Text({ content, tags }: { content: string; tags: string[][] }) {
+interface TextProps {
+ content: string;
+ tags: Tags;
+ customComponents?: NostrComponents;
+}
+
+export function Text({ content, tags, customComponents }: TextProps) {
// todo: RTL langugage support
const element = useMemo(() => {
- return {transformText([content], tags)};
+ return (
+
+ {transformText([content], tags, customComponents)}
+
+ );
}, [content, tags]);
return <>{element}>;
diff --git a/src/hooks/placeholders.ts b/src/hooks/placeholders.ts
new file mode 100644
index 0000000..98f62e2
--- /dev/null
+++ b/src/hooks/placeholders.ts
@@ -0,0 +1,9 @@
+import { useMemo } from "react";
+
+export default function usePlaceholder(pubkey: string) {
+ const url = useMemo(
+ () => `https://robohash.v0l.io/${pubkey}.png?set=2`,
+ [pubkey]
+ );
+ return url;
+}
diff --git a/src/index.css b/src/index.css
index 0031ad1..c277153 100644
--- a/src/index.css
+++ b/src/index.css
@@ -15,7 +15,15 @@ body {
--text-muted: #797979;
--text-link: #f838d9;
--text-danger: #ff563f;
- --border: #333;
+ --surface: #222;
+ --border: #171717;
+ --gradient-purple: linear-gradient(135deg, #882bff 0%, #f83838 100%);
+ --gradient-yellow: linear-gradient(270deg, #adff27 0%, #ffd027 100%);
+ --gradient-orange: linear-gradient(
+ 270deg,
+ #ff5b27 0%,
+ rgba(255, 182, 39, 0.99) 100%
+ );
}
@media (max-width: 1020px) {
@@ -102,6 +110,12 @@ a {
gap: 8px;
}
+.btn-small {
+ font-size: 14px;
+ line-height: 18px;
+ padding: 4px 8px;
+}
+
.btn-border {
border: 1px solid transparent;
color: inherit;
@@ -277,3 +291,19 @@ div.paper {
height: 15px;
margin-bottom: -2px;
}
+
+.surface {
+ padding: 8px 12px 12px 12px;
+ background: var(--surface);
+ border-radius: 10px;
+}
+
+.outline {
+ padding: 8px 12px 12px 12px;
+ border-radius: 10px;
+ border: 1px solid var(--border);
+}
+
+.secondary {
+ color: #909090;
+}
diff --git a/src/pages/profile-page.css b/src/pages/profile-page.css
index fbd4be0..f1c323b 100644
--- a/src/pages/profile-page.css
+++ b/src/pages/profile-page.css
@@ -46,14 +46,29 @@
margin-left: 16px;
}
+@media (min-width: 480px) {
+ .profile-page .status-indicator {
+ position: absolute;
+ top: 16px;
+ left: 120px;
+ }
+}
+
.profile-page .profile-actions {
position: absolute;
display: flex;
- gap: 12px;
+ align-items: flex-start;
+ gap: 4px;
top: 12px;
right: 12px;
}
+@media (min-width: 480px) {
+ .profile-page .profile-actions {
+ gap: 12px;
+ }
+}
+
.profile-page .profile-information {
margin: 12px;
margin-left: 16px;
@@ -79,22 +94,11 @@
line-height: 24px;
}
-.profile-page .icon-button {
+.profile-page .zap-button {
display: flex;
- align-items: center;
gap: 8px;
}
-.profile-page .icon-button span {
- display: none;
-}
-
-@media (min-width: 420px) {
- .profile-page .icon-button span {
- display: block;
- }
-}
-
.profile-page .zap-button-icon {
color: #171717;
}
@@ -187,9 +191,7 @@
align-items: center;
justify-content: space-between;
font-size: 18px;
- font-style: normal;
font-weight: 500;
- line-height: normal;
}
.profile-page .zapper .zapper-amount {
@@ -197,7 +199,6 @@
align-items: center;
gap: 4px;
font-size: 18px;
- font-style: normal;
font-weight: 500;
line-height: 22px;
}
@@ -229,3 +230,19 @@
font-weight: 500;
line-height: 24px;
}
+
+.profile-page .live-button span {
+ display: none;
+}
+.profile-page .zap-button span {
+ display: none;
+}
+
+@media (min-width: 480px) {
+ .profile-page .zap-button span {
+ display: block;
+ }
+ .profile-page .live-button span {
+ display: block;
+ }
+}
diff --git a/src/pages/profile-page.tsx b/src/pages/profile-page.tsx
index 55183ec..b4ad6fd 100644
--- a/src/pages/profile-page.tsx
+++ b/src/pages/profile-page.tsx
@@ -18,6 +18,7 @@ import { FollowButton } from "element/follow-button";
import { MuteButton } from "element/mute-button";
import { useProfile } from "hooks/profile";
import useTopZappers from "hooks/top-zappers";
+import usePlaceholder from "hooks/placeholders";
import { Text } from "element/text";
import { StreamState, System } from "index";
import { findTag } from "utils";
@@ -52,6 +53,7 @@ export function ProfilePage() {
const navigate = useNavigate();
const params = useParams();
const link = parseNostrLink(params.npub!);
+ const placeholder = usePlaceholder(link.id);
const profile = useUserProfile(System, link.id);
const zapTarget = profile?.lud16 ?? profile?.lud06;
const { streams, zaps } = useProfile(link, true);
@@ -91,16 +93,22 @@ export function ProfilePage() {
src={profile?.banner || defaultBanner}
/>
- {profile?.picture && (
+ {profile?.picture ? (
+ ) : (
+
)}
{isLive ? (
-
+
live
@@ -122,9 +130,9 @@ export function ProfilePage() {
lnurl={zapTarget}
button={