import './filters.css';
import { i18n } from '@lingui/core';
import { msg, Plural, t, Trans } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { useEffect, useReducer, useRef, useState } from 'preact/hooks';
import Icon from '../components/icon';
import Link from '../components/link';
import Loader from '../components/loader';
import MenuConfirm from '../components/menu-confirm';
import Modal from '../components/modal';
import NavMenu from '../components/nav-menu';
import RelativeTime from '../components/relative-time';
import { api } from '../utils/api';
import i18nDuration from '../utils/i18n-duration';
import useInterval from '../utils/useInterval';
import useTitle from '../utils/useTitle';
const FILTER_CONTEXT = ['home', 'public', 'notifications', 'thread', 'account'];
const FILTER_CONTEXT_UNIMPLEMENTED = ['notifications', 'thread', 'account'];
const FILTER_CONTEXT_LABELS = {
home: msg`Home and lists`,
notifications: msg`Notifications`,
public: msg`Public timelines`,
thread: msg`Conversations`,
account: msg`Profiles`,
};
const EXPIRY_DURATIONS = [
0, // forever
30 * 60, // 30 minutes
60 * 60, // 1 hour
6 * 60 * 60, // 6 hours
12 * 60 * 60, // 12 hours
60 * 60 * 24, // 24 hours
60 * 60 * 24 * 7, // 7 days
60 * 60 * 24 * 30, // 30 days
];
const EXPIRY_DURATIONS_LABELS = {
0: msg`Never`,
1800: i18nDuration(30, 'minute'),
3600: i18nDuration(1, 'hour'),
21600: i18nDuration(6, 'hour'),
43200: i18nDuration(12, 'hour'),
86_400: i18nDuration(24, 'hour'),
604_800: i18nDuration(7, 'day'),
2_592_000: i18nDuration(30, 'day'),
};
function Filters() {
const { masto } = api();
useTitle(t`Filters`, `/ft`);
const [uiState, setUIState] = useState('default');
const [showFiltersAddEditModal, setShowFiltersAddEditModal] = useState(false);
const [reloadCount, reload] = useReducer((c) => c + 1, 0);
const [filters, setFilters] = useState([]);
useEffect(() => {
setUIState('loading');
(async () => {
try {
const filters = await masto.v2.filters.list();
filters.sort((a, b) => a.title.localeCompare(b.title));
filters.forEach((filter) => {
if (filter.keywords?.length) {
filter.keywords.sort((a, b) => a.id - b.id);
}
});
console.log(filters);
setFilters(filters);
setUIState('default');
} catch (e) {
console.error(e);
setUIState('error');
}
})();
}, [reloadCount]);
return (
{filters.length > 0 ? (
<>
{filters.map((filter) => {
const { id, title, expiresAt, keywords } = filter;
return (
-
{title}
{keywords?.length > 0 && (
{keywords.map((k) => (
<>
{k.wholeWord ? `“${k.keyword}”` : k.keyword}
{' '}
>
))}
)}
);
})}
{filters.length > 1 && (
)}
>
) : uiState === 'loading' ? (
) : uiState === 'error' ? (
Unable to load filters.
) : (
No filters yet.
)}
{!!showFiltersAddEditModal && (
{
setShowFiltersAddEditModal(false);
}}
>
{
if (result.state === 'success') {
reload();
}
setShowFiltersAddEditModal(false);
}}
/>
)}
);
}
let _id = 1;
const incID = () => _id++;
function FiltersAddEdit({ filter, onClose }) {
const { _ } = useLingui();
const { masto } = api();
const [uiState, setUIState] = useState('default');
const editMode = !!filter;
const { context, expiresAt, id, keywords, title, filterAction } =
filter || {};
const hasExpiry = !!expiresAt;
const expiresAtDate = hasExpiry && new Date(expiresAt);
const [editKeywords, setEditKeywords] = useState(keywords || []);
const keywordsRef = useRef();
// Hacky way of handling removed keywords for both existing and new ones
const [removedKeywordIDs, setRemovedKeywordIDs] = useState([]);
const [removedKeyword_IDs, setRemovedKeyword_IDs] = useState([]);
const filteredEditKeywords = editKeywords.filter(
(k) =>
!removedKeywordIDs.includes(k.id) && !removedKeyword_IDs.includes(k._id),
);
return (
{!!onClose && (
)}
{editMode ? t`Edit filter` : t`New filter`}
);
}
function ExpiryStatus({ expiresAt, showNeverExpires }) {
const hasExpiry = !!expiresAt;
const expiresAtDate = hasExpiry && new Date(expiresAt);
const expired = hasExpiry && expiresAtDate <= new Date();
// If less than a minute left, re-render interval every second, else every minute
const [_, rerender] = useReducer((c) => c + 1, 0);
useInterval(rerender, expired || 30_000);
return expired ? (
t`Expired`
) : hasExpiry ? (
Expiring
) : (
showNeverExpires && t`Never expires`
);
}
export default Filters;