mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-17 14:04:21 +01:00
feat: support search column
This commit is contained in:
@@ -29,7 +29,7 @@ const ColumnSettingsSection: Component<ColumnSettingsSectionProps> = (props) =>
|
||||
|
||||
const ColumnSettings: Component<ColumnSettingsProps> = (props) => {
|
||||
const { saveColumn, removeColumn, moveColumn } = useConfig();
|
||||
const requestCommand = useRequestCommand();
|
||||
const request = useRequestCommand();
|
||||
|
||||
const setColumnWidth = (width: ColumnType['width']) => {
|
||||
saveColumn({ ...props.column, width });
|
||||
@@ -37,9 +37,7 @@ const ColumnSettings: Component<ColumnSettingsProps> = (props) => {
|
||||
|
||||
const move = (index: number) => {
|
||||
moveColumn(props.column.id, index);
|
||||
requestCommand({ command: 'moveToColumn', columnIndex: index }).catch((err) =>
|
||||
console.error(err),
|
||||
);
|
||||
request({ command: 'moveToColumn', columnIndex: index }).catch((err) => console.error(err));
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -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 BasicColumnHeader from '@/components/column/BasicColumnHeader';
|
||||
import Column from '@/components/column/Column';
|
||||
import ColumnSettings from '@/components/column/ColumnSettings';
|
||||
import Timeline from '@/components/timeline/Timeline';
|
||||
@@ -12,7 +12,60 @@ import { relaysForSearching } from '@/core/relayUrls';
|
||||
import useConfig from '@/core/useConfig';
|
||||
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;
|
||||
lastColumn: boolean;
|
||||
column: SearchColumnType;
|
||||
@@ -21,12 +74,17 @@ type SearchColumnDisplayProps = {
|
||||
const SearchColumn: Component<SearchColumnDisplayProps> = (props) => {
|
||||
const { removeColumn } = useConfig();
|
||||
|
||||
const { events } = useSubscription(() => ({
|
||||
const { events } = useSubscription(() => {
|
||||
const { query } = props.column;
|
||||
|
||||
if (query.length === 0) return null;
|
||||
|
||||
return {
|
||||
relayUrls: relaysForSearching,
|
||||
filters: [
|
||||
{
|
||||
kinds: [1, 6],
|
||||
search: props.column.query,
|
||||
search: query,
|
||||
limit: 25,
|
||||
},
|
||||
],
|
||||
@@ -34,14 +92,15 @@ const SearchColumn: Component<SearchColumnDisplayProps> = (props) => {
|
||||
if (props.column.contentFilter == null) return true;
|
||||
return applyContentFilter(props.column.contentFilter)(event.content);
|
||||
},
|
||||
}));
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<Column
|
||||
header={
|
||||
<BasicColumnHeader
|
||||
<SearchColumnHeader
|
||||
name={props.column.name ?? '検索'}
|
||||
icon={<MagnifyingGlass />}
|
||||
column={props.column}
|
||||
settings={() => <ColumnSettings column={props.column} columnIndex={props.columnIndex} />}
|
||||
onClose={() => removeColumn(props.column.id)}
|
||||
/>
|
||||
|
||||
@@ -4,6 +4,7 @@ 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 MagnifyingGlass from 'heroicons/24/outline/magnifying-glass.svg';
|
||||
import User from 'heroicons/24/outline/user.svg';
|
||||
|
||||
import BasicModal from '@/components/modal/BasicModal';
|
||||
@@ -13,8 +14,10 @@ import {
|
||||
createNotificationColumn,
|
||||
createPostsColumn,
|
||||
createReactionsColumn,
|
||||
createSearchColumn,
|
||||
} from '@/core/column';
|
||||
import useConfig from '@/core/useConfig';
|
||||
import { useRequestCommand } from '@/hooks/useCommandBus';
|
||||
import usePubkey from '@/nostr/usePubkey';
|
||||
import ensureNonNull from '@/utils/ensureNonNull';
|
||||
|
||||
@@ -25,38 +28,49 @@ type AddColumnProps = {
|
||||
const AddColumn: Component<AddColumnProps> = (props) => {
|
||||
const pubkey = usePubkey();
|
||||
const { saveColumn } = useConfig();
|
||||
const request = useRequestCommand();
|
||||
|
||||
const finish = () => {
|
||||
props.onClose();
|
||||
request({ command: 'moveToLastColumn' }).catch((err) => console.error(err));
|
||||
};
|
||||
|
||||
const addFollowingColumn = () => {
|
||||
ensureNonNull([pubkey()] as const)(([pubkeyNonNull]) => {
|
||||
saveColumn(createFollowingColumn({ pubkey: pubkeyNonNull }));
|
||||
});
|
||||
props.onClose();
|
||||
finish();
|
||||
};
|
||||
|
||||
const addNotificationColumn = () => {
|
||||
ensureNonNull([pubkey()] as const)(([pubkeyNonNull]) => {
|
||||
saveColumn(createNotificationColumn({ pubkey: pubkeyNonNull }));
|
||||
});
|
||||
props.onClose();
|
||||
finish();
|
||||
};
|
||||
|
||||
const addJapanRelaysColumn = () => {
|
||||
saveColumn(createJapanRelaysColumn());
|
||||
props.onClose();
|
||||
finish();
|
||||
};
|
||||
|
||||
const addSearchColumn = () => {
|
||||
saveColumn(createSearchColumn({ query: '' }));
|
||||
finish();
|
||||
};
|
||||
|
||||
const addMyPostsColumn = () => {
|
||||
ensureNonNull([pubkey()] as const)(([pubkeyNonNull]) => {
|
||||
saveColumn(createPostsColumn({ pubkey: pubkeyNonNull }));
|
||||
});
|
||||
props.onClose();
|
||||
finish();
|
||||
};
|
||||
|
||||
const addMyReactionsColumn = () => {
|
||||
ensureNonNull([pubkey()] as const)(([pubkeyNonNull]) => {
|
||||
saveColumn(createReactionsColumn({ pubkey: pubkeyNonNull }));
|
||||
});
|
||||
props.onClose();
|
||||
finish();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -89,6 +103,15 @@ const AddColumn: Component<AddColumnProps> = (props) => {
|
||||
</span>
|
||||
日本リレー
|
||||
</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
|
||||
class="flex basis-1/2 flex-col items-center gap-2 py-8 sm:basis-1/4"
|
||||
onClick={() => addMyPostsColumn()}
|
||||
|
||||
@@ -8,7 +8,9 @@ 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 { createSearchColumn } from '@/core/column';
|
||||
import useConfig from '@/core/useConfig';
|
||||
import { useRequestCommand } from '@/hooks/useCommandBus';
|
||||
import eventWrapper from '@/nostr/event';
|
||||
import parseTextNote, { resolveTagReference, type ParsedTextNoteNode } from '@/nostr/parseTextNote';
|
||||
import { isImageUrl } from '@/utils/imageUrl';
|
||||
@@ -21,8 +23,17 @@ export type TextNoteContentDisplayProps = {
|
||||
};
|
||||
|
||||
const TextNoteContentDisplay = (props: TextNoteContentDisplayProps) => {
|
||||
const { config } = useConfig();
|
||||
const { config, saveColumn } = useConfig();
|
||||
|
||||
const request = useRequestCommand();
|
||||
|
||||
const event = () => eventWrapper(props.event);
|
||||
|
||||
const addHashTagColumn = (query: string) => {
|
||||
saveColumn(createSearchColumn({ query }));
|
||||
request({ command: 'moveToLastColumn' }).catch((err) => console.error(err));
|
||||
};
|
||||
|
||||
return (
|
||||
<For each={parseTextNote(props.event.content)}>
|
||||
{(item: ParsedTextNoteNode) => {
|
||||
@@ -59,7 +70,11 @@ const TextNoteContentDisplay = (props: TextNoteContentDisplayProps) => {
|
||||
return <span class="text-blue-500 underline">{item.content}</span>;
|
||||
}
|
||||
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 (isImageUrl(item.content)) {
|
||||
|
||||
Reference in New Issue
Block a user