feat: support search column

This commit is contained in:
Shusui MOYATANI
2023-05-08 21:30:45 +09:00
parent 7ff82e68a3
commit e74ec77dcf
4 changed files with 124 additions and 29 deletions

View File

@@ -29,7 +29,7 @@ const ColumnSettingsSection: Component<ColumnSettingsSectionProps> = (props) =>
const ColumnSettings: Component<ColumnSettingsProps> = (props) => { const ColumnSettings: Component<ColumnSettingsProps> = (props) => {
const { saveColumn, removeColumn, moveColumn } = useConfig(); const { saveColumn, removeColumn, moveColumn } = useConfig();
const requestCommand = useRequestCommand(); const request = useRequestCommand();
const setColumnWidth = (width: ColumnType['width']) => { const setColumnWidth = (width: ColumnType['width']) => {
saveColumn({ ...props.column, width }); saveColumn({ ...props.column, width });
@@ -37,9 +37,7 @@ const ColumnSettings: Component<ColumnSettingsProps> = (props) => {
const move = (index: number) => { const move = (index: number) => {
moveColumn(props.column.id, index); moveColumn(props.column.id, index);
requestCommand({ command: 'moveToColumn', columnIndex: index }).catch((err) => request({ command: 'moveToColumn', columnIndex: index }).catch((err) => console.error(err));
console.error(err),
);
}; };
return ( return (

View File

@@ -1,8 +1,8 @@
import { Component } from 'solid-js'; import { Component, createSignal, Show, JSX, onMount } from 'solid-js';
import EllipsisVertical from 'heroicons/24/outline/ellipsis-vertical.svg';
import MagnifyingGlass from 'heroicons/24/outline/magnifying-glass.svg'; import MagnifyingGlass from 'heroicons/24/outline/magnifying-glass.svg';
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';
@@ -12,7 +12,60 @@ import { relaysForSearching } from '@/core/relayUrls';
import useConfig from '@/core/useConfig'; import useConfig from '@/core/useConfig';
import useSubscription from '@/nostr/useSubscription'; import useSubscription from '@/nostr/useSubscription';
type SearchColumnDisplayProps = { export type SearchColumnHeaderProps = {
name: string;
column: SearchColumnType;
settings: () => JSX.Element;
onClose?: () => void;
};
const SearchColumnHeader: Component<SearchColumnHeaderProps> = (props) => {
const [isSettingsOpened, setIsSettingOpened] = createSignal(false);
const [query, setQuery] = createSignal('');
const { saveColumn } = useConfig();
const toggleSettingsOpened = () => setIsSettingOpened((current) => !current);
const handleBlur: JSX.EventHandler<HTMLInputElement, Event> = () => {
if (query() === props.column.query) return;
saveColumn({ ...props.column, query: query() });
};
const handleChange: JSX.EventHandler<HTMLInputElement, Event> = (ev) => {
setQuery(ev.currentTarget.value);
};
onMount(() => {
setQuery(props.column.query);
});
return (
<div class="flex flex-col">
<div class="flex h-8 items-center gap-1 px-2">
<h2 class="flex items-center gap-1">
<span class="inline-block h-4 w-4 text-gray-700">
<MagnifyingGlass />
</span>
</h2>
<input
class="flex-1 rounded border border-stone-300 px-1 py-0 focus:border-rose-100 focus:ring-rose-300"
type="text"
name="query"
value={query()}
onChange={handleChange}
onBlur={handleBlur}
/>
<button class="h-4 w-4" onClick={() => toggleSettingsOpened()}>
<EllipsisVertical />
</button>
</div>
<Show when={isSettingsOpened()}>{props.settings()}</Show>
</div>
);
};
export type SearchColumnDisplayProps = {
columnIndex: number; columnIndex: number;
lastColumn: boolean; lastColumn: boolean;
column: SearchColumnType; column: SearchColumnType;
@@ -21,12 +74,17 @@ type SearchColumnDisplayProps = {
const SearchColumn: Component<SearchColumnDisplayProps> = (props) => { const SearchColumn: Component<SearchColumnDisplayProps> = (props) => {
const { removeColumn } = useConfig(); const { removeColumn } = useConfig();
const { events } = useSubscription(() => ({ const { events } = useSubscription(() => {
const { query } = props.column;
if (query.length === 0) return null;
return {
relayUrls: relaysForSearching, relayUrls: relaysForSearching,
filters: [ filters: [
{ {
kinds: [1, 6], kinds: [1, 6],
search: props.column.query, search: query,
limit: 25, limit: 25,
}, },
], ],
@@ -34,14 +92,15 @@ const SearchColumn: Component<SearchColumnDisplayProps> = (props) => {
if (props.column.contentFilter == null) return true; if (props.column.contentFilter == null) return true;
return applyContentFilter(props.column.contentFilter)(event.content); return applyContentFilter(props.column.contentFilter)(event.content);
}, },
})); };
});
return ( return (
<Column <Column
header={ header={
<BasicColumnHeader <SearchColumnHeader
name={props.column.name ?? '検索'} name={props.column.name ?? '検索'}
icon={<MagnifyingGlass />} column={props.column}
settings={() => <ColumnSettings column={props.column} columnIndex={props.columnIndex} />} settings={() => <ColumnSettings column={props.column} columnIndex={props.columnIndex} />}
onClose={() => removeColumn(props.column.id)} onClose={() => removeColumn(props.column.id)}
/> />

View File

@@ -4,6 +4,7 @@ import Bell from 'heroicons/24/outline/bell.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 User from 'heroicons/24/outline/user.svg'; import User from 'heroicons/24/outline/user.svg';
import BasicModal from '@/components/modal/BasicModal'; import BasicModal from '@/components/modal/BasicModal';
@@ -13,8 +14,10 @@ import {
createNotificationColumn, createNotificationColumn,
createPostsColumn, createPostsColumn,
createReactionsColumn, createReactionsColumn,
createSearchColumn,
} from '@/core/column'; } from '@/core/column';
import useConfig from '@/core/useConfig'; import useConfig from '@/core/useConfig';
import { useRequestCommand } from '@/hooks/useCommandBus';
import usePubkey from '@/nostr/usePubkey'; import usePubkey from '@/nostr/usePubkey';
import ensureNonNull from '@/utils/ensureNonNull'; import ensureNonNull from '@/utils/ensureNonNull';
@@ -25,38 +28,49 @@ type AddColumnProps = {
const AddColumn: Component<AddColumnProps> = (props) => { const AddColumn: Component<AddColumnProps> = (props) => {
const pubkey = usePubkey(); const pubkey = usePubkey();
const { saveColumn } = useConfig(); const { saveColumn } = useConfig();
const request = useRequestCommand();
const finish = () => {
props.onClose();
request({ command: 'moveToLastColumn' }).catch((err) => console.error(err));
};
const addFollowingColumn = () => { const addFollowingColumn = () => {
ensureNonNull([pubkey()] as const)(([pubkeyNonNull]) => { ensureNonNull([pubkey()] as const)(([pubkeyNonNull]) => {
saveColumn(createFollowingColumn({ pubkey: pubkeyNonNull })); saveColumn(createFollowingColumn({ pubkey: pubkeyNonNull }));
}); });
props.onClose(); finish();
}; };
const addNotificationColumn = () => { const addNotificationColumn = () => {
ensureNonNull([pubkey()] as const)(([pubkeyNonNull]) => { ensureNonNull([pubkey()] as const)(([pubkeyNonNull]) => {
saveColumn(createNotificationColumn({ pubkey: pubkeyNonNull })); saveColumn(createNotificationColumn({ pubkey: pubkeyNonNull }));
}); });
props.onClose(); finish();
}; };
const addJapanRelaysColumn = () => { const addJapanRelaysColumn = () => {
saveColumn(createJapanRelaysColumn()); saveColumn(createJapanRelaysColumn());
props.onClose(); finish();
};
const addSearchColumn = () => {
saveColumn(createSearchColumn({ query: '' }));
finish();
}; };
const addMyPostsColumn = () => { const addMyPostsColumn = () => {
ensureNonNull([pubkey()] as const)(([pubkeyNonNull]) => { ensureNonNull([pubkey()] as const)(([pubkeyNonNull]) => {
saveColumn(createPostsColumn({ pubkey: pubkeyNonNull })); saveColumn(createPostsColumn({ pubkey: pubkeyNonNull }));
}); });
props.onClose(); finish();
}; };
const addMyReactionsColumn = () => { const addMyReactionsColumn = () => {
ensureNonNull([pubkey()] as const)(([pubkeyNonNull]) => { ensureNonNull([pubkey()] as const)(([pubkeyNonNull]) => {
saveColumn(createReactionsColumn({ pubkey: pubkeyNonNull })); saveColumn(createReactionsColumn({ pubkey: pubkeyNonNull }));
}); });
props.onClose(); finish();
}; };
return ( return (
@@ -89,6 +103,15 @@ const AddColumn: Component<AddColumnProps> = (props) => {
</span> </span>
</button> </button>
<button
class="flex basis-1/2 flex-col items-center gap-2 py-8 sm:basis-1/4"
onClick={() => addSearchColumn()}
>
<span class="inline-block h-8 w-8">
<MagnifyingGlass />
</span>
</button>
<button <button
class="flex basis-1/2 flex-col items-center gap-2 py-8 sm:basis-1/4" class="flex basis-1/2 flex-col items-center gap-2 py-8 sm:basis-1/4"
onClick={() => addMyPostsColumn()} onClick={() => addMyPostsColumn()}

View File

@@ -8,7 +8,9 @@ import MentionedUserDisplay from '@/components/textNote/MentionedUserDisplay';
import PlainTextDisplay from '@/components/textNote/PlainTextDisplay'; import PlainTextDisplay from '@/components/textNote/PlainTextDisplay';
import TextNoteDisplayById from '@/components/textNote/TextNoteDisplayById'; import TextNoteDisplayById from '@/components/textNote/TextNoteDisplayById';
import SafeLink from '@/components/utils/SafeLink'; import SafeLink from '@/components/utils/SafeLink';
import { createSearchColumn } from '@/core/column';
import useConfig from '@/core/useConfig'; import useConfig from '@/core/useConfig';
import { useRequestCommand } from '@/hooks/useCommandBus';
import eventWrapper from '@/nostr/event'; import eventWrapper from '@/nostr/event';
import parseTextNote, { resolveTagReference, type ParsedTextNoteNode } from '@/nostr/parseTextNote'; import parseTextNote, { resolveTagReference, type ParsedTextNoteNode } from '@/nostr/parseTextNote';
import { isImageUrl } from '@/utils/imageUrl'; import { isImageUrl } from '@/utils/imageUrl';
@@ -21,8 +23,17 @@ export type TextNoteContentDisplayProps = {
}; };
const TextNoteContentDisplay = (props: TextNoteContentDisplayProps) => { const TextNoteContentDisplay = (props: TextNoteContentDisplayProps) => {
const { config } = useConfig(); const { config, saveColumn } = useConfig();
const request = useRequestCommand();
const event = () => eventWrapper(props.event); const event = () => eventWrapper(props.event);
const addHashTagColumn = (query: string) => {
saveColumn(createSearchColumn({ query }));
request({ command: 'moveToLastColumn' }).catch((err) => console.error(err));
};
return ( return (
<For each={parseTextNote(props.event.content)}> <For each={parseTextNote(props.event.content)}>
{(item: ParsedTextNoteNode) => { {(item: ParsedTextNoteNode) => {
@@ -59,7 +70,11 @@ const TextNoteContentDisplay = (props: TextNoteContentDisplayProps) => {
return <span class="text-blue-500 underline">{item.content}</span>; return <span class="text-blue-500 underline">{item.content}</span>;
} }
if (item.type === 'HashTag') { if (item.type === 'HashTag') {
return <span class="text-blue-500 underline">{item.content}</span>; return (
<button class="text-blue-500 underline" onClick={() => addHashTagColumn(item.content)}>
{item.content}
</button>
);
} }
if (item.type === 'URL') { if (item.type === 'URL') {
if (isImageUrl(item.content)) { if (isImageUrl(item.content)) {