Breaking: rewrote filters implementation

This commit is contained in:
Lim Chee Aun 2023-11-03 21:45:31 +08:00
parent 1cdc4ebbe8
commit 0bc1b598c3
13 changed files with 287 additions and 207 deletions

View file

@ -1,9 +1,13 @@
import './media-post.css'; import './media-post.css';
import { memo } from 'preact/compat'; import { memo } from 'preact/compat';
import { useContext, useMemo } from 'preact/hooks';
import { useSnapshot } from 'valtio'; import { useSnapshot } from 'valtio';
import FilterContext from '../utils/filter-context';
import { isFiltered } from '../utils/filters';
import states, { statusKey } from '../utils/states'; import states, { statusKey } from '../utils/states';
import store from '../utils/store';
import Media from './media'; import Media from './media';
@ -13,7 +17,7 @@ function MediaPost({
status, status,
instance, instance,
parent, parent,
allowFilters, // allowFilters,
onMediaClick, onMediaClick,
}) { }) {
let sKey = statusKey(statusID, instance); let sKey = statusKey(statusID, instance);
@ -68,7 +72,7 @@ function MediaPost({
// Non-API props // Non-API props
_deleted, _deleted,
_pinned, _pinned,
_filtered, // _filtered,
} = status; } = status;
if (!mediaAttachments?.length) { if (!mediaAttachments?.length) {
@ -83,6 +87,20 @@ function MediaPost({
} }
}; };
const currentAccount = useMemo(() => {
return store.session.get('currentAccount');
}, []);
const isSelf = useMemo(() => {
return currentAccount && currentAccount === accountId;
}, [accountId, currentAccount]);
const filterContext = useContext(FilterContext);
const filterInfo = !isSelf && isFiltered(filtered, filterContext);
if (filterInfo?.action === 'hide') {
return null;
}
console.debug('RENDER Media post', id, status?.account.displayName); console.debug('RENDER Media post', id, status?.account.displayName);
// const readingExpandSpoilers = useMemo(() => { // const readingExpandSpoilers = useMemo(() => {
@ -95,6 +113,7 @@ function MediaPost({
return mediaAttachments.map((media, i) => { return mediaAttachments.map((media, i) => {
const mediaKey = `${sKey}-${media.id}`; const mediaKey = `${sKey}-${media.id}`;
const filterTitleStr = filterInfo?.titlesStr;
return ( return (
<Parent <Parent
onMouseEnter={debugHover} onMouseEnter={debugHover}
@ -102,10 +121,14 @@ function MediaPost({
data-spoiler-text={ data-spoiler-text={
spoilerText || (sensitive ? 'Sensitive media' : undefined) spoilerText || (sensitive ? 'Sensitive media' : undefined)
} }
data-filtered-text={_filtered ? 'Filtered' : undefined} data-filtered-text={
filterInfo
? `Filtered${filterTitleStr ? `: ${filterTitleStr}` : ''}`
: undefined
}
class={` class={`
media-post media-post
${allowFilters && _filtered ? 'filtered' : ''} ${filterInfo ? 'filtered' : ''}
${hasSpoiler ? 'has-spoiler' : ''} ${hasSpoiler ? 'has-spoiler' : ''}
`} `}
> >

View file

@ -13,6 +13,7 @@ import pThrottle from 'p-throttle';
import { memo } from 'preact/compat'; import { memo } from 'preact/compat';
import { import {
useCallback, useCallback,
useContext,
useEffect, useEffect,
useMemo, useMemo,
useRef, useRef,
@ -34,6 +35,8 @@ import Poll from '../components/poll';
import { api } from '../utils/api'; import { api } from '../utils/api';
import emojifyText from '../utils/emojify-text'; import emojifyText from '../utils/emojify-text';
import enhanceContent from '../utils/enhance-content'; import enhanceContent from '../utils/enhance-content';
import FilterContext from '../utils/filter-context';
import { isFiltered } from '../utils/filters';
import getTranslateTargetLanguage from '../utils/get-translate-target-language'; import getTranslateTargetLanguage from '../utils/get-translate-target-language';
import getHTMLText from '../utils/getHTMLText'; import getHTMLText from '../utils/getHTMLText';
import handleContentLinks from '../utils/handle-content-links'; import handleContentLinks from '../utils/handle-content-links';
@ -90,7 +93,7 @@ function Status({
enableTranslate, enableTranslate,
forceTranslate: _forceTranslate, forceTranslate: _forceTranslate,
previewMode, previewMode,
allowFilters, // allowFilters,
onMediaClick, onMediaClick,
quoted, quoted,
onStatusLinkClick = () => {}, onStatusLinkClick = () => {},
@ -166,9 +169,24 @@ function Status({
// Non-API props // Non-API props
_deleted, _deleted,
_pinned, _pinned,
_filtered, // _filtered,
} = status; } = status;
const currentAccount = useMemo(() => {
return store.session.get('currentAccount');
}, []);
const isSelf = useMemo(() => {
return currentAccount && currentAccount === accountId;
}, [accountId, currentAccount]);
const filterContext = useContext(FilterContext);
const filterInfo =
!isSelf && !readOnly && !previewMode && isFiltered(filtered, filterContext);
if (filterInfo?.action === 'hide') {
return null;
}
console.debug('RENDER Status', id, status?.account.displayName, quoted); console.debug('RENDER Status', id, status?.account.displayName, quoted);
const debugHover = (e) => { const debugHover = (e) => {
@ -179,11 +197,11 @@ function Status({
} }
}; };
if (allowFilters && size !== 'l' && _filtered) { if (/*allowFilters && */ size !== 'l' && filterInfo) {
return ( return (
<FilteredStatus <FilteredStatus
status={status} status={status}
filterInfo={_filtered} filterInfo={filterInfo}
instance={instance} instance={instance}
containerProps={{ containerProps={{
onMouseEnter: debugHover, onMouseEnter: debugHover,
@ -195,13 +213,6 @@ function Status({
const createdAtDate = new Date(createdAt); const createdAtDate = new Date(createdAt);
const editedAtDate = new Date(editedAt); const editedAtDate = new Date(editedAt);
const currentAccount = useMemo(() => {
return store.session.get('currentAccount');
}, []);
const isSelf = useMemo(() => {
return currentAccount && currentAccount === accountId;
}, [accountId, currentAccount]);
let inReplyToAccountRef = mentions?.find( let inReplyToAccountRef = mentions?.find(
(mention) => mention.id === inReplyToAccountId, (mention) => mention.id === inReplyToAccountId,
); );

View file

@ -4,6 +4,8 @@ import { InView } from 'react-intersection-observer';
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
import { useSnapshot } from 'valtio'; import { useSnapshot } from 'valtio';
import FilterContext from '../utils/filter-context';
import { isFiltered } from '../utils/filters';
import states, { statusKey } from '../utils/states'; import states, { statusKey } from '../utils/states';
import statusPeek from '../utils/status-peek'; import statusPeek from '../utils/status-peek';
import { groupBoosts, groupContext } from '../utils/timeline-utils'; import { groupBoosts, groupContext } from '../utils/timeline-utils';
@ -13,7 +15,6 @@ import useScroll from '../utils/useScroll';
import Icon from './icon'; import Icon from './icon';
import Link from './link'; import Link from './link';
import Media from './media';
import MediaPost from './media-post'; import MediaPost from './media-post';
import NavMenu from './nav-menu'; import NavMenu from './nav-menu';
import Status from './status'; import Status from './status';
@ -39,9 +40,10 @@ function Timeline({
headerStart, headerStart,
headerEnd, headerEnd,
timelineStart, timelineStart,
allowFilters, // allowFilters,
refresh, refresh,
view, view,
filterContext,
}) { }) {
const snapStates = useSnapshot(states); const snapStates = useSnapshot(states);
const [items, setItems] = useState([]); const [items, setItems] = useState([]);
@ -285,172 +287,182 @@ function Timeline({
const hiddenUI = scrollDirection === 'end' && !nearReachStart; const hiddenUI = scrollDirection === 'end' && !nearReachStart;
return ( return (
<div <FilterContext.Provider value={filterContext}>
id={`${id}-page`} <div
class="deck-container" id={`${id}-page`}
ref={(node) => { class="deck-container"
scrollableRef.current = node; ref={(node) => {
jRef.current = node; scrollableRef.current = node;
kRef.current = node; jRef.current = node;
oRef.current = node; kRef.current = node;
}} oRef.current = node;
tabIndex="-1" }}
> tabIndex="-1"
<div class="timeline-deck deck"> >
<header <div class="timeline-deck deck">
hidden={hiddenUI} <header
onClick={(e) => { hidden={hiddenUI}
if (!e.target.closest('a, button')) { onClick={(e) => {
scrollableRef.current?.scrollTo({ if (!e.target.closest('a, button')) {
top: 0, scrollableRef.current?.scrollTo({
behavior: 'smooth', top: 0,
}); behavior: 'smooth',
} });
}} }
onDblClick={(e) => { }}
if (!e.target.closest('a, button')) { onDblClick={(e) => {
loadItems(true); if (!e.target.closest('a, button')) {
} loadItems(true);
}} }
class={uiState === 'loading' ? 'loading' : ''} }}
> class={uiState === 'loading' ? 'loading' : ''}
<div class="header-grid">
<div class="header-side">
<NavMenu />
{headerStart !== null && headerStart !== undefined ? (
headerStart
) : (
<Link to="/" class="button plain home-button">
<Icon icon="home" size="l" />
</Link>
)}
</div>
{title && (titleComponent ? titleComponent : <h1>{title}</h1>)}
<div class="header-side">
{/* <Loader hidden={uiState !== 'loading'} /> */}
{!!headerEnd && headerEnd}
</div>
</div>
{items.length > 0 &&
uiState !== 'loading' &&
!hiddenUI &&
showNew && (
<button
class="updates-button shiny-pill"
type="button"
onClick={() => {
loadItems(true);
scrollableRef.current?.scrollTo({
top: 0,
behavior: 'smooth',
});
}}
>
<Icon icon="arrow-up" /> New posts
</button>
)}
</header>
{!!timelineStart && (
<div
class={`timeline-start ${uiState === 'loading' ? 'loading' : ''}`}
> >
{timelineStart} <div class="header-grid">
</div> <div class="header-side">
)} <NavMenu />
{!!items.length ? ( {headerStart !== null && headerStart !== undefined ? (
<> headerStart
<ul class={`timeline ${view ? `timeline-${view}` : ''}`}> ) : (
{items.map((status) => ( <Link to="/" class="button plain home-button">
<TimelineItem <Icon icon="home" size="l" />
status={status} </Link>
instance={instance} )}
useItemID={useItemID} </div>
allowFilters={allowFilters} {title && (titleComponent ? titleComponent : <h1>{title}</h1>)}
key={status.id + status?._pinned} <div class="header-side">
view={view} {/* <Loader hidden={uiState !== 'loading'} /> */}
/> {!!headerEnd && headerEnd}
))} </div>
{showMore && </div>
uiState === 'loading' && {items.length > 0 &&
(view === 'media' ? null : ( uiState !== 'loading' &&
<> !hiddenUI &&
<li showNew && (
style={{ <button
height: '20vh', class="updates-button shiny-pill"
}} type="button"
> onClick={() => {
<Status skeleton /> loadItems(true);
</li> scrollableRef.current?.scrollTo({
<li top: 0,
style={{ behavior: 'smooth',
height: '25vh', });
}}
>
<Status skeleton />
</li>
</>
))}
</ul>
{uiState === 'default' &&
(showMore ? (
<InView
onChange={(inView) => {
if (inView) {
loadItems();
}
}} }}
> >
<button <Icon icon="arrow-up" /> New posts
type="button" </button>
class="plain block" )}
onClick={() => loadItems()} </header>
style={{ marginBlockEnd: '6em' }} {!!timelineStart && (
> <div
Show more&hellip; class={`timeline-start ${uiState === 'loading' ? 'loading' : ''}`}
</button>
</InView>
) : (
<p class="ui-state insignificant">The end.</p>
))}
</>
) : uiState === 'loading' ? (
<ul class="timeline">
{Array.from({ length: 5 }).map((_, i) =>
view === 'media' ? (
<div
style={{
height: '50vh',
}}
/>
) : (
<li key={i}>
<Status skeleton />
</li>
),
)}
</ul>
) : (
uiState !== 'error' && <p class="ui-state">{emptyText}</p>
)}
{uiState === 'error' && (
<p class="ui-state">
{errorText}
<br />
<br />
<button
class="button plain"
onClick={() => loadItems(!items.length)}
> >
Try again {timelineStart}
</button> </div>
</p> )}
)} {!!items.length ? (
<>
<ul class={`timeline ${view ? `timeline-${view}` : ''}`}>
{items.map((status) => (
<TimelineItem
status={status}
instance={instance}
useItemID={useItemID}
// allowFilters={allowFilters}
filterContext={filterContext}
key={status.id + status?._pinned}
view={view}
/>
))}
{showMore &&
uiState === 'loading' &&
(view === 'media' ? null : (
<>
<li
style={{
height: '20vh',
}}
>
<Status skeleton />
</li>
<li
style={{
height: '25vh',
}}
>
<Status skeleton />
</li>
</>
))}
</ul>
{uiState === 'default' &&
(showMore ? (
<InView
onChange={(inView) => {
if (inView) {
loadItems();
}
}}
>
<button
type="button"
class="plain block"
onClick={() => loadItems()}
style={{ marginBlockEnd: '6em' }}
>
Show more&hellip;
</button>
</InView>
) : (
<p class="ui-state insignificant">The end.</p>
))}
</>
) : uiState === 'loading' ? (
<ul class="timeline">
{Array.from({ length: 5 }).map((_, i) =>
view === 'media' ? (
<div
style={{
height: '50vh',
}}
/>
) : (
<li key={i}>
<Status skeleton />
</li>
),
)}
</ul>
) : (
uiState !== 'error' && <p class="ui-state">{emptyText}</p>
)}
{uiState === 'error' && (
<p class="ui-state">
{errorText}
<br />
<br />
<button
class="button plain"
onClick={() => loadItems(!items.length)}
>
Try again
</button>
</p>
)}
</div>
</div> </div>
</div> </FilterContext.Provider>
); );
} }
function TimelineItem({ status, instance, useItemID, allowFilters, view }) { function TimelineItem({
status,
instance,
useItemID,
// allowFilters,
filterContext,
view,
}) {
const { id: statusID, reblog, items, type, _pinned } = status; const { id: statusID, reblog, items, type, _pinned } = status;
const actualStatusID = reblog?.id || statusID; const actualStatusID = reblog?.id || statusID;
const url = instance const url = instance
@ -467,10 +479,18 @@ function TimelineItem({ status, instance, useItemID, allowFilters, view }) {
if (isCarousel) { if (isCarousel) {
// Here, we don't hide filtered posts, but we sort them last // Here, we don't hide filtered posts, but we sort them last
items.sort((a, b) => { items.sort((a, b) => {
if (a._filtered && !b._filtered) { // if (a._filtered && !b._filtered) {
// return 1;
// }
// if (!a._filtered && b._filtered) {
// return -1;
// }
const aFiltered = isFiltered(a.filtered, filterContext);
const bFiltered = isFiltered(b.filtered, filterContext);
if (aFiltered && !bFiltered) {
return 1; return 1;
} }
if (!a._filtered && b._filtered) { if (!aFiltered && bFiltered) {
return -1; return -1;
} }
return 0; return 0;
@ -493,7 +513,7 @@ function TimelineItem({ status, instance, useItemID, allowFilters, view }) {
instance={instance} instance={instance}
size="s" size="s"
contentTextWeight contentTextWeight
allowFilters={allowFilters} // allowFilters={allowFilters}
/> />
) : ( ) : (
<Status <Status
@ -501,7 +521,7 @@ function TimelineItem({ status, instance, useItemID, allowFilters, view }) {
instance={instance} instance={instance}
size="s" size="s"
contentTextWeight contentTextWeight
allowFilters={allowFilters} // allowFilters={allowFilters}
/> />
)} )}
</Link> </Link>
@ -541,13 +561,13 @@ function TimelineItem({ status, instance, useItemID, allowFilters, view }) {
<Status <Status
statusID={statusID} statusID={statusID}
instance={instance} instance={instance}
allowFilters={allowFilters} // allowFilters={allowFilters}
/> />
) : ( ) : (
<Status <Status
status={item} status={item}
instance={instance} instance={instance}
allowFilters={allowFilters} // allowFilters={allowFilters}
/> />
)} )}
</Link> </Link>
@ -566,7 +586,7 @@ function TimelineItem({ status, instance, useItemID, allowFilters, view }) {
key={itemKey} key={itemKey}
statusID={statusID} statusID={statusID}
instance={instance} instance={instance}
allowFilters={allowFilters} // allowFilters={allowFilters}
/> />
) : ( ) : (
<MediaPost <MediaPost
@ -575,7 +595,7 @@ function TimelineItem({ status, instance, useItemID, allowFilters, view }) {
key={itemKey} key={itemKey}
status={status} status={status}
instance={instance} instance={instance}
allowFilters={allowFilters} // allowFilters={allowFilters}
/> />
); );
} }
@ -587,13 +607,13 @@ function TimelineItem({ status, instance, useItemID, allowFilters, view }) {
<Status <Status
statusID={statusID} statusID={statusID}
instance={instance} instance={instance}
allowFilters={allowFilters} // allowFilters={allowFilters}
/> />
) : ( ) : (
<Status <Status
status={status} status={status}
instance={instance} instance={instance}
allowFilters={allowFilters} // allowFilters={allowFilters}
/> />
)} )}
</Link> </Link>

View file

@ -32,7 +32,7 @@ function Following({ title, path, id, ...props }) {
console.log('First load', latestItem.current); console.log('First load', latestItem.current);
} }
value = filteredItems(value, 'home'); // value = filteredItems(value, 'home');
value.forEach((item) => { value.forEach((item) => {
saveStatus(item, instance); saveStatus(item, instance);
}); });
@ -115,7 +115,8 @@ function Following({ title, path, id, ...props }) {
useItemID useItemID
boostsCarousel={snapStates.settings.boostsCarousel} boostsCarousel={snapStates.settings.boostsCarousel}
{...props} {...props}
allowFilters // allowFilters
filterContext="home"
/> />
); );
} }

View file

@ -78,7 +78,7 @@ function Hashtags({ media: mediaView, columnMode, ...props }) {
latestItem.current = value[0].id; latestItem.current = value[0].id;
} }
value = filteredItems(value, 'public'); // value = filteredItems(value, 'public');
value.forEach((item) => { value.forEach((item) => {
saveStatus(item, instance, { saveStatus(item, instance, {
skipThreading: media, // If media view, no need to form threads skipThreading: media, // If media view, no need to form threads
@ -153,7 +153,8 @@ function Hashtags({ media: mediaView, columnMode, ...props }) {
useItemID useItemID
view={media ? 'media' : undefined} view={media ? 'media' : undefined}
refresh={media} refresh={media}
allowFilters // allowFilters
filterContext="public"
headerEnd={ headerEnd={
<Menu2 <Menu2
portal portal

View file

@ -43,7 +43,7 @@ function List(props) {
latestItem.current = value[0].id; latestItem.current = value[0].id;
} }
value = filteredItems(value, 'home'); // value = filteredItems(value, 'home');
value.forEach((item) => { value.forEach((item) => {
saveStatus(item, instance); saveStatus(item, instance);
}); });
@ -102,7 +102,8 @@ function List(props) {
checkForUpdates={checkForUpdates} checkForUpdates={checkForUpdates}
useItemID useItemID
boostsCarousel={snapStates.settings.boostsCarousel} boostsCarousel={snapStates.settings.boostsCarousel}
allowFilters // allowFilters
filterContext="home"
// refresh={reloadCount} // refresh={reloadCount}
headerStart={ headerStart={
<Link to="/l" class="button plain"> <Link to="/l" class="button plain">

View file

@ -195,6 +195,7 @@ function Notifications({ columnMode }) {
snapStates.notificationsShowNew && snapStates.notificationsShowNew &&
uiState !== 'loading' uiState !== 'loading'
) { ) {
setShowNew(false);
loadNotifications(true); loadNotifications(true);
} else { } else {
setShowNew(snapStates.notificationsShowNew); setShowNew(snapStates.notificationsShowNew);

View file

@ -41,7 +41,7 @@ function Public({ local, columnMode, ...props }) {
latestItem.current = value[0].id; latestItem.current = value[0].id;
} }
value = filteredItems(value, 'public'); // value = filteredItems(value, 'public');
value.forEach((item) => { value.forEach((item) => {
saveStatus(item, instance); saveStatus(item, instance);
}); });
@ -91,7 +91,8 @@ function Public({ local, columnMode, ...props }) {
useItemID useItemID
headerStart={<></>} headerStart={<></>}
boostsCarousel={snapStates.settings.boostsCarousel} boostsCarousel={snapStates.settings.boostsCarousel}
allowFilters // allowFilters
filterContext="public"
headerEnd={ headerEnd={
<Menu2 <Menu2
portal portal

View file

@ -85,7 +85,7 @@ function Trending({ columnMode, ...props }) {
latestItem.current = value[0].id; latestItem.current = value[0].id;
} }
value = filteredItems(value, 'public'); // Might not work here // value = filteredItems(value, 'public'); // Might not work here
value.forEach((item) => { value.forEach((item) => {
saveStatus(item, instance); saveStatus(item, instance);
}); });
@ -257,7 +257,8 @@ function Trending({ columnMode, ...props }) {
useItemID useItemID
headerStart={<></>} headerStart={<></>}
boostsCarousel={snapStates.settings.boostsCarousel} boostsCarousel={snapStates.settings.boostsCarousel}
allowFilters // allowFilters
filterContext="public"
timelineStart={TimelineStart} timelineStart={TimelineStart}
headerEnd={ headerEnd={
<Menu2 <Menu2

View file

@ -0,0 +1,4 @@
import { createContext } from 'preact';
const FilterContext = createContext();
export default FilterContext;

View file

@ -1,10 +1,8 @@
import mem from './mem';
import store from './store'; import store from './store';
export function filteredItem(item, filterContext, currentAccountID) { function _isFiltered(filtered, filterContext) {
const { filtered } = item; if (!filtered?.length) return false;
if (!filtered?.length) return true;
const isSelf = currentAccountID && item.account?.id === currentAccountID;
if (isSelf) return true;
const appliedFilters = filtered.filter((f) => { const appliedFilters = filtered.filter((f) => {
const { filter } = f; const { filter } = f;
const hasContext = filter.context.includes(filterContext); const hasContext = filter.context.includes(filterContext);
@ -12,19 +10,35 @@ export function filteredItem(item, filterContext, currentAccountID) {
if (!filter.expiresAt) return hasContext; if (!filter.expiresAt) return hasContext;
return new Date(filter.expiresAt) > new Date(); return new Date(filter.expiresAt) > new Date();
}); });
if (!appliedFilters.length) return true; if (!appliedFilters.length) return false;
const isHidden = appliedFilters.some((f) => f.filter.filterAction === 'hide'); const isHidden = appliedFilters.some((f) => f.filter.filterAction === 'hide');
console.log({ isHidden, filtered, appliedFilters, item }); if (isHidden)
if (isHidden) return false; return {
action: 'hide',
};
const isWarn = appliedFilters.some((f) => f.filter.filterAction === 'warn'); const isWarn = appliedFilters.some((f) => f.filter.filterAction === 'warn');
if (isWarn) { if (isWarn) {
const filterTitles = appliedFilters.map((f) => f.filter.title); const filterTitles = appliedFilters.map((f) => f.filter.title);
item._filtered = { return {
action: 'warn',
titles: filterTitles, titles: filterTitles,
titlesStr: filterTitles.join(' • '), titlesStr: filterTitles.join(' • '),
}; };
} }
return isWarn; return false;
}
export const isFiltered = mem(_isFiltered);
export function filteredItem(item, filterContext, currentAccountID) {
const { filtered } = item;
if (!filtered?.length) return true;
const isSelf = currentAccountID && item.account?.id === currentAccountID;
if (isSelf) return true;
const filterState = isFiltered(filtered, filterContext);
if (!filterState) return true;
if (filterState.action === 'hide') return false;
// item._filtered = filterState;
return true;
} }
export function filteredItems(items, filterContext) { export function filteredItems(items, filterContext) {
if (!items?.length) return []; if (!items?.length) return [];

View file

@ -1,5 +1,7 @@
import moize from 'moize'; import moize from 'moize';
window._moize = moize;
export default function mem(fn, opts = {}) { export default function mem(fn, opts = {}) {
return moize(fn, { ...opts, maxSize: 100 }); return moize(fn, { ...opts, maxSize: 100, isDeepEqual: true });
} }

View file

@ -168,7 +168,7 @@ export function saveStatus(status, instance, opts) {
if (!override && oldStatus) return; if (!override && oldStatus) return;
const key = statusKey(status.id, instance); const key = statusKey(status.id, instance);
if (oldStatus?._pinned) status._pinned = oldStatus._pinned; if (oldStatus?._pinned) status._pinned = oldStatus._pinned;
if (oldStatus?._filtered) status._filtered = oldStatus._filtered; // if (oldStatus?._filtered) status._filtered = oldStatus._filtered;
states.statuses[key] = status; states.statuses[key] = status;
if (status.reblog) { if (status.reblog) {
const key = statusKey(status.reblog.id, instance); const key = statusKey(status.reblog.id, instance);