feat: enable to copy event info

This commit is contained in:
Shusui MOYATANI
2023-04-06 02:12:52 +09:00
parent ed03386e50
commit 8e1714f476
4 changed files with 148 additions and 28 deletions

View File

@@ -42,7 +42,7 @@ const Column: Component<ColumnProps> = (props) => {
<TimelineContext.Provider value={timelineState}>
<div
ref={columnDivRef}
class="relative flex w-[80vw] shrink-0 snap-center snap-always flex-col border-r sm:snap-align-none"
class="flex w-[80vw] shrink-0 snap-center snap-always flex-col border-r sm:snap-align-none"
classList={{
'sm:w-[500px]': width() === 'widest',
'sm:w-[350px]': width() === 'wide',
@@ -50,14 +50,21 @@ const Column: Component<ColumnProps> = (props) => {
'sm:w-[270px]': width() === 'narrow',
}}
>
<div class="flex h-8 shrink-0 items-center border-b bg-white px-2">
{/* <span class="column-icon">🏠</span> */}
<span class="column-name">{props.name}</span>
</div>
<div class="flex flex-col overflow-y-scroll scroll-smooth">{props.children}</div>
<Show when={timelineState.timelineState.content} keyed>
<Show
when={timelineState.timelineState.content}
keyed
fallback={
<>
<div class="flex h-8 shrink-0 items-center border-b bg-white px-2">
{/* <span class="column-icon">🏠</span> */}
<span class="column-name">{props.name}</span>
</div>
<div class="flex flex-col overflow-y-scroll scroll-smooth">{props.children}</div>
</>
}
>
{(timeline) => (
<div class="absolute h-full w-full bg-white">
<div class="h-full w-full bg-white">
<div class="flex h-8 shrink-0 items-center border-b bg-white px-2">
<button
class="flex w-full items-center gap-1"

View File

@@ -0,0 +1,100 @@
import { createSignal, onCleanup, createEffect, For, type Component, type JSX } from 'solid-js';
export type MenuItem = {
content: () => JSX.Element;
onSelect?: () => void;
};
export type ContextMenuProps = {
menu: MenuItem[];
children: JSX.Element;
};
export type MenuDisplayProps = {
menu: MenuItem[];
onClose: () => void;
};
export type MenuItemDisplayProps = {
item: MenuItem;
onClose: () => void;
};
const MenuItemDisplay: Component<MenuItemDisplayProps> = (props) => {
const handleClick = () => {
props.item?.onSelect?.();
props.onClose();
};
return (
<li class="border-b hover:bg-stone-200">
<button class="px-4 py-1" onClick={handleClick}>
{props.item.content()}
</button>
</li>
);
};
const MenuDisplay: Component<MenuDisplayProps> = (props) => {
return (
<ul>
<For each={props.menu}>
{(item) => <MenuItemDisplay item={item} onClose={props.onClose} />}
</For>
</ul>
);
};
const ContextMenu: Component<ContextMenuProps> = (props) => {
let menuRef: HTMLDivElement | undefined;
const [isOpen, setIsOpen] = createSignal(false);
const handleClickOutside = (ev: MouseEvent) => {
const target = ev.target as HTMLElement;
if (target != null && !menuRef?.contains(target)) {
setIsOpen(false);
}
};
const addClickOutsideHandler = () => {
document.addEventListener('mousedown', handleClickOutside);
};
const removeClickOutsideHandler = () => {
document.removeEventListener('mousedown', handleClickOutside);
};
const handleClick: JSX.EventHandler<HTMLButtonElement, MouseEvent> = (ev) => {
if (menuRef == null) return;
const buttonRect = ev.target.getBoundingClientRect();
menuRef.style.left = `${buttonRect.left}px`;
menuRef.style.top = `${buttonRect.top + buttonRect.height}px`;
setIsOpen(true);
};
createEffect(() => {
if (isOpen()) {
addClickOutsideHandler();
} else {
removeClickOutsideHandler();
}
});
onCleanup(() => removeClickOutsideHandler());
return (
<div>
<button onClick={handleClick}>{props.children}</button>
<div
ref={menuRef}
class="absolute z-20 min-w-[48px] rounded border bg-white shadow-md"
classList={{ hidden: !isOpen(), block: isOpen() }}
>
<MenuDisplay menu={props.menu} onClose={() => setIsOpen(false)} />
</div>
</div>
);
};
export default ContextMenu;

View File

@@ -1,5 +1,5 @@
import { Show, For, createSignal, createMemo, onMount, type JSX, type Component } from 'solid-js';
import type { Event as NostrEvent } from 'nostr-tools';
import { nip19, type Event as NostrEvent } from 'nostr-tools';
import { createMutation } from '@tanstack/solid-query';
import HeartOutlined from 'heroicons/24/outline/heart.svg';
@@ -31,6 +31,7 @@ import NotePostForm from '@/components/NotePostForm';
import ensureNonNull from '@/utils/ensureNonNull';
import npubEncodeFallback from '@/utils/npubEncodeFallback';
import useSubscription from '@/nostr/useSubscription';
import ContextMenu, { MenuItem } from '../ContextMenu';
export type TextNoteDisplayProps = {
event: NostrEvent;
@@ -38,9 +39,26 @@ export type TextNoteDisplayProps = {
actions?: boolean;
};
const { noteEncode } = nip19;
const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
let contentRef: HTMLDivElement | undefined;
const menu: MenuItem[] = [
{
content: () => 'IDをコピー',
onSelect: () => {
navigator.clipboard.writeText(noteEncode(props.event.id)).catch((err) => window.alert(err));
},
},
{
content: () => 'JSONとしてコピー',
onSelect: () => {
navigator.clipboard.writeText(JSON.stringify(props.event)).catch((err) => window.alert(err));
},
},
];
const { config } = useConfig();
const formatDate = useFormatDate();
const pubkey = usePubkey();
@@ -51,7 +69,6 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
const closeReplyForm = () => setShowReplyForm(false);
const [showOverflow, setShowOverflow] = createSignal(false);
const [overflow, setOverflow] = createSignal(false);
const [showMenu, setShowMenu] = createSignal(false);
const event = createMemo(() => eventWrapper(props.event));
@@ -88,13 +105,11 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
mutationKey: ['publishDeprecatedRepost', event().id],
mutationFn: commands.publishDeprecatedRepost.bind(commands),
onSuccess: () => {
console.log('succeeded to publish deprecated reposts');
invalidateDeprecatedReposts().catch((err) =>
console.error('failed to refetch deprecated reposts', err),
);
console.log('succeeded to publish reposts');
invalidateDeprecatedReposts().catch((err) => console.error('failed to refetch reposts', err));
},
onError: (err) => {
console.error('failed to publish deprecated repost: ', err);
console.error('failed to publish repost: ', err);
},
});
@@ -205,10 +220,12 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
</div>
</button>
<div class="created-at shrink-0">
<button
<a
href={`nostr:${noteEncode(event().id)}`}
type="button"
class="hover:underline"
onClick={() => {
onClick={(ev) => {
ev.preventDefault();
timelineContext?.setTimeline({
type: 'Replies',
event: props.event,
@@ -216,7 +233,7 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
}}
>
{createdAt()}
</button>
</a>
</div>
</div>
<div
@@ -318,15 +335,11 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
</Show>
</div>
<div>
<button
class="h-4 w-4 text-zinc-400"
onClick={(ev) => {
ev.stopPropagation();
setShowMenu((current) => !current);
}}
>
<EllipsisHorizontal />
</button>
<ContextMenu menu={menu}>
<span class="inline-block h-4 w-4 text-zinc-400">
<EllipsisHorizontal />
</span>
</ContextMenu>
</div>
</div>
</Show>