This commit is contained in:
Shusui MOYATANI
2023-04-24 00:22:20 +09:00
parent e7432dad93
commit 7b27ed288f
51 changed files with 224 additions and 174 deletions

View File

@@ -34,6 +34,21 @@ module.exports = {
}, },
], ],
'prettier/prettier': 'error', 'prettier/prettier': 'error',
'no-console': ['off'],
'no-alert': ['off'],
'import/order': [
'warn',
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
'newlines-between': 'always',
pathGroupsExcludedImportTypes: ['builtin'],
alphabetize: { order: 'asc', caseInsensitive: true },
pathGroups: [
{ pattern: 'solid-js*', group: 'external', position: 'before' },
{ pattern: '@/', group: 'internal', position: 'before' },
],
},
],
}, },
settings: { settings: {
linkComponents: ['Link'], linkComponents: ['Link'],
@@ -45,23 +60,6 @@ module.exports = {
extensions: ['.ts', '.tsx'], extensions: ['.ts', '.tsx'],
}, },
}, },
'import/order': [
'warn',
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
'newlines-between': 'always',
pathGroupsExcludedImportTypes: ['builtin'],
alphabetize: { order: 'asc', caseInsensitive: true },
pathGroups: [
{ pattern: 'src/types/**', group: 'internal', position: 'before' },
{
pattern: 'src/repositories/**',
group: 'internal',
position: 'before',
},
],
},
],
tailwindcss: { tailwindcss: {
whitelist: [ whitelist: [
'h-fill-available', 'h-fill-available',

View File

@@ -9,12 +9,12 @@
"dev": "npm run generatePackageInfo && vite", "dev": "npm run generatePackageInfo && vite",
"build": "npm run generatePackageInfo && vite build", "build": "npm run generatePackageInfo && vite build",
"serve": "npm run generatePackageInfo && vite preview", "serve": "npm run generatePackageInfo && vite preview",
"lint": "eslint .", "lint": "eslint --cache .",
"fix": "eslint --cache --fix .",
"tsc": "tsc --noEmit --skipLibCheck", "tsc": "tsc --noEmit --skipLibCheck",
"test": "vitest run --no-watch", "test": "vitest run --no-watch",
"watch-test": "vitest --watch", "watch-test": "vitest --watch",
"cover": "vitest run --coverage", "cover": "vitest run --coverage",
"fix": "eslint --fix .",
"prepare": "husky install", "prepare": "husky install",
"generatePackageInfo": "node -e 'import(\"./scripts/generatePackageInfo.mjs\").then((m) => m.default())'", "generatePackageInfo": "node -e 'import(\"./scripts/generatePackageInfo.mjs\").then((m) => m.default())'",
"checkLicense": "node -e 'import(\"./scripts/checkLicense.mjs\").then((m) => m.default())'" "checkLicense": "node -e 'import(\"./scripts/checkLicense.mjs\").then((m) => m.default())'"

View File

@@ -1,8 +1,9 @@
import { createEffect, onCleanup, lazy, type Component } from 'solid-js'; import { createEffect, onCleanup, lazy, type Component } from 'solid-js';
import { Routes, Route } from '@solidjs/router'; import { Routes, Route } from '@solidjs/router';
import { QueryClient, QueryClientProvider } from '@tanstack/solid-query';
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
import { persistQueryClient } from '@tanstack/query-persist-client-core'; import { persistQueryClient } from '@tanstack/query-persist-client-core';
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
import { QueryClient, QueryClientProvider } from '@tanstack/solid-query';
const Home = lazy(() => import('@/pages/Home')); const Home = lazy(() => import('@/pages/Home'));
const Hello = lazy(() => import('@/pages/Hello')); const Hello = lazy(() => import('@/pages/Hello'));

View File

@@ -1,9 +1,10 @@
import { Show, type JSX, type Component } from 'solid-js'; import { Show, type JSX, type Component } from 'solid-js';
import ArrowLeft from 'heroicons/24/outline/arrow-left.svg'; import ArrowLeft from 'heroicons/24/outline/arrow-left.svg';
import { useHandleCommand } from '@/hooks/useCommandBus';
import { TimelineContext, useTimelineState } from '@/components/TimelineContext';
import TimelineContentDisplay from '@/components/TimelineContentDisplay'; import TimelineContentDisplay from '@/components/TimelineContentDisplay';
import { TimelineContext, useTimelineState } from '@/components/TimelineContext';
import { useHandleCommand } from '@/hooks/useCommandBus';
export type ColumnProps = { export type ColumnProps = {
name: string; name: string;

View File

@@ -1,8 +1,10 @@
import useConfig, { type Config } from '@/nostr/useConfig';
import { createSignal, For, type JSX } from 'solid-js'; import { createSignal, For, type JSX } from 'solid-js';
import XMark from 'heroicons/24/outline/x-mark.svg'; import XMark from 'heroicons/24/outline/x-mark.svg';
import Modal from '@/components/Modal'; import Modal from '@/components/Modal';
import useConfig, { type Config } from '@/nostr/useConfig';
import UserNameDisplay from './UserDisplayName'; import UserNameDisplay from './UserDisplayName';
type ConfigProps = { type ConfigProps = {

View File

@@ -1,13 +1,15 @@
// NIP-18 (DEPRECATED) // NIP-18 (DEPRECATED)
import { type Component, createMemo } from 'solid-js'; import { type Component, createMemo } from 'solid-js';
import { Event as NostrEvent } from 'nostr-tools';
import ArrowPathRoundedSquare from 'heroicons/24/outline/arrow-path-rounded-square.svg'; import ArrowPathRoundedSquare from 'heroicons/24/outline/arrow-path-rounded-square.svg';
import { Event as NostrEvent } from 'nostr-tools';
import ColumnItem from '@/components/ColumnItem'; import ColumnItem from '@/components/ColumnItem';
import UserDisplayName from '@/components/UserDisplayName'; import UserDisplayName from '@/components/UserDisplayName';
import eventWrapper from '@/core/event'; import eventWrapper from '@/core/event';
import useFormatDate from '@/hooks/useFormatDate'; import useFormatDate from '@/hooks/useFormatDate';
import useModalState from '@/hooks/useModalState'; import useModalState from '@/hooks/useModalState';
import TextNoteDisplayById from './textNote/TextNoteDisplayById'; import TextNoteDisplayById from './textNote/TextNoteDisplayById';
export type DeprecatedRepostProps = { export type DeprecatedRepostProps = {

View File

@@ -1,4 +1,5 @@
import { Component } from 'solid-js'; import { Component } from 'solid-js';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
const { noteEncode } = nip19; const { noteEncode } = nip19;

View File

@@ -15,6 +15,7 @@ const Modal: Component<ModalProps> = (props) => {
}; };
return ( return (
/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */
<div <div
ref={containerRef} ref={containerRef}
class="absolute top-0 left-0 z-10 flex h-screen w-screen cursor-default place-content-center place-items-center bg-black/30" class="absolute top-0 left-0 z-10 flex h-screen w-screen cursor-default place-content-center place-items-center bg-black/30"

View File

@@ -8,25 +8,22 @@ import {
type JSX, type JSX,
type Accessor, type Accessor,
} from 'solid-js'; } from 'solid-js';
import { createMutation } from '@tanstack/solid-query';
import { Event as NostrEvent } from 'nostr-tools';
import uniq from 'lodash/uniq';
import PaperAirplane from 'heroicons/24/solid/paper-airplane.svg'; import { createMutation } from '@tanstack/solid-query';
import Photo from 'heroicons/24/outline/photo.svg'; import Photo from 'heroicons/24/outline/photo.svg';
import XMark from 'heroicons/24/outline/x-mark.svg'; import XMark from 'heroicons/24/outline/x-mark.svg';
import PaperAirplane from 'heroicons/24/solid/paper-airplane.svg';
import uniq from 'lodash/uniq';
import { Event as NostrEvent } from 'nostr-tools';
import UserNameDisplay from '@/components/UserDisplayName'; import UserNameDisplay from '@/components/UserDisplayName';
import eventWrapper from '@/core/event'; import eventWrapper from '@/core/event';
import useConfig from '@/nostr/useConfig';
import useCommands, { PublishTextNoteParams } from '@/nostr/useCommands';
import usePubkey from '@/nostr/usePubkey';
import { useHandleCommand } from '@/hooks/useCommandBus';
import { uploadNostrBuild, uploadFiles } from '@/utils/imageUpload';
import parseTextNote from '@/core/parseTextNote'; import parseTextNote from '@/core/parseTextNote';
import { useHandleCommand } from '@/hooks/useCommandBus';
import useCommands, { PublishTextNoteParams } from '@/nostr/useCommands';
import useConfig from '@/nostr/useConfig';
import usePubkey from '@/nostr/usePubkey';
import { uploadNostrBuild, uploadFiles } from '@/utils/imageUpload';
type NotePostFormProps = { type NotePostFormProps = {
replyTo?: NostrEvent; replyTo?: NostrEvent;

View File

@@ -1,9 +1,10 @@
import { For, Switch, Match, type Component } from 'solid-js'; import { For, Switch, Match, type Component } from 'solid-js';
import { Kind, type Event as NostrEvent } from 'nostr-tools'; import { Kind, type Event as NostrEvent } from 'nostr-tools';
import TextNote from '@/components/TextNote';
import Reaction from '@/components/notification/Reaction';
import DeprecatedRepost from '@/components/DeprecatedRepost'; import DeprecatedRepost from '@/components/DeprecatedRepost';
import Reaction from '@/components/notification/Reaction';
import TextNote from '@/components/TextNote';
export type NotificationProps = { export type NotificationProps = {
events: NostrEvent[]; events: NostrEvent[];

View File

@@ -1,31 +1,30 @@
import { Component, createSignal, createMemo, Show, Switch, Match, createEffect } from 'solid-js'; import { Component, createSignal, createMemo, Show, Switch, Match, createEffect } from 'solid-js';
import { createMutation } from '@tanstack/solid-query';
import { createMutation } from '@tanstack/solid-query';
import ArrowPath from 'heroicons/24/outline/arrow-path.svg';
import EllipsisHorizontal from 'heroicons/24/outline/ellipsis-horizontal.svg';
import GlobeAlt from 'heroicons/24/outline/globe-alt.svg'; import GlobeAlt from 'heroicons/24/outline/globe-alt.svg';
import XMark from 'heroicons/24/outline/x-mark.svg'; import XMark from 'heroicons/24/outline/x-mark.svg';
import CheckCircle from 'heroicons/24/solid/check-circle.svg'; import CheckCircle from 'heroicons/24/solid/check-circle.svg';
import ExclamationCircle from 'heroicons/24/solid/exclamation-circle.svg'; import ExclamationCircle from 'heroicons/24/solid/exclamation-circle.svg';
import ArrowPath from 'heroicons/24/outline/arrow-path.svg';
import EllipsisHorizontal from 'heroicons/24/outline/ellipsis-horizontal.svg';
import Modal from '@/components/Modal'; import Modal from '@/components/Modal';
import Timeline from '@/components/Timeline'; import Timeline from '@/components/Timeline';
import Copy from '@/components/utils/Copy'; import Copy from '@/components/utils/Copy';
import SafeLink from '@/components/utils/SafeLink'; import SafeLink from '@/components/utils/SafeLink';
import usePubkey from '@/nostr/usePubkey';
import useProfile from '@/nostr/useProfile';
import useVerification from '@/nostr/useVerification';
import useFollowings from '@/nostr/useFollowings';
import useFollowers from '@/nostr/useFollowers';
import useConfig from '@/nostr/useConfig';
import useCommands from '@/nostr/useCommands'; import useCommands from '@/nostr/useCommands';
import useConfig from '@/nostr/useConfig';
import useFollowers from '@/nostr/useFollowers';
import useFollowings from '@/nostr/useFollowings';
import useProfile from '@/nostr/useProfile';
import usePubkey from '@/nostr/usePubkey';
import useSubscription from '@/nostr/useSubscription'; import useSubscription from '@/nostr/useSubscription';
import useVerification from '@/nostr/useVerification';
import npubEncodeFallback from '@/utils/npubEncodeFallback';
import ensureNonNull from '@/utils/ensureNonNull'; import ensureNonNull from '@/utils/ensureNonNull';
import epoch from '@/utils/epoch'; import epoch from '@/utils/epoch';
import npubEncodeFallback from '@/utils/npubEncodeFallback';
import timeout from '@/utils/timeout'; import timeout from '@/utils/timeout';
import ContextMenu, { MenuItem } from './ContextMenu'; import ContextMenu, { MenuItem } from './ContextMenu';
export type ProfileDisplayProps = { export type ProfileDisplayProps = {

View File

@@ -1,11 +1,11 @@
import { createSignal, Show, type JSX, Component } from 'solid-js'; import { createSignal, Show, type JSX, Component } from 'solid-js';
import Cog6Tooth from 'heroicons/24/outline/cog-6-tooth.svg';
import MagnifyingGlass from 'heroicons/24/solid/magnifying-glass.svg'; import MagnifyingGlass from 'heroicons/24/solid/magnifying-glass.svg';
import PencilSquare from 'heroicons/24/solid/pencil-square.svg'; import PencilSquare from 'heroicons/24/solid/pencil-square.svg';
import Cog6Tooth from 'heroicons/24/outline/cog-6-tooth.svg';
import NotePostForm from '@/components/NotePostForm';
import Config from '@/components/Config'; import Config from '@/components/Config';
import NotePostForm from '@/components/NotePostForm';
import { useHandleCommand } from '@/hooks/useCommandBus'; import { useHandleCommand } from '@/hooks/useCommandBus';
import useConfig from '@/nostr/useConfig'; import useConfig from '@/nostr/useConfig';

View File

@@ -2,6 +2,7 @@ import { Show, type Component } from 'solid-js';
import ColumnItem from '@/components/ColumnItem'; import ColumnItem from '@/components/ColumnItem';
import useConfig from '@/nostr/useConfig'; import useConfig from '@/nostr/useConfig';
import TextNoteDisplay, { TextNoteDisplayProps } from './textNote/TextNoteDisplay'; import TextNoteDisplay, { TextNoteDisplayProps } from './textNote/TextNoteDisplay';
export type TextNoteProps = TextNoteDisplayProps; export type TextNoteProps = TextNoteDisplayProps;

View File

@@ -1,8 +1,9 @@
import { For, Switch, Match, type Component } from 'solid-js'; import { For, Switch, Match, type Component } from 'solid-js';
import { Kind, type Event as NostrEvent } from 'nostr-tools'; import { Kind, type Event as NostrEvent } from 'nostr-tools';
import TextNote from '@/components/TextNote';
import DeprecatedRepost from '@/components/DeprecatedRepost'; import DeprecatedRepost from '@/components/DeprecatedRepost';
import TextNote from '@/components/TextNote';
export type TimelineProps = { export type TimelineProps = {
events: NostrEvent[]; events: NostrEvent[];

View File

@@ -1,13 +1,13 @@
import { Switch, Match, type Component } from 'solid-js'; import { Switch, Match, type Component } from 'solid-js';
import { Filter, Event as NostrEvent } from 'nostr-tools';
import uniq from 'lodash/uniq'; import uniq from 'lodash/uniq';
import { Filter, Event as NostrEvent } from 'nostr-tools';
import useConfig from '@/nostr/useConfig';
import { type TimelineContent } from '@/components/TimelineContext';
import Timeline from '@/components/Timeline'; import Timeline from '@/components/Timeline';
import useSubscription from '@/nostr/useSubscription'; import { type TimelineContent } from '@/components/TimelineContext';
import eventWrapper from '@/core/event'; import eventWrapper from '@/core/event';
import useConfig from '@/nostr/useConfig';
import useSubscription from '@/nostr/useSubscription';
const relatedEvents = (rawEvent: NostrEvent) => { const relatedEvents = (rawEvent: NostrEvent) => {
const event = () => eventWrapper(rawEvent); const event = () => eventWrapper(rawEvent);

View File

@@ -1,6 +1,7 @@
import { createContext, useContext } from 'solid-js'; import { createContext, useContext } from 'solid-js';
import { createStore } from 'solid-js/store';
import { Event as NostrEvent } from 'nostr-tools'; import { Event as NostrEvent } from 'nostr-tools';
import { createStore } from 'solid-js/store';
export type TimelineContent = { export type TimelineContent = {
type: 'Replies'; type: 'Replies';

View File

@@ -1,15 +1,15 @@
import { Switch, Match, type Component, Show } from 'solid-js'; import { Switch, Match, type Component, Show } from 'solid-js';
import { type Event as NostrEvent } from 'nostr-tools';
import HeartSolid from 'heroicons/24/solid/heart.svg'; import HeartSolid from 'heroicons/24/solid/heart.svg';
import { type Event as NostrEvent } from 'nostr-tools';
import ColumnItem from '@/components/ColumnItem'; import ColumnItem from '@/components/ColumnItem';
import TextNoteDisplay from '@/components/textNote/TextNoteDisplay'; import TextNoteDisplay from '@/components/textNote/TextNoteDisplay';
import UserDisplayName from '@/components/UserDisplayName'; import UserDisplayName from '@/components/UserDisplayName';
import useProfile from '@/nostr/useProfile';
import useEvent from '@/nostr/useEvent';
import eventWrapper from '@/core/event'; import eventWrapper from '@/core/event';
import useModalState from '@/hooks/useModalState'; import useModalState from '@/hooks/useModalState';
import useEvent from '@/nostr/useEvent';
import useProfile from '@/nostr/useProfile';
type ReactionProps = { type ReactionProps = {
event: NostrEvent; event: NostrEvent;

View File

@@ -1,4 +1,5 @@
import { createSignal, type Component, type JSX, Show } from 'solid-js'; import { createSignal, type Component, type JSX, Show } from 'solid-js';
import { ContentWarning } from '@/core/event'; import { ContentWarning } from '@/core/event';
export type ContentWarningDisplayProps = { export type ContentWarningDisplayProps = {

View File

@@ -1,5 +1,7 @@
import { Component, createEffect, createSignal, onMount, Show, JSX } from 'solid-js'; import { Component, createSignal, Show } from 'solid-js';
import { fixUrl } from '@/utils/imageUrl'; import { fixUrl } from '@/utils/imageUrl';
import SafeLink from '../utils/SafeLink'; import SafeLink from '../utils/SafeLink';
type ImageDisplayProps = { type ImageDisplayProps = {
@@ -14,12 +16,13 @@ const ImageDisplay: Component<ImageDisplayProps> = (props) => {
const [hidden, setHidden] = createSignal(props.initialHidden); const [hidden, setHidden] = createSignal(props.initialHidden);
const [playing, setPlaying] = createSignal(true); const [playing, setPlaying] = createSignal(true);
const isGIF = () => props.url.match(/\.gif/i); // const isGIF = () => props.url.match(/\.gif/i);
const play = () => { const play = () => {
setPlaying(true); setPlaying(true);
}; };
/*
const stop = () => { const stop = () => {
if (canvasRef == null || imageRef == null) return; if (canvasRef == null || imageRef == null) return;
canvasRef.width = imageRef.width; canvasRef.width = imageRef.width;
@@ -39,6 +42,7 @@ const ImageDisplay: Component<ImageDisplayProps> = (props) => {
); );
setPlaying(false); setPlaying(false);
}; };
*/
return ( return (
<Show <Show

View File

@@ -1,6 +1,9 @@
import { Show } from 'solid-js'; import { Show } from 'solid-js';
// eslint-disable-next-line import/no-cycle
import TextNoteDisplayById from '@/components/textNote/TextNoteDisplayById'; import TextNoteDisplayById from '@/components/textNote/TextNoteDisplayById';
import { type MentionedEvent } from '@/core/parseTextNote'; import { type MentionedEvent } from '@/core/parseTextNote';
import EventLink from '../EventLink'; import EventLink from '../EventLink';
export type MentionedEventDisplayProps = { export type MentionedEventDisplayProps = {

View File

@@ -1,7 +1,8 @@
import type { MentionedUser } from '@/core/parseTextNote';
import GeneralUserMentionDisplay from '@/components/textNote/GeneralUserMentionDisplay'; import GeneralUserMentionDisplay from '@/components/textNote/GeneralUserMentionDisplay';
import useModalState from '@/hooks/useModalState'; import useModalState from '@/hooks/useModalState';
import type { MentionedUser } from '@/core/parseTextNote';
export type MentionedUserDisplayProps = { export type MentionedUserDisplayProps = {
pubkey: string; pubkey: string;
}; };

View File

@@ -1,17 +1,20 @@
import { For } from 'solid-js'; import { For } from 'solid-js';
import parseTextNote, { resolveTagReference, type ParsedTextNoteNode } from '@/core/parseTextNote';
import type { Event as NostrEvent } from 'nostr-tools'; import EventLink from '@/components/EventLink';
import PlainTextDisplay from '@/components/textNote/PlainTextDisplay'; import ImageDisplay from '@/components/textNote/ImageDisplay';
import MentionedUserDisplay from '@/components/textNote/MentionedUserDisplay';
// eslint-disable-next-line import/no-cycle // eslint-disable-next-line import/no-cycle
import MentionedEventDisplay from '@/components/textNote/MentionedEventDisplay'; import MentionedEventDisplay from '@/components/textNote/MentionedEventDisplay';
import ImageDisplay from '@/components/textNote/ImageDisplay'; import MentionedUserDisplay from '@/components/textNote/MentionedUserDisplay';
import PlainTextDisplay from '@/components/textNote/PlainTextDisplay';
import TextNoteDisplayById from '@/components/textNote/TextNoteDisplayById';
import SafeLink from '@/components/utils/SafeLink'; import SafeLink from '@/components/utils/SafeLink';
import eventWrapper from '@/core/event'; import eventWrapper from '@/core/event';
import { isImageUrl } from '@/utils/imageUrl'; import parseTextNote, { resolveTagReference, type ParsedTextNoteNode } from '@/core/parseTextNote';
import type { Event as NostrEvent } from 'nostr-tools';
import useConfig from '@/nostr/useConfig'; import useConfig from '@/nostr/useConfig';
import EventLink from '../EventLink'; import { isImageUrl } from '@/utils/imageUrl';
import TextNoteDisplayById from './TextNoteDisplayById';
export type TextNoteContentDisplayProps = { export type TextNoteContentDisplayProps = {
event: NostrEvent; event: NostrEvent;

View File

@@ -1,36 +1,33 @@
import { Show, For, createSignal, createMemo, onMount, type JSX, type Component } from 'solid-js'; import { Show, For, createSignal, createMemo, onMount, type JSX, type Component } from 'solid-js';
import { nip19, type Event as NostrEvent } from 'nostr-tools';
import { createMutation } from '@tanstack/solid-query';
import HeartOutlined from 'heroicons/24/outline/heart.svg'; import { createMutation } from '@tanstack/solid-query';
import HeartSolid from 'heroicons/24/solid/heart.svg';
import ArrowPathRoundedSquare from 'heroicons/24/outline/arrow-path-rounded-square.svg'; import ArrowPathRoundedSquare from 'heroicons/24/outline/arrow-path-rounded-square.svg';
import ChatBubbleLeft from 'heroicons/24/outline/chat-bubble-left.svg'; import ChatBubbleLeft from 'heroicons/24/outline/chat-bubble-left.svg';
import EllipsisHorizontal from 'heroicons/24/outline/ellipsis-horizontal.svg'; import EllipsisHorizontal from 'heroicons/24/outline/ellipsis-horizontal.svg';
import HeartOutlined from 'heroicons/24/outline/heart.svg';
import HeartSolid from 'heroicons/24/solid/heart.svg';
import { nip19, type Event as NostrEvent } from 'nostr-tools';
import eventWrapper from '@/core/event'; import NotePostForm from '@/components/NotePostForm';
import ContentWarningDisplay from '@/components/textNote/ContentWarningDisplay';
import useProfile from '@/nostr/useProfile'; import GeneralUserMentionDisplay from '@/components/textNote/GeneralUserMentionDisplay';
import useConfig from '@/nostr/useConfig'; // eslint-disable-next-line import/no-cycle
import usePubkey from '@/nostr/usePubkey'; import TextNoteContentDisplay from '@/components/textNote/TextNoteContentDisplay';
import useCommands from '@/nostr/useCommands';
import useReactions from '@/nostr/useReactions';
import useDeprecatedReposts from '@/nostr/useDeprecatedReposts';
import useFormatDate from '@/hooks/useFormatDate';
import useModalState from '@/hooks/useModalState';
import UserNameDisplay from '@/components/UserDisplayName';
import TextNoteDisplayById from '@/components/textNote/TextNoteDisplayById'; import TextNoteDisplayById from '@/components/textNote/TextNoteDisplayById';
import { useTimelineContext } from '@/components/TimelineContext'; import { useTimelineContext } from '@/components/TimelineContext';
import GeneralUserMentionDisplay from '@/components/textNote/GeneralUserMentionDisplay'; import eventWrapper from '@/core/event';
import ContentWarningDisplay from '@/components/textNote/ContentWarningDisplay'; import useFormatDate from '@/hooks/useFormatDate';
import TextNoteContentDisplay from '@/components/textNote/TextNoteContentDisplay'; import useModalState from '@/hooks/useModalState';
import NotePostForm from '@/components/NotePostForm'; import useCommands from '@/nostr/useCommands';
import useConfig from '@/nostr/useConfig';
import useProfile from '@/nostr/useProfile';
import usePubkey from '@/nostr/usePubkey';
import useReactions from '@/nostr/useReactions';
import useReposts from '@/nostr/useReposts';
import useSubscription from '@/nostr/useSubscription';
import ensureNonNull from '@/utils/ensureNonNull'; import ensureNonNull from '@/utils/ensureNonNull';
import npubEncodeFallback from '@/utils/npubEncodeFallback'; import npubEncodeFallback from '@/utils/npubEncodeFallback';
import useSubscription from '@/nostr/useSubscription';
import ContextMenu, { MenuItem } from '../ContextMenu'; import ContextMenu, { MenuItem } from '../ContextMenu';
export type TextNoteDisplayProps = { export type TextNoteDisplayProps = {
@@ -85,7 +82,7 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
eventId: props.event.id, eventId: props.event.id,
})); }));
const { reposts, isRepostedBy, invalidateDeprecatedReposts } = useDeprecatedReposts(() => ({ const { reposts, isRepostedBy, invalidateReposts } = useReposts(() => ({
eventId: props.event.id, eventId: props.event.id,
})); }));
@@ -105,9 +102,9 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
}, },
}); });
const publishDeprecatedRepostMutation = createMutation({ const publishRepostMutation = createMutation({
mutationKey: ['publishDeprecatedRepost', event().id], mutationKey: ['publishRepost', event().id],
mutationFn: commands.publishDeprecatedRepost.bind(commands), mutationFn: commands.publishRepost.bind(commands),
onSuccess: () => { onSuccess: () => {
console.log('succeeded to publish reposts'); console.log('succeeded to publish reposts');
}, },
@@ -115,7 +112,7 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
console.error('failed to publish repost: ', err); console.error('failed to publish repost: ', err);
}, },
onSettled: () => { onSettled: () => {
invalidateDeprecatedReposts().catch((err) => console.error('failed to refetch reposts', err)); invalidateReposts().catch((err) => console.error('failed to refetch reposts', err));
}, },
}); });
@@ -151,7 +148,7 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
} }
ensureNonNull([pubkey(), props.event.id] as const)(([pubkeyNonNull, eventIdNonNull]) => { ensureNonNull([pubkey(), props.event.id] as const)(([pubkeyNonNull, eventIdNonNull]) => {
publishDeprecatedRepostMutation.mutate({ publishRepostMutation.mutate({
relayUrls: config().relayUrls, relayUrls: config().relayUrls,
pubkey: pubkeyNonNull, pubkey: pubkeyNonNull,
eventId: eventIdNonNull, eventId: eventIdNonNull,
@@ -306,13 +303,13 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
class="flex shrink-0 items-center gap-1" class="flex shrink-0 items-center gap-1"
classList={{ classList={{
'text-zinc-400': !isRepostedByMe(), 'text-zinc-400': !isRepostedByMe(),
'text-green-400': isRepostedByMe() || publishDeprecatedRepostMutation.isLoading, 'text-green-400': isRepostedByMe() || publishRepostMutation.isLoading,
}} }}
> >
<button <button
class="h-4 w-4" class="h-4 w-4"
onClick={handleRepost} onClick={handleRepost}
disabled={publishDeprecatedRepostMutation.isLoading} disabled={publishRepostMutation.isLoading}
> >
<ArrowPathRoundedSquare /> <ArrowPathRoundedSquare />
</button> </button>

View File

@@ -2,11 +2,10 @@ import { Switch, Match, type Component } from 'solid-js';
// eslint-disable-next-line import/no-cycle // eslint-disable-next-line import/no-cycle
import TextNoteDisplay, { type TextNoteDisplayProps } from '@/components/textNote/TextNoteDisplay'; import TextNoteDisplay, { type TextNoteDisplayProps } from '@/components/textNote/TextNoteDisplay';
import useConfig from '@/nostr/useConfig'; import useConfig from '@/nostr/useConfig';
import useEvent from '@/nostr/useEvent'; import useEvent from '@/nostr/useEvent';
import ensureNonNull from '@/utils/ensureNonNull'; import ensureNonNull from '@/utils/ensureNonNull';
import EventLink from '../EventLink'; import EventLink from '../EventLink';
type TextNoteDisplayByIdProps = Omit<TextNoteDisplayProps, 'event'> & { type TextNoteDisplayByIdProps = Omit<TextNoteDisplayProps, 'event'> & {

View File

@@ -1,5 +1,6 @@
// import { z } from 'zod'; // import { z } from 'zod';
import { type Filter } from 'nostr-tools'; import { type Filter } from 'nostr-tools';
import { type ColumnProps } from '@/components/Column'; import { type ColumnProps } from '@/components/Column';
export type NotificationType = export type NotificationType =

View File

@@ -1,6 +1,7 @@
import type { Event as NostrEvent } from 'nostr-tools';
import uniq from 'lodash/uniq'; import uniq from 'lodash/uniq';
import type { Event as NostrEvent } from 'nostr-tools';
export type EventMarker = 'reply' | 'root' | 'mention'; export type EventMarker = 'reply' | 'root' | 'mention';
export type TaggedEvent = { export type TaggedEvent = {

View File

@@ -1,6 +1,7 @@
import assert from 'assert'; import assert from 'assert';
import { describe, it } from 'vitest';
import { type Event as NostrEvent } from 'nostr-tools'; import { type Event as NostrEvent } from 'nostr-tools';
import { describe, it } from 'vitest';
import parseTextNote, { import parseTextNote, {
resolveTagReference, resolveTagReference,

View File

@@ -1,4 +1,5 @@
import { nip19, type Event as NostrEvent } from 'nostr-tools'; import { nip19, type Event as NostrEvent } from 'nostr-tools';
import eventWrapper from './event'; import eventWrapper from './event';
type ProfilePointer = nip19.ProfilePointer; type ProfilePointer = nip19.ProfilePointer;

View File

@@ -1,4 +1,5 @@
import { createSignal, createEffect, onMount, type Signal } from 'solid-js'; import { createSignal, createEffect, onMount, type Signal } from 'solid-js';
import { createStore, SetStoreFunction, type Store, type StoreNode } from 'solid-js/store'; import { createStore, SetStoreFunction, type Store, type StoreNode } from 'solid-js/store';
type GenericStorage<T> = { type GenericStorage<T> = {

View File

@@ -1,7 +1,5 @@
import useConfig from '@/nostr/useConfig';
import useDatePulser from '@/hooks/useDatePulser'; import useDatePulser from '@/hooks/useDatePulser';
import useConfig from '@/nostr/useConfig';
import { formatRelative, formatAbsoluteLong, formatAbsoluteShort } from '@/utils/formatDate'; import { formatRelative, formatAbsoluteLong, formatAbsoluteShort } from '@/utils/formatDate';
// 7 seconds is used here so that the last digit of relative time is changed. // 7 seconds is used here so that the last digit of relative time is changed.

View File

@@ -1,4 +1,5 @@
import { Accessor } from 'solid-js'; import { Accessor } from 'solid-js';
import { import {
createSignalWithStorage, createSignalWithStorage,
createStorageWithSerializer, createStorageWithSerializer,

View File

@@ -1,5 +1,5 @@
import type { Accessor } from 'solid-js';
import { createSignal, createEffect } from 'solid-js'; import { createSignal, createEffect } from 'solid-js';
import type { Accessor } from 'solid-js';
export type UseResizedImageProps = { export type UseResizedImageProps = {
imageUrl: Accessor<string | undefined>; imageUrl: Accessor<string | undefined>;

View File

@@ -2,6 +2,7 @@
// type Commands = (typeof commands)[number]; // type Commands = (typeof commands)[number];
import { onMount, onCleanup, type JSX } from 'solid-js'; import { onMount, onCleanup, type JSX } from 'solid-js';
import throttle from 'lodash/throttle'; import throttle from 'lodash/throttle';
import { useRequestCommand, type Command } from '@/hooks/useCommandBus'; import { useRequestCommand, type Command } from '@/hooks/useCommandBus';

View File

@@ -1,6 +1,6 @@
/* @refresh reload */ /* @refresh reload */
import { render } from 'solid-js/web';
import { Router } from '@solidjs/router'; import { Router } from '@solidjs/router';
import { render } from 'solid-js/web';
import './index.css'; import './index.css';
import App from './App'; import App from './App';

View File

@@ -4,6 +4,7 @@ export type Task<TaskArgs, TaskResult> = {
id: number; id: number;
args: TaskArgs; args: TaskArgs;
resolve: (result: TaskResult) => void; resolve: (result: TaskResult) => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
reject: (error: any) => void; reject: (error: any) => void;
}; };
@@ -16,11 +17,13 @@ export type UseBatchProps<TaskArgs, TaskResult> = {
export type PromiseWithCallbacks<T> = { export type PromiseWithCallbacks<T> = {
promise: Promise<T>; promise: Promise<T>;
resolve: (e: T) => void; resolve: (e: T) => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
reject: (e: any) => void; reject: (e: any) => void;
}; };
const promiseWithCallbacks = <T>(): PromiseWithCallbacks<T> => { const promiseWithCallbacks = <T>(): PromiseWithCallbacks<T> => {
let resolve: ((e: T) => void) | undefined; let resolve: ((e: T) => void) | undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let reject: ((e: any) => void) | undefined; let reject: ((e: any) => void) | undefined;
const promise = new Promise<T>((resolveFn, rejectFn) => { const promise = new Promise<T>((resolveFn, rejectFn) => {

View File

@@ -6,23 +6,23 @@ import {
type Accessor, type Accessor,
type Signal, type Signal,
} from 'solid-js'; } from 'solid-js';
import { type Event as NostrEvent, type Filter, Kind } from 'nostr-tools';
import { createQuery, useQueryClient, type CreateQueryResult } from '@tanstack/solid-query'; import { createQuery, useQueryClient, type CreateQueryResult } from '@tanstack/solid-query';
import { type Event as NostrEvent, type Filter, Kind } from 'nostr-tools';
import eventWrapper from '@/core/event'; import eventWrapper from '@/core/event';
import useBatch, { type Task } from '@/nostr/useBatch'; import useBatch, { type Task } from '@/nostr/useBatch';
import useStats from '@/nostr/useStats';
import useConfig from '@/nostr/useConfig'; import useConfig from '@/nostr/useConfig';
import usePool from '@/nostr/usePool'; import usePool from '@/nostr/usePool';
import useStats from '@/nostr/useStats';
import timeout from '@/utils/timeout'; import timeout from '@/utils/timeout';
type TaskArg = type TaskArg =
| { type: 'Profile'; pubkey: string } | { type: 'Profile'; pubkey: string }
| { type: 'TextNote'; eventId: string } | { type: 'TextNote'; eventId: string }
| { type: 'Reactions'; mentionedEventId: string } | { type: 'Reactions'; mentionedEventId: string }
| { type: 'DeprecatedReposts'; mentionedEventId: string } | { type: 'ZapReceipts'; mentionedEventId: string }
| { type: 'Reposts'; mentionedEventId: string }
| { type: 'Followings'; pubkey: string }; | { type: 'Followings'; pubkey: string };
type BatchedEvents = { completed: boolean; events: NostrEvent[] }; type BatchedEvents = { completed: boolean; events: NostrEvent[] };
@@ -83,15 +83,15 @@ export type UseReactions = {
query: CreateQueryResult<NostrEvent[]>; query: CreateQueryResult<NostrEvent[]>;
}; };
// DeprecatedReposts // Reposts
export type UseDeprecatedRepostsProps = { export type UseRepostsProps = {
eventId: string; eventId: string;
}; };
export type UseDeprecatedReposts = { export type UseReposts = {
reposts: () => NostrEvent[]; reposts: () => NostrEvent[];
isRepostedBy: (pubkey: string) => boolean; isRepostedBy: (pubkey: string) => boolean;
invalidateDeprecatedReposts: () => Promise<void>; invalidateReposts: () => Promise<void>;
query: CreateQueryResult<NostrEvent[]>; query: CreateQueryResult<NostrEvent[]>;
}; };
@@ -121,7 +121,7 @@ setInterval(() => {
setActiveBatchSubscriptions(count); setActiveBatchSubscriptions(count);
}, 1000); }, 1000);
const EmptyBatchedEvents = Object.freeze({ events: Object.freeze([]), completed: true }); const EmptyBatchedEvents = { events: [], completed: true };
const emptyBatchedEvents = () => EmptyBatchedEvents; const emptyBatchedEvents = () => EmptyBatchedEvents;
const { exec } = useBatch<TaskArg, TaskRes>(() => ({ const { exec } = useBatch<TaskArg, TaskRes>(() => ({
@@ -132,6 +132,7 @@ const { exec } = useBatch<TaskArg, TaskRes>(() => ({
const textNoteTasks = new Map<string, Task<TaskArg, TaskRes>[]>(); const textNoteTasks = new Map<string, Task<TaskArg, TaskRes>[]>();
const reactionsTasks = new Map<string, Task<TaskArg, TaskRes>[]>(); const reactionsTasks = new Map<string, Task<TaskArg, TaskRes>[]>();
const repostsTasks = new Map<string, Task<TaskArg, TaskRes>[]>(); const repostsTasks = new Map<string, Task<TaskArg, TaskRes>[]>();
const zapReceiptsTasks = new Map<string, Task<TaskArg, TaskRes>[]>();
const followingsTasks = new Map<string, Task<TaskArg, TaskRes>[]>(); const followingsTasks = new Map<string, Task<TaskArg, TaskRes>[]>();
tasks.forEach((task) => { tasks.forEach((task) => {
@@ -144,9 +145,12 @@ const { exec } = useBatch<TaskArg, TaskRes>(() => ({
} else if (task.args.type === 'Reactions') { } else if (task.args.type === 'Reactions') {
const current = reactionsTasks.get(task.args.mentionedEventId) ?? []; const current = reactionsTasks.get(task.args.mentionedEventId) ?? [];
reactionsTasks.set(task.args.mentionedEventId, [...current, task]); reactionsTasks.set(task.args.mentionedEventId, [...current, task]);
} else if (task.args.type === 'DeprecatedReposts') { } else if (task.args.type === 'Reposts') {
const current = repostsTasks.get(task.args.mentionedEventId) ?? []; const current = repostsTasks.get(task.args.mentionedEventId) ?? [];
repostsTasks.set(task.args.mentionedEventId, [...current, task]); repostsTasks.set(task.args.mentionedEventId, [...current, task]);
} else if (task.args.type === 'ZapReceipts') {
const current = zapReceiptsTasks.get(task.args.mentionedEventId) ?? [];
repostsTasks.set(task.args.mentionedEventId, [...current, task]);
} else if (task.args.type === 'Followings') { } else if (task.args.type === 'Followings') {
const current = followingsTasks.get(task.args.pubkey) ?? []; const current = followingsTasks.get(task.args.pubkey) ?? [];
followingsTasks.set(task.args.pubkey, [...current, task]); followingsTasks.set(task.args.pubkey, [...current, task]);
@@ -157,6 +161,7 @@ const { exec } = useBatch<TaskArg, TaskRes>(() => ({
const textNoteIds = [...textNoteTasks.keys()]; const textNoteIds = [...textNoteTasks.keys()];
const reactionsIds = [...reactionsTasks.keys()]; const reactionsIds = [...reactionsTasks.keys()];
const repostsIds = [...repostsTasks.keys()]; const repostsIds = [...repostsTasks.keys()];
const zapReceiptsIds = [...zapReceiptsTasks.keys()];
const followingsIds = [...followingsTasks.keys()]; const followingsIds = [...followingsTasks.keys()];
const filters: Filter[] = []; const filters: Filter[] = [];
@@ -173,6 +178,9 @@ const { exec } = useBatch<TaskArg, TaskRes>(() => ({
if (repostsIds.length > 0) { if (repostsIds.length > 0) {
filters.push({ kinds: [6], '#e': repostsIds }); filters.push({ kinds: [6], '#e': repostsIds });
} }
if (zapReceiptsIds.length > 0) {
filters.push({ kinds: [9735], '#e': zapReceiptsIds });
}
if (followingsIds.length > 0) { if (followingsIds.length > 0) {
filters.push({ kinds: [Kind.Contacts], authors: followingsIds }); filters.push({ kinds: [Kind.Contacts], authors: followingsIds });
} }
@@ -239,6 +247,13 @@ const { exec } = useBatch<TaskArg, TaskRes>(() => ({
const registeredTasks = repostsTasks.get(taggedEventId) ?? []; const registeredTasks = repostsTasks.get(taggedEventId) ?? [];
resolveTasks(registeredTasks, event); resolveTasks(registeredTasks, event);
}); });
} else if (event.kind === Kind.Zap) {
const eventTags = eventWrapper(event).taggedEvents();
eventTags.forEach((eventTag) => {
const taggedEventId = eventTag.id;
const registeredTasks = repostsTasks.get(taggedEventId) ?? [];
resolveTasks(registeredTasks, event);
});
} else if (event.kind === Kind.Contacts) { } else if (event.kind === Kind.Contacts) {
const registeredTasks = followingsTasks.get(event.pubkey) ?? []; const registeredTasks = followingsTasks.get(event.pubkey) ?? [];
resolveTasks(registeredTasks, event); resolveTasks(registeredTasks, event);
@@ -393,12 +408,10 @@ export const useReactions = (propsProvider: () => UseReactionsProps | null): Use
return { reactions, reactionsGroupedByContent, isReactedBy, invalidateReactions, query }; return { reactions, reactionsGroupedByContent, isReactedBy, invalidateReactions, query };
}; };
export const useDeprecatedReposts = ( export const useReposts = (propsProvider: () => UseRepostsProps): UseReposts => {
propsProvider: () => UseDeprecatedRepostsProps,
): UseDeprecatedReposts => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const props = createMemo(propsProvider); const props = createMemo(propsProvider);
const genQueryKey = createMemo(() => ['useDeprecatedReposts', props()] as const); const genQueryKey = createMemo(() => ['useReposts', props()] as const);
const query = createQuery( const query = createQuery(
genQueryKey, genQueryKey,
@@ -406,16 +419,14 @@ export const useDeprecatedReposts = (
const [, currentProps] = queryKey; const [, currentProps] = queryKey;
if (currentProps == null) return []; if (currentProps == null) return [];
const { eventId: mentionedEventId } = currentProps; const { eventId: mentionedEventId } = currentProps;
const promise = exec({ type: 'DeprecatedReposts', mentionedEventId }, signal).then( const promise = exec({ type: 'Reposts', mentionedEventId }, signal).then((batchedEvents) => {
(batchedEvents) => { const events = () => batchedEvents().events;
const events = () => batchedEvents().events; observable(batchedEvents).subscribe(() => {
observable(batchedEvents).subscribe(() => { queryClient.setQueryData(queryKey, events());
queryClient.setQueryData(queryKey, events()); });
}); return events();
return events(); });
}, return timeout(15000, `useReposts: ${mentionedEventId}`)(promise);
);
return timeout(15000, `useDeprecatedReposts: ${mentionedEventId}`)(promise);
}, },
{ {
staleTime: 1 * 60 * 1000, // 1 min staleTime: 1 * 60 * 1000, // 1 min
@@ -429,10 +440,9 @@ export const useDeprecatedReposts = (
const isRepostedBy = (pubkey: string): boolean => const isRepostedBy = (pubkey: string): boolean =>
reposts().findIndex((event) => event.pubkey === pubkey) !== -1; reposts().findIndex((event) => event.pubkey === pubkey) !== -1;
const invalidateDeprecatedReposts = (): Promise<void> => const invalidateReposts = (): Promise<void> => queryClient.invalidateQueries(genQueryKey());
queryClient.invalidateQueries(genQueryKey());
return { reposts, isRepostedBy, invalidateDeprecatedReposts, query }; return { reposts, isRepostedBy, invalidateReposts, query };
}; };
export const useFollowings = (propsProvider: () => UseFollowingsProps | null): UseFollowings => { export const useFollowings = (propsProvider: () => UseFollowingsProps | null): UseFollowings => {

View File

@@ -1,5 +1,7 @@
import assert from 'assert'; import assert from 'assert';
import { describe, it } from 'vitest'; import { describe, it } from 'vitest';
import { buildTags } from './useCommands'; import { buildTags } from './useCommands';
describe('buildTags', () => { describe('buildTags', () => {

View File

@@ -2,7 +2,6 @@ import { getEventHash, Kind, type UnsignedEvent, type Pub } from 'nostr-tools';
// import '@/types/nostr.d'; // import '@/types/nostr.d';
import usePool from '@/nostr/usePool'; import usePool from '@/nostr/usePool';
import epoch from '@/utils/epoch'; import epoch from '@/utils/epoch';
export type TagParams = { export type TagParams = {
@@ -148,7 +147,7 @@ const useCommands = () => {
return publishEvent(relayUrls, preSignedEvent); return publishEvent(relayUrls, preSignedEvent);
}, },
// NIP-18 // NIP-18
async publishDeprecatedRepost({ async publishRepost({
relayUrls, relayUrls,
pubkey, pubkey,
eventId, eventId,

View File

@@ -1,10 +1,12 @@
import { type Accessor, type Setter } from 'solid-js'; import { type Accessor, type Setter } from 'solid-js';
import { Kind, type Event as NostrEvent } from 'nostr-tools'; import { Kind, type Event as NostrEvent } from 'nostr-tools';
import { ColumnConfig } from '@/core/column';
import { import {
createStorageWithSerializer, createStorageWithSerializer,
createStoreWithStorage, createStoreWithStorage,
} from '@/hooks/createSignalWithStorage'; } from '@/hooks/createSignalWithStorage';
import { ColumnConfig } from '@/core/column';
export type Config = { export type Config = {
relayUrls: string[]; relayUrls: string[];
@@ -111,7 +113,8 @@ const useConfig = (): UseConfig => {
return false; return false;
}; };
const shouldMuteEvent = (event: NostrEvent) => isPubkeyMuted(event.pubkey) || hasMutedKeyword(event); const shouldMuteEvent = (event: NostrEvent) =>
isPubkeyMuted(event.pubkey) || hasMutedKeyword(event);
const initializeColumns = ({ pubkey }: { pubkey: string }) => { const initializeColumns = ({ pubkey }: { pubkey: string }) => {
// すでに設定されている場合は終了 // すでに設定されている場合は終了

View File

@@ -1,3 +0,0 @@
import { useDeprecatedReposts } from '@/nostr/useBatchedEvents';
export default useDeprecatedReposts;

View File

@@ -1,6 +1,7 @@
import { createMemo, createSignal } from 'solid-js'; import { createMemo, createSignal } from 'solid-js';
import { Kind } from 'nostr-tools';
import uniq from 'lodash/uniq'; import uniq from 'lodash/uniq';
import { Kind } from 'nostr-tools';
import useConfig from '@/nostr/useConfig'; import useConfig from '@/nostr/useConfig';
import useSubscription from '@/nostr/useSubscription'; import useSubscription from '@/nostr/useSubscription';

View File

@@ -1,4 +1,5 @@
import { createSignal } from 'solid-js'; import { createSignal } from 'solid-js';
import { SimplePool } from 'nostr-tools'; import { SimplePool } from 'nostr-tools';
const [pool] = createSignal<SimplePool>(new SimplePool()); const [pool] = createSignal<SimplePool>(new SimplePool());

3
src/nostr/useReposts.ts Normal file
View File

@@ -0,0 +1,3 @@
import { useReposts } from '@/nostr/useBatchedEvents';
export default useReposts;

View File

@@ -1,9 +1,13 @@
import { createSignal, createEffect, onCleanup, on } from 'solid-js'; import { createSignal, createEffect, onCleanup, on } from 'solid-js';
import type { Event as NostrEvent, Filter, SubscriptionOptions } from 'nostr-tools';
import uniqBy from 'lodash/uniqBy'; import uniqBy from 'lodash/uniqBy';
import usePool from '@/nostr/usePool'; import usePool from '@/nostr/usePool';
import useStats from './useStats';
import useConfig from './useConfig'; import useConfig from './useConfig';
import useStats from './useStats';
import type { Event as NostrEvent, Filter, SubscriptionOptions } from 'nostr-tools';
export type UseSubscriptionProps = { export type UseSubscriptionProps = {
relayUrls: string[]; relayUrls: string[];

View File

@@ -1,4 +1,5 @@
import { createMemo, type Accessor } from 'solid-js'; import { createMemo, type Accessor } from 'solid-js';
import { createQuery, type CreateQueryResult } from '@tanstack/solid-query'; import { createQuery, type CreateQueryResult } from '@tanstack/solid-query';
import { nip05, nip19 } from 'nostr-tools'; import { nip05, nip19 } from 'nostr-tools';

View File

@@ -1,5 +1,7 @@
import { createSignal, onMount, Switch, Match, type Component } from 'solid-js'; import { createSignal, onMount, Switch, Match, type Component } from 'solid-js';
import { useNavigate } from '@solidjs/router'; import { useNavigate } from '@solidjs/router';
import usePersistStatus from '@/hooks/usePersistStatus'; import usePersistStatus from '@/hooks/usePersistStatus';
type SignerStatus = 'checking' | 'available' | 'unavailable'; type SignerStatus = 'checking' | 'available' | 'unavailable';

View File

@@ -8,26 +8,24 @@ import {
Match, Match,
type Component, type Component,
} from 'solid-js'; } from 'solid-js';
import { useNavigate } from '@solidjs/router'; import { useNavigate } from '@solidjs/router';
import { createVirtualizer } from '@tanstack/solid-virtual'; import { createVirtualizer } from '@tanstack/solid-virtual';
import uniq from 'lodash/uniq'; import uniq from 'lodash/uniq';
import Column from '@/components/Column'; import Column from '@/components/Column';
import SideBar from '@/components/SideBar';
import Timeline from '@/components/Timeline';
import Notification from '@/components/Notification'; import Notification from '@/components/Notification';
import ProfileDisplay from '@/components/ProfileDisplay'; import ProfileDisplay from '@/components/ProfileDisplay';
import SideBar from '@/components/SideBar';
import usePool from '@/nostr/usePool'; import Timeline from '@/components/Timeline';
import useConfig from '@/nostr/useConfig';
import useSubscription from '@/nostr/useSubscription';
import useFollowings from '@/nostr/useFollowings';
import usePubkey from '@/nostr/usePubkey';
import { useMountShortcutKeys } from '@/hooks/useShortcutKeys';
import usePersistStatus from '@/hooks/usePersistStatus';
import useModalState from '@/hooks/useModalState'; import useModalState from '@/hooks/useModalState';
import usePersistStatus from '@/hooks/usePersistStatus';
import { useMountShortcutKeys } from '@/hooks/useShortcutKeys';
import useConfig from '@/nostr/useConfig';
import useFollowings from '@/nostr/useFollowings';
import usePool from '@/nostr/usePool';
import usePubkey from '@/nostr/usePubkey';
import useSubscription from '@/nostr/useSubscription';
import ensureNonNull from '@/utils/ensureNonNull'; import ensureNonNull from '@/utils/ensureNonNull';
import epoch from '@/utils/epoch'; import epoch from '@/utils/epoch';

View File

@@ -1,14 +1,18 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type TupleNonNull<T extends readonly any[]> = { export type TupleNonNull<T extends readonly any[]> = {
[P in keyof T]: NonNullable<T[P]>; [P in keyof T]: NonNullable<T[P]>;
}; };
const ensureNonNull = const ensureNonNull =
<T extends readonly any[]>(tuple: T) => // eslint-disable-next-line @typescript-eslint/no-explicit-any
<R>(f: (tupleNonNull: TupleNonNull<T>) => R): R | null => {
if (tuple.some((e) => e == null)) {
return null; <T extends readonly any[]>(tuple: T) =>
} <R>(f: (tupleNonNull: TupleNonNull<T>) => R): R | null => {
return f(tuple as TupleNonNull<T>); if (tuple.some((e) => e == null)) {
}; return null;
}
return f(tuple as TupleNonNull<T>);
};
export default ensureNonNull; export default ensureNonNull;

View File

@@ -1,5 +1,7 @@
import assert from 'assert'; import assert from 'assert';
import { describe, it } from 'vitest'; import { describe, it } from 'vitest';
import { fixUrl } from './imageUrl'; import { fixUrl } from './imageUrl';
describe('fixUrl', () => { describe('fixUrl', () => {

View File

@@ -1,4 +1,4 @@
/* eslint global-require: "off" */ /* eslint global-require: "off", @typescript-eslint/no-var-requires: "off" */
const colors = require('tailwindcss/colors'); const colors = require('tailwindcss/colors');
module.exports = { module.exports = {

View File

@@ -1,4 +1,5 @@
import path from 'path'; import path from 'path';
// eslint-disable-next-line import/no-extraneous-dependencies // eslint-disable-next-line import/no-extraneous-dependencies
import { defineConfig } from 'vitest/config'; import { defineConfig } from 'vitest/config';