mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-17 05:54:19 +01:00
update
This commit is contained in:
32
.eslintrc.js
32
.eslintrc.js
@@ -34,6 +34,21 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
'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: {
|
||||
linkComponents: ['Link'],
|
||||
@@ -45,23 +60,6 @@ module.exports = {
|
||||
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: {
|
||||
whitelist: [
|
||||
'h-fill-available',
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
"dev": "npm run generatePackageInfo && vite",
|
||||
"build": "npm run generatePackageInfo && vite build",
|
||||
"serve": "npm run generatePackageInfo && vite preview",
|
||||
"lint": "eslint .",
|
||||
"lint": "eslint --cache .",
|
||||
"fix": "eslint --cache --fix .",
|
||||
"tsc": "tsc --noEmit --skipLibCheck",
|
||||
"test": "vitest run --no-watch",
|
||||
"watch-test": "vitest --watch",
|
||||
"cover": "vitest run --coverage",
|
||||
"fix": "eslint --fix .",
|
||||
"prepare": "husky install",
|
||||
"generatePackageInfo": "node -e 'import(\"./scripts/generatePackageInfo.mjs\").then((m) => m.default())'",
|
||||
"checkLicense": "node -e 'import(\"./scripts/checkLicense.mjs\").then((m) => m.default())'"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { createEffect, onCleanup, lazy, type Component } from 'solid-js';
|
||||
|
||||
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 { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/solid-query';
|
||||
|
||||
const Home = lazy(() => import('@/pages/Home'));
|
||||
const Hello = lazy(() => import('@/pages/Hello'));
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Show, type JSX, type Component } from 'solid-js';
|
||||
|
||||
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 { TimelineContext, useTimelineState } from '@/components/TimelineContext';
|
||||
import { useHandleCommand } from '@/hooks/useCommandBus';
|
||||
|
||||
export type ColumnProps = {
|
||||
name: string;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import useConfig, { type Config } from '@/nostr/useConfig';
|
||||
import { createSignal, For, type JSX } from 'solid-js';
|
||||
|
||||
import XMark from 'heroicons/24/outline/x-mark.svg';
|
||||
|
||||
import Modal from '@/components/Modal';
|
||||
import useConfig, { type Config } from '@/nostr/useConfig';
|
||||
|
||||
import UserNameDisplay from './UserDisplayName';
|
||||
|
||||
type ConfigProps = {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
// NIP-18 (DEPRECATED)
|
||||
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 { Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
import ColumnItem from '@/components/ColumnItem';
|
||||
import UserDisplayName from '@/components/UserDisplayName';
|
||||
import eventWrapper from '@/core/event';
|
||||
import useFormatDate from '@/hooks/useFormatDate';
|
||||
import useModalState from '@/hooks/useModalState';
|
||||
|
||||
import TextNoteDisplayById from './textNote/TextNoteDisplayById';
|
||||
|
||||
export type DeprecatedRepostProps = {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component } from 'solid-js';
|
||||
|
||||
import { nip19 } from 'nostr-tools';
|
||||
|
||||
const { noteEncode } = nip19;
|
||||
|
||||
@@ -15,6 +15,7 @@ const Modal: Component<ModalProps> = (props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */
|
||||
<div
|
||||
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"
|
||||
|
||||
@@ -8,25 +8,22 @@ import {
|
||||
type JSX,
|
||||
type Accessor,
|
||||
} 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 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 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 { 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 = {
|
||||
replyTo?: NostrEvent;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { For, Switch, Match, type Component } from 'solid-js';
|
||||
|
||||
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 Reaction from '@/components/notification/Reaction';
|
||||
import TextNote from '@/components/TextNote';
|
||||
|
||||
export type NotificationProps = {
|
||||
events: NostrEvent[];
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
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 XMark from 'heroicons/24/outline/x-mark.svg';
|
||||
import CheckCircle from 'heroicons/24/solid/check-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 Timeline from '@/components/Timeline';
|
||||
import Copy from '@/components/utils/Copy';
|
||||
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 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 npubEncodeFallback from '@/utils/npubEncodeFallback';
|
||||
import useVerification from '@/nostr/useVerification';
|
||||
import ensureNonNull from '@/utils/ensureNonNull';
|
||||
import epoch from '@/utils/epoch';
|
||||
import npubEncodeFallback from '@/utils/npubEncodeFallback';
|
||||
import timeout from '@/utils/timeout';
|
||||
|
||||
import ContextMenu, { MenuItem } from './ContextMenu';
|
||||
|
||||
export type ProfileDisplayProps = {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
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 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 NotePostForm from '@/components/NotePostForm';
|
||||
import { useHandleCommand } from '@/hooks/useCommandBus';
|
||||
import useConfig from '@/nostr/useConfig';
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Show, type Component } from 'solid-js';
|
||||
|
||||
import ColumnItem from '@/components/ColumnItem';
|
||||
import useConfig from '@/nostr/useConfig';
|
||||
|
||||
import TextNoteDisplay, { TextNoteDisplayProps } from './textNote/TextNoteDisplay';
|
||||
|
||||
export type TextNoteProps = TextNoteDisplayProps;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { For, Switch, Match, type Component } from 'solid-js';
|
||||
|
||||
import { Kind, type Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
import TextNote from '@/components/TextNote';
|
||||
import DeprecatedRepost from '@/components/DeprecatedRepost';
|
||||
import TextNote from '@/components/TextNote';
|
||||
|
||||
export type TimelineProps = {
|
||||
events: NostrEvent[];
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Switch, Match, type Component } from 'solid-js';
|
||||
import { Filter, Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
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 useSubscription from '@/nostr/useSubscription';
|
||||
import { type TimelineContent } from '@/components/TimelineContext';
|
||||
import eventWrapper from '@/core/event';
|
||||
import useConfig from '@/nostr/useConfig';
|
||||
import useSubscription from '@/nostr/useSubscription';
|
||||
|
||||
const relatedEvents = (rawEvent: NostrEvent) => {
|
||||
const event = () => eventWrapper(rawEvent);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createContext, useContext } from 'solid-js';
|
||||
import { createStore } from 'solid-js/store';
|
||||
|
||||
import { Event as NostrEvent } from 'nostr-tools';
|
||||
import { createStore } from 'solid-js/store';
|
||||
|
||||
export type TimelineContent = {
|
||||
type: 'Replies';
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
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 { type Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
import ColumnItem from '@/components/ColumnItem';
|
||||
import TextNoteDisplay from '@/components/textNote/TextNoteDisplay';
|
||||
import UserDisplayName from '@/components/UserDisplayName';
|
||||
|
||||
import useProfile from '@/nostr/useProfile';
|
||||
import useEvent from '@/nostr/useEvent';
|
||||
import eventWrapper from '@/core/event';
|
||||
import useModalState from '@/hooks/useModalState';
|
||||
import useEvent from '@/nostr/useEvent';
|
||||
import useProfile from '@/nostr/useProfile';
|
||||
|
||||
type ReactionProps = {
|
||||
event: NostrEvent;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createSignal, type Component, type JSX, Show } from 'solid-js';
|
||||
|
||||
import { ContentWarning } from '@/core/event';
|
||||
|
||||
export type ContentWarningDisplayProps = {
|
||||
|
||||
@@ -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 SafeLink from '../utils/SafeLink';
|
||||
|
||||
type ImageDisplayProps = {
|
||||
@@ -14,12 +16,13 @@ const ImageDisplay: Component<ImageDisplayProps> = (props) => {
|
||||
const [hidden, setHidden] = createSignal(props.initialHidden);
|
||||
const [playing, setPlaying] = createSignal(true);
|
||||
|
||||
const isGIF = () => props.url.match(/\.gif/i);
|
||||
// const isGIF = () => props.url.match(/\.gif/i);
|
||||
|
||||
const play = () => {
|
||||
setPlaying(true);
|
||||
};
|
||||
|
||||
/*
|
||||
const stop = () => {
|
||||
if (canvasRef == null || imageRef == null) return;
|
||||
canvasRef.width = imageRef.width;
|
||||
@@ -39,6 +42,7 @@ const ImageDisplay: Component<ImageDisplayProps> = (props) => {
|
||||
);
|
||||
setPlaying(false);
|
||||
};
|
||||
*/
|
||||
|
||||
return (
|
||||
<Show
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Show } from 'solid-js';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import TextNoteDisplayById from '@/components/textNote/TextNoteDisplayById';
|
||||
import { type MentionedEvent } from '@/core/parseTextNote';
|
||||
|
||||
import EventLink from '../EventLink';
|
||||
|
||||
export type MentionedEventDisplayProps = {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { MentionedUser } from '@/core/parseTextNote';
|
||||
import GeneralUserMentionDisplay from '@/components/textNote/GeneralUserMentionDisplay';
|
||||
import useModalState from '@/hooks/useModalState';
|
||||
|
||||
import type { MentionedUser } from '@/core/parseTextNote';
|
||||
|
||||
export type MentionedUserDisplayProps = {
|
||||
pubkey: string;
|
||||
};
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { For } from 'solid-js';
|
||||
import parseTextNote, { resolveTagReference, type ParsedTextNoteNode } from '@/core/parseTextNote';
|
||||
import type { Event as NostrEvent } from 'nostr-tools';
|
||||
import PlainTextDisplay from '@/components/textNote/PlainTextDisplay';
|
||||
import MentionedUserDisplay from '@/components/textNote/MentionedUserDisplay';
|
||||
|
||||
import EventLink from '@/components/EventLink';
|
||||
import ImageDisplay from '@/components/textNote/ImageDisplay';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
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 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 EventLink from '../EventLink';
|
||||
import TextNoteDisplayById from './TextNoteDisplayById';
|
||||
import { isImageUrl } from '@/utils/imageUrl';
|
||||
|
||||
export type TextNoteContentDisplayProps = {
|
||||
event: NostrEvent;
|
||||
|
||||
@@ -1,36 +1,33 @@
|
||||
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 HeartSolid from 'heroicons/24/solid/heart.svg';
|
||||
import { createMutation } from '@tanstack/solid-query';
|
||||
import ArrowPathRoundedSquare from 'heroicons/24/outline/arrow-path-rounded-square.svg';
|
||||
import ChatBubbleLeft from 'heroicons/24/outline/chat-bubble-left.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 useProfile from '@/nostr/useProfile';
|
||||
import useConfig from '@/nostr/useConfig';
|
||||
import usePubkey from '@/nostr/usePubkey';
|
||||
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 NotePostForm from '@/components/NotePostForm';
|
||||
import ContentWarningDisplay from '@/components/textNote/ContentWarningDisplay';
|
||||
import GeneralUserMentionDisplay from '@/components/textNote/GeneralUserMentionDisplay';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import TextNoteContentDisplay from '@/components/textNote/TextNoteContentDisplay';
|
||||
import TextNoteDisplayById from '@/components/textNote/TextNoteDisplayById';
|
||||
import { useTimelineContext } from '@/components/TimelineContext';
|
||||
import GeneralUserMentionDisplay from '@/components/textNote/GeneralUserMentionDisplay';
|
||||
import ContentWarningDisplay from '@/components/textNote/ContentWarningDisplay';
|
||||
import TextNoteContentDisplay from '@/components/textNote/TextNoteContentDisplay';
|
||||
import NotePostForm from '@/components/NotePostForm';
|
||||
|
||||
import eventWrapper from '@/core/event';
|
||||
import useFormatDate from '@/hooks/useFormatDate';
|
||||
import useModalState from '@/hooks/useModalState';
|
||||
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 npubEncodeFallback from '@/utils/npubEncodeFallback';
|
||||
import useSubscription from '@/nostr/useSubscription';
|
||||
|
||||
import ContextMenu, { MenuItem } from '../ContextMenu';
|
||||
|
||||
export type TextNoteDisplayProps = {
|
||||
@@ -85,7 +82,7 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
eventId: props.event.id,
|
||||
}));
|
||||
|
||||
const { reposts, isRepostedBy, invalidateDeprecatedReposts } = useDeprecatedReposts(() => ({
|
||||
const { reposts, isRepostedBy, invalidateReposts } = useReposts(() => ({
|
||||
eventId: props.event.id,
|
||||
}));
|
||||
|
||||
@@ -105,9 +102,9 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
},
|
||||
});
|
||||
|
||||
const publishDeprecatedRepostMutation = createMutation({
|
||||
mutationKey: ['publishDeprecatedRepost', event().id],
|
||||
mutationFn: commands.publishDeprecatedRepost.bind(commands),
|
||||
const publishRepostMutation = createMutation({
|
||||
mutationKey: ['publishRepost', event().id],
|
||||
mutationFn: commands.publishRepost.bind(commands),
|
||||
onSuccess: () => {
|
||||
console.log('succeeded to publish reposts');
|
||||
},
|
||||
@@ -115,7 +112,7 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
console.error('failed to publish repost: ', err);
|
||||
},
|
||||
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]) => {
|
||||
publishDeprecatedRepostMutation.mutate({
|
||||
publishRepostMutation.mutate({
|
||||
relayUrls: config().relayUrls,
|
||||
pubkey: pubkeyNonNull,
|
||||
eventId: eventIdNonNull,
|
||||
@@ -306,13 +303,13 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
class="flex shrink-0 items-center gap-1"
|
||||
classList={{
|
||||
'text-zinc-400': !isRepostedByMe(),
|
||||
'text-green-400': isRepostedByMe() || publishDeprecatedRepostMutation.isLoading,
|
||||
'text-green-400': isRepostedByMe() || publishRepostMutation.isLoading,
|
||||
}}
|
||||
>
|
||||
<button
|
||||
class="h-4 w-4"
|
||||
onClick={handleRepost}
|
||||
disabled={publishDeprecatedRepostMutation.isLoading}
|
||||
disabled={publishRepostMutation.isLoading}
|
||||
>
|
||||
<ArrowPathRoundedSquare />
|
||||
</button>
|
||||
|
||||
@@ -2,11 +2,10 @@ import { Switch, Match, type Component } from 'solid-js';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import TextNoteDisplay, { type TextNoteDisplayProps } from '@/components/textNote/TextNoteDisplay';
|
||||
|
||||
import useConfig from '@/nostr/useConfig';
|
||||
import useEvent from '@/nostr/useEvent';
|
||||
|
||||
import ensureNonNull from '@/utils/ensureNonNull';
|
||||
|
||||
import EventLink from '../EventLink';
|
||||
|
||||
type TextNoteDisplayByIdProps = Omit<TextNoteDisplayProps, 'event'> & {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// import { z } from 'zod';
|
||||
import { type Filter } from 'nostr-tools';
|
||||
|
||||
import { type ColumnProps } from '@/components/Column';
|
||||
|
||||
export type NotificationType =
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Event as NostrEvent } from 'nostr-tools';
|
||||
import uniq from 'lodash/uniq';
|
||||
|
||||
import type { Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
export type EventMarker = 'reply' | 'root' | 'mention';
|
||||
|
||||
export type TaggedEvent = {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import assert from 'assert';
|
||||
import { describe, it } from 'vitest';
|
||||
|
||||
import { type Event as NostrEvent } from 'nostr-tools';
|
||||
import { describe, it } from 'vitest';
|
||||
|
||||
import parseTextNote, {
|
||||
resolveTagReference,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { nip19, type Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
import eventWrapper from './event';
|
||||
|
||||
type ProfilePointer = nip19.ProfilePointer;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createSignal, createEffect, onMount, type Signal } from 'solid-js';
|
||||
|
||||
import { createStore, SetStoreFunction, type Store, type StoreNode } from 'solid-js/store';
|
||||
|
||||
type GenericStorage<T> = {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import useConfig from '@/nostr/useConfig';
|
||||
|
||||
import useDatePulser from '@/hooks/useDatePulser';
|
||||
|
||||
import useConfig from '@/nostr/useConfig';
|
||||
import { formatRelative, formatAbsoluteLong, formatAbsoluteShort } from '@/utils/formatDate';
|
||||
|
||||
// 7 seconds is used here so that the last digit of relative time is changed.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Accessor } from 'solid-js';
|
||||
|
||||
import {
|
||||
createSignalWithStorage,
|
||||
createStorageWithSerializer,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Accessor } from 'solid-js';
|
||||
import { createSignal, createEffect } from 'solid-js';
|
||||
import type { Accessor } from 'solid-js';
|
||||
|
||||
export type UseResizedImageProps = {
|
||||
imageUrl: Accessor<string | undefined>;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// type Commands = (typeof commands)[number];
|
||||
|
||||
import { onMount, onCleanup, type JSX } from 'solid-js';
|
||||
|
||||
import throttle from 'lodash/throttle';
|
||||
|
||||
import { useRequestCommand, type Command } from '@/hooks/useCommandBus';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* @refresh reload */
|
||||
import { render } from 'solid-js/web';
|
||||
import { Router } from '@solidjs/router';
|
||||
import { render } from 'solid-js/web';
|
||||
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
|
||||
@@ -4,6 +4,7 @@ export type Task<TaskArgs, TaskResult> = {
|
||||
id: number;
|
||||
args: TaskArgs;
|
||||
resolve: (result: TaskResult) => void;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
reject: (error: any) => void;
|
||||
};
|
||||
|
||||
@@ -16,11 +17,13 @@ export type UseBatchProps<TaskArgs, TaskResult> = {
|
||||
export type PromiseWithCallbacks<T> = {
|
||||
promise: Promise<T>;
|
||||
resolve: (e: T) => void;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
reject: (e: any) => void;
|
||||
};
|
||||
|
||||
const promiseWithCallbacks = <T>(): PromiseWithCallbacks<T> => {
|
||||
let resolve: ((e: T) => void) | undefined;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let reject: ((e: any) => void) | undefined;
|
||||
|
||||
const promise = new Promise<T>((resolveFn, rejectFn) => {
|
||||
|
||||
@@ -6,23 +6,23 @@ import {
|
||||
type Accessor,
|
||||
type Signal,
|
||||
} from 'solid-js';
|
||||
import { type Event as NostrEvent, type Filter, Kind } from 'nostr-tools';
|
||||
|
||||
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 useBatch, { type Task } from '@/nostr/useBatch';
|
||||
import useStats from '@/nostr/useStats';
|
||||
import useConfig from '@/nostr/useConfig';
|
||||
import usePool from '@/nostr/usePool';
|
||||
|
||||
import useStats from '@/nostr/useStats';
|
||||
import timeout from '@/utils/timeout';
|
||||
|
||||
type TaskArg =
|
||||
| { type: 'Profile'; pubkey: string }
|
||||
| { type: 'TextNote'; eventId: string }
|
||||
| { type: 'Reactions'; mentionedEventId: string }
|
||||
| { type: 'DeprecatedReposts'; mentionedEventId: string }
|
||||
| { type: 'ZapReceipts'; mentionedEventId: string }
|
||||
| { type: 'Reposts'; mentionedEventId: string }
|
||||
| { type: 'Followings'; pubkey: string };
|
||||
|
||||
type BatchedEvents = { completed: boolean; events: NostrEvent[] };
|
||||
@@ -83,15 +83,15 @@ export type UseReactions = {
|
||||
query: CreateQueryResult<NostrEvent[]>;
|
||||
};
|
||||
|
||||
// DeprecatedReposts
|
||||
export type UseDeprecatedRepostsProps = {
|
||||
// Reposts
|
||||
export type UseRepostsProps = {
|
||||
eventId: string;
|
||||
};
|
||||
|
||||
export type UseDeprecatedReposts = {
|
||||
export type UseReposts = {
|
||||
reposts: () => NostrEvent[];
|
||||
isRepostedBy: (pubkey: string) => boolean;
|
||||
invalidateDeprecatedReposts: () => Promise<void>;
|
||||
invalidateReposts: () => Promise<void>;
|
||||
query: CreateQueryResult<NostrEvent[]>;
|
||||
};
|
||||
|
||||
@@ -121,7 +121,7 @@ setInterval(() => {
|
||||
setActiveBatchSubscriptions(count);
|
||||
}, 1000);
|
||||
|
||||
const EmptyBatchedEvents = Object.freeze({ events: Object.freeze([]), completed: true });
|
||||
const EmptyBatchedEvents = { events: [], completed: true };
|
||||
const emptyBatchedEvents = () => EmptyBatchedEvents;
|
||||
|
||||
const { exec } = useBatch<TaskArg, TaskRes>(() => ({
|
||||
@@ -132,6 +132,7 @@ const { exec } = useBatch<TaskArg, TaskRes>(() => ({
|
||||
const textNoteTasks = new Map<string, Task<TaskArg, TaskRes>[]>();
|
||||
const reactionsTasks = 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>[]>();
|
||||
|
||||
tasks.forEach((task) => {
|
||||
@@ -144,9 +145,12 @@ const { exec } = useBatch<TaskArg, TaskRes>(() => ({
|
||||
} else if (task.args.type === 'Reactions') {
|
||||
const current = reactionsTasks.get(task.args.mentionedEventId) ?? [];
|
||||
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) ?? [];
|
||||
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') {
|
||||
const current = followingsTasks.get(task.args.pubkey) ?? [];
|
||||
followingsTasks.set(task.args.pubkey, [...current, task]);
|
||||
@@ -157,6 +161,7 @@ const { exec } = useBatch<TaskArg, TaskRes>(() => ({
|
||||
const textNoteIds = [...textNoteTasks.keys()];
|
||||
const reactionsIds = [...reactionsTasks.keys()];
|
||||
const repostsIds = [...repostsTasks.keys()];
|
||||
const zapReceiptsIds = [...zapReceiptsTasks.keys()];
|
||||
const followingsIds = [...followingsTasks.keys()];
|
||||
|
||||
const filters: Filter[] = [];
|
||||
@@ -173,6 +178,9 @@ const { exec } = useBatch<TaskArg, TaskRes>(() => ({
|
||||
if (repostsIds.length > 0) {
|
||||
filters.push({ kinds: [6], '#e': repostsIds });
|
||||
}
|
||||
if (zapReceiptsIds.length > 0) {
|
||||
filters.push({ kinds: [9735], '#e': zapReceiptsIds });
|
||||
}
|
||||
if (followingsIds.length > 0) {
|
||||
filters.push({ kinds: [Kind.Contacts], authors: followingsIds });
|
||||
}
|
||||
@@ -239,6 +247,13 @@ const { exec } = useBatch<TaskArg, TaskRes>(() => ({
|
||||
const registeredTasks = repostsTasks.get(taggedEventId) ?? [];
|
||||
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) {
|
||||
const registeredTasks = followingsTasks.get(event.pubkey) ?? [];
|
||||
resolveTasks(registeredTasks, event);
|
||||
@@ -393,12 +408,10 @@ export const useReactions = (propsProvider: () => UseReactionsProps | null): Use
|
||||
return { reactions, reactionsGroupedByContent, isReactedBy, invalidateReactions, query };
|
||||
};
|
||||
|
||||
export const useDeprecatedReposts = (
|
||||
propsProvider: () => UseDeprecatedRepostsProps,
|
||||
): UseDeprecatedReposts => {
|
||||
export const useReposts = (propsProvider: () => UseRepostsProps): UseReposts => {
|
||||
const queryClient = useQueryClient();
|
||||
const props = createMemo(propsProvider);
|
||||
const genQueryKey = createMemo(() => ['useDeprecatedReposts', props()] as const);
|
||||
const genQueryKey = createMemo(() => ['useReposts', props()] as const);
|
||||
|
||||
const query = createQuery(
|
||||
genQueryKey,
|
||||
@@ -406,16 +419,14 @@ export const useDeprecatedReposts = (
|
||||
const [, currentProps] = queryKey;
|
||||
if (currentProps == null) return [];
|
||||
const { eventId: mentionedEventId } = currentProps;
|
||||
const promise = exec({ type: 'DeprecatedReposts', mentionedEventId }, signal).then(
|
||||
(batchedEvents) => {
|
||||
const promise = exec({ type: 'Reposts', mentionedEventId }, signal).then((batchedEvents) => {
|
||||
const events = () => batchedEvents().events;
|
||||
observable(batchedEvents).subscribe(() => {
|
||||
queryClient.setQueryData(queryKey, events());
|
||||
});
|
||||
return events();
|
||||
},
|
||||
);
|
||||
return timeout(15000, `useDeprecatedReposts: ${mentionedEventId}`)(promise);
|
||||
});
|
||||
return timeout(15000, `useReposts: ${mentionedEventId}`)(promise);
|
||||
},
|
||||
{
|
||||
staleTime: 1 * 60 * 1000, // 1 min
|
||||
@@ -429,10 +440,9 @@ export const useDeprecatedReposts = (
|
||||
const isRepostedBy = (pubkey: string): boolean =>
|
||||
reposts().findIndex((event) => event.pubkey === pubkey) !== -1;
|
||||
|
||||
const invalidateDeprecatedReposts = (): Promise<void> =>
|
||||
queryClient.invalidateQueries(genQueryKey());
|
||||
const invalidateReposts = (): Promise<void> => queryClient.invalidateQueries(genQueryKey());
|
||||
|
||||
return { reposts, isRepostedBy, invalidateDeprecatedReposts, query };
|
||||
return { reposts, isRepostedBy, invalidateReposts, query };
|
||||
};
|
||||
|
||||
export const useFollowings = (propsProvider: () => UseFollowingsProps | null): UseFollowings => {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import assert from 'assert';
|
||||
|
||||
import { describe, it } from 'vitest';
|
||||
|
||||
import { buildTags } from './useCommands';
|
||||
|
||||
describe('buildTags', () => {
|
||||
|
||||
@@ -2,7 +2,6 @@ import { getEventHash, Kind, type UnsignedEvent, type Pub } from 'nostr-tools';
|
||||
|
||||
// import '@/types/nostr.d';
|
||||
import usePool from '@/nostr/usePool';
|
||||
|
||||
import epoch from '@/utils/epoch';
|
||||
|
||||
export type TagParams = {
|
||||
@@ -148,7 +147,7 @@ const useCommands = () => {
|
||||
return publishEvent(relayUrls, preSignedEvent);
|
||||
},
|
||||
// NIP-18
|
||||
async publishDeprecatedRepost({
|
||||
async publishRepost({
|
||||
relayUrls,
|
||||
pubkey,
|
||||
eventId,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { type Accessor, type Setter } from 'solid-js';
|
||||
|
||||
import { Kind, type Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
import { ColumnConfig } from '@/core/column';
|
||||
import {
|
||||
createStorageWithSerializer,
|
||||
createStoreWithStorage,
|
||||
} from '@/hooks/createSignalWithStorage';
|
||||
import { ColumnConfig } from '@/core/column';
|
||||
|
||||
export type Config = {
|
||||
relayUrls: string[];
|
||||
@@ -111,7 +113,8 @@ const useConfig = (): UseConfig => {
|
||||
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 }) => {
|
||||
// すでに設定されている場合は終了
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import { useDeprecatedReposts } from '@/nostr/useBatchedEvents';
|
||||
|
||||
export default useDeprecatedReposts;
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createMemo, createSignal } from 'solid-js';
|
||||
import { Kind } from 'nostr-tools';
|
||||
|
||||
import uniq from 'lodash/uniq';
|
||||
import { Kind } from 'nostr-tools';
|
||||
|
||||
import useConfig from '@/nostr/useConfig';
|
||||
import useSubscription from '@/nostr/useSubscription';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createSignal } from 'solid-js';
|
||||
|
||||
import { SimplePool } from 'nostr-tools';
|
||||
|
||||
const [pool] = createSignal<SimplePool>(new SimplePool());
|
||||
|
||||
3
src/nostr/useReposts.ts
Normal file
3
src/nostr/useReposts.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { useReposts } from '@/nostr/useBatchedEvents';
|
||||
|
||||
export default useReposts;
|
||||
@@ -1,9 +1,13 @@
|
||||
import { createSignal, createEffect, onCleanup, on } from 'solid-js';
|
||||
import type { Event as NostrEvent, Filter, SubscriptionOptions } from 'nostr-tools';
|
||||
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
|
||||
import usePool from '@/nostr/usePool';
|
||||
import useStats from './useStats';
|
||||
|
||||
import useConfig from './useConfig';
|
||||
import useStats from './useStats';
|
||||
|
||||
import type { Event as NostrEvent, Filter, SubscriptionOptions } from 'nostr-tools';
|
||||
|
||||
export type UseSubscriptionProps = {
|
||||
relayUrls: string[];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createMemo, type Accessor } from 'solid-js';
|
||||
|
||||
import { createQuery, type CreateQueryResult } from '@tanstack/solid-query';
|
||||
import { nip05, nip19 } from 'nostr-tools';
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createSignal, onMount, Switch, Match, type Component } from 'solid-js';
|
||||
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
|
||||
import usePersistStatus from '@/hooks/usePersistStatus';
|
||||
|
||||
type SignerStatus = 'checking' | 'available' | 'unavailable';
|
||||
|
||||
@@ -8,26 +8,24 @@ import {
|
||||
Match,
|
||||
type Component,
|
||||
} from 'solid-js';
|
||||
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import { createVirtualizer } from '@tanstack/solid-virtual';
|
||||
import uniq from 'lodash/uniq';
|
||||
|
||||
import Column from '@/components/Column';
|
||||
import SideBar from '@/components/SideBar';
|
||||
import Timeline from '@/components/Timeline';
|
||||
import Notification from '@/components/Notification';
|
||||
import ProfileDisplay from '@/components/ProfileDisplay';
|
||||
|
||||
import usePool from '@/nostr/usePool';
|
||||
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 SideBar from '@/components/SideBar';
|
||||
import Timeline from '@/components/Timeline';
|
||||
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 epoch from '@/utils/epoch';
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type TupleNonNull<T extends readonly any[]> = {
|
||||
[P in keyof T]: NonNullable<T[P]>;
|
||||
};
|
||||
|
||||
const ensureNonNull =
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
||||
|
||||
<T extends readonly any[]>(tuple: T) =>
|
||||
<R>(f: (tupleNonNull: TupleNonNull<T>) => R): R | null => {
|
||||
if (tuple.some((e) => e == null)) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import assert from 'assert';
|
||||
|
||||
import { describe, it } from 'vitest';
|
||||
|
||||
import { fixUrl } from './imageUrl';
|
||||
|
||||
describe('fixUrl', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* eslint global-require: "off" */
|
||||
/* eslint global-require: "off", @typescript-eslint/no-var-requires: "off" */
|
||||
const colors = require('tailwindcss/colors');
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import path from 'path';
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user