mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-18 22:44:26 +01:00
feat: show profile json
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { Component, createMemo } from 'solid-js';
|
import { Component, createMemo, Show } from 'solid-js';
|
||||||
|
|
||||||
import { type Event as NostrEvent } from 'nostr-tools/pure';
|
import { type Event as NostrEvent } from 'nostr-tools/pure';
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@ import usePool from '@/nostr/usePool';
|
|||||||
|
|
||||||
export type EventDebugModalProps = {
|
export type EventDebugModalProps = {
|
||||||
event: NostrEvent;
|
event: NostrEvent;
|
||||||
|
extra?: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -22,17 +23,26 @@ const EventDebugModal: Component<EventDebugModalProps> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<BasicModal onClose={props.onClose}>
|
<BasicModal onClose={props.onClose}>
|
||||||
<div class="p-2">
|
<div class="p-4">
|
||||||
<h2 class="text-lg font-bold">JSON</h2>
|
<h2 class="text-lg font-bold">Event JSON</h2>
|
||||||
<pre class="whitespace-pre-wrap break-all rounded-lg border border-border p-4 text-xs">
|
<pre class="whitespace-pre-wrap break-all rounded-lg border border-border p-4 text-xs">
|
||||||
{json()}
|
{json()}
|
||||||
</pre>
|
</pre>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<Copy class="h-4 w-4 hover:text-primary" text={json()} />
|
<Copy class="size-4 hover:text-primary" text={json()} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-2">
|
<Show when={props.extra}>
|
||||||
|
<div class="p-4">
|
||||||
|
<h2 class="text-lg font-bold">Extra</h2>
|
||||||
|
<pre class="whitespace-pre-wrap break-all rounded-lg border border-border p-4 text-xs">
|
||||||
|
{props.extra}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
<div class="p-4">
|
||||||
<h2 class="text-lg font-bold">Found in these relays</h2>
|
<h2 class="text-lg font-bold">Found in these relays</h2>
|
||||||
|
<p>If this is empty, it is from the cache.</p>
|
||||||
<pre class="whitespace-pre-wrap break-all rounded-lg border border-border p-2 text-xs">
|
<pre class="whitespace-pre-wrap break-all rounded-lg border border-border p-2 text-xs">
|
||||||
{seenOn()}
|
{seenOn()}
|
||||||
</pre>
|
</pre>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import ExclamationCircle from 'heroicons/24/solid/exclamation-circle.svg';
|
|||||||
|
|
||||||
import TextNoteContentDisplay from '@/components/event/textNote/TextNoteContentDisplay';
|
import TextNoteContentDisplay from '@/components/event/textNote/TextNoteContentDisplay';
|
||||||
import BasicModal from '@/components/modal/BasicModal';
|
import BasicModal from '@/components/modal/BasicModal';
|
||||||
|
import EventDebugModal from '@/components/modal/EventDebugModal';
|
||||||
import UserList from '@/components/modal/UserList';
|
import UserList from '@/components/modal/UserList';
|
||||||
import Timeline from '@/components/timeline/Timeline';
|
import Timeline from '@/components/timeline/Timeline';
|
||||||
import SafeLink from '@/components/utils/SafeLink';
|
import SafeLink from '@/components/utils/SafeLink';
|
||||||
@@ -58,7 +59,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
const [updatingContacts, setUpdatingContacts] = createSignal(false);
|
const [updatingContacts, setUpdatingContacts] = createSignal(false);
|
||||||
const [hoverFollowButton, setHoverFollowButton] = createSignal(false);
|
const [hoverFollowButton, setHoverFollowButton] = createSignal(false);
|
||||||
const [showFollowers, setShowFollowers] = createSignal(false);
|
const [showFollowers, setShowFollowers] = createSignal(false);
|
||||||
const [modal, setModal] = createSignal<'Following' | null>(null);
|
const [modal, setModal] = createSignal<'Following' | 'EventDebugModal' | null>(null);
|
||||||
const closeModal = () => setModal(null);
|
const closeModal = () => setModal(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -219,22 +220,33 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: i18n.t('profile.addUserColumn'),
|
content: i18n.t('profile.showJSON'),
|
||||||
onSelect: () => {
|
onSelect: () => {
|
||||||
const columnName = profile()?.name ?? npub();
|
setModal('EventDebugModal');
|
||||||
saveColumn(createPostsColumn({ name: columnName, pubkey: props.pubkey }));
|
|
||||||
request({ command: 'moveToLastColumn' }).catch((err) => console.error(err));
|
|
||||||
props.onClose?.();
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: i18n.t('profile.addUserHomeColumn'),
|
content: i18n.t('profile.addColumn'),
|
||||||
onSelect: () => {
|
items: [
|
||||||
const columnName = `${i18n.t('column.home')} / ${profile()?.name ?? npub()}`;
|
{
|
||||||
saveColumn(createFollowingColumn({ name: columnName, pubkey: props.pubkey }));
|
content: i18n.t('profile.addUserColumn'),
|
||||||
request({ command: 'moveToLastColumn' }).catch((err) => console.error(err));
|
onSelect: () => {
|
||||||
props.onClose?.();
|
const columnName = profile()?.name ?? npub();
|
||||||
},
|
saveColumn(createPostsColumn({ name: columnName, pubkey: props.pubkey }));
|
||||||
|
request({ command: 'moveToLastColumn' }).catch((err) => console.error(err));
|
||||||
|
props.onClose?.();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: i18n.t('profile.addUserHomeColumn'),
|
||||||
|
onSelect: () => {
|
||||||
|
const columnName = `${i18n.t('column.home')} / ${profile()?.name ?? npub()}`;
|
||||||
|
saveColumn(createFollowingColumn({ name: columnName, pubkey: props.pubkey }));
|
||||||
|
request({ command: 'moveToLastColumn' }).catch((err) => console.error(err));
|
||||||
|
props.onClose?.();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: !isMuted() ? i18n.t('profile.mute') : i18n.t('profile.unmute'),
|
content: !isMuted() ? i18n.t('profile.mute') : i18n.t('profile.unmute'),
|
||||||
@@ -461,6 +473,15 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
<Match when={modal() === 'Following'}>
|
<Match when={modal() === 'Following'}>
|
||||||
<UserList data={userFollowingPubkeys()} pubkeyExtractor={(e) => e} onClose={closeModal} />
|
<UserList data={userFollowingPubkeys()} pubkeyExtractor={(e) => e} onClose={closeModal} />
|
||||||
</Match>
|
</Match>
|
||||||
|
<Match when={modal() === 'EventDebugModal' && profileQuery.data} keyed>
|
||||||
|
{(event) => (
|
||||||
|
<EventDebugModal
|
||||||
|
event={event}
|
||||||
|
extra={JSON.stringify(profile(), null, 2)}
|
||||||
|
onClose={closeModal}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
<ul class="border-t border-border p-1">
|
<ul class="border-t border-border p-1">
|
||||||
<Timeline events={events()} />
|
<Timeline events={events()} />
|
||||||
|
|||||||
@@ -1,23 +1,37 @@
|
|||||||
import { createMemo, For, type Component, type JSX } from 'solid-js';
|
import { createMemo, For, Switch, Match, type Component, type JSX } from 'solid-js';
|
||||||
|
|
||||||
|
import ChevronRight from 'heroicons/24/outline/chevron-right.svg';
|
||||||
|
|
||||||
import usePopup, { type UsePopupProps } from '@/components/utils/usePopup';
|
import usePopup, { type UsePopupProps } from '@/components/utils/usePopup';
|
||||||
|
|
||||||
export type MenuItem = {
|
export type SelectableItem = {
|
||||||
content: JSX.Element;
|
content: JSX.Element;
|
||||||
when?: () => boolean;
|
when?: () => boolean;
|
||||||
onSelect?: () => void;
|
onSelect: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SubMenu = Omit<SelectableItem, 'type' | 'onSelect'> & {
|
||||||
|
content: JSX.Element;
|
||||||
|
items: (SelectableItem | SubMenu)[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MenuItem = SelectableItem | SubMenu;
|
||||||
|
|
||||||
export type UseContextMenuProps = Omit<UsePopupProps, 'popup'> & {
|
export type UseContextMenuProps = Omit<UsePopupProps, 'popup'> & {
|
||||||
menu: MenuItem[];
|
menu: (MenuItem | SubMenu)[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MenuItemDisplayProps = {
|
export type SelectableItemDisplayProps = {
|
||||||
item: MenuItem;
|
item: SelectableItem;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MenuItemDisplay: Component<MenuItemDisplayProps> = (props) => {
|
export type SubMenuDisplayProps = {
|
||||||
|
submenu: SubMenu;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SelectableItemDisplay: Component<SelectableItemDisplayProps> = (props) => {
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
props.item?.onSelect?.();
|
props.item?.onSelect?.();
|
||||||
props.onClose();
|
props.onClose();
|
||||||
@@ -32,15 +46,59 @@ const MenuItemDisplay: Component<MenuItemDisplayProps> = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SubMenuDisplay: Component<SubMenuDisplayProps> = (props) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||||
|
const contextMenuPopup = useContextMenu(() => ({
|
||||||
|
menu: props.submenu.items,
|
||||||
|
position: 'right',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
contextMenuPopup.open();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li ref={contextMenuPopup.targetRef} class="border-b border-border hover:bg-bg-tertiary">
|
||||||
|
<button class="flex w-full items-center py-1 pe-2 ps-4 text-start" onClick={handleClick}>
|
||||||
|
<span class="flex-1">{props.submenu.content}</span>
|
||||||
|
<span class="inline-block size-4 shrink-0">
|
||||||
|
<ChevronRight />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
{contextMenuPopup.popup()}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ensureSelectableItem = (item: MenuItem): SelectableItem | null => {
|
||||||
|
if ('onSelect' in item) return item;
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ensureSubMenu = (item: MenuItem): SubMenu | null => {
|
||||||
|
if ('items' in item) return item;
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
const useContextMenu = (propsProvider: () => UseContextMenuProps) => {
|
const useContextMenu = (propsProvider: () => UseContextMenuProps) => {
|
||||||
const props = createMemo(propsProvider);
|
const props = createMemo(propsProvider);
|
||||||
|
|
||||||
const popup = usePopup(() => ({
|
const popup = usePopup(() => ({
|
||||||
|
ensureWidth: 300,
|
||||||
...props(),
|
...props(),
|
||||||
popup: (
|
popup: (
|
||||||
<ul class="min-w-[96px] rounded border border-border bg-bg shadow-md">
|
<ul class="min-w-[96px] rounded border border-border bg-bg shadow-md">
|
||||||
<For each={props().menu.filter((e) => e.when == null || e.when())}>
|
<For each={props().menu.filter((e) => e.when == null || e.when())}>
|
||||||
{(item: MenuItem) => <MenuItemDisplay item={item} onClose={() => popup.close()} />}
|
{(item) => (
|
||||||
|
<Switch>
|
||||||
|
<Match when={ensureSelectableItem(item)} keyed>
|
||||||
|
{(v) => <SelectableItemDisplay item={v} onClose={() => popup.close()} />}
|
||||||
|
</Match>
|
||||||
|
<Match when={ensureSubMenu(item)} keyed>
|
||||||
|
{(v) => <SubMenuDisplay submenu={v} onClose={() => popup.close()} />}
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
)}
|
||||||
</For>
|
</For>
|
||||||
</ul>
|
</ul>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -59,10 +59,12 @@ export default {
|
|||||||
followingCurrently: 'Following',
|
followingCurrently: 'Following',
|
||||||
followsYou: 'follows you',
|
followsYou: 'follows you',
|
||||||
copyPubkey: 'Copy ID',
|
copyPubkey: 'Copy ID',
|
||||||
|
showJSON: 'Show JSON',
|
||||||
mute: 'Mute',
|
mute: 'Mute',
|
||||||
unmute: 'Unmute',
|
unmute: 'Unmute',
|
||||||
followMyself: 'Follow myself',
|
followMyself: 'Follow myself',
|
||||||
unfollowMyself: 'Unfollow myself',
|
unfollowMyself: 'Unfollow myself',
|
||||||
|
addColumn: 'Add column',
|
||||||
addUserColumn: 'Add user column',
|
addUserColumn: 'Add user column',
|
||||||
addUserHomeColumn: 'Add home column',
|
addUserHomeColumn: 'Add home column',
|
||||||
confirmUnfollow: 'Do you really want to unfollow?',
|
confirmUnfollow: 'Do you really want to unfollow?',
|
||||||
|
|||||||
@@ -58,10 +58,12 @@ export default {
|
|||||||
followingCurrently: 'フォロー中',
|
followingCurrently: 'フォロー中',
|
||||||
followsYou: 'フォローされています',
|
followsYou: 'フォローされています',
|
||||||
copyPubkey: 'IDをコピー',
|
copyPubkey: 'IDをコピー',
|
||||||
|
showJSON: 'JSONを確認',
|
||||||
mute: 'ミュート',
|
mute: 'ミュート',
|
||||||
unmute: 'ミュート解除',
|
unmute: 'ミュート解除',
|
||||||
followMyself: '自分をフォロー',
|
followMyself: '自分をフォロー',
|
||||||
unfollowMyself: '自分をフォロー解除',
|
unfollowMyself: '自分をフォロー解除',
|
||||||
|
addColumn: 'カラムを追加',
|
||||||
addUserColumn: 'ユーザカラムを追加',
|
addUserColumn: 'ユーザカラムを追加',
|
||||||
addUserHomeColumn: 'ホームカラムを追加',
|
addUserHomeColumn: 'ホームカラムを追加',
|
||||||
confirmUnfollow: '本当にフォロー解除しますか?',
|
confirmUnfollow: '本当にフォロー解除しますか?',
|
||||||
|
|||||||
Reference in New Issue
Block a user