refactor all widgets
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { FocusIcon } from '@shared/icons';
|
||||
import { NoteReaction } from '@shared/notes/actions/reaction';
|
||||
import { NoteReply } from '@shared/notes/actions/reply';
|
||||
import { NoteRepost } from '@shared/notes/actions/repost';
|
||||
import { NoteZap } from '@shared/notes/actions/zap';
|
||||
|
||||
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||
import { WidgetKinds } from '@stores/constants';
|
||||
|
||||
import { useWidget } from '@utils/hooks/useWidget';
|
||||
|
||||
export function NoteActions({
|
||||
id,
|
||||
@@ -21,8 +21,7 @@ export function NoteActions({
|
||||
extraButtons?: boolean;
|
||||
root?: string;
|
||||
}) {
|
||||
const { db } = useStorage();
|
||||
const setWidget = useWidgets((state) => state.setWidget);
|
||||
const { addWidget } = useWidget();
|
||||
|
||||
return (
|
||||
<Tooltip.Provider>
|
||||
@@ -40,7 +39,7 @@ export function NoteActions({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setWidget(db, {
|
||||
addWidget.mutate({
|
||||
kind: WidgetKinds.local.thread,
|
||||
title: 'Thread',
|
||||
content: id,
|
||||
|
||||
@@ -4,8 +4,6 @@ import {
|
||||
MediaController,
|
||||
MediaMuteButton,
|
||||
MediaPlayButton,
|
||||
MediaSeekBackwardButton,
|
||||
MediaSeekForwardButton,
|
||||
MediaTimeDisplay,
|
||||
MediaTimeRange,
|
||||
MediaVolumeRange,
|
||||
@@ -37,19 +35,19 @@ export function FileNote(props: { event?: NDKEvent }) {
|
||||
if (type === 'video') {
|
||||
return (
|
||||
<div className="mb-2 mt-3">
|
||||
<MediaController key={url} className="aspect-video overflow-hidden rounded-lg">
|
||||
<MediaController
|
||||
key={url}
|
||||
className="aspect-video w-full overflow-hidden rounded-lg"
|
||||
>
|
||||
<video
|
||||
slot="media"
|
||||
src={url}
|
||||
poster={`https://thumbnail.video/api/get?url=${url}&seconds=1`}
|
||||
preload="none"
|
||||
muted
|
||||
crossOrigin=""
|
||||
/>
|
||||
<MediaControlBar>
|
||||
<MediaPlayButton></MediaPlayButton>
|
||||
<MediaSeekBackwardButton></MediaSeekBackwardButton>
|
||||
<MediaSeekForwardButton></MediaSeekForwardButton>
|
||||
<MediaTimeRange></MediaTimeRange>
|
||||
<MediaTimeDisplay showDuration></MediaTimeDisplay>
|
||||
<MediaMuteButton></MediaMuteButton>
|
||||
|
||||
@@ -4,12 +4,20 @@ import { ImagePreview, LinkPreview, MentionNote, VideoPreview } from '@shared/no
|
||||
|
||||
import { parser } from '@utils/parser';
|
||||
|
||||
export function TextNote(props: { content?: string }) {
|
||||
export function TextNote(props: { content?: string; truncate?: boolean }) {
|
||||
const richContent = parser(props.content);
|
||||
|
||||
if (props.truncate) {
|
||||
return (
|
||||
<div className="break-p prose prose-neutral line-clamp-4 max-w-none select-text whitespace-pre-line leading-normal dark:prose-invert prose-headings:mb-1 prose-headings:mt-3 prose-p:mb-0 prose-p:mt-0 prose-p:last:mb-1 prose-a:font-normal prose-a:text-blue-500 prose-blockquote:mb-1 prose-blockquote:mt-1 prose-blockquote:border-l-[2px] prose-blockquote:border-blue-500 prose-blockquote:pl-2 prose-pre:whitespace-pre-wrap prose-pre:bg-white/10 prose-ol:m-0 prose-ol:mb-1 prose-ul:mb-1 prose-ul:mt-1 prose-img:mb-2 prose-img:mt-3 prose-hr:mx-0 prose-hr:my-2 hover:prose-a:text-blue-600 prose-a:hover:underline">
|
||||
{props.content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="break-p prose prose-neutral max-w-none select-text whitespace-pre-line leading-normal dark:prose-invert prose-headings:mb-1 prose-headings:mt-3 prose-p:mb-0 prose-p:mt-0 prose-p:last:mb-1 prose-a:font-normal prose-a:text-blue-500 prose-blockquote:mb-1 prose-blockquote:mt-1 prose-blockquote:border-l-[2px] prose-blockquote:border-blue-500 prose-blockquote:pl-2 prose-pre:whitespace-pre-wrap prose-pre:bg-white/10 prose-ol:m-0 prose-ol:mb-1 prose-ul:mb-1 prose-ul:mt-1 prose-img:mb-2 prose-img:mt-3 prose-hr:mx-0 prose-hr:my-2 hover:prose-a:text-blue-500">
|
||||
<div className="break-p prose prose-neutral max-w-none select-text whitespace-pre-line leading-normal dark:prose-invert prose-headings:mb-1 prose-headings:mt-3 prose-p:mb-0 prose-p:mt-0 prose-p:last:mb-1 prose-a:font-normal prose-a:text-blue-500 prose-blockquote:mb-1 prose-blockquote:mt-1 prose-blockquote:border-l-[2px] prose-blockquote:border-blue-500 prose-blockquote:pl-2 prose-pre:whitespace-pre-wrap prose-pre:bg-white/10 prose-ol:m-0 prose-ol:mb-1 prose-ul:mb-1 prose-ul:mt-1 prose-img:mb-2 prose-img:mt-3 prose-hr:mx-0 prose-hr:my-2 hover:prose-a:text-blue-600 prose-a:hover:underline">
|
||||
{richContent.parsed}
|
||||
</div>
|
||||
{richContent.images.length ? <ImagePreview urls={richContent.images} /> : null}
|
||||
|
||||
@@ -1,24 +1,15 @@
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
import { WidgetKinds } from '@stores/constants';
|
||||
|
||||
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||
import { useWidget } from '@utils/hooks/useWidget';
|
||||
|
||||
export function Hashtag({ tag }: { tag: string }) {
|
||||
const { db } = useStorage();
|
||||
const setWidget = useWidgets((state) => state.setWidget);
|
||||
const { addWidget } = useWidget();
|
||||
|
||||
return (
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setWidget(db, {
|
||||
kind: WidgetKinds.global.hashtag,
|
||||
title: tag,
|
||||
content: tag.replace('#', ''),
|
||||
})
|
||||
}
|
||||
onKeyDown={() =>
|
||||
setWidget(db, {
|
||||
addWidget.mutate({
|
||||
kind: WidgetKinds.global.hashtag,
|
||||
title: tag,
|
||||
content: tag.replace('#', ''),
|
||||
@@ -27,6 +18,6 @@ export function Hashtag({ tag }: { tag: string }) {
|
||||
className="cursor-default break-all text-blue-500 hover:text-blue-600"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ import { memo } from 'react';
|
||||
|
||||
export const Invoice = memo(function Invoice({ invoice }: { invoice: string }) {
|
||||
return (
|
||||
<span className="mt-2 flex items-center rounded-lg bg-neutral-200 p-2 dark:bg-neutral-800">
|
||||
<div className="mt-2 flex items-center rounded-lg bg-neutral-200 p-2 dark:bg-neutral-800">
|
||||
<QRCodeSVG value={invoice} includeMargin={true} className="rounded-lg" />
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2,8 +2,6 @@ import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import {
|
||||
ArticleNote,
|
||||
FileNote,
|
||||
@@ -14,20 +12,23 @@ import {
|
||||
} from '@shared/notes';
|
||||
import { User } from '@shared/user';
|
||||
|
||||
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||
import { WidgetKinds } from '@stores/constants';
|
||||
|
||||
import { useEvent } from '@utils/hooks/useEvent';
|
||||
import { useWidget } from '@utils/hooks/useWidget';
|
||||
|
||||
export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
||||
const { db } = useStorage();
|
||||
const { status, data } = useEvent(id);
|
||||
|
||||
const setWidget = useWidgets((state) => state.setWidget);
|
||||
const { addWidget } = useWidget();
|
||||
|
||||
const openThread = (event, thread: string) => {
|
||||
const selection = window.getSelection();
|
||||
if (selection.toString().length === 0) {
|
||||
setWidget(db, { kind: WidgetKinds.local.thread, title: 'Thread', content: thread });
|
||||
addWidget.mutate({
|
||||
kind: WidgetKinds.local.thread,
|
||||
title: 'Thread',
|
||||
content: thread,
|
||||
});
|
||||
} else {
|
||||
event.stopPropagation();
|
||||
}
|
||||
@@ -74,15 +75,13 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => openThread(e, id)}
|
||||
onKeyDown={(e) => openThread(e, id)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="mt-3 cursor-default rounded-lg border border-neutral-300 bg-neutral-200 p-3 dark:border-neutral-700 dark:bg-neutral-800"
|
||||
>
|
||||
<User pubkey={data.pubkey} time={data.created_at} variant="mention" />
|
||||
<div className="mt-1">{renderKind(data)}</div>
|
||||
</div>
|
||||
<div className="mt-1 text-left">{renderKind(data)}</div>
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
import { memo } from 'react';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||
import { WidgetKinds } from '@stores/constants';
|
||||
|
||||
import { useProfile } from '@utils/hooks/useProfile';
|
||||
import { useWidget } from '@utils/hooks/useWidget';
|
||||
|
||||
export const MentionUser = memo(function MentionUser({ pubkey }: { pubkey: string }) {
|
||||
const { db } = useStorage();
|
||||
const { user } = useProfile(pubkey);
|
||||
|
||||
const setWidget = useWidgets((state) => state.setWidget);
|
||||
const { addWidget } = useWidget();
|
||||
|
||||
return (
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setWidget(db, {
|
||||
addWidget.mutate({
|
||||
kind: WidgetKinds.local.user,
|
||||
title: user?.name || user?.display_name || user?.displayName,
|
||||
content: pubkey,
|
||||
})
|
||||
}
|
||||
onKeyDown={() =>
|
||||
setWidget(db, {
|
||||
addWidget.mutate({
|
||||
kind: WidgetKinds.local.user,
|
||||
title: user?.name || user?.display_name || user?.displayName,
|
||||
content: pubkey,
|
||||
@@ -38,6 +34,6 @@ export const MentionUser = memo(function MentionUser({ pubkey }: { pubkey: strin
|
||||
user?.displayName ||
|
||||
user?.username ||
|
||||
'unknown')}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,19 +3,13 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { decode } from 'light-bolt11-decoder';
|
||||
|
||||
import { useNDK } from '@libs/ndk/provider';
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { LoaderIcon } from '@shared/icons';
|
||||
import { User } from '@shared/user';
|
||||
|
||||
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||
|
||||
import { compactNumber } from '@utils/number';
|
||||
|
||||
export function NoteMetadata({ id }: { id: string }) {
|
||||
const setWidget = useWidgets((state) => state.setWidget);
|
||||
|
||||
const { db } = useStorage();
|
||||
const { ndk } = useNDK();
|
||||
const { status, data } = useQuery({
|
||||
queryKey: ['note-metadata', id],
|
||||
@@ -89,17 +83,7 @@ export function NoteMetadata({ id }: { id: string }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 inline-flex h-6 items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setWidget(db, {
|
||||
kind: WidgetKinds.local.thread,
|
||||
title: 'Thread',
|
||||
content: id,
|
||||
})
|
||||
}
|
||||
className="text-neutral-600 dark:text-neutral-400"
|
||||
>
|
||||
<button type="button" className="text-neutral-600 dark:text-neutral-400">
|
||||
<span className="font-semibold text-white">{data.replies}</span> replies
|
||||
</button>
|
||||
<span className="text-neutral-600 dark:text-neutral-400">·</span>
|
||||
|
||||
@@ -2,6 +2,10 @@ import { Link } from 'react-router-dom';
|
||||
|
||||
import { useOpenGraph } from '@utils/hooks/useOpenGraph';
|
||||
|
||||
function isImage(url: string) {
|
||||
return /^https?:\/\/.+\.(jpg|jpeg|png|webp|avif)$/.test(url);
|
||||
}
|
||||
|
||||
export function LinkPreview({ urls }: { urls: string[] }) {
|
||||
const { status, data, error } = useOpenGraph(urls[0]);
|
||||
const domain = new URL(urls[0]);
|
||||
@@ -37,25 +41,25 @@ export function LinkPreview({ urls }: { urls: string[] }) {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{data.image && (
|
||||
{isImage(data.image) ? (
|
||||
<img
|
||||
src={data.image}
|
||||
alt={urls[0]}
|
||||
className="h-44 w-full rounded-t-lg bg-white object-cover"
|
||||
/>
|
||||
)}
|
||||
<div className="flex flex-col px-3 py-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
) : null}
|
||||
<div className="flex flex-col items-start px-3 py-3">
|
||||
<div className="flex flex-col items-start gap-1 text-left">
|
||||
{data.title && (
|
||||
<h5 className="line-clamp-1 text-base font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{data.title}
|
||||
</h5>
|
||||
)}
|
||||
{data.description && (
|
||||
{data.description ? (
|
||||
<p className="mb-2.5 line-clamp-3 break-all text-sm text-neutral-700 dark:text-neutral-400">
|
||||
{data.description}
|
||||
</p>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
<span className="break-all text-sm text-neutral-600 dark:text-neutral-400">
|
||||
{domain.hostname}
|
||||
|
||||
Reference in New Issue
Block a user