feat: nostr activity and tag support

This commit is contained in:
benalleng
2023-09-14 20:08:38 -04:00
committed by Paul Miller
parent 54e2db2ce5
commit fa1dab3eb4
7 changed files with 69 additions and 14 deletions

View File

@@ -56,6 +56,7 @@ export function ContactViewer(props: {
showToast(result.error); showToast(result.error);
return; return;
} else { } else {
result.value.privateTag = props.contact.name;
if ( if (
result.value?.address || result.value?.address ||
result.value?.invoice || result.value?.invoice ||

View File

@@ -6,9 +6,11 @@ import {
Show, Show,
Switch Switch
} from "solid-js"; } from "solid-js";
import { Dynamic } from "solid-js/web";
import rightArrow from "~/assets/icons/right-arrow.svg"; import rightArrow from "~/assets/icons/right-arrow.svg";
import { AmountSats, VStack } from "~/components"; import { AmountSats, TinyText, VStack } from "~/components";
import { useI18n } from "~/i18n/context";
import { useMegaStore } from "~/state/megaStore"; import { useMegaStore } from "~/state/megaStore";
import { fetchZaps, getHexpubFromNpub } from "~/utils"; import { fetchZaps, getHexpubFromNpub } from "~/utils";
import { timeAgo } from "~/utils/prettyPrintTime"; import { timeAgo } from "~/utils/prettyPrintTime";
@@ -31,6 +33,7 @@ function formatProfileLink(hexpub: string): string {
} }
export function NostrActivity() { export function NostrActivity() {
const i18n = useI18n();
const [state, _actions] = useMegaStore(); const [state, _actions] = useMegaStore();
const [data, { refetch }] = createResource(state.npub, fetchZaps); const [data, { refetch }] = createResource(state.npub, fetchZaps);
@@ -93,10 +96,10 @@ export function NostrActivity() {
</a> </a>
</Match> </Match>
<Match when={zap.kind === "private"}> <Match when={zap.kind === "private"}>
Private {i18n.t("activity.private")}
</Match> </Match>
<Match when={zap.kind === "anonymous"}> <Match when={zap.kind === "anonymous"}>
Anonymous {i18n.t("activity.anonymous")}
</Match> </Match>
</Switch> </Switch>
</span> </span>
@@ -151,10 +154,36 @@ export function NostrActivity() {
</div> </div>
<Show when={zap.content}> <Show when={zap.content}>
<hr class="my-2 border-m-grey-750" /> <hr class="my-2 border-m-grey-750" />
<p <TinyText>
class="truncate text-center text-sm font-light text-neutral-200" <Dynamic
textContent={zap.content} component={
/> zap.content?.includes("From:") ||
zap.content?.includes("://")
? "a"
: "p"
}
href={
zap.content?.split("nostr:")[1]
? formatProfileLink(
getHexpubFromNpub(
zap.content?.split(
"nostr:"
)[1]
) ?? ""
)
: zap.content
}
class="block truncate text-center text-sm font-light text-neutral-200"
target="_blank"
rel="noopener noreferrer"
>
{zap.content?.includes("From:")
? `${i18n.t(
"activity.from"
)} ${zap.content?.split("nostr:")[1]}`
: zap.content}
</Dynamic>
</TinyText>
</Show> </Show>
</div> </div>
)} )}

View File

@@ -16,6 +16,7 @@ export function TagEditor(props: {
selectedValues: Partial<MutinyTagItem>[]; selectedValues: Partial<MutinyTagItem>[];
setSelectedValues: (value: Partial<MutinyTagItem>[]) => void; setSelectedValues: (value: Partial<MutinyTagItem>[]) => void;
placeholder: string; placeholder: string;
autoFillTag?: string | undefined;
}) { }) {
const [_state, actions] = useMegaStore(); const [_state, actions] = useMegaStore();
const [availableTags, setAvailableTags] = createSignal<MutinyTagItem[]>([]); const [availableTags, setAvailableTags] = createSignal<MutinyTagItem[]>([]);
@@ -28,12 +29,24 @@ export function TagEditor(props: {
.filter((tag) => tag.kind === "Contact") .filter((tag) => tag.kind === "Contact")
.sort(sortByLastUsed) .sort(sortByLastUsed)
); );
if (props.autoFillTag && availableTags()) {
const tagToAutoSelect = availableTags().find(
(tag) => tag.name === props.autoFillTag
);
if (tagToAutoSelect) {
props.setSelectedValues([
...props.selectedValues,
tagToAutoSelect
]);
}
}
} }
}); });
const selectProps = createMemo(() => { const selectProps = createMemo(() => {
return createOptions(availableTags() || [], { return createOptions(availableTags() || [], {
key: "name", key: "name",
disable: (value) => props.selectedValues.includes(value),
filterable: true, // Default filterable: true, // Default
createable: createLabelValue createable: createLabelValue
}); });
@@ -42,8 +55,6 @@ export function TagEditor(props: {
const onChange = (selected: MutinyTagItem[]) => { const onChange = (selected: MutinyTagItem[]) => {
props.setSelectedValues(selected); props.setSelectedValues(selected);
console.log(selected);
const lastValue = selected[selected.length - 1]; const lastValue = selected[selected.length - 1];
if ( if (
lastValue && lastValue &&
@@ -54,7 +65,6 @@ export function TagEditor(props: {
} }
}; };
// FIXME: eslint is mad about reactivity
const onTagTap = (tag: MutinyTagItem) => { const onTagTap = (tag: MutinyTagItem) => {
props.setSelectedValues([...props.selectedValues!, tag]); props.setSelectedValues([...props.selectedValues!, tag]);
}; };
@@ -70,10 +80,16 @@ export function TagEditor(props: {
/> />
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<Show when={availableTags() && availableTags()!.length > 0}> <Show when={availableTags() && availableTags()!.length > 0}>
<For each={availableTags()!.slice(0, 3)}> <For
each={availableTags()!.slice(0, 3).sort(sortByLastUsed)}
>
{(tag) => ( {(tag) => (
<TinyButton
hidden={props.selectedValues.includes(tag)}
tag={tag}
// eslint-disable-next-line solid/reactivity // eslint-disable-next-line solid/reactivity
<TinyButton tag={tag} onClick={() => onTagTap(tag)}> onClick={() => onTagTap(tag)}
>
{tag.name} {tag.name}
</TinyButton> </TinyButton>
)} )}

View File

@@ -258,6 +258,7 @@ export const TinyText: ParentComponent = (props) => {
export const TinyButton: ParentComponent<{ export const TinyButton: ParentComponent<{
onClick: () => void; onClick: () => void;
tag?: MutinyTagItem; tag?: MutinyTagItem;
hidden?: boolean;
}> = (props) => { }> = (props) => {
// TODO: don't need to run this if it's not a contact // TODO: don't need to run this if it's not a contact
const [gradient] = createResource(async () => { const [gradient] = createResource(async () => {
@@ -272,6 +273,7 @@ export const TinyButton: ParentComponent<{
return ( return (
<button <button
class="rounded-lg bg-white/10 px-2 py-1" class="rounded-lg bg-white/10 px-2 py-1"
classList={{ hidden: props.hidden }}
onClick={() => props.onClick()} onClick={() => props.onClick()}
style={{ background: bg() }} style={{ background: bg() }}
> >

View File

@@ -151,7 +151,10 @@ export default {
unknown: "Unknown", unknown: "Unknown",
import_contacts: import_contacts:
"Import your contacts from nostr to see who they're zapping.", "Import your contacts from nostr to see who they're zapping.",
coming_soon: "Coming soon" coming_soon: "Coming soon",
private: "Private",
anonymous: "Anonymous",
from: "From:"
}, },
redshift: { redshift: {
title: "Redshift", title: "Redshift",

View File

@@ -11,6 +11,7 @@ export type ParsedParams = {
amount_sats?: bigint; amount_sats?: bigint;
network?: string; network?: string;
memo?: string; memo?: string;
privateTag?: string;
node_pubkey?: string; node_pubkey?: string;
lnurl?: string; lnurl?: string;
}; };

View File

@@ -685,6 +685,9 @@ export default function Send() {
{i18n.t("common.private_tags")} {i18n.t("common.private_tags")}
</SmallHeader> </SmallHeader>
<TagEditor <TagEditor
autoFillTag={
destination()?.privateTag
}
selectedValues={selectedContacts()} selectedValues={selectedContacts()}
setSelectedValues={ setSelectedValues={
setSelectedContacts setSelectedContacts