mirror of
https://github.com/lumehq/lume.git
synced 2025-09-20 11:00:37 +02:00
add muted popover
This commit is contained in:
52
src/components/channels/channelBlackList.tsx
Normal file
52
src/components/channels/channelBlackList.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { UserMuted } from '@components/user/muted';
|
||||||
|
|
||||||
|
import { Popover, Transition } from '@headlessui/react';
|
||||||
|
import { MicMute } from 'iconoir-react';
|
||||||
|
import { Fragment } from 'react';
|
||||||
|
|
||||||
|
export const ChannelBlackList = ({ blacklist }: { blacklist: any }) => {
|
||||||
|
return (
|
||||||
|
<Popover className="relative">
|
||||||
|
{({ open }) => (
|
||||||
|
<>
|
||||||
|
<Popover.Button
|
||||||
|
className={`group inline-flex h-8 w-8 items-center justify-center rounded-md ${
|
||||||
|
open ? 'bg-zinc-800 hover:bg-zinc-700' : 'bg-zinc-900 hover:bg-zinc-800'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<MicMute width={16} height={16} className="text-zinc-400 group-hover:text-zinc-200" />
|
||||||
|
</Popover.Button>
|
||||||
|
<Transition
|
||||||
|
as={Fragment}
|
||||||
|
enter="transition ease-out duration-200"
|
||||||
|
enterFrom="opacity-0 translate-y-1"
|
||||||
|
enterTo="opacity-100 translate-y-0"
|
||||||
|
leave="transition ease-in duration-150"
|
||||||
|
leaveFrom="opacity-100 translate-y-0"
|
||||||
|
leaveTo="opacity-0 translate-y-1"
|
||||||
|
>
|
||||||
|
<Popover.Panel className="absolute right-0 z-10 mt-1 w-screen max-w-xs transform px-4 sm:px-0">
|
||||||
|
<div className="flex flex-col gap-2 overflow-hidden rounded-lg border border-zinc-800 bg-zinc-900 shadow-popover">
|
||||||
|
<div className="h-min w-full shrink-0 border-b border-zinc-800 p-3">
|
||||||
|
<div className="flex flex-col gap-0.5">
|
||||||
|
<h3 className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text font-semibold leading-none text-transparent">
|
||||||
|
Your muted list
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs leading-tight text-zinc-400">
|
||||||
|
Currently, unmute only affect locally, when you move to new client, muted list will loaded again
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="px-3 pb-3 pt-1">
|
||||||
|
{blacklist.map((item: any) => (
|
||||||
|
<UserMuted key={item.id} data={item} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Popover.Panel>
|
||||||
|
</Transition>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
@@ -125,8 +125,8 @@ export const UpdateChannelModal = ({ id }: { id: string }) => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<Dialog.Description className="leading-tight text-zinc-400">
|
<Dialog.Description className="leading-tight text-zinc-400">
|
||||||
Channels are freedom square, everyone can speech freely, no one can stop you or deceive what to
|
New metadata will be published on all relays, and will be immediately available to all users, so
|
||||||
speech
|
please carefully.
|
||||||
</Dialog.Description>
|
</Dialog.Description>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
65
src/components/user/muted.tsx
Normal file
65
src/components/user/muted.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||||
|
|
||||||
|
import { useProfileMetadata } from '@utils/hooks/useProfileMetadata';
|
||||||
|
import { shortenKey } from '@utils/shortenKey';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
export const UserMuted = ({ data }: { data: any }) => {
|
||||||
|
const profile = useProfileMetadata(data.content);
|
||||||
|
const [status, setStatus] = useState(data.status);
|
||||||
|
|
||||||
|
const unmute = async () => {
|
||||||
|
const { updateItemInBlacklist } = await import('@utils/storage');
|
||||||
|
const res = await updateItemInBlacklist(data.content, 0);
|
||||||
|
if (res) {
|
||||||
|
setStatus(0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mute = async () => {
|
||||||
|
const { updateItemInBlacklist } = await import('@utils/storage');
|
||||||
|
const res = await updateItemInBlacklist(data.content, 1);
|
||||||
|
if (res) {
|
||||||
|
setStatus(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<div className="relative h-9 w-9 shrink rounded-md">
|
||||||
|
<img
|
||||||
|
src={profile?.picture || DEFAULT_AVATAR}
|
||||||
|
alt={data.content}
|
||||||
|
className="h-9 w-9 rounded-md object-cover"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-full flex-1 flex-col items-start gap-0.5 text-start">
|
||||||
|
<span className="truncate text-sm font-medium leading-none text-zinc-200">
|
||||||
|
{profile?.display_name || profile?.name}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs leading-none text-zinc-400">{shortenKey(data.content)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{status === 1 ? (
|
||||||
|
<button
|
||||||
|
onClick={() => unmute()}
|
||||||
|
className="inline-flex h-6 w-min items-center justify-center rounded px-1.5 text-xs font-medium leading-none text-zinc-400 hover:bg-zinc-800 hover:text-fuchsia-500"
|
||||||
|
>
|
||||||
|
Unmute
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={() => mute()}
|
||||||
|
className="inline-flex h-6 w-min items-center justify-center rounded px-1.5 text-xs font-medium leading-none text-zinc-400 hover:bg-zinc-800 hover:text-fuchsia-500"
|
||||||
|
>
|
||||||
|
Mute
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@@ -1,4 +1,5 @@
|
|||||||
import { AccountContext } from '@components/accountProvider';
|
import { AccountContext } from '@components/accountProvider';
|
||||||
|
import { ChannelBlackList } from '@components/channels/channelBlackList';
|
||||||
import { ChannelProfile } from '@components/channels/channelProfile';
|
import { ChannelProfile } from '@components/channels/channelProfile';
|
||||||
import { UpdateChannelModal } from '@components/channels/updateChannelModal';
|
import { UpdateChannelModal } from '@components/channels/updateChannelModal';
|
||||||
import { FormChannel } from '@components/form/channel';
|
import { FormChannel } from '@components/form/channel';
|
||||||
@@ -12,7 +13,6 @@ import { dateToUnix, hoursAgo } from '@utils/getDate';
|
|||||||
import { usePageContext } from '@utils/hooks/usePageContext';
|
import { usePageContext } from '@utils/hooks/usePageContext';
|
||||||
import { arrayObjToPureArr } from '@utils/transform';
|
import { arrayObjToPureArr } from '@utils/transform';
|
||||||
|
|
||||||
import { EyeClose } from 'iconoir-react';
|
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import { useResetAtom } from 'jotai/utils';
|
import { useResetAtom } from 'jotai/utils';
|
||||||
import { Suspense, lazy, useContext, useRef } from 'react';
|
import { Suspense, lazy, useContext, useRef } from 'react';
|
||||||
@@ -21,12 +21,14 @@ import useSWRSubscription from 'swr/subscription';
|
|||||||
const ChannelMessages = lazy(() => import('@components/channels/messages'));
|
const ChannelMessages = lazy(() => import('@components/channels/messages'));
|
||||||
|
|
||||||
let mutedList: any = [];
|
let mutedList: any = [];
|
||||||
let hidedList: any = [];
|
let activeMutedList: any = [];
|
||||||
|
let activeHidedList: any = [];
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
const { getBlacklist, getActiveAccount } = await import('@utils/storage');
|
const { getBlacklist, getActiveBlacklist, getActiveAccount } = await import('@utils/storage');
|
||||||
const activeAccount = await getActiveAccount();
|
const activeAccount = await getActiveAccount();
|
||||||
hidedList = await getBlacklist(activeAccount.id, 43);
|
activeHidedList = await getActiveBlacklist(activeAccount.id, 43);
|
||||||
|
activeMutedList = await getActiveBlacklist(activeAccount.id, 44);
|
||||||
mutedList = await getBlacklist(activeAccount.id, 44);
|
mutedList = await getBlacklist(activeAccount.id, 44);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,8 +47,8 @@ export function Page() {
|
|||||||
const resetChannelReply = useResetAtom(channelReplyAtom);
|
const resetChannelReply = useResetAtom(channelReplyAtom);
|
||||||
|
|
||||||
const now = useRef(new Date());
|
const now = useRef(new Date());
|
||||||
const hided = arrayObjToPureArr(hidedList);
|
const hided = arrayObjToPureArr(activeHidedList);
|
||||||
const muted = arrayObjToPureArr(mutedList);
|
const muted = arrayObjToPureArr(activeMutedList);
|
||||||
|
|
||||||
useSWRSubscription(id, () => {
|
useSWRSubscription(id, () => {
|
||||||
// reset channel reply
|
// reset channel reply
|
||||||
@@ -87,9 +89,7 @@ export function Page() {
|
|||||||
<ChannelProfile id={id} pubkey={channelPubkey} />
|
<ChannelProfile id={id} pubkey={channelPubkey} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="inline-flex h-8 w-8 items-center justify-center rounded-md bg-zinc-900">
|
<ChannelBlackList blacklist={mutedList} />
|
||||||
<EyeClose width={16} height={16} className="text-zinc-400" />
|
|
||||||
</div>
|
|
||||||
{activeAccount.pubkey === channelPubkey && <UpdateChannelModal id={id} />}
|
{activeAccount.pubkey === channelPubkey && <UpdateChannelModal id={id} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -174,7 +174,15 @@ export async function updateLastLogin(value: number) {
|
|||||||
// get blacklist by kind and account id
|
// get blacklist by kind and account id
|
||||||
export async function getBlacklist(account_id: number, kind: number) {
|
export async function getBlacklist(account_id: number, kind: number) {
|
||||||
const db = await connect();
|
const db = await connect();
|
||||||
return await db.select(`SELECT content FROM blacklist WHERE account_id = "${account_id}" AND kind = "${kind}";`);
|
return await db.select(`SELECT * FROM blacklist WHERE account_id = "${account_id}" AND kind = "${kind}";`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get active blacklist by kind and account id
|
||||||
|
export async function getActiveBlacklist(account_id: number, kind: number) {
|
||||||
|
const db = await connect();
|
||||||
|
return await db.select(
|
||||||
|
`SELECT content FROM blacklist WHERE account_id = "${account_id}" AND kind = "${kind}" AND status = 1;`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add to blacklist
|
// add to blacklist
|
||||||
|
@@ -25,6 +25,7 @@ module.exports = {
|
|||||||
inset 0 0 0 0.3px hsl(0deg 0% 100% / 30%),
|
inset 0 0 0 0.3px hsl(0deg 0% 100% / 30%),
|
||||||
0 0 0 0.5px hsl(0deg 0% 100% / 40%);
|
0 0 0 0.5px hsl(0deg 0% 100% / 40%);
|
||||||
`,
|
`,
|
||||||
|
popover: `0px 0px 7px rgba(0,0,0,0.52)`,
|
||||||
inner: `
|
inner: `
|
||||||
0 2px 2px rgb(4 4 7 / 45%),
|
0 2px 2px rgb(4 4 7 / 45%),
|
||||||
0 8px 24px rgb(4 4 7 / 60%)
|
0 8px 24px rgb(4 4 7 / 60%)
|
||||||
|
Reference in New Issue
Block a user