mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-11 21:29:26 +02:00
add basic share button
This commit is contained in:
parent
ff94abe0da
commit
c9ba9193e5
@ -32,7 +32,7 @@ I would recomend you use a browser extension like [Alby](https://getalby.com/) o
|
||||
- [x] User tipping
|
||||
- [x] Manage followers ( Contact List )
|
||||
- [x] Relay management
|
||||
- [ ] Profile management
|
||||
- [x] Profile management
|
||||
- [ ] Image upload
|
||||
- [ ] Reactions
|
||||
- [ ] Dynamically connect to relays (start with one relay then connect to others as required)
|
||||
@ -71,14 +71,13 @@ I would recomend you use a browser extension like [Alby](https://getalby.com/) o
|
||||
|
||||
## TODO
|
||||
|
||||
- Show reactions and zaps on notes
|
||||
- Create a "event posting" service that can show modals (for qr code scanning), warnings (signed by wrong pubkey), and results (what relays responded) when posting events.
|
||||
- Create notifications service that keeps track of read notifications. (show unread count in sidenav)
|
||||
- Rebuild relays view to show relay info and settings NIP-11
|
||||
- filter list of followers by users the user has blocked/reported (stops bots/spammers from showing up at followers)
|
||||
- Add note embeds
|
||||
- Add "repost" button that mentions the note
|
||||
- Add preview tab to note modal
|
||||
- Add mentions in posts (https://css-tricks.com/so-you-want-to-build-an-mention-autocomplete-feature/)
|
||||
- Add mentions in notes (https://css-tricks.com/so-you-want-to-build-an-mention-autocomplete-feature/)
|
||||
- add `client` tag to published events
|
||||
- Save note drafts and let users manage them
|
||||
- make app a valid web share target https://developer.chrome.com/articles/web-share-target/
|
||||
|
@ -6,6 +6,7 @@ import useSubject from "../hooks/use-subject";
|
||||
import { useUserMetadata } from "../hooks/use-user-metadata";
|
||||
import clientFollowingService from "../services/client-following";
|
||||
import { UserAvatar } from "./user-avatar";
|
||||
import { UserDnsIdentityIcon } from "./user-dns-identity";
|
||||
|
||||
const FollowingListItem = ({ pubkey }: { pubkey: string }) => {
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
@ -20,6 +21,7 @@ const FollowingListItem = ({ pubkey }: { pubkey: string }) => {
|
||||
variant="outline"
|
||||
to={`/u/${normalizeToBech32(pubkey, Bech32Prefix.Pubkey)}`}
|
||||
justifyContent="flex-start"
|
||||
rightIcon={<UserDnsIdentityIcon pubkey={pubkey} onlyIcon />}
|
||||
>
|
||||
{getUserDisplayName(metadata, pubkey)}
|
||||
</Button>
|
||||
|
@ -134,7 +134,7 @@ export const ShareIcon = createIcon({
|
||||
|
||||
export const ReplyIcon = createIcon({
|
||||
displayName: "reply-icon",
|
||||
d: "M11 20L1 12l10-8v5c5.523 0 10 4.477 10 10 0 .273-.01.543-.032.81-1.463-2.774-4.33-4.691-7.655-4.805L13 15h-2v5zm-2-7h4.034l.347.007c1.285.043 2.524.31 3.676.766C15.59 12.075 13.42 11 11 11H9V8.161L4.202 12 9 15.839V13z",
|
||||
d: "M5.763 17H20V5H4v13.385L5.763 17zm.692 2L2 22.5V4a1 1 0 0 1 1-1h18a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H6.455z",
|
||||
defaultProps,
|
||||
});
|
||||
|
||||
@ -150,6 +150,12 @@ export const VerificationFailed = createIcon({
|
||||
defaultProps,
|
||||
});
|
||||
|
||||
export const VerificationMissing = createIcon({
|
||||
displayName: "VerificationFailed",
|
||||
d: "M12 19c.828 0 1.5.672 1.5 1.5S12.828 22 12 22s-1.5-.672-1.5-1.5.672-1.5 1.5-1.5zm0-17c3.314 0 6 2.686 6 6 0 2.165-.753 3.29-2.674 4.923C13.399 14.56 13 15.297 13 17h-2c0-2.474.787-3.695 3.031-5.601C15.548 10.11 16 9.434 16 8c0-2.21-1.79-4-4-4S8 5.79 8 8v1H6V8c0-3.314 2.686-6 6-6z",
|
||||
defaultProps,
|
||||
});
|
||||
|
||||
export const SpyIcon = createIcon({
|
||||
displayName: "SpyIcon",
|
||||
d: "M17 13a4 4 0 1 1-4 4h-2a4 4 0 1 1-.535-2h3.07A3.998 3.998 0 0 1 17 13zM7 15a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm10 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4zM16 3a4 4 0 0 1 4 4v3h2v2H2v-2h2V7a4 4 0 0 1 4-4h8zm0 2H8c-1.054 0-2 .95-2 2v3h12V7c0-1.054-.95-2-2-2z",
|
||||
|
@ -13,9 +13,9 @@ import { UserTipButton } from "../user-tip-button";
|
||||
import { NoteRelays } from "./note-relays";
|
||||
import { useIsMobile } from "../../hooks/use-is-mobile";
|
||||
import { UserLink } from "../user-link";
|
||||
import { ReplyIcon } from "../icons";
|
||||
import { ReplyIcon, ShareIcon } from "../icons";
|
||||
import { PostModalContext } from "../../providers/post-modal-provider";
|
||||
import { buildReply } from "../../helpers/nostr-event";
|
||||
import { buildReply, buildShare } from "../../helpers/nostr-event";
|
||||
import { UserDnsIdentityIcon } from "../user-dns-identity";
|
||||
import { convertTimestampToDate } from "../../helpers/date";
|
||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||
@ -33,6 +33,7 @@ export const Note = React.memo(({ event, maxHeight }: NoteProps) => {
|
||||
const following = contacts?.contacts || [];
|
||||
|
||||
const reply = () => openModal(buildReply(event));
|
||||
const share = () => openModal(buildShare(event));
|
||||
|
||||
return (
|
||||
<Card variant="outline">
|
||||
@ -54,6 +55,7 @@ export const Note = React.memo(({ event, maxHeight }: NoteProps) => {
|
||||
<NoteContents event={event} trusted={following.includes(event.pubkey)} maxHeight={maxHeight} />
|
||||
</CardBody>
|
||||
<CardFooter padding="2" display="flex" gap="2">
|
||||
<UserTipButton pubkey={event.pubkey} eventId={event.id} size="xs" />
|
||||
<IconButton
|
||||
icon={<ReplyIcon />}
|
||||
title="Reply"
|
||||
@ -62,8 +64,15 @@ export const Note = React.memo(({ event, maxHeight }: NoteProps) => {
|
||||
size="xs"
|
||||
isDisabled={account.readonly}
|
||||
/>
|
||||
<IconButton
|
||||
icon={<ShareIcon />}
|
||||
onClick={share}
|
||||
aria-label="Share Note"
|
||||
title="Share Note"
|
||||
size="xs"
|
||||
isDisabled={account.readonly}
|
||||
/>
|
||||
<Box flexGrow={1} />
|
||||
<UserTipButton pubkey={event.pubkey} size="xs" />
|
||||
<NoteRelays event={event} size="xs" />
|
||||
<NoteMenu event={event} />
|
||||
</CardFooter>
|
||||
|
@ -15,7 +15,7 @@ import { Bech32Prefix, normalizeToBech32 } from "../../helpers/nip-19";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { MenuIconButton } from "../menu-icon-button";
|
||||
|
||||
import { ClipboardIcon, CodeIcon, IMAGE_ICONS, ShareIcon } from "../icons";
|
||||
import { ClipboardIcon, CodeIcon, IMAGE_ICONS } from "../icons";
|
||||
import { getReferences } from "../../helpers/nostr-event";
|
||||
|
||||
export const NoteMenu = ({ event }: { event: NostrEvent }) => {
|
||||
@ -26,10 +26,6 @@ export const NoteMenu = ({ event }: { event: NostrEvent }) => {
|
||||
return (
|
||||
<>
|
||||
<MenuIconButton>
|
||||
{/* TODO: should open the post modal and mention the note/relay */}
|
||||
{/* <MenuItem icon={<ShareIcon />} onClick={() => console.log("Share Note", event)}>
|
||||
Repost
|
||||
</MenuItem> */}
|
||||
<MenuItem
|
||||
as="a"
|
||||
icon={<Avatar src={IMAGE_ICONS.nostrGuruIcon} size="xs" />}
|
||||
|
@ -54,6 +54,10 @@ export const PostModal = ({ isOpen, onClose, initialDraft }: PostModalProps) =>
|
||||
const handleSubmit = async () => {
|
||||
setWaiting(true);
|
||||
const updatedDraft: DraftNostrEvent = { ...draft, created_at: moment().unix() };
|
||||
// add client tag, TODO: find a better place for this
|
||||
if (!updatedDraft.tags.some((t) => t[0] === "client")) {
|
||||
updatedDraft.tags.push(["client", "noStrudel"]);
|
||||
}
|
||||
const event = await requestSignature(updatedDraft);
|
||||
setWaiting(false);
|
||||
if (!event) return;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Spinner, Tooltip } from "@chakra-ui/react";
|
||||
import { useDnsIdentity } from "../hooks/use-dns-identity";
|
||||
import { useUserMetadata } from "../hooks/use-user-metadata";
|
||||
import { VerificationFailed, VerifiedIcon } from "./icons";
|
||||
import { VerificationFailed, VerificationMissing, VerifiedIcon } from "./icons";
|
||||
|
||||
export const UserDnsIdentityIcon = ({ pubkey, onlyIcon }: { pubkey: string; onlyIcon?: boolean }) => {
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
@ -11,21 +11,22 @@ export const UserDnsIdentityIcon = ({ pubkey, onlyIcon }: { pubkey: string; only
|
||||
return null;
|
||||
}
|
||||
|
||||
let title = metadata.nip05;
|
||||
|
||||
const renderIcon = () => {
|
||||
if (loading) {
|
||||
return <Spinner size="xs" ml="1" />;
|
||||
} else if (error) {
|
||||
return <VerificationFailed color="yellow.500" />;
|
||||
} else if (!identity) {
|
||||
return <VerificationMissing color="red.500" />;
|
||||
} else if (pubkey === identity.pubkey) {
|
||||
return <VerifiedIcon color="purple.500" />;
|
||||
} else {
|
||||
const isValid = !!identity && pubkey === identity.pubkey;
|
||||
return isValid ? <VerifiedIcon color="purple.500" /> : <VerificationFailed color="red.500" />;
|
||||
return <VerificationFailed color="red.500" />;
|
||||
}
|
||||
};
|
||||
|
||||
if (onlyIcon) {
|
||||
return <Tooltip label={title}>{renderIcon()}</Tooltip>;
|
||||
return <Tooltip label={metadata.nip05}>{renderIcon()}</Tooltip>;
|
||||
}
|
||||
return (
|
||||
<span>
|
||||
|
@ -2,6 +2,8 @@ import moment from "moment";
|
||||
import { getEventRelays } from "../services/event-relays";
|
||||
import { DraftNostrEvent, isETag, isPTag, NostrEvent, RTag } from "../types/nostr-event";
|
||||
import { RelayConfig, RelayMode } from "../classes/relay";
|
||||
import accountService from "../services/account";
|
||||
import { Kind } from "nostr-tools";
|
||||
|
||||
export function isReply(event: NostrEvent | DraftNostrEvent) {
|
||||
return !!event.tags.find((tag) => isETag(tag) && tag[3] !== "mention");
|
||||
@ -55,7 +57,7 @@ export function getReferences(event: NostrEvent | DraftNostrEvent) {
|
||||
};
|
||||
}
|
||||
|
||||
export function buildReply(event: NostrEvent): DraftNostrEvent {
|
||||
export function buildReply(event: NostrEvent, account = accountService.current.value): DraftNostrEvent {
|
||||
const refs = getReferences(event);
|
||||
const relay = getEventRelays(event.id).value?.[0] ?? "";
|
||||
|
||||
@ -70,14 +72,15 @@ export function buildReply(event: NostrEvent): DraftNostrEvent {
|
||||
}
|
||||
// add all ptags
|
||||
// TODO: omit my own pubkey
|
||||
const ptags = event.tags.filter(isPTag);
|
||||
const ptags = event.tags.filter(isPTag).filter((t) => !account || t[1] !== account.pubkey);
|
||||
tags.push(...ptags);
|
||||
if (!ptags.find((t) => t[1] === event.pubkey)) {
|
||||
// add the original authors pubkey if its not already there
|
||||
if (!ptags.some((t) => t[1] === event.pubkey)) {
|
||||
tags.push(["p", event.pubkey]);
|
||||
}
|
||||
|
||||
return {
|
||||
kind: 1,
|
||||
kind: Kind.Text,
|
||||
// TODO: be smarter about picking relay
|
||||
tags,
|
||||
content: "",
|
||||
@ -85,6 +88,22 @@ export function buildReply(event: NostrEvent): DraftNostrEvent {
|
||||
};
|
||||
}
|
||||
|
||||
export function buildShare(event: NostrEvent): DraftNostrEvent {
|
||||
const relay = getEventRelays(event.id).value?.[0] ?? "";
|
||||
|
||||
const tags: NostrEvent["tags"] = [];
|
||||
tags.push(["e", event.id, relay, "mention"]);
|
||||
tags.push(["p", event.pubkey]);
|
||||
|
||||
return {
|
||||
kind: Kind.Reaction,
|
||||
// TODO: be smarter about picking relay
|
||||
tags,
|
||||
content: "#[0]",
|
||||
created_at: moment().unix(),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseRTag(tag: RTag): RelayConfig {
|
||||
switch (tag[2]) {
|
||||
case "write":
|
||||
|
@ -32,7 +32,6 @@ class UserRelaysService extends CachedPubkeyEventRequester {
|
||||
return db.put("userRelays", event);
|
||||
}
|
||||
|
||||
// TODO: rxjs behavior subject dose not feel like the right thing to use here
|
||||
private parsedSubjects = new SuperMap<string, Subject<UserRelays>>(() => new Subject<UserRelays>());
|
||||
requestRelays(pubkey: string, relays: string[], alwaysRequest = false) {
|
||||
const sub = this.parsedSubjects.get(pubkey);
|
||||
|
Loading…
x
Reference in New Issue
Block a user