From f8fbc95ba7df1ec346d6b79b91ba06f681a69ecd Mon Sep 17 00:00:00 2001 From: Shusui MOYATANI Date: Mon, 8 May 2023 19:58:59 +0900 Subject: [PATCH] feat: column customization --- .eslintrc.js | 11 +- package-lock.json | 13 ++ package.json | 1 + scripts/checkLicense.mjs | 13 +- scripts/generatePackageInfo.mjs | 3 +- src/components/DeprecatedRepost.tsx | 3 +- src/components/Modal.tsx | 2 +- src/components/SideBar.tsx | 25 ++- src/components/TextNote.tsx | 3 +- src/components/column/BasicColumnHeader.tsx | 35 +++++ src/components/{ => column}/Column.tsx | 21 ++- src/components/column/ColumnSettings.tsx | 101 +++++++++++++ src/components/column/Columns.tsx | 74 +++++++++ src/components/column/FollwingColumn.tsx | 67 ++++++++ src/components/column/NotificationColumn.tsx | 57 +++++++ src/components/column/PostsColumn.tsx | 57 +++++++ src/components/column/ReactionsColumn.tsx | 57 +++++++ src/components/column/RelaysColumn.tsx | 58 +++++++ src/components/column/SearchColumn.tsx | 58 +++++++ src/components/modal/About.tsx | 104 +++++++++++++ src/components/modal/AddColumn.tsx | 121 +++++++++++++-- src/components/modal/BasicModal.tsx | 2 +- src/components/modal/GlobalModal.tsx | 45 ++++++ src/components/modal/ProfileDisplay.tsx | 6 +- src/components/textNote/ImageDisplay.tsx | 3 +- .../textNote/MentionedEventDisplay.tsx | 5 +- .../textNote/TextNoteContentDisplay.tsx | 5 +- src/components/textNote/TextNoteDisplay.tsx | 5 +- .../textNote/TextNoteDisplayById.tsx | 3 +- .../{ => timeline}/Notification.tsx | 0 src/components/{ => timeline}/Timeline.tsx | 0 .../{ => timeline}/TimelineContentDisplay.tsx | 4 +- .../{ => timeline}/TimelineContext.tsx | 0 src/core/column.ts | 104 +++++++++++-- src/core/contentFilter.test.ts | 19 +++ src/core/contentFilter.ts | 50 ++++++ src/core/relayUrls.ts | 23 +++ src/core/useConfig.ts | 96 +++++++----- src/core/userFilter/contentFilter.ts | 47 ------ src/hooks/useModalState.ts | 18 ++- src/index.tsx | 4 +- src/nostr/parseTextNote.test.ts | 2 +- src/nostr/parseTextNote.ts | 2 +- src/nostr/useCommands.test.ts | 2 +- src/pages/Hello.tsx | 3 +- src/pages/Home.tsx | 143 ++---------------- src/utils/batch/ObservableTask.ts | 2 +- src/utils/imageUrl.test.ts | 2 +- src/utils/resolveAsset.tsx | 6 + 49 files changed, 1178 insertions(+), 307 deletions(-) create mode 100644 src/components/column/BasicColumnHeader.tsx rename src/components/{ => column}/Column.tsx (80%) create mode 100644 src/components/column/ColumnSettings.tsx create mode 100644 src/components/column/Columns.tsx create mode 100644 src/components/column/FollwingColumn.tsx create mode 100644 src/components/column/NotificationColumn.tsx create mode 100644 src/components/column/PostsColumn.tsx create mode 100644 src/components/column/ReactionsColumn.tsx create mode 100644 src/components/column/RelaysColumn.tsx create mode 100644 src/components/column/SearchColumn.tsx create mode 100644 src/components/modal/About.tsx create mode 100644 src/components/modal/GlobalModal.tsx rename src/components/{ => timeline}/Notification.tsx (100%) rename src/components/{ => timeline}/Timeline.tsx (100%) rename src/components/{ => timeline}/TimelineContentDisplay.tsx (91%) rename src/components/{ => timeline}/TimelineContext.tsx (100%) create mode 100644 src/core/contentFilter.test.ts create mode 100644 src/core/contentFilter.ts create mode 100644 src/core/relayUrls.ts delete mode 100644 src/core/userFilter/contentFilter.ts create mode 100644 src/utils/resolveAsset.tsx diff --git a/.eslintrc.js b/.eslintrc.js index 9f90987..0066aa5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -23,10 +23,19 @@ module.exports = { ecmaFeatures: { jsx: true }, ecmaVersion: 2021, }, - plugins: ['import', 'solid', 'jsx-a11y', 'prettier', '@typescript-eslint', 'tailwindcss'], + plugins: [ + 'import', + 'no-relative-import-paths', + 'solid', + 'jsx-a11y', + 'prettier', + '@typescript-eslint', + 'tailwindcss', + ], rules: { 'no-alert': ['off'], 'no-console': ['off'], + 'no-relative-import-paths/no-relative-import-paths': ['error', { rootDir: 'src', prefix: '@' }], 'import/extensions': [ 'error', 'ignorePackages', diff --git a/package-lock.json b/package-lock.json index 8d8cb69..9010a30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "eslint-import-resolver-typescript": "^3.5.5", "eslint-plugin-import": "^2.27.5", "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-no-relative-import-paths": "^1.5.2", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-solid": "^0.12.1", "eslint-plugin-tailwindcss": "^3.11.0", @@ -3174,6 +3175,12 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, + "node_modules/eslint-plugin-no-relative-import-paths": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-relative-import-paths/-/eslint-plugin-no-relative-import-paths-1.5.2.tgz", + "integrity": "sha512-wMlL+TVuDhKk1plP+w3L4Hc7+u89vUkrOYq6/0ARjcYqwc9/YaS9uEXNzaqAk+WLoEgakzNL5JgJJw6m4qd5zw==", + "dev": true + }, "node_modules/eslint-plugin-prettier": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", @@ -9621,6 +9628,12 @@ "semver": "^6.3.0" } }, + "eslint-plugin-no-relative-import-paths": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-relative-import-paths/-/eslint-plugin-no-relative-import-paths-1.5.2.tgz", + "integrity": "sha512-wMlL+TVuDhKk1plP+w3L4Hc7+u89vUkrOYq6/0ARjcYqwc9/YaS9uEXNzaqAk+WLoEgakzNL5JgJJw6m4qd5zw==", + "dev": true + }, "eslint-plugin-prettier": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", diff --git a/package.json b/package.json index b733ea5..2e307e2 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "eslint-import-resolver-typescript": "^3.5.5", "eslint-plugin-import": "^2.27.5", "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-no-relative-import-paths": "^1.5.2", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-solid": "^0.12.1", "eslint-plugin-tailwindcss": "^3.11.0", diff --git a/scripts/checkLicense.mjs b/scripts/checkLicense.mjs index 0a154d5..bf78f22 100644 --- a/scripts/checkLicense.mjs +++ b/scripts/checkLicense.mjs @@ -11,28 +11,27 @@ const acceptableLicenses = [ '0BSD', 'BSD-3-Clause', 'CC-BY-4.0', + 'Unlicense', ]; const asyncLicenseChecker = (options) => { return new Promise((resolve, reject) => { licenseChecker.init(options, (err, data) => { if (err != null) reject(err); - else resolve(data) + else resolve(data); }); }); }; -export default async function() { - const packageInfo = await util.promisify(fs.readFile)('package.json', { encoding: 'utf8' }) +export default async function () { + const packageInfo = await util + .promisify(fs.readFile)('package.json', { encoding: 'utf8' }) .then((data) => JSON.parse(data)); const packages = await asyncLicenseChecker({ start: path.resolve(), production: true }); let ok = true; - const ignorePackageNames = [ - packageInfo.name, - 'nostr-tools', // nostr-tools is licensed under public domain - ]; + const ignorePackageNames = [packageInfo.name]; for (const [name, info] of Object.entries(packages)) { const acceptable = acceptableLicenses.includes(info.licenses); diff --git a/scripts/generatePackageInfo.mjs b/scripts/generatePackageInfo.mjs index bf703a7..4db9dbc 100644 --- a/scripts/generatePackageInfo.mjs +++ b/scripts/generatePackageInfo.mjs @@ -2,7 +2,8 @@ import fs from 'fs/promises'; import path from 'path'; import util from 'util'; -const readDepFile = (key, filename) => fs.readFile(path.resolve(key, filename), { encoding: 'utf8' }); +const readDepFile = (key, filename) => + fs.readFile(path.resolve(key, filename), { encoding: 'utf8' }); const getPackageInfo = async (key) => { try { diff --git a/src/components/DeprecatedRepost.tsx b/src/components/DeprecatedRepost.tsx index 4512ff0..171a038 100644 --- a/src/components/DeprecatedRepost.tsx +++ b/src/components/DeprecatedRepost.tsx @@ -5,13 +5,12 @@ import ArrowPathRoundedSquare from 'heroicons/24/outline/arrow-path-rounded-squa import { Event as NostrEvent } from 'nostr-tools'; import ColumnItem from '@/components/ColumnItem'; +import TextNoteDisplayById from '@/components/textNote/TextNoteDisplayById'; import UserDisplayName from '@/components/UserDisplayName'; import useFormatDate from '@/hooks/useFormatDate'; import useModalState from '@/hooks/useModalState'; import eventWrapper from '@/nostr/event'; -import TextNoteDisplayById from './textNote/TextNoteDisplayById'; - export type DeprecatedRepostProps = { event: NostrEvent; }; diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx index 8650a63..d97292d 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -18,7 +18,7 @@ const Modal: Component = (props) => { /* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */
{props.children} diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx index 1334cd2..38cc690 100644 --- a/src/components/SideBar.tsx +++ b/src/components/SideBar.tsx @@ -1,6 +1,7 @@ import { createSignal, Show, type JSX, Component } from 'solid-js'; import Cog6Tooth from 'heroicons/24/outline/cog-6-tooth.svg'; +import Plus from 'heroicons/24/outline/plus.svg'; import MagnifyingGlass from 'heroicons/24/solid/magnifying-glass.svg'; import PencilSquare from 'heroicons/24/solid/pencil-square.svg'; @@ -8,10 +9,13 @@ import Config from '@/components/modal/Config'; import NotePostForm from '@/components/NotePostForm'; import useConfig from '@/core/useConfig'; import { useHandleCommand } from '@/hooks/useCommandBus'; +import useModalState from '@/hooks/useModalState'; +import resolveAsset from '@/utils/resolveAsset'; const SideBar: Component = () => { let textAreaRef: HTMLTextAreaElement | undefined; + const { showAddColumn, showAbout } = useModalState(); const { config } = useConfig(); const [formOpened, setFormOpened] = createSignal(false); const [configOpened, setConfigOpened] = createSignal(false); @@ -39,7 +43,7 @@ const SideBar: Component = () => {
*/} - {/*
column 1
*/} - {/*
column 2
*/}
-
+
+ +
= (props) => { diff --git a/src/components/column/BasicColumnHeader.tsx b/src/components/column/BasicColumnHeader.tsx new file mode 100644 index 0000000..32eb35e --- /dev/null +++ b/src/components/column/BasicColumnHeader.tsx @@ -0,0 +1,35 @@ +import { Component, JSX, Show, createSignal } from 'solid-js'; + +import EllipsisVertical from 'heroicons/24/outline/ellipsis-vertical.svg'; + +export type BasicColumnHeaderProps = { + name: string; + icon?: JSX.Element; + settings: () => JSX.Element; + onClose?: () => void; +}; + +const BasicColumnHeader: Component = (props) => { + const [isSettingsOpened, setIsSettingOpened] = createSignal(false); + + const toggleSettingsOpened = () => setIsSettingOpened((current) => !current); + + return ( +
+
+

+ + {(icon) => {icon}} + + {props.name} +

+ +
+ {props.settings()} +
+ ); +}; + +export default BasicColumnHeader; diff --git a/src/components/Column.tsx b/src/components/column/Column.tsx similarity index 80% rename from src/components/Column.tsx rename to src/components/column/Column.tsx index df0b5c7..3761f3b 100644 --- a/src/components/Column.tsx +++ b/src/components/column/Column.tsx @@ -2,15 +2,15 @@ import { Show, type JSX, type Component } from 'solid-js'; import ArrowLeft from 'heroicons/24/outline/arrow-left.svg'; -import TimelineContentDisplay from '@/components/TimelineContentDisplay'; -import { TimelineContext, useTimelineState } from '@/components/TimelineContext'; +import TimelineContentDisplay from '@/components/timeline/TimelineContentDisplay'; +import { TimelineContext, useTimelineState } from '@/components/timeline/TimelineContext'; import { useHandleCommand } from '@/hooks/useCommandBus'; export type ColumnProps = { - name: string; columnIndex: number; - lastColumn?: true; + lastColumn: boolean; width: 'widest' | 'wide' | 'medium' | 'narrow' | null | undefined; + header: JSX.Element; children: JSX.Element; }; @@ -46,9 +46,9 @@ const Column: Component = (props) => { class="flex w-[80vw] shrink-0 snap-center snap-always flex-col border-r sm:snap-align-none" classList={{ 'sm:w-[500px]': width() === 'widest', - 'sm:w-[350px]': width() === 'wide', - 'sm:w-[310px]': width() === 'medium', - 'sm:w-[270px]': width() === 'narrow', + 'sm:w-[360px]': width() === 'wide', + 'sm:w-[320px]': width() === 'medium', + 'sm:w-[280px]': width() === 'narrow', }} > = (props) => { keyed fallback={ <> -
- {/* 🏠 */} - {props.name} -
+
{props.header}
{props.children}
} > {(timeline) => (
-
+
+ + + +
+ +
+ + +
+ +
+
+ ); +}; + +export default ColumnSettings; diff --git a/src/components/column/Columns.tsx b/src/components/column/Columns.tsx new file mode 100644 index 0000000..83b938d --- /dev/null +++ b/src/components/column/Columns.tsx @@ -0,0 +1,74 @@ +import { For, Switch, Match } from 'solid-js'; + +import FollowingColumn from '@/components/column/FollwingColumn'; +import NotificationColumn from '@/components/column/NotificationColumn'; +import PostsColumn from '@/components/column/PostsColumn'; +import ReactionsColumn from '@/components/column/ReactionsColumn'; +import RelaysColumn from '@/components/column/RelaysColumn'; +import useConfig from '@/core/useConfig'; + +const Columns = () => { + const { config } = useConfig(); + + return ( +
+ + {(column, index) => { + const columnIndex = () => index() + 1; + const lastColumn = () => columnIndex() === config().columns.length; + return ( + + + {(followingColumn) => ( + + )} + + + {(notificationColumn) => ( + + )} + + + {(postsColumn) => ( + + )} + + + {(reactionsColumn) => ( + + )} + + + {(reactionsColumn) => ( + + )} + + + ); + }} + +
+ ); +}; + +export default Columns; diff --git a/src/components/column/FollwingColumn.tsx b/src/components/column/FollwingColumn.tsx new file mode 100644 index 0000000..551e2d6 --- /dev/null +++ b/src/components/column/FollwingColumn.tsx @@ -0,0 +1,67 @@ +import { Component } from 'solid-js'; + +import Home from 'heroicons/24/outline/home.svg'; +import { uniq } from 'lodash'; + +import BasicColumnHeader from '@/components/column/BasicColumnHeader'; +import Column from '@/components/column/Column'; +import ColumnSettings from '@/components/column/ColumnSettings'; +import Timeline from '@/components/timeline/Timeline'; +import { FollowingColumnType } from '@/core/column'; +import { applyContentFilter } from '@/core/contentFilter'; +import useConfig from '@/core/useConfig'; +import { useFollowings } from '@/nostr/useBatchedEvents'; +import useSubscription from '@/nostr/useSubscription'; +import epoch from '@/utils/epoch'; + +type FollowingColumnDisplayProps = { + columnIndex: number; + lastColumn: boolean; + column: FollowingColumnType; +}; + +const FollowingColumn: Component = (props) => { + const { config, removeColumn } = useConfig(); + + const { followingPubkeys } = useFollowings(() => ({ pubkey: props.column.pubkey })); + + const { events: followingsPosts } = useSubscription(() => { + const authors = uniq([...followingPubkeys()]); + if (authors.length === 0) return null; + return { + relayUrls: config().relayUrls, + filters: [ + { + kinds: [1, 6], + authors, + limit: 10, + since: epoch() - 4 * 60 * 60, + }, + ], + clientEventFilter: (event) => { + if (props.column.contentFilter == null) return true; + return applyContentFilter(props.column.contentFilter)(event.content); + }, + }; + }); + + return ( + } + settings={() => } + onClose={() => removeColumn(props.column.id)} + /> + } + width={props.column.width} + columnIndex={props.columnIndex} + lastColumn={props.lastColumn} + > + + + ); +}; + +export default FollowingColumn; diff --git a/src/components/column/NotificationColumn.tsx b/src/components/column/NotificationColumn.tsx new file mode 100644 index 0000000..f9413c9 --- /dev/null +++ b/src/components/column/NotificationColumn.tsx @@ -0,0 +1,57 @@ +import { Component } from 'solid-js'; + +import Bell from 'heroicons/24/outline/bell.svg'; + +import BasicColumnHeader from '@/components/column/BasicColumnHeader'; +import Column from '@/components/column/Column'; +import ColumnSettings from '@/components/column/ColumnSettings'; +import Notification from '@/components/timeline/Notification'; +import { NotificationColumnType } from '@/core/column'; +import { applyContentFilter } from '@/core/contentFilter'; +import useConfig from '@/core/useConfig'; +import useSubscription from '@/nostr/useSubscription'; + +type NotificationColumnDisplayProps = { + columnIndex: number; + lastColumn: boolean; + column: NotificationColumnType; +}; + +const NotificationColumn: Component = (props) => { + const { config, removeColumn } = useConfig(); + + const { events: notifications } = useSubscription(() => ({ + relayUrls: config().relayUrls, + filters: [ + { + kinds: [1, 6, 7], + '#p': [props.column.pubkey], + limit: 10, + }, + ], + clientEventFilter: (event) => { + if (props.column.contentFilter == null) return true; + return applyContentFilter(props.column.contentFilter)(event.content); + }, + })); + + return ( + } + settings={() => } + onClose={() => removeColumn(props.column.id)} + /> + } + width={props.column.width} + columnIndex={props.columnIndex} + lastColumn={props.lastColumn} + > + + + ); +}; + +export default NotificationColumn; diff --git a/src/components/column/PostsColumn.tsx b/src/components/column/PostsColumn.tsx new file mode 100644 index 0000000..6e4af7f --- /dev/null +++ b/src/components/column/PostsColumn.tsx @@ -0,0 +1,57 @@ +import { Component } from 'solid-js'; + +import User from 'heroicons/24/outline/user.svg'; + +import BasicColumnHeader from '@/components/column/BasicColumnHeader'; +import Column from '@/components/column/Column'; +import ColumnSettings from '@/components/column/ColumnSettings'; +import Timeline from '@/components/timeline/Timeline'; +import { PostsColumnType } from '@/core/column'; +import { applyContentFilter } from '@/core/contentFilter'; +import useConfig from '@/core/useConfig'; +import useSubscription from '@/nostr/useSubscription'; + +type PostsColumnDisplayProps = { + columnIndex: number; + lastColumn: boolean; + column: PostsColumnType; +}; + +const PostsColumn: Component = (props) => { + const { config, removeColumn } = useConfig(); + + const { events } = useSubscription(() => ({ + relayUrls: config().relayUrls, + filters: [ + { + kinds: [1, 6], + authors: [props.column.pubkey], + limit: 10, + }, + ], + clientEventFilter: (event) => { + if (props.column.contentFilter == null) return true; + return applyContentFilter(props.column.contentFilter)(event.content); + }, + })); + + return ( + } + settings={() => } + onClose={() => removeColumn(props.column.id)} + /> + } + width={props.column.width} + columnIndex={props.columnIndex} + lastColumn={props.lastColumn} + > + + + ); +}; + +export default PostsColumn; diff --git a/src/components/column/ReactionsColumn.tsx b/src/components/column/ReactionsColumn.tsx new file mode 100644 index 0000000..93f54c5 --- /dev/null +++ b/src/components/column/ReactionsColumn.tsx @@ -0,0 +1,57 @@ +import { Component } from 'solid-js'; + +import Heart from 'heroicons/24/outline/heart.svg'; + +import BasicColumnHeader from '@/components/column/BasicColumnHeader'; +import Column from '@/components/column/Column'; +import ColumnSettings from '@/components/column/ColumnSettings'; +import Notification from '@/components/timeline/Notification'; +import { ReactionsColumnType } from '@/core/column'; +import { applyContentFilter } from '@/core/contentFilter'; +import useConfig from '@/core/useConfig'; +import useSubscription from '@/nostr/useSubscription'; + +type ReactionsColumnDisplayProps = { + columnIndex: number; + lastColumn: boolean; + column: ReactionsColumnType; +}; + +const ReactionsColumn: Component = (props) => { + const { config, removeColumn } = useConfig(); + + const { events: reactions } = useSubscription(() => ({ + relayUrls: config().relayUrls, + filters: [ + { + kinds: [7], + authors: [props.column.pubkey], + limit: 10, + }, + ], + clientEventFilter: (event) => { + if (props.column.contentFilter == null) return true; + return applyContentFilter(props.column.contentFilter)(event.content); + }, + })); + + return ( + } + settings={() => } + onClose={() => removeColumn(props.column.id)} + /> + } + width={props.column.width} + columnIndex={props.columnIndex} + lastColumn={props.lastColumn} + > + + + ); +}; + +export default ReactionsColumn; diff --git a/src/components/column/RelaysColumn.tsx b/src/components/column/RelaysColumn.tsx new file mode 100644 index 0000000..8fc4a0b --- /dev/null +++ b/src/components/column/RelaysColumn.tsx @@ -0,0 +1,58 @@ +import { Component } from 'solid-js'; + +import GlobeAlt from 'heroicons/24/outline/globe-alt.svg'; + +import BasicColumnHeader from '@/components/column/BasicColumnHeader'; +import Column from '@/components/column/Column'; +import ColumnSettings from '@/components/column/ColumnSettings'; +import Timeline from '@/components/timeline/Timeline'; +import { RelaysColumnType } from '@/core/column'; +import { applyContentFilter } from '@/core/contentFilter'; +import useConfig from '@/core/useConfig'; +import useSubscription from '@/nostr/useSubscription'; +import epoch from '@/utils/epoch'; + +type RelaysColumnDisplayProps = { + columnIndex: number; + lastColumn: boolean; + column: RelaysColumnType; +}; + +const RelaysColumn: Component = (props) => { + const { removeColumn } = useConfig(); + + const { events } = useSubscription(() => ({ + relayUrls: props.column.relayUrls, + filters: [ + { + kinds: [1, 6], + limit: 25, + since: epoch() - 4 * 60 * 60, + }, + ], + clientEventFilter: (event) => { + if (props.column.contentFilter == null) return true; + return applyContentFilter(props.column.contentFilter)(event.content); + }, + })); + + return ( + } + settings={() => } + onClose={() => removeColumn(props.column.id)} + /> + } + width={props.column.width} + columnIndex={props.columnIndex} + lastColumn={props.lastColumn} + > + + + ); +}; + +export default RelaysColumn; diff --git a/src/components/column/SearchColumn.tsx b/src/components/column/SearchColumn.tsx new file mode 100644 index 0000000..fef8f53 --- /dev/null +++ b/src/components/column/SearchColumn.tsx @@ -0,0 +1,58 @@ +import { Component } from 'solid-js'; + +import MagnifyingGlass from 'heroicons/24/outline/magnifying-glass.svg'; + +import BasicColumnHeader from '@/components/column/BasicColumnHeader'; +import Column from '@/components/column/Column'; +import ColumnSettings from '@/components/column/ColumnSettings'; +import Timeline from '@/components/timeline/Timeline'; +import { SearchColumnType } from '@/core/column'; +import { applyContentFilter } from '@/core/contentFilter'; +import { relaysForSearching } from '@/core/relayUrls'; +import useConfig from '@/core/useConfig'; +import useSubscription from '@/nostr/useSubscription'; + +type SearchColumnDisplayProps = { + columnIndex: number; + lastColumn: boolean; + column: SearchColumnType; +}; + +const SearchColumn: Component = (props) => { + const { removeColumn } = useConfig(); + + const { events } = useSubscription(() => ({ + relayUrls: relaysForSearching, + filters: [ + { + kinds: [1, 6], + search: props.column.query, + limit: 25, + }, + ], + clientEventFilter: (event) => { + if (props.column.contentFilter == null) return true; + return applyContentFilter(props.column.contentFilter)(event.content); + }, + })); + + return ( + } + settings={() => } + onClose={() => removeColumn(props.column.id)} + /> + } + width={props.column.width} + columnIndex={props.columnIndex} + lastColumn={props.lastColumn} + > + + + ); +}; + +export default SearchColumn; diff --git a/src/components/modal/About.tsx b/src/components/modal/About.tsx new file mode 100644 index 0000000..e33c444 --- /dev/null +++ b/src/components/modal/About.tsx @@ -0,0 +1,104 @@ +import { Component, createResource, For, Show } from 'solid-js'; + +import BasicModal from '@/components/modal/BasicModal'; +import resolveAsset from '@/utils/resolveAsset'; + +type AboutProps = { + onClose: () => void; +}; + +type PackageInfo = { + self: { + name: string; + author: string; + version: string; + homepage: string; + licenseSpdx: string; + licenseText: string; + }; + packages: { + name: string; + version: string; + licenseSpdx: string; + licenseText: string; + }[]; +}; + +const fetchPackageInfo = async (): Promise => { + const res = await fetch(resolveAsset('packageInfo.json')); + const body = await res.text(); + return JSON.parse(body) as PackageInfo; +}; + +const commit = import.meta.env.COMMIT as string | null; + +const About: Component = (props) => { + const [packageInfo] = createResource(fetchPackageInfo); + + return ( + +
+
+ Logo + +

+ Rabbit v{packageInfo()?.self?.version} +

+
+ +

利用規約

+ +

Copyright (C) 2023 Shusui Moyatani

+ +

+ このプログラムは自由ソフトウェアです。フリーソフトウェア財団から発行された + GNUアフェロー一般公衆ライセンス(バージョン3か、(任意で)より新しいバージョンのいずれか)の条件の下で + 再頒布や改変、あるいはその両方を行うことができます。 +

+ +

+ このプログラムは役立つことを願って頒布されていますが、 + いかなる保証もありません商品性や + 特定目的適合性 に対する保証は暗示されたものも含めて存在しません。 + 詳しくはGNUアフェロー一般公衆ライセンスをご覧ください。 +

+ +

+ あなたは、このプログラムに付随してGNUアフェロー一般公衆ライセンスのコピーを受け取っていることでしょう。 + そうでなければ、 + + https://www.gnu.org/licenses/ + + をご参照ください。 +

+ + + 参考訳 + + +
+          {packageInfo()?.self.licenseText}
+        
+ +

使用ライブラリ

+ + + {(p) => { + return ( + <> +

+ {p.name}@{p.version} ({p.licenseSpdx}) +

+
+                  {p.licenseText}
+                
+ + ); + }} +
+
+
+ ); +}; + +export default About; diff --git a/src/components/modal/AddColumn.tsx b/src/components/modal/AddColumn.tsx index e82525a..65e13bf 100644 --- a/src/components/modal/AddColumn.tsx +++ b/src/components/modal/AddColumn.tsx @@ -1,20 +1,113 @@ -import BasicModal from '@/components/modal/BasicModal'; -import useConfig from '@/core/useConfig'; +import { Component } from 'solid-js'; -const AddColumn = () => { - const { addColumn } = useConfig(); +import Bell from 'heroicons/24/outline/bell.svg'; +import GlobeAlt from 'heroicons/24/outline/globe-alt.svg'; +import Heart from 'heroicons/24/outline/heart.svg'; +import Home from 'heroicons/24/outline/home.svg'; +import User from 'heroicons/24/outline/user.svg'; + +import BasicModal from '@/components/modal/BasicModal'; +import { + createFollowingColumn, + createJapanRelaysColumn, + createNotificationColumn, + createPostsColumn, + createReactionsColumn, +} from '@/core/column'; +import useConfig from '@/core/useConfig'; +import usePubkey from '@/nostr/usePubkey'; +import ensureNonNull from '@/utils/ensureNonNull'; + +type AddColumnProps = { + onClose: () => void; +}; + +const AddColumn: Component = (props) => { + const pubkey = usePubkey(); + const { saveColumn } = useConfig(); + + const addFollowingColumn = () => { + ensureNonNull([pubkey()] as const)(([pubkeyNonNull]) => { + saveColumn(createFollowingColumn({ pubkey: pubkeyNonNull })); + }); + props.onClose(); + }; + + const addNotificationColumn = () => { + ensureNonNull([pubkey()] as const)(([pubkeyNonNull]) => { + saveColumn(createNotificationColumn({ pubkey: pubkeyNonNull })); + }); + props.onClose(); + }; + + const addJapanRelaysColumn = () => { + saveColumn(createJapanRelaysColumn()); + props.onClose(); + }; + + const addMyPostsColumn = () => { + ensureNonNull([pubkey()] as const)(([pubkeyNonNull]) => { + saveColumn(createPostsColumn({ pubkey: pubkeyNonNull })); + }); + props.onClose(); + }; + + const addMyReactionsColumn = () => { + ensureNonNull([pubkey()] as const)(([pubkeyNonNull]) => { + saveColumn(createReactionsColumn({ pubkey: pubkeyNonNull })); + }); + props.onClose(); + }; return ( - console.log('closed')}> -
    -
  • ホーム
  • -
  • 通知
  • -
  • 検索
  • -
  • リレー
  • -
  • ユーザー
  • -
  • いいね
  • -
  • ダイレクトメッセージ
  • -
+ +
+ + + + + +
); }; diff --git a/src/components/modal/BasicModal.tsx b/src/components/modal/BasicModal.tsx index 928fdba..f3ebdd3 100644 --- a/src/components/modal/BasicModal.tsx +++ b/src/components/modal/BasicModal.tsx @@ -13,7 +13,7 @@ export type BasicModalProps = { const BasicModal: Component = (props) => { return ( props.onClose?.()}> -
+