Just to save first changes

merge-requests/3337/head
danidfra 2025-02-14 18:08:17 -03:00
rodzic eca2978fc2
commit d93439f396
7 zmienionych plików z 546 dodań i 46 usunięć

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 70 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 11 KiB

Wyświetl plik

@ -1,18 +1,22 @@
import xIcon from '@tabler/icons/outline/x.svg';
import clsx from 'clsx'; import clsx from 'clsx';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import {
FormattedMessage,
// defineMessages,
// useIntl
} from 'react-intl';
import { expandSearch, setFilter, setSearchAccount } from 'soapbox/actions/search.ts'; import {
expandSearch,
// setFilter,
// setSearchAccount
} from 'soapbox/actions/search.ts';
import { expandTrendingStatuses, fetchTrendingStatuses } from 'soapbox/actions/trending-statuses.ts'; import { expandTrendingStatuses, fetchTrendingStatuses } from 'soapbox/actions/trending-statuses.ts';
import { useAccount } from 'soapbox/api/hooks/index.ts'; // import { useAccount } from 'soapbox/api/hooks/index.ts';
import Hashtag from 'soapbox/components/hashtag.tsx'; import Hashtag from 'soapbox/components/hashtag.tsx';
import IconButton from 'soapbox/components/icon-button.tsx';
import ScrollableList from 'soapbox/components/scrollable-list.tsx'; import ScrollableList from 'soapbox/components/scrollable-list.tsx';
import HStack from 'soapbox/components/ui/hstack.tsx';
import Spinner from 'soapbox/components/ui/spinner.tsx'; import Spinner from 'soapbox/components/ui/spinner.tsx';
import Tabs from 'soapbox/components/ui/tabs.tsx'; // import Tabs from 'soapbox/components/ui/tabs.tsx';
import Text from 'soapbox/components/ui/text.tsx';
import AccountContainer from 'soapbox/containers/account-container.tsx'; import AccountContainer from 'soapbox/containers/account-container.tsx';
import StatusContainer from 'soapbox/containers/status-container.tsx'; import StatusContainer from 'soapbox/containers/status-container.tsx';
import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder-account.tsx'; import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder-account.tsx';
@ -24,18 +28,18 @@ import { useSuggestions } from 'soapbox/queries/suggestions.ts';
import type { OrderedSet as ImmutableOrderedSet } from 'immutable'; import type { OrderedSet as ImmutableOrderedSet } from 'immutable';
import type { VirtuosoHandle } from 'react-virtuoso'; import type { VirtuosoHandle } from 'react-virtuoso';
import type { SearchFilter } from 'soapbox/reducers/search.ts'; // import type { SearchFilter } from 'soapbox/reducers/search.ts';
const messages = defineMessages({ // const messages = defineMessages({
accounts: { id: 'search_results.accounts', defaultMessage: 'People' }, // accounts: { id: 'search_results.accounts', defaultMessage: 'People' },
statuses: { id: 'search_results.statuses', defaultMessage: 'Posts' }, // statuses: { id: 'search_results.statuses', defaultMessage: 'Posts' },
hashtags: { id: 'search_results.hashtags', defaultMessage: 'Hashtags' }, // hashtags: { id: 'search_results.hashtags', defaultMessage: 'Hashtags' },
}); // });
const SearchResults = () => { const SearchResults = () => {
const node = useRef<VirtuosoHandle>(null); const node = useRef<VirtuosoHandle>(null);
const intl = useIntl(); // const intl = useIntl();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { data: suggestions } = useSuggestions(); const { data: suggestions } = useSuggestions();
@ -47,8 +51,8 @@ const SearchResults = () => {
const trends = useAppSelector((state) => state.trends.items); const trends = useAppSelector((state) => state.trends.items);
const submitted = useAppSelector((state) => state.search.submitted); const submitted = useAppSelector((state) => state.search.submitted);
const selectedFilter = useAppSelector((state) => state.search.filter); const selectedFilter = useAppSelector((state) => state.search.filter);
const filterByAccount = useAppSelector((state) => state.search.accountId || undefined); // const filterByAccount = useAppSelector((state) => state.search.accountId || undefined);
const { account } = useAccount(filterByAccount); // const { account } = useAccount(filterByAccount);
const handleLoadMore = () => { const handleLoadMore = () => {
if (results.accounts.size || results.statuses.size || results.hashtags.size) { if (results.accounts.size || results.statuses.size || results.hashtags.size) {
@ -58,35 +62,35 @@ const SearchResults = () => {
} }
}; };
const handleUnsetAccount = () => dispatch(setSearchAccount(null)); // const handleUnsetAccount = () => dispatch(setSearchAccount(null));
const selectFilter = (newActiveFilter: SearchFilter) => dispatch(setFilter(newActiveFilter)); // const selectFilter = (newActiveFilter: SearchFilter) => dispatch(setFilter(newActiveFilter));
const renderFilterBar = () => { // const renderFilterBar = () => {
const items = []; // const items = [];
items.push( // items.push(
{ // {
text: intl.formatMessage(messages.statuses), // text: intl.formatMessage(messages.statuses),
action: () => selectFilter('statuses'), // action: () => selectFilter('statuses'),
name: 'statuses', // name: 'statuses',
}, // },
{ // {
text: intl.formatMessage(messages.accounts), // text: intl.formatMessage(messages.accounts),
action: () => selectFilter('accounts'), // action: () => selectFilter('accounts'),
name: 'accounts', // name: 'accounts',
}, // },
); // );
items.push( // items.push(
{ // {
text: intl.formatMessage(messages.hashtags), // text: intl.formatMessage(messages.hashtags),
action: () => selectFilter('hashtags'), // action: () => selectFilter('hashtags'),
name: 'hashtags', // name: 'hashtags',
}, // },
); // );
return <Tabs items={items} activeItem={selectedFilter} />; // return <Tabs items={items} activeItem={selectedFilter} />;
}; // };
const getCurrentIndex = (id: string): number => { const getCurrentIndex = (id: string): number => {
return resultsIds?.keySeq().findIndex(key => key === id); return resultsIds?.keySeq().findIndex(key => key === id);
@ -215,7 +219,7 @@ const SearchResults = () => {
return ( return (
<> <>
{filterByAccount ? ( {/* {filterByAccount ? (
<HStack className='mb-4 border-b border-solid border-gray-200 px-2 pb-4 dark:border-gray-800' space={2}> <HStack className='mb-4 border-b border-solid border-gray-200 px-2 pb-4 dark:border-gray-800' space={2}>
<IconButton iconClassName='h-5 w-5' src={xIcon} onClick={handleUnsetAccount} /> <IconButton iconClassName='h-5 w-5' src={xIcon} onClick={handleUnsetAccount} />
<Text truncate> <Text truncate>
@ -228,7 +232,7 @@ const SearchResults = () => {
</HStack> </HStack>
) : ( ) : (
<div className='px-4'>{renderFilterBar()}</div> <div className='px-4'>{renderFilterBar()}</div>
)} )} */}
{noResultsMessage || ( {noResultsMessage || (
<ScrollableList <ScrollableList

Wyświetl plik

@ -0,0 +1,105 @@
import arrowIcon from '@tabler/icons/outline/chevron-down.svg';
import rocketIcon from '@tabler/icons/outline/rocket.svg';
import { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import bridgeImg from 'soapbox/assets/images/bridge-image.png';
import nostrImg from 'soapbox/assets/images/nostr-image.png';
import HStack from 'soapbox/components/ui/hstack.tsx';
import IconButton from 'soapbox/components/ui/icon-button.tsx';
import Stack from 'soapbox/components/ui/stack.tsx';
import SvgIcon from 'soapbox/components/ui/svg-icon.tsx';
import Text from 'soapbox/components/ui/text.tsx';
const messages = defineMessages({
welcomeTitle: { id: 'column.explorer.welcome_card.title', defaultMessage: 'Welcome to Explorer' },
welcomeText: { id: 'column.explorer.welcome_card.text', defaultMessage: 'Explore the world of <span>decentralized social media</span>, dive into <span>Nostr</span> or cross the bridge to other networks, and connect with a global community. All in one place.' },
nostrTitle: { id: 'column.explorer.nostr_card.title', defaultMessage: 'Nostr' },
nostrText: { id: 'column.explorer.nostr_card.text', defaultMessage: 'Wondering about Nostr? Find Out More' },
bridgeTitle: { id: 'column.explorer.bridge_card.title', defaultMessage: 'Bridge' },
bridgeText: { id: 'column.explorer.bridge_card.text', defaultMessage: 'Curious about Bridges? Click here' },
});
const ExplorerCards = () => {
const [isOpen, setIsOpen] = useState(true);
const intl = useIntl();
return (
<Stack>
<Stack
space={4}
className={`rounded-xl bg-gradient-to-r from-pink-400 via-purple-500 to-blue-400 ${isOpen ? 'mx-4 mb-4 px-5 pb-8 pt-4' : 'm-4 p-4'}`}
>
<HStack justifyContent='between' className='text-white'>
<HStack space={2}>
<SvgIcon src={rocketIcon} />
<p className='text-xl font-bold'>
{intl.formatMessage(messages.welcomeTitle)}
</p>
</HStack>
<IconButton
src={arrowIcon}
theme='transparent'
onClick={() => setIsOpen(!isOpen)}
className={`transition-transform duration-300${
isOpen ? 'rotate-0' : 'rotate-180'
}`}
/>
</HStack>
<Text className={`text-white ${isOpen ? 'max-h-96 opacity-100' : 'hidden max-h-0 opacity-0'}`}>
{intl.formatMessage(messages.welcomeText, {
span: (node) => <span className='text-black'>{node}</span>,
})}
</Text>
</Stack>
<HStack className={`mx-4 mb-4 ${isOpen ? 'max-h-96 opacity-100' : 'hidden max-h-0 opacity-0'}`} space={4}>
{/* Nostr */}
<Stack
space={4}
className='w-1/2 rounded-xl bg-gradient-to-r from-pink-400 to-purple-500 px-5 pb-8 pt-4'
justifyContent='center'
>
<HStack space={2} alignItems='center' justifyContent='center'>
{/* Title */}
<Stack space={2}>
<p className='text-xl font-bold text-white'>
{intl.formatMessage(messages.nostrTitle)}
</p>
<Text className='text-white'>
{intl.formatMessage(messages.nostrText)}
</Text>
</Stack>
<div className='w-1/2 rounded-full bg-white p-2'>
<img src={nostrImg} alt='' />
</div>
</HStack>
</Stack>
{/* Bridge */}
<Stack
space={4}
className='w-1/2 rounded-xl bg-gradient-to-r from-purple-500 to-blue-400 px-5 pb-8 pt-4'
>
<HStack space={2} alignItems='center'>
{/* Title */}
<Stack space={2}>
<p className='text-xl font-bold text-white'> {intl.formatMessage(messages.bridgeTitle)} </p>
<Text className='text-white'>
{intl.formatMessage(messages.bridgeText)}
</Text>
</Stack>
<div className='w-1/2 rounded-full bg-white p-2'>
<img src={bridgeImg} alt='' />
</div>
</HStack>
</Stack>
</HStack>
</Stack>
);
};
export default ExplorerCards;

Wyświetl plik

@ -0,0 +1,358 @@
import arrowIcon from '@tabler/icons/outline/chevron-down.svg';
import searchIcon from '@tabler/icons/outline/search.svg';
import xIcon from '@tabler/icons/outline/x.svg';
import clsx from 'clsx';
import { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import Button from 'soapbox/components/ui/button.tsx';
import Checkbox from 'soapbox/components/ui/checkbox.tsx';
import Divider from 'soapbox/components/ui/divider.tsx';
import HStack from 'soapbox/components/ui/hstack.tsx';
import IconButton from 'soapbox/components/ui/icon-button.tsx';
import Input from 'soapbox/components/ui/input.tsx';
import Stack from 'soapbox/components/ui/stack.tsx';
import SvgIcon from 'soapbox/components/ui/svg-icon.tsx';
import Text from 'soapbox/components/ui/text.tsx';
import Toggle from 'soapbox/components/ui/toggle.tsx';
import { SelectDropdown } from 'soapbox/features/forms/index.tsx';
const messages = defineMessages({
filters: { id: 'column.explorer.filters', defaultMessage: 'Filters:' },
showReplies: { id: 'column.explorer.filters.show_replies', defaultMessage: 'Show replies:' },
language: { id: 'column.explorer.filters.language', defaultMessage: 'Language:' },
platforms: { id: 'column.explorer.filters.platforms', defaultMessage: 'Platforms:' },
createYourFilter: { id: 'column.explorer.filters.create_your_filter', defaultMessage: 'Create your filter' },
filterByWords: { id: 'column.explorer.filters.filter_by_words', defaultMessage: 'Filter by this/these words' },
include: { id: 'column.explorer.filters.include', defaultMessage: 'Include' },
exclude: { id: 'column.explorer.filters.exclude', defaultMessage: 'Exclude' },
nostr: { id: 'column.explorer.filters.nostr', defaultMessage: 'Nostr' },
bluesky: { id: 'column.explorer.filters.bluesky', defaultMessage: 'Bluesky' },
fediverse: { id: 'column.explorer.filters.fediverse', defaultMessage: 'Fediverse' },
cancel: { id: 'column.explorer.filters.cancel', defaultMessage: 'Cancel' },
applyFilter: { id: 'column.explorer.filters.apply_filter', defaultMessage: 'Apply Filter' },
});
const languages = {
en: 'English',
ar: 'العربية',
ast: 'Asturianu',
bg: 'Български',
bn: 'বাংলা',
ca: 'Català',
co: 'Corsu',
cs: 'Čeština',
cy: 'Cymraeg',
da: 'Dansk',
de: 'Deutsch',
el: 'Ελληνικά',
'en-Shaw': '𐑖𐑱𐑝𐑾𐑯',
eo: 'Esperanto',
es: 'Español',
eu: 'Euskara',
fa: 'فارسی',
fi: 'Suomi',
fr: 'Français',
ga: 'Gaeilge',
gl: 'Galego',
he: 'עברית',
hi: 'हिन्दी',
hr: 'Hrvatski',
hu: 'Magyar',
hy: 'Հայերեն',
id: 'Bahasa Indonesia',
io: 'Ido',
is: 'íslenska',
it: 'Italiano',
ja: '日本語',
jv: 'ꦧꦱꦗꦮ',
ka: 'ქართული',
kk: 'Қазақша',
ko: '한국어',
lt: 'Lietuvių',
lv: 'Latviešu',
ml: 'മലയാളം',
ms: 'Bahasa Melayu',
nl: 'Nederlands',
no: 'Norsk',
oc: 'Occitan',
pl: 'Polski',
pt: 'Português',
'pt-BR': 'Português do Brasil',
ro: 'Română',
ru: 'Русский',
sk: 'Slovenčina',
sl: 'Slovenščina',
sq: 'Shqip',
sr: 'Српски',
'sr-Latn': 'Srpski (latinica)',
sv: 'Svenska',
ta: 'தமிழ்',
te: 'తెలుగు',
th: 'ไทย',
tr: 'Türkçe',
uk: 'Українська',
zh: '中文',
'zh-CN': '简体中文',
'zh-HK': '繁體中文(香港)',
'zh-TW': '繁體中文(臺灣)',
};
interface IGenerateFilter {
name: string;
status: boolean | null;
}
const ExplorerFilter = () => {
const [showReplies, setShowReplies] = useState(false);
const [inputValue, setInputValue] = useState('');
const [include, setInclude] = useState(true);
const [isOpen, setIsOpen] = useState(true);
const hasValue = inputValue.length > 0;
const intl = useIntl();
const [testList, setTestList] = useState([
{ 'name': 'EN', 'status': true },
{ 'name': 'Nostr', 'status': null },
{ 'name': 'Bluesky', 'status': null },
{ 'name': 'Fediverse', 'status': null },
{ 'name': 'Bitcoin', 'status': false },
{ 'name': 'NostrInArgentina', 'status': true },
{ 'name': 'ElonForPresident', 'status': false },
]);
const generateFilter = ({ name, status }: IGenerateFilter) => {
let borderColor = '';
let textColor = '';
switch (name.toLowerCase()) {
case 'nostr':
borderColor = 'border-purple-500';
textColor = 'text-purple-500';
break;
case 'bluesky':
borderColor = 'border-blue-500';
textColor = 'text-blue-500';
break;
case 'fediverse':
borderColor = 'border-indigo-500';
textColor = 'text-indigo-500';
break;
default:
borderColor = status ? 'border-green-500' : 'border-red-500';
textColor = status ? 'text-green-500' : 'text-red-500';
}
return (
<div
key={name}
className={`group m-1 flex items-center gap-0.5 whitespace-normal break-words rounded-full border-2 bg-transparent px-3 text-lg font-medium shadow-sm hover:cursor-pointer hover:pr-1 ${borderColor} `}
>
{name}
<IconButton
iconClassName='!w-4' className={`hidden !p-0 px-1 group-hover:block ${textColor}`} src={xIcon} onClick={() => setTestList((prevValue) => {
return prevValue.filter((x) => x.name !== name);
})}
/>
</div>
);
};
return (
<Stack className='px-4' space={3}>
{/* Filters */}
<HStack alignItems='start' space={1}>
<HStack className='flex-wrap whitespace-normal' alignItems='center' space={2}>
<Text size='lg' weight='bold'>
{intl.formatMessage(messages.filters)}
</Text>
{testList.map(generateFilter)}
</HStack>
<IconButton
src={arrowIcon}
theme='transparent'
className={`transition-transform duration-300${ isOpen ? 'rotate-0' : 'rotate-180'}`}
onClick={() => setIsOpen(!isOpen)}
/>
</HStack>
<Stack className={`overflow-hidden transition-all duration-500 ease-in-out ${isOpen ? 'max-h-[1000px] opacity-100' : 'max-h-0 opacity-0'}`} space={3}>
{/* Show Reply toggle */}
<HStack className='flex-wrap whitespace-normal' alignItems='center' space={2}>
<Text size='lg' weight='bold'>
{intl.formatMessage(messages.showReplies)}
</Text>
<Toggle
checked={showReplies}
onChange={() => setShowReplies(!showReplies)}
/>
</HStack>
{/* Language */}
<HStack alignItems='center' space={2}>
<Text size='lg' weight='bold'>
{intl.formatMessage(messages.language)}
</Text>
<SelectDropdown
className='max-w-[200px]'
items={languages}
defaultValue={languages.en}
/>
</HStack>
{/* Platforms */}
<HStack className='flex-wrap whitespace-normal' alignItems='center' space={2}>
<Text size='lg' weight='bold'>
{intl.formatMessage(messages.platforms)}
</Text>
{/* Nostr */}
<HStack alignItems='center' space={2}>
<Checkbox
name='nostr'
checked={testList.some((tag)=> tag.name.toLowerCase() === 'nostr')}
onChange={() => setTestList((prevValue) => {
const exists = prevValue.some((x) => x.name.toLowerCase() === 'nostr');
if (exists) {
return prevValue.filter((x) => x.name.toLowerCase() !== 'nostr');
} else {
return [...prevValue, { name: 'Nostr', status: null }];
}
})}
// checked={params.get('agreement', false)}
// required
/>
<Text size='lg'>
{intl.formatMessage(messages.nostr)}
</Text>
</HStack>
{/* Bluesky */}
<HStack alignItems='center' space={2}>
<Checkbox
name='bluesky'
checked={testList.some((tag)=> tag.name.toLowerCase() === 'bluesky')}
onChange={() => setTestList((prevValue) => {
const exists = prevValue.some((x) => x.name.toLowerCase() === 'bluesky');
if (exists) {
return prevValue.filter((x) => x.name.toLowerCase() !== 'bluesky');
} else {
return [...prevValue, { name: 'Bluesky', status: null }];
}
})}
/>
<Text size='lg'>
{intl.formatMessage(messages.bluesky)}
</Text>
</HStack>
{/* Fediverse */}
<HStack alignItems='center' space={2}>
<Checkbox
name='fediverse'
checked={testList.some((tag)=> tag.name.toLowerCase() === 'fediverse')}
onChange={() => setTestList((prevValue) => {
const exists = prevValue.some((x) => x.name.toLowerCase() === 'fediverse');
if (exists) {
return prevValue.filter((x) => x.name.toLowerCase() !== 'fediverse');
} else {
return [...prevValue, { name: 'Fediverse', status: null }];
}
})}
/>
<Text size='lg'>
{intl.formatMessage(messages.fediverse)}
</Text>
</HStack>
</HStack>
<Divider />
{/* Create your filter */}
<Stack space={3}>
<Text size='lg' weight='bold'>
{intl.formatMessage(messages.createYourFilter)}
</Text>
<Stack>
<Text size='lg'>
{intl.formatMessage(messages.filterByWords)}
</Text>
<HStack space={6}>
<div className='relative w-full items-center'>
<Input theme='search' value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
<div
role='button'
tabIndex={0}
className='absolute inset-y-0 right-0 flex cursor-pointer items-center px-3 rtl:left-0 rtl:right-auto'
>
<SvgIcon
src={searchIcon}
className={clsx('size-4 text-gray-600', { hidden: hasValue })}
/>
<SvgIcon
src={xIcon}
className={clsx('size-4 text-gray-600', { hidden: !hasValue })}
/>
</div>
</div>
{/* Include */}
<HStack alignItems='center' space={2}>
<Checkbox
name='include'
checked={include}
onChange={() => setInclude(!include)}
/>
<Text size='lg'>
{intl.formatMessage(messages.include)}
</Text>
</HStack>
{/* Exclude */}
<HStack alignItems='center' space={2}>
<Checkbox
name='exclude'
checked={!include}
onChange={() => setInclude(!include)}
/>
<Text size='lg'>
{intl.formatMessage(messages.exclude)}
</Text>
</HStack>
</HStack>
</Stack>
<HStack className='w-full' space={2}>
<Button className='w-1/2' theme='secondary'>
{intl.formatMessage(messages.cancel)}
</Button>
<Button className='w-1/2' theme='primary'>
{intl.formatMessage(messages.applyFilter)}
</Button>
</HStack>
</Stack>
</Stack>
</Stack>
);
};
export default ExplorerFilter;

Wyświetl plik

@ -1,12 +1,15 @@
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import { Column } from 'soapbox/components/ui/column.tsx'; import { Column } from 'soapbox/components/ui/column.tsx';
import Divider from 'soapbox/components/ui/divider.tsx';
import Stack from 'soapbox/components/ui/stack.tsx'; import Stack from 'soapbox/components/ui/stack.tsx';
import SearchResults from 'soapbox/features/compose/components/search-results.tsx'; import SearchResults from 'soapbox/features/compose/components/search-results.tsx';
import Search from 'soapbox/features/compose/components/search.tsx'; import Search from 'soapbox/features/compose/components/search.tsx';
import ExplorerCards from 'soapbox/features/search/components/explorerCards.tsx';
import ExplorerFilter from 'soapbox/features/search/components/explorerFilter.tsx';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.search', defaultMessage: 'Discover' }, heading: { id: 'column.search', defaultMessage: 'Explorer' },
}); });
const SearchPage = () => { const SearchPage = () => {
@ -14,10 +17,20 @@ const SearchPage = () => {
return ( return (
<Column label={intl.formatMessage(messages.heading)} slim> <Column label={intl.formatMessage(messages.heading)} slim>
<Stack space={4}> <Stack space={4}>
<ExplorerCards />
<Divider text='Explorer' />
<ExplorerFilter />
<Divider />
<div className='px-4'> <div className='px-4'>
<Search autoSubmit /> <Search autoSubmit />
</div> </div>
<SearchResults /> <SearchResults />
</Stack> </Stack>
</Column> </Column>

Wyświetl plik

@ -430,13 +430,33 @@
"column.reblogs": "Reposts", "column.reblogs": "Reposts",
"column.registration": "Sign Up", "column.registration": "Sign Up",
"column.scheduled_statuses": "Scheduled Posts", "column.scheduled_statuses": "Scheduled Posts",
"column.search": "Discover", "column.search": "Explorer",
"column.settings_store": "Settings store", "column.settings_store": "Settings store",
"column.soapbox_config": "Soapbox config", "column.soapbox_config": "Soapbox config",
"column.test": "Test timeline", "column.test": "Test timeline",
"column.zaps": "Zaps", "column.zaps": "Zaps",
"column_forbidden.body": "You do not have permission to access this page.", "column_forbidden.body": "You do not have permission to access this page.",
"column_forbidden.title": "Forbidden", "column_forbidden.title": "Forbidden",
"column.explorer": "Explorer",
"column.explorer.welcome_card.title": "Welcome to Explorer",
"column.explorer.welcome_card.text": "Explore the world of <span>decentralized social media</span>, dive into <span>Nostr</span> or cross the bridge to other networks, and connect with a global community. All in one place.",
"column.explorer.nostr_card.title": "Nostr",
"column.explorer.nostr_card.text": "Wondering about Nostr? Find Out More",
"column.explorer.bridge_card.title": "Bridge",
"column.explorer.bridge_card.text": "Curious about Bridges? Click Here",
"column.explorer.filters.show_replies": "Show replies:",
"column.explorer.filters": "Fiters:",
"column.explorer.filters.language": "Language:",
"column.explorer.filters.platforms": "Platforms:",
"column.explorer.filters.create_filter": "Create your filter",
"column.explorer.filters.filter_words": "Filter by this/these words",
"column.explorer.filters.checkbox.include": "Include",
"column.explorer.filters.checkbox.exclude": "Exclude",
"column.explorer.filters.checkbox.nostr": "Nostr",
"column.explorer.filters.checkbox.bluesky": "Bluesky",
"column.explorer.filters.checkbox.fediverse": "Fediverse",
"column.explorer.filters.cancel": "Cancel",
"column.explorer.filters.apply_filter": "Apply Filter",
"common.cancel": "Cancel", "common.cancel": "Cancel",
"compare_history_modal.header": "Edit history", "compare_history_modal.header": "Edit history",
"compose.character_counter.title": "Used {chars} out of {maxChars} {maxChars, plural, one {character} other {characters}}", "compose.character_counter.title": "Used {chars} out of {maxChars} {maxChars, plural, one {character} other {characters}}",