mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-17 14:04:21 +01:00
fix: eslint errors
This commit is contained in:
@@ -81,6 +81,13 @@ module.exports = {
|
|||||||
'created-at',
|
'created-at',
|
||||||
'actions',
|
'actions',
|
||||||
'content',
|
'content',
|
||||||
|
'profile',
|
||||||
|
'profile-icon',
|
||||||
|
'profile-name',
|
||||||
|
'profile-username',
|
||||||
|
'notification-icon',
|
||||||
|
'notification-user',
|
||||||
|
'notification-event',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
18
.github/ISSUE_TEMPLATE/bug_report.md
vendored
18
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -4,7 +4,6 @@ about: Create a report to help us improve
|
|||||||
title: ''
|
title: ''
|
||||||
labels: bug
|
labels: bug
|
||||||
assignees: syusui-s
|
assignees: syusui-s
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
@@ -12,6 +11,7 @@ A clear and concise description of what the bug is.
|
|||||||
|
|
||||||
**To Reproduce**
|
**To Reproduce**
|
||||||
Steps to reproduce the behavior:
|
Steps to reproduce the behavior:
|
||||||
|
|
||||||
1. Go to '...'
|
1. Go to '...'
|
||||||
2. Click on '....'
|
2. Click on '....'
|
||||||
3. Scroll down to '....'
|
3. Scroll down to '....'
|
||||||
@@ -24,15 +24,17 @@ A clear and concise description of what you expected to happen.
|
|||||||
If applicable, add screenshots to help explain your problem.
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
**Desktop (please complete the following information):**
|
||||||
- OS: [e.g. iOS]
|
|
||||||
- Browser [e.g. chrome, safari]
|
- OS: [e.g. iOS]
|
||||||
- Version [e.g. 22]
|
- Browser [e.g. chrome, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
**Smartphone (please complete the following information):**
|
**Smartphone (please complete the following information):**
|
||||||
- Device: [e.g. iPhone6]
|
|
||||||
- OS: [e.g. iOS8.1]
|
- Device: [e.g. iPhone6]
|
||||||
- Browser [e.g. stock browser, safari]
|
- OS: [e.g. iOS8.1]
|
||||||
- Version [e.g. 22]
|
- Browser [e.g. stock browser, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
Add any other context about the problem here.
|
Add any other context about the problem here.
|
||||||
|
|||||||
18
.github/ISSUE_TEMPLATE/bug_report_jp.md
vendored
18
.github/ISSUE_TEMPLATE/bug_report_jp.md
vendored
@@ -4,7 +4,6 @@ about: 改善に取り組めるように報告を作成します
|
|||||||
title: ''
|
title: ''
|
||||||
labels: bug
|
labels: bug
|
||||||
assignees: syusui-s
|
assignees: syusui-s
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**バグの説明**
|
**バグの説明**
|
||||||
@@ -12,6 +11,7 @@ assignees: syusui-s
|
|||||||
|
|
||||||
**再現手順**
|
**再現手順**
|
||||||
発生させる手順を説明してください:
|
発生させる手順を説明してください:
|
||||||
|
|
||||||
1. '...'に移動
|
1. '...'に移動
|
||||||
2. '....'をクリック
|
2. '....'をクリック
|
||||||
3. '....'までスクロールする
|
3. '....'までスクロールする
|
||||||
@@ -24,15 +24,17 @@ assignees: syusui-s
|
|||||||
スクリーンショットがあればこちらに。
|
スクリーンショットがあればこちらに。
|
||||||
|
|
||||||
**デスクトップ (該当する場合は以下の情報を埋めてください):**
|
**デスクトップ (該当する場合は以下の情報を埋めてください):**
|
||||||
- OS: (例: Windows 11, macOS Ventura)
|
|
||||||
- ブラウザ: (例: Chrome, Firefox, Safari, Edge)
|
- OS: (例: Windows 11, macOS Ventura)
|
||||||
- バージョン: (例: 110)
|
- ブラウザ: (例: Chrome, Firefox, Safari, Edge)
|
||||||
|
- バージョン: (例: 110)
|
||||||
|
|
||||||
**スマートフォン (該当する場合は以下の情報を埋めてください):**
|
**スマートフォン (該当する場合は以下の情報を埋めてください):**
|
||||||
- デバイス: (例: iPhone 12)
|
|
||||||
- OS: (例: iOS 16.2, Android 13)
|
- デバイス: (例: iPhone 12)
|
||||||
- ブラウザ: (例: Safari, Chrome)
|
- OS: (例: iOS 16.2, Android 13)
|
||||||
- バージョン: (例: 110)
|
- ブラウザ: (例: Safari, Chrome)
|
||||||
|
- バージョン: (例: 110)
|
||||||
|
|
||||||
**追加の情報**
|
**追加の情報**
|
||||||
問題に関する他の情報があればこちらに。
|
問題に関する他の情報があればこちらに。
|
||||||
|
|||||||
1
.github/ISSUE_TEMPLATE/feature_request.md
vendored
1
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -4,7 +4,6 @@ about: Suggest an idea for this project
|
|||||||
title: ''
|
title: ''
|
||||||
labels: enhancement
|
labels: enhancement
|
||||||
assignees: syusui-s
|
assignees: syusui-s
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
|||||||
1
.github/ISSUE_TEMPLATE/feature_request_jp.md
vendored
1
.github/ISSUE_TEMPLATE/feature_request_jp.md
vendored
@@ -4,7 +4,6 @@ about: このプロジェクトにアイデアを提案します
|
|||||||
title: ''
|
title: ''
|
||||||
labels: enhancement
|
labels: enhancement
|
||||||
assignees: syusui-s
|
assignees: syusui-s
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**要望は何らかの問題に関連していますか?説明してください**
|
**要望は何らかの問題に関連していますか?説明してください**
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ name: Pull request
|
|||||||
about: Suggest changes
|
about: Suggest changes
|
||||||
title: ''
|
title: ''
|
||||||
labels:
|
labels:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Describe the changes**
|
**Describe the changes**
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ name: プルリクエスト
|
|||||||
about: 変更を提案します
|
about: 変更を提案します
|
||||||
title: ''
|
title: ''
|
||||||
labels:
|
labels:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**変更について説明してください**
|
**変更について説明してください**
|
||||||
|
|||||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -3,7 +3,7 @@ name: CI
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branch:
|
branch:
|
||||||
- "*"
|
- '*'
|
||||||
jobs:
|
jobs:
|
||||||
ci:
|
ci:
|
||||||
permissions:
|
permissions:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ A nostr client like TweetDeck made with SolidJS.
|
|||||||
## 使い方
|
## 使い方
|
||||||
|
|
||||||
1. NIP-07に対応したブラウザ拡張機能のインストールが事前に必要です
|
1. NIP-07に対応したブラウザ拡張機能のインストールが事前に必要です
|
||||||
* [NIP-07](https://scrapbox.io/nostr/NIP-07#63e1c10c8b8fcb00000584fc) を参考に拡張機能をインストールしてください。
|
- [NIP-07](https://scrapbox.io/nostr/NIP-07#63e1c10c8b8fcb00000584fc) を参考に拡張機能をインストールしてください。
|
||||||
1. https://syusui-s.github.io/rabbit/ にアクセス
|
1. https://syusui-s.github.io/rabbit/ にアクセス
|
||||||
1. NIP-07拡張機能に許可を求められるので許可ボタンを押す
|
1. NIP-07拡張機能に許可を求められるので許可ボタンを押す
|
||||||
1. タイムラインが表示されます
|
1. タイムラインが表示されます
|
||||||
@@ -41,8 +41,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
GNUアフェロー一般公衆利用許諾書(バージョン3か、それ以降のいずれかのバージョン)
|
GNUアフェロー一般公衆利用許諾書(バージョン3か、それ以降のいずれかのバージョン)
|
||||||
が定める条件の下で再頒布または改変することができます。
|
が定める条件の下で再頒布または改変することができます。
|
||||||
|
|
||||||
このプログラムは有用であることを願って頒布されますが、 *全くの無保証* です。
|
このプログラムは有用であることを願って頒布されますが、 _全くの無保証_ です。
|
||||||
*商業可能性* や *特定目的への適合性* に対する保証は言外に示されたものも含め、全く存在しません。
|
_商業可能性_ や _特定目的への適合性_ に対する保証は言外に示されたものも含め、全く存在しません。
|
||||||
詳しくはGNUアフェロー一般公衆利用許諾書をご覧ください。
|
詳しくはGNUアフェロー一般公衆利用許諾書をご覧ください。
|
||||||
|
|
||||||
あなたはこのプログラムと共にGNUアフェロー一般公衆利用許諾書のコピーを一部受け取っているはずです。
|
あなたはこのプログラムと共にGNUアフェロー一般公衆利用許諾書のコピーを一部受け取っているはずです。
|
||||||
|
|||||||
12
index.html
12
index.html
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="ja">
|
<html lang="ja">
|
||||||
<head prefix="og: http://ogp.me/ns# website: http://ogp.me/ns/website#">
|
<head prefix="og: http://ogp.me/ns# website: http://ogp.me/ns/website#">
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
@@ -6,12 +6,18 @@
|
|||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta name="twitter:card" content="summary" />
|
<meta name="twitter:card" content="summary" />
|
||||||
<meta name="twitter:creator" content="@syusui-s" />
|
<meta name="twitter:creator" content="@syusui-s" />
|
||||||
<meta name="twitter:image" content="https://syusui-s.github.io/rabbit/images/rabbit_app_1280.png" />
|
<meta
|
||||||
|
name="twitter:image"
|
||||||
|
content="https://syusui-s.github.io/rabbit/images/rabbit_app_1280.png"
|
||||||
|
/>
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:url" content="https://syusui-s.github.io/rabbit/" />
|
<meta property="og:url" content="https://syusui-s.github.io/rabbit/" />
|
||||||
<meta property="og:title" content="🐰rabbit" />
|
<meta property="og:title" content="🐰rabbit" />
|
||||||
<meta property="og:description" content="Nostr Client" />
|
<meta property="og:description" content="Nostr Client" />
|
||||||
<meta property="og:image" content="https://syusui-s.github.io/rabbit/images/rabbit_app_1280.png" />
|
<meta
|
||||||
|
property="og:image"
|
||||||
|
content="https://syusui-s.github.io/rabbit/images/rabbit_app_1280.png"
|
||||||
|
/>
|
||||||
<meta property="og:locale" content="ja_JP" />
|
<meta property="og:locale" content="ja_JP" />
|
||||||
<link rel="manifest" href="manifest.json" />
|
<link rel="manifest" href="manifest.json" />
|
||||||
<link rel="shortcut icon" type="image/png" href="./images/rabbit_256.png" />
|
<link rel="shortcut icon" type="image/png" href="./images/rabbit_256.png" />
|
||||||
|
|||||||
@@ -9,9 +9,11 @@
|
|||||||
"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",
|
||||||
"eslint": "eslint --cache .",
|
"lint": "npm run prettier && npm run eslint",
|
||||||
|
"fix": "npm run prettier-fix && npm run eslint-fix",
|
||||||
|
"eslint": "eslint .",
|
||||||
"prettier": "prettier -c .",
|
"prettier": "prettier -c .",
|
||||||
"eslint-fix": "eslint --cache --fix .",
|
"eslint-fix": "eslint --fix .",
|
||||||
"prettier-fix": "prettier --write .",
|
"prettier-fix": "prettier --write .",
|
||||||
"tsc": "tsc --noEmit --skipLibCheck",
|
"tsc": "tsc --noEmit --skipLibCheck",
|
||||||
"test": "vitest run --no-watch",
|
"test": "vitest run --no-watch",
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="ja">
|
<html lang="ja">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Rabbit</title>
|
<title>Rabbit</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>お探しのページは見つかりませんでした</h1>
|
<h1>お探しのページは見つかりませんでした</h1>
|
||||||
<p>
|
<p>
|
||||||
<a href="/rabbit/">トップに戻る</a>
|
<a href="/rabbit/">トップに戻る</a>
|
||||||
</p>
|
</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,16 @@
|
|||||||
"display": "browser",
|
"display": "browser",
|
||||||
"background_color": "#fff",
|
"background_color": "#fff",
|
||||||
"description": "A deck style nostr client.",
|
"description": "A deck style nostr client.",
|
||||||
"icons": [{
|
"icons": [
|
||||||
|
{
|
||||||
"src": "images/rabbit_app_256.png",
|
"src": "images/rabbit_app_256.png",
|
||||||
"sizes": "256x256",
|
"sizes": "256x256",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"src": "images/rabbit_app_1280.png",
|
"src": "images/rabbit_app_1280.png",
|
||||||
"sizes": "1280x1280",
|
"sizes": "1280x1280",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
prettier --write "$@"
|
prettier --write "$@"
|
||||||
eslint --cache --fix "$@"
|
eslint --fix "$@"
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ const EmojiPicker: Component<EmojiPickerProps> = (props) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
pickerElement = picker as any as HTMLElement;
|
pickerElement = picker as any as HTMLElement;
|
||||||
popupRef?.elem?.appendChild(pickerElement);
|
popupRef?.elem?.appendChild(pickerElement);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, Show, createEffect, onCleanup, onMount } from 'solid-js';
|
import { Component, Show } from 'solid-js';
|
||||||
|
|
||||||
import BookmarkIcon from 'heroicons/24/outline/bookmark.svg';
|
import BookmarkIcon from 'heroicons/24/outline/bookmark.svg';
|
||||||
|
|
||||||
@@ -9,7 +9,6 @@ import Bookmark from '@/components/timeline/Bookmark';
|
|||||||
import { BookmarkColumnType } from '@/core/column';
|
import { BookmarkColumnType } from '@/core/column';
|
||||||
import useConfig from '@/core/useConfig';
|
import useConfig from '@/core/useConfig';
|
||||||
import { useTranslation } from '@/i18n/useTranslation';
|
import { useTranslation } from '@/i18n/useTranslation';
|
||||||
import useDecrypt from '@/nostr/useDecrypt';
|
|
||||||
import useParameterizedReplaceableEvent from '@/nostr/useParameterizedReplaceableEvent';
|
import useParameterizedReplaceableEvent from '@/nostr/useParameterizedReplaceableEvent';
|
||||||
|
|
||||||
type BookmarkColumnDisplayProps = {
|
type BookmarkColumnDisplayProps = {
|
||||||
@@ -28,6 +27,8 @@ const BookmarkColumn: Component<BookmarkColumnDisplayProps> = (props) => {
|
|||||||
identifier: props.column.identifier,
|
identifier: props.column.identifier,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// TODO 暗号化されたデータがある場合は復号する
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column
|
<Column
|
||||||
header={
|
header={
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { Component, createEffect, onCleanup, onMount } from 'solid-js';
|
import { Component } from 'solid-js';
|
||||||
|
|
||||||
import ChatBubbleLeftRight from 'heroicons/24/outline/chat-bubble-left-right.svg';
|
import ChatBubbleLeftRight from 'heroicons/24/outline/chat-bubble-left-right.svg';
|
||||||
import { uniq } from 'lodash';
|
|
||||||
import { Kind } from 'nostr-tools';
|
import { Kind } from 'nostr-tools';
|
||||||
|
|
||||||
import BasicColumnHeader from '@/components/column/BasicColumnHeader';
|
import BasicColumnHeader from '@/components/column/BasicColumnHeader';
|
||||||
import Column from '@/components/column/Column';
|
import Column from '@/components/column/Column';
|
||||||
import ColumnSettings from '@/components/column/ColumnSettings';
|
import ColumnSettings from '@/components/column/ColumnSettings';
|
||||||
import Timeline from '@/components/timeline/Timeline';
|
import Timeline from '@/components/timeline/Timeline';
|
||||||
import { ChannelColumnType, FollowingColumnType } from '@/core/column';
|
import { ChannelColumnType } from '@/core/column';
|
||||||
import { applyContentFilter } from '@/core/contentFilter';
|
import { applyContentFilter } from '@/core/contentFilter';
|
||||||
import useConfig from '@/core/useConfig';
|
import useConfig from '@/core/useConfig';
|
||||||
import { useTranslation } from '@/i18n/useTranslation';
|
import { useTranslation } from '@/i18n/useTranslation';
|
||||||
|
|||||||
@@ -19,14 +19,12 @@ type ColumnSettingsSectionProps = {
|
|||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ColumnSettingsSection: Component<ColumnSettingsSectionProps> = (props) => {
|
const ColumnSettingsSection: Component<ColumnSettingsSectionProps> = (props) => (
|
||||||
return (
|
|
||||||
<div class="flex flex-col gap-2 border-b p-2">
|
<div class="flex flex-col gap-2 border-b p-2">
|
||||||
<div>{props.title}</div>
|
<div>{props.title}</div>
|
||||||
<div>{props.children}</div>
|
<div>{props.children}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
const ColumnSettings: Component<ColumnSettingsProps> = (props) => {
|
const ColumnSettings: Component<ColumnSettingsProps> = (props) => {
|
||||||
const i18n = useTranslation();
|
const i18n = useTranslation();
|
||||||
|
|||||||
@@ -39,10 +39,10 @@ const EventDisplay: Component<EventDisplayProps> = (props) => {
|
|||||||
<EventLink eventId={props.event.id} kind={props.event.kind} />
|
<EventLink eventId={props.event.id} kind={props.event.kind} />
|
||||||
</span>
|
</span>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={props.event.kind === Kind.Text}>
|
<Match when={props.event.kind === (Kind.Text as number)}>
|
||||||
<TextNote event={props.event} embedding={props.actions} actions={props.actions} />
|
<TextNote event={props.event} embedding={props.actions} actions={props.actions} />
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={(props.event.kind as number) === 6}>
|
<Match when={props.event.kind === (Kind.Repost as number)}>
|
||||||
<Repost event={props.event} />
|
<Repost event={props.event} />
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import { Component } from 'solid-js';
|
|
||||||
|
|
||||||
import DocumentText from 'heroicons/24/outline/document-text.svg';
|
|
||||||
import { Kind, Event as NostrEvent } from 'nostr-tools';
|
|
||||||
|
|
||||||
import { genericEvent } from '@/nostr/event';
|
|
||||||
|
|
||||||
export type LongFormContentProps = {
|
|
||||||
event: NostrEvent;
|
|
||||||
};
|
|
||||||
|
|
||||||
const LongFormContent: Component<LongFormContentProps> = (props) => (
|
|
||||||
// const event = () => genericEvent(props.event);
|
|
||||||
|
|
||||||
<button class="flex flex-col gap-1 px-1">
|
|
||||||
<div class="flex items-center gap-1">
|
|
||||||
<span class="inline-block h-4 w-4 text-purple-400">
|
|
||||||
<DocumentText />
|
|
||||||
</span>
|
|
||||||
<span>TODO</span>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
export default LongFormContent;
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Show, type Component } from 'solid-js';
|
import { Show, type Component } from 'solid-js';
|
||||||
|
|
||||||
import ColumnItem from '@/components/ColumnItem';
|
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
import TextNoteDisplay, { TextNoteDisplayProps } from '@/components/event/textNote/TextNoteDisplay';
|
import TextNoteDisplay, { TextNoteDisplayProps } from '@/components/event/textNote/TextNoteDisplay';
|
||||||
import useConfig from '@/core/useConfig';
|
import useConfig from '@/core/useConfig';
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Component } from 'solid-js';
|
import { Component } from 'solid-js';
|
||||||
|
|
||||||
import Bell from 'heroicons/24/outline/bell.svg';
|
import Bell from 'heroicons/24/outline/bell.svg';
|
||||||
import BookmarkIcon from 'heroicons/24/outline/bookmark.svg';
|
|
||||||
import ChatBubbleLeftRight from 'heroicons/24/outline/chat-bubble-left-right.svg';
|
|
||||||
import GlobeAlt from 'heroicons/24/outline/globe-alt.svg';
|
import GlobeAlt from 'heroicons/24/outline/globe-alt.svg';
|
||||||
import Heart from 'heroicons/24/outline/heart.svg';
|
import Heart from 'heroicons/24/outline/heart.svg';
|
||||||
import Home from 'heroicons/24/outline/home.svg';
|
import Home from 'heroicons/24/outline/home.svg';
|
||||||
import MagnifyingGlass from 'heroicons/24/outline/magnifying-glass.svg';
|
import MagnifyingGlass from 'heroicons/24/outline/magnifying-glass.svg';
|
||||||
import User from 'heroicons/24/outline/user.svg';
|
import User from 'heroicons/24/outline/user.svg';
|
||||||
|
// import BookmarkIcon from 'heroicons/24/outline/bookmark.svg';
|
||||||
|
// import ChatBubbleLeftRight from 'heroicons/24/outline/chat-bubble-left-right.svg';
|
||||||
|
|
||||||
import BasicModal from '@/components/modal/BasicModal';
|
import BasicModal from '@/components/modal/BasicModal';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, For, createMemo } from 'solid-js';
|
import { Component, createMemo } from 'solid-js';
|
||||||
|
|
||||||
import { type Event as NostrEvent } from 'nostr-tools';
|
import { type Event as NostrEvent } from 'nostr-tools';
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import useVerification from '@/nostr/useVerification';
|
|||||||
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 npubEncodeFallback from '@/utils/npubEncodeFallback';
|
||||||
import stripMargin from '@/utils/stripMargin';
|
|
||||||
import timeout from '@/utils/timeout';
|
import timeout from '@/utils/timeout';
|
||||||
|
|
||||||
export type ProfileDisplayProps = {
|
export type ProfileDisplayProps = {
|
||||||
@@ -83,7 +82,6 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
const following = () => myFollowingPubkeys().includes(props.pubkey);
|
const following = () => myFollowingPubkeys().includes(props.pubkey);
|
||||||
const refetchMyFollowing = () => myFollowingQuery.refetch();
|
|
||||||
|
|
||||||
const { followingPubkeys: userFollowingPubkeys, query: userFollowingQuery } = useFollowings(
|
const { followingPubkeys: userFollowingPubkeys, query: userFollowingQuery } = useFollowings(
|
||||||
() => ({ pubkey: props.pubkey }),
|
() => ({ pubkey: props.pubkey }),
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import ColumnItem from '@/components/ColumnItem';
|
|||||||
import Reaction from '@/components/event/Reaction';
|
import Reaction from '@/components/event/Reaction';
|
||||||
import Repost from '@/components/event/Repost';
|
import Repost from '@/components/event/Repost';
|
||||||
import TextNote from '@/components/event/TextNote';
|
import TextNote from '@/components/event/TextNote';
|
||||||
import ZapReceipt from '@/components/event/ZapReceipt';
|
|
||||||
import useConfig from '@/core/useConfig';
|
import useConfig from '@/core/useConfig';
|
||||||
|
|
||||||
export type NotificationProps = {
|
export type NotificationProps = {
|
||||||
@@ -21,29 +20,22 @@ const Notification: Component<NotificationProps> = (props) => {
|
|||||||
{(event) => (
|
{(event) => (
|
||||||
<Show when={!shouldMuteEvent(event)}>
|
<Show when={!shouldMuteEvent(event)}>
|
||||||
<Switch fallback={<div>unknown event</div>}>
|
<Switch fallback={<div>unknown event</div>}>
|
||||||
<Match when={event.kind === Kind.Text}>
|
<Match when={event.kind === (Kind.Text as number)}>
|
||||||
<ColumnItem>
|
<ColumnItem>
|
||||||
<TextNote event={event} />
|
<TextNote event={event} />
|
||||||
</ColumnItem>
|
</ColumnItem>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={event.kind === Kind.Reaction}>
|
<Match when={event.kind === (Kind.Reaction as number)}>
|
||||||
<ColumnItem>
|
<ColumnItem>
|
||||||
<Reaction event={event} />
|
<Reaction event={event} />
|
||||||
</ColumnItem>
|
</ColumnItem>
|
||||||
</Match>
|
</Match>
|
||||||
{/* TODO ちゃんとnotification用のコンポーネント使う */}
|
{/* TODO ちゃんとnotification用のコンポーネント使う */}
|
||||||
<Match when={(event.kind as number) === 6}>
|
<Match when={event.kind === (Kind.Repost as number)}>
|
||||||
<ColumnItem>
|
<ColumnItem>
|
||||||
<Repost event={event} />
|
<Repost event={event} />
|
||||||
</ColumnItem>
|
</ColumnItem>
|
||||||
</Match>
|
</Match>
|
||||||
{/*
|
|
||||||
<Match when={event.kind === Kind.Zap}>
|
|
||||||
<ColumnItem>
|
|
||||||
<ZapReceipt event={event} />
|
|
||||||
</ColumnItem>
|
|
||||||
</Match>
|
|
||||||
*/}
|
|
||||||
</Switch>
|
</Switch>
|
||||||
</Show>
|
</Show>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createSignal, Show, type Component, type JSX } from 'solid-js';
|
import { createSignal, Show, type Component } from 'solid-js';
|
||||||
|
|
||||||
import ClipboardDocument from 'heroicons/24/outline/clipboard-document.svg';
|
import ClipboardDocument from 'heroicons/24/outline/clipboard-document.svg';
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ const Copy: Component<CopyProps> = (props) => {
|
|||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
navigator.clipboard
|
navigator.clipboard
|
||||||
.writeText(props.text)
|
.writeText(props.text)
|
||||||
.then((e) => {
|
.then(() => {
|
||||||
setShowPopup(true);
|
setShowPopup(true);
|
||||||
setTimeout(() => setShowPopup(false), 1000);
|
setTimeout(() => setShowPopup(false), 1000);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ const useConfig = (): UseConfig => {
|
|||||||
const isPubkeyMuted = (pubkey: string) => config.mutedPubkeys.includes(pubkey);
|
const isPubkeyMuted = (pubkey: string) => config.mutedPubkeys.includes(pubkey);
|
||||||
|
|
||||||
const hasMutedKeyword = (event: NostrEvent) => {
|
const hasMutedKeyword = (event: NostrEvent) => {
|
||||||
if (event.kind === Kind.Text) {
|
if (event.kind === (Kind.Text as number)) {
|
||||||
return config.mutedKeywords.some((keyword) => event.content.includes(keyword));
|
return config.mutedKeywords.some((keyword) => event.content.includes(keyword));
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -192,7 +192,7 @@ const useConfig = (): UseConfig => {
|
|||||||
return (
|
return (
|
||||||
isPubkeyMuted(event.pubkey) ||
|
isPubkeyMuted(event.pubkey) ||
|
||||||
ev.taggedPubkeys().some(isPubkeyMuted) ||
|
ev.taggedPubkeys().some(isPubkeyMuted) ||
|
||||||
(event.kind === Kind.Text && hasMutedKeyword(event))
|
(event.kind === (Kind.Text as number) && hasMutedKeyword(event))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import { createSignal, type JSX } from 'solid-js';
|
|
||||||
|
|
||||||
const useFileInput = () => {
|
|
||||||
const [file, setFile] = createSignal<File | undefined>();
|
|
||||||
|
|
||||||
const handleChange: JSX.EventHandler<HTMLInputElement, Event> = (ev) => {
|
|
||||||
setFile(ev.currentTarget.files?.[0]);
|
|
||||||
};
|
|
||||||
|
|
||||||
return { file, handleChange };
|
|
||||||
};
|
|
||||||
@@ -16,6 +16,7 @@ export type MessageChannelRequest<T> = {
|
|||||||
|
|
||||||
export type MessageChannelResponse<T> =
|
export type MessageChannelResponse<T> =
|
||||||
| { requestId: string; ok: true; response: T }
|
| { requestId: string; ok: true; response: T }
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
| { requestId: string; ok: false; error: any };
|
| { requestId: string; ok: false; error: any };
|
||||||
|
|
||||||
const [channels, setChannels]: Signal<Record<string, MessageChannel>> = createSignal({});
|
const [channels, setChannels]: Signal<Record<string, MessageChannel>> = createSignal({});
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ export type UseResizedImageProps = {
|
|||||||
imageUrl: Accessor<string | undefined>;
|
imageUrl: Accessor<string | undefined>;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
encoderOption?: number;
|
encoderOptions?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useResizedImage = ({
|
const useResizedImage = ({
|
||||||
imageUrl,
|
imageUrl,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
encoderOption,
|
encoderOptions,
|
||||||
}: UseResizedImageProps): Accessor<string | undefined> => {
|
}: UseResizedImageProps): Accessor<string | undefined> => {
|
||||||
const [resizedImage, setResizedImage] = createSignal<string | undefined>(undefined);
|
const [resizedImage, setResizedImage] = createSignal<string | undefined>(undefined);
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ const useResizedImage = ({
|
|||||||
|
|
||||||
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, dw, dh);
|
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, dw, dh);
|
||||||
|
|
||||||
const dataUrl = canvas.toDataURL('image/jpeg');
|
const dataUrl = canvas.toDataURL('image/jpeg', encoderOptions);
|
||||||
|
|
||||||
setResizedImage(dataUrl);
|
setResizedImage(dataUrl);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
// const commands = ['openPostForm'] as const;
|
import { onMount, onCleanup } from 'solid-js';
|
||||||
// type Commands = (typeof commands)[number];
|
|
||||||
|
|
||||||
import { onMount, onCleanup, type JSX } from 'solid-js';
|
|
||||||
|
|
||||||
import throttle from 'lodash/throttle';
|
import throttle from 'lodash/throttle';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Kind, Event as NostrEvent } from 'nostr-tools';
|
import { Event as NostrEvent } from 'nostr-tools';
|
||||||
|
|
||||||
import GenericEvent from '@/nostr/event/GenericEvent';
|
import GenericEvent from '@/nostr/event/GenericEvent';
|
||||||
import Reaction from '@/nostr/event/Reaction';
|
import Reaction from '@/nostr/event/Reaction';
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
import { Event as NostrEvent, Kind } from 'nostr-tools';
|
|
||||||
|
|
||||||
import GenericEvent from '@/nostr/event/GenericEvent';
|
|
||||||
|
|
||||||
class LongFormContent extends GenericEvent {
|
|
||||||
constructor(rawEvent: NostrEvent) {
|
|
||||||
if (rawEvent.kind !== Kind.Article) {
|
|
||||||
throw new TypeError('kind should be 30023');
|
|
||||||
}
|
|
||||||
super(rawEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
#getMeta(tagName: string): string | null {
|
|
||||||
const tags = this.findTagsByName(tagName);
|
|
||||||
if (tags.length === 0) return null;
|
|
||||||
const [, value] = tags[0];
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
title(): string | null {
|
|
||||||
return this.#getMeta('title');
|
|
||||||
}
|
|
||||||
|
|
||||||
image(): string | null {
|
|
||||||
return this.#getMeta('image');
|
|
||||||
}
|
|
||||||
|
|
||||||
summary(): string | null {
|
|
||||||
return this.#getMeta('image');
|
|
||||||
}
|
|
||||||
|
|
||||||
publishedAt(): string | null {
|
|
||||||
return this.#getMeta('publishedAt');
|
|
||||||
}
|
|
||||||
|
|
||||||
publishedAtAsDate(): Date | null {
|
|
||||||
const publishedAt = this.publishedAt();
|
|
||||||
if (publishedAt == null) return null;
|
|
||||||
|
|
||||||
return new Date(parseInt(publishedAt, 10) * 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LongFormContent;
|
|
||||||
@@ -19,6 +19,7 @@ export type NonStandardProfile = {
|
|||||||
|
|
||||||
export type Profile = StandardProfile & NonStandardProfile;
|
export type Profile = StandardProfile & NonStandardProfile;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export type ProfileWithOtherProperties = Profile & Record<string, any>;
|
export type ProfileWithOtherProperties = Profile & Record<string, any>;
|
||||||
|
|
||||||
export const parseProfile = (content: string | null): Profile => {
|
export const parseProfile = (content: string | null): Profile => {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const reactionToReactionTypes = (event: Reaction): ReactionTypes => {
|
|||||||
|
|
||||||
export default class Reaction extends GenericEvent {
|
export default class Reaction extends GenericEvent {
|
||||||
constructor(rawEvent: NostrEvent) {
|
constructor(rawEvent: NostrEvent) {
|
||||||
if (rawEvent.kind !== Kind.Reaction) {
|
if (rawEvent.kind !== (Kind.Reaction as number)) {
|
||||||
throw new TypeError('kind should be 7');
|
throw new TypeError('kind should be 7');
|
||||||
}
|
}
|
||||||
super(rawEvent);
|
super(rawEvent);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Event as NostrEvent, Kind } from 'nostr-tools';
|
import { Event as NostrEvent, Kind } from 'nostr-tools';
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
import GenericEvent from '@/nostr/event/GenericEvent';
|
import GenericEvent from '@/nostr/event/GenericEvent';
|
||||||
import isValidId from '@/nostr/event/isValidId';
|
import isValidId from '@/nostr/event/isValidId';
|
||||||
@@ -9,7 +8,6 @@ import parseTextNote, {
|
|||||||
ParsedTextNote,
|
ParsedTextNote,
|
||||||
TagReference,
|
TagReference,
|
||||||
} from '@/nostr/parseTextNote';
|
} from '@/nostr/parseTextNote';
|
||||||
import ensureSchema from '@/utils/ensureSchema';
|
|
||||||
|
|
||||||
export type EventMarker = 'reply' | 'root' | 'mention';
|
export type EventMarker = 'reply' | 'root' | 'mention';
|
||||||
|
|
||||||
@@ -69,7 +67,7 @@ export default class TextNote extends GenericEvent {
|
|||||||
#memoizedParsed: ParsedTextNote | undefined;
|
#memoizedParsed: ParsedTextNote | undefined;
|
||||||
|
|
||||||
constructor(rawEvent: NostrEvent) {
|
constructor(rawEvent: NostrEvent) {
|
||||||
if (rawEvent.kind !== Kind.Text) {
|
if (rawEvent.kind !== (Kind.Text as number)) {
|
||||||
throw new TypeError('kind should be 1');
|
throw new TypeError('kind should be 1');
|
||||||
}
|
}
|
||||||
super(rawEvent);
|
super(rawEvent);
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
|
||||||
import { type Event as NostrEvent } from 'nostr-tools';
|
|
||||||
import { describe, it } from 'vitest';
|
import { describe, it } from 'vitest';
|
||||||
|
|
||||||
import parseTextNote, { type ParsedTextNoteNode, TagReference } from '@/nostr/parseTextNote';
|
import parseTextNote, { type ParsedTextNoteNode } from '@/nostr/parseTextNote';
|
||||||
|
|
||||||
describe('parseTextNote', () => {
|
describe('parseTextNote', () => {
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { nip19, type Event as NostrEvent } from 'nostr-tools';
|
import { nip19 } from 'nostr-tools';
|
||||||
import { DecodeResult } from 'nostr-tools/lib/nip19';
|
import { DecodeResult } from 'nostr-tools/lib/nip19';
|
||||||
|
|
||||||
const { decode } = nip19;
|
const { decode } = nip19;
|
||||||
|
|||||||
@@ -162,33 +162,33 @@ const { addTask, removeTask } = useBatch<BatchedEventsTask>(() => ({
|
|||||||
count += 1;
|
count += 1;
|
||||||
|
|
||||||
sub.on('event', (event: NostrEvent & { id: string }) => {
|
sub.on('event', (event: NostrEvent & { id: string }) => {
|
||||||
if (event.kind === Kind.Metadata) {
|
if (event.kind === (Kind.Metadata as number)) {
|
||||||
const registeredTasks = profileTasks.get(event.pubkey) ?? [];
|
const registeredTasks = profileTasks.get(event.pubkey) ?? [];
|
||||||
resolveTasks(registeredTasks, event);
|
resolveTasks(registeredTasks, event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.kind === Kind.Reaction) {
|
if (event.kind === (Kind.Reaction as number)) {
|
||||||
// Use the last event id
|
// Use the last event id
|
||||||
const id = genericEvent(event).lastTaggedEventId();
|
const id = genericEvent(event).lastTaggedEventId();
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
const registeredTasks = reactionsTasks.get(id) ?? [];
|
const registeredTasks = reactionsTasks.get(id) ?? [];
|
||||||
resolveTasks(registeredTasks, event);
|
resolveTasks(registeredTasks, event);
|
||||||
}
|
}
|
||||||
} else if ((event.kind as number) === 6) {
|
} else if (event.kind === (Kind.Repost as number)) {
|
||||||
// Use the last event id
|
// Use the last event id
|
||||||
const id = genericEvent(event).lastTaggedEventId();
|
const id = genericEvent(event).lastTaggedEventId();
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
const registeredTasks = repostsTasks.get(id) ?? [];
|
const registeredTasks = repostsTasks.get(id) ?? [];
|
||||||
resolveTasks(registeredTasks, event);
|
resolveTasks(registeredTasks, event);
|
||||||
}
|
}
|
||||||
} else if (event.kind === Kind.Zap) {
|
} else if (event.kind === (Kind.Zap as number)) {
|
||||||
const eTags = genericEvent(event).eTags();
|
const eTags = genericEvent(event).eTags();
|
||||||
eTags.forEach(([, id]) => {
|
eTags.forEach(([, id]) => {
|
||||||
const registeredTasks = repostsTasks.get(id) ?? [];
|
const registeredTasks = repostsTasks.get(id) ?? [];
|
||||||
resolveTasks(registeredTasks, event);
|
resolveTasks(registeredTasks, event);
|
||||||
});
|
});
|
||||||
} else if (event.kind === Kind.Contacts) {
|
} else if (event.kind === (Kind.Contacts as number)) {
|
||||||
const registeredTasks = followingsTasks.get(event.pubkey) ?? [];
|
const registeredTasks = followingsTasks.get(event.pubkey) ?? [];
|
||||||
resolveTasks(registeredTasks, event);
|
resolveTasks(registeredTasks, event);
|
||||||
} else if (isParameterizedReplaceableEvent(event)) {
|
} else if (isParameterizedReplaceableEvent(event)) {
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ type UseDecryptProps = {
|
|||||||
encrypted: string;
|
encrypted: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line solid/reactivity
|
||||||
const [memo, setMemo] = createRoot(() => createSignal<Record<string, string>>({}));
|
const [memo, setMemo] = createRoot(() => createSignal<Record<string, string>>({}));
|
||||||
|
// eslint-disable-next-line solid/reactivity
|
||||||
const [decrypting, setDecrypting] = createRoot(() => createSignal<Record<string, boolean>>({}));
|
const [decrypting, setDecrypting] = createRoot(() => createSignal<Record<string, boolean>>({}));
|
||||||
|
|
||||||
const useDecrypt = (propsProvider: () => UseDecryptProps | null) => {
|
const useDecrypt = (propsProvider: () => UseDecryptProps | null) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createMemo } from 'solid-js';
|
import { createMemo } from 'solid-js';
|
||||||
|
|
||||||
import { createQuery, useQueryClient, type CreateQueryResult } from '@tanstack/solid-query';
|
import { createQuery, type CreateQueryResult } from '@tanstack/solid-query';
|
||||||
import { Event as NostrEvent } from 'nostr-tools';
|
import { Event as NostrEvent } from 'nostr-tools';
|
||||||
|
|
||||||
import { registerTask, BatchedEventsTask } from '@/nostr/useBatchedEvents';
|
import { registerTask, BatchedEventsTask } from '@/nostr/useBatchedEvents';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createMemo, observable } from 'solid-js';
|
import { createMemo } from 'solid-js';
|
||||||
|
|
||||||
import { createQuery, useQueryClient, type CreateQueryResult } from '@tanstack/solid-query';
|
import { createQuery, useQueryClient, type CreateQueryResult } from '@tanstack/solid-query';
|
||||||
import { Event as NostrEvent } from 'nostr-tools';
|
import { Event as NostrEvent } from 'nostr-tools';
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const useVerification = (propsProvider: () => UseVerificationProps | null): UseV
|
|||||||
const props = createMemo(propsProvider);
|
const props = createMemo(propsProvider);
|
||||||
const query = createQuery(
|
const query = createQuery(
|
||||||
() => ['useVerification', props()] as const,
|
() => ['useVerification', props()] as const,
|
||||||
({ queryKey, signal }) => {
|
({ queryKey }) => {
|
||||||
const [, currentProps] = queryKey;
|
const [, currentProps] = queryKey;
|
||||||
if (currentProps == null) return Promise.resolve(null);
|
if (currentProps == null) return Promise.resolve(null);
|
||||||
const { nip05: nip05string } = currentProps;
|
const { nip05: nip05string } = currentProps;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import Columns from '@/components/column/Columns';
|
|||||||
import GlobalModal from '@/components/modal/GlobalModal';
|
import GlobalModal from '@/components/modal/GlobalModal';
|
||||||
import SideBar from '@/components/SideBar';
|
import SideBar from '@/components/SideBar';
|
||||||
import useConfig from '@/core/useConfig';
|
import useConfig from '@/core/useConfig';
|
||||||
import useModalState from '@/hooks/useModalState';
|
|
||||||
import usePersistStatus from '@/hooks/usePersistStatus';
|
import usePersistStatus from '@/hooks/usePersistStatus';
|
||||||
import { useMountShortcutKeys } from '@/hooks/useShortcutKeys';
|
import { useMountShortcutKeys } from '@/hooks/useShortcutKeys';
|
||||||
import usePool from '@/nostr/usePool';
|
import usePool from '@/nostr/usePool';
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
/* eslint-disable @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 =
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
|
|
||||||
|
|
||||||
<T extends readonly any[]>(tuple: T) =>
|
<T extends readonly any[]>(tuple: T) =>
|
||||||
<R>(f: (tupleNonNull: TupleNonNull<T>) => R): R | null => {
|
<R>(f: (tupleNonNull: TupleNonNull<T>) => R): R | null => {
|
||||||
if (tuple.some((e) => e == null)) {
|
if (tuple.some((e) => e == null)) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
const ensureSchema =
|
const ensureSchema =
|
||||||
<T>(schema: z.Schema<T>) =>
|
<T>(schema: z.Schema<T>) =>
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(value: any): value is T =>
|
(value: any): value is T =>
|
||||||
schema.safeParse(value).success;
|
schema.safeParse(value).success;
|
||||||
|
|
||||||
|
|||||||
@@ -36,17 +36,6 @@ export type NostrBuildResult = {
|
|||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const toHexString = (buff: ArrayBuffer): string => {
|
|
||||||
const arr = new Array(buff.byteLength);
|
|
||||||
const view = new Int8Array(buff);
|
|
||||||
|
|
||||||
for (let i = 0; i < view.byteLength; i += 1) {
|
|
||||||
arr[i] = view[i].toString(16).padStart(2, '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
return arr.join();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const uploadNostrBuild = async (blob: Blob): Promise<UploadResult> => {
|
export const uploadNostrBuild = async (blob: Blob): Promise<UploadResult> => {
|
||||||
const form = new FormData();
|
const form = new FormData();
|
||||||
form.set('file', blob);
|
form.set('file', blob);
|
||||||
@@ -69,27 +58,6 @@ export const uploadNostrBuild = async (blob: Blob): Promise<UploadResult> => {
|
|||||||
return { imageUrl: result.data[0].url };
|
return { imageUrl: result.data[0].url };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uploadVoidCat = async (blob: Blob): Promise<any> => {
|
|
||||||
const data = await blob.arrayBuffer();
|
|
||||||
const digestBuffer = await window.crypto.subtle.digest('SHA-256', data);
|
|
||||||
const digest = toHexString(digestBuffer);
|
|
||||||
|
|
||||||
const res = await fetch('https://void.cat/upload', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'V-Content-Type': blob.type,
|
|
||||||
'V-Full-Digest': digest,
|
|
||||||
},
|
|
||||||
mode: 'cors',
|
|
||||||
body: data,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) throw new Error('failed to post image: status code was not 2xx');
|
|
||||||
|
|
||||||
return res.json();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const uploaders = {
|
export const uploaders = {
|
||||||
nostrBuild: {
|
nostrBuild: {
|
||||||
name: 'nostr.build',
|
name: 'nostr.build',
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const stripMargin = (strings: TemplateStringsArray, ...values: any[]) => {
|
const stripMargin = (strings: TemplateStringsArray, ...values: any[]) => {
|
||||||
const s = String.raw(strings, values);
|
const s = String.raw(strings, values);
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,7 @@
|
|||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"lib": [
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"dom",
|
|
||||||
"dom.iterable",
|
|
||||||
"esnext"
|
|
||||||
],
|
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
@@ -20,19 +16,10 @@
|
|||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"],
|
"@/*": ["src/*"]
|
||||||
},
|
},
|
||||||
"incremental": true
|
"incremental": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["vite.config.ts", "**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "./.eslintrc.js"],
|
||||||
"vite.config.ts",
|
"exclude": ["node_modules"]
|
||||||
"**/*.ts",
|
|
||||||
"**/*.tsx",
|
|
||||||
"**/*.js",
|
|
||||||
"**/*.jsx",
|
|
||||||
"./.eslintrc.js"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user