Add all the relationships

This commit is contained in:
Lim Chee Aun 2023-12-20 13:55:56 +08:00
parent c16532d4c2
commit 8ce720f305
14 changed files with 350 additions and 242 deletions

View file

@ -1578,6 +1578,13 @@ body:has(.media-modal-container + .status-deck) .media-post-link {
.tag.danger { .tag.danger {
background-color: var(--red-color); background-color: var(--red-color);
} }
.tag.minimal {
margin: 0;
color: var(--text-insignificant-color);
background-color: var(--bg-faded-color);
text-shadow: 0 1px var(--bg-color);
line-height: 1;
}
/* MENU POPUP */ /* MENU POPUP */

View file

@ -4,6 +4,10 @@
gap: 8px; gap: 8px;
color: var(--text-color); color: var(--text-color);
text-decoration: none; text-decoration: none;
.account-block-acct {
display: inline-block;
}
} }
.account-block:hover b { .account-block:hover b {
text-decoration: underline; text-decoration: underline;
@ -13,44 +17,54 @@
color: var(--bg-faded-color); color: var(--bg-faded-color);
} }
.account-block .short-desc { .account-block .verified-field {
max-height: 1.2em; /* just in case clamping ain't working */ display: inline-flex;
} align-items: baseline;
.account-block .short-desc, gap: 2px;
.account-block .short-desc > * {
* {
-webkit-box-orient: vertical;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 1; -webkit-line-clamp: 1;
-webkit-box-orient: vertical; line-clamp: 1;
text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
} }
.account-block .short-desc > * + * {
display: none;
}
.account-block .short-desc * {
margin: 0;
padding: 0;
color: inherit;
pointer-events: none;
}
.account-block .verified-field { a {
pointer-events: none;
color: color-mix(
in lch,
var(--green-color) 20%,
var(--text-insignificant-color) 80%
) !important;
}
.icon {
color: var(--green-color); color: var(--green-color);
display: inline-flex; transform: translateY(1px);
align-items: center; }
gap: 2px;
} .invisible {
.account-block .verified-field .icon {
}
.account-block .verified-field .invisible {
display: none; display: none;
}
.ellipsis:after {
content: '…';
}
} }
.account-block .account-block-stats { .account-block .account-block-stats {
line-height: 1.25;
margin-top: 2px; margin-top: 2px;
font-size: 0.9em; font-size: 0.9em;
color: var(--text-insignificant-color); color: var(--text-insignificant-color);
} display: flex;
.account-block .account-block-stats a { flex-wrap: wrap;
align-items: center;
column-gap: 4px;
a {
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
}
} }

View file

@ -3,6 +3,7 @@ import './account-block.css';
// import { useNavigate } from 'react-router-dom'; // import { useNavigate } from 'react-router-dom';
import enhanceContent from '../utils/enhance-content'; import enhanceContent from '../utils/enhance-content';
import niceDateTime from '../utils/nice-date-time'; import niceDateTime from '../utils/nice-date-time';
import shortenNumber from '../utils/shorten-number';
import states from '../utils/states'; import states from '../utils/states';
import Avatar from './avatar'; import Avatar from './avatar';
@ -22,6 +23,8 @@ function AccountBlock({
showStats = false, showStats = false,
accountInstance, accountInstance,
hideDisplayName = false, hideDisplayName = false,
relationship = {},
excludeRelationshipAttrs = [],
}) { }) {
if (skeleton) { if (skeleton) {
return ( return (
@ -53,6 +56,7 @@ function AccountBlock({
fields, fields,
note, note,
group, group,
followersCount,
} = account; } = account;
let [_, acct1, acct2] = acct.match(/([^@]+)(@.+)/i) || [, acct]; let [_, acct1, acct2] = acct.match(/([^@]+)(@.+)/i) || [, acct];
if (accountInstance) { if (accountInstance) {
@ -61,6 +65,17 @@ function AccountBlock({
const verifiedField = fields?.find((f) => !!f.verifiedAt && !!f.value); const verifiedField = fields?.find((f) => !!f.verifiedAt && !!f.value);
const excludedRelationship = {};
for (const r in relationship) {
if (!excludeRelationshipAttrs.includes(r)) {
excludedRelationship[r] = relationship[r];
}
}
const hasRelationship =
excludedRelationship.following ||
excludedRelationship.followedBy ||
excludedRelationship.requested;
return ( return (
<a <a
class="account-block" class="account-block"
@ -97,9 +112,8 @@ function AccountBlock({
) : ( ) : (
<b>{username}</b> <b>{username}</b>
)} )}
<br />
</> </>
)} )}{' '}
<span class="account-block-acct"> <span class="account-block-acct">
@{acct1} @{acct1}
<wbr /> <wbr />
@ -124,28 +138,44 @@ function AccountBlock({
)} )}
{showStats && ( {showStats && (
<div class="account-block-stats"> <div class="account-block-stats">
<div
class="short-desc"
dangerouslySetInnerHTML={{
__html: enhanceContent(note, { emojis }),
}}
/>
{bot && ( {bot && (
<> <>
<span class="tag"> <span class="tag collapsed">
<Icon icon="bot" /> Automated <Icon icon="bot" /> Automated
</span> </span>
</> </>
)} )}
{!!group && ( {!!group && (
<> <>
<span class="tag"> <span class="tag collapsed">
<Icon icon="group" /> Group <Icon icon="group" /> Group
</span> </span>
</> </>
)} )}
{hasRelationship && (
<div key={relationship.id} class="shazam-container-horizontal">
<div class="shazam-container-inner">
{excludedRelationship.following &&
excludedRelationship.followedBy ? (
<span class="tag minimal">Mutual</span>
) : excludedRelationship.requested ? (
<span class="tag minimal">Requested</span>
) : excludedRelationship.following ? (
<span class="tag minimal">Following</span>
) : excludedRelationship.followedBy ? (
<span class="tag minimal">Follows you</span>
) : null}
</div>
</div>
)}
{!!followersCount && (
<span class="ib">
{shortenNumber(followersCount)}{' '}
{followersCount === 1 ? 'follower' : 'followers'}
</span>
)}
{!!verifiedField && ( {!!verifiedField && (
<span class="verified-field ib"> <span class="verified-field">
<Icon icon="check-circle" size="s" />{' '} <Icon icon="check-circle" size="s" />{' '}
<span <span
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{

View file

@ -177,6 +177,7 @@
} }
.account-container .account-block .account-block-acct { .account-container .account-block .account-block-acct {
display: block;
opacity: 0.7; opacity: 0.7;
} }

View file

@ -604,6 +604,8 @@ function AccountInfo({
states.showGenericAccounts = { states.showGenericAccounts = {
heading: 'Followers', heading: 'Followers',
fetchAccounts: fetchFollowers, fetchAccounts: fetchFollowers,
instance,
excludeRelationshipAttrs: ['followedBy'],
}; };
}, 0); }, 0);
}} }}
@ -637,6 +639,8 @@ function AccountInfo({
states.showGenericAccounts = { states.showGenericAccounts = {
heading: 'Following', heading: 'Following',
fetchAccounts: fetchFollowing, fetchAccounts: fetchFollowing,
instance,
excludeRelationshipAttrs: ['following'],
}; };
}, 0); }, 0);
}} }}

View file

@ -1,5 +1,6 @@
#generic-accounts-container { #generic-accounts-container {
.accounts-list { .accounts-list {
--list-gap: 16px;
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 8px 0; padding: 8px 0;
@ -7,29 +8,46 @@
flex-wrap: wrap; flex-wrap: wrap;
flex-direction: row; flex-direction: row;
column-gap: 1.5em; column-gap: 1.5em;
row-gap: 16px; row-gap: var(--list-gap);
li { li {
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;
flex-basis: 16em; flex-basis: 16em;
align-items: center; /* align-items: center; */
margin: 0; margin: 0;
padding: 0; padding: 0;
gap: 8px; gap: 8px;
position: relative;
&:before {
content: '';
display: block;
border-top: var(--hairline-width) solid var(--divider-color);
position: absolute;
bottom: calc(-1 * var(--list-gap) / 2);
left: 40px;
right: 0;
}
&:has(.reactions-block):before {
/* avatar + reactions + gap */
left: calc(40px + 16px + 8px);
}
} }
.account-block-acct { .account-block-acct {
font-size: 80%; font-size: 0.9em;
color: var(--text-insignificant-color); color: var(--text-insignificant-color);
display: block; /* display: block; */
} }
} }
.reactions-block { .reactions-block {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-self: center; /* align-self: center; */
.favourite-icon { .favourite-icon {
color: var(--favourite-color); color: var(--favourite-color);
@ -38,5 +56,21 @@
.reblog-icon { .reblog-icon {
color: var(--reblog-color); color: var(--reblog-color);
} }
> .icon:only-child {
margin-top: 8px; /* half of icon dimension */
}
}
.account-relationships {
flex-grow: 1;
.tag {
animation: appear 0.3s ease-out;
}
}
.account-block {
align-items: flex-start;
} }
} }

View file

@ -4,6 +4,8 @@ import { useEffect, useRef, useState } from 'preact/hooks';
import { InView } from 'react-intersection-observer'; import { InView } from 'react-intersection-observer';
import { useSnapshot } from 'valtio'; import { useSnapshot } from 'valtio';
import { api } from '../utils/api';
import { fetchRelationships } from '../utils/relationships';
import states from '../utils/states'; import states from '../utils/states';
import useLocationChange from '../utils/useLocationChange'; import useLocationChange from '../utils/useLocationChange';
@ -11,8 +13,15 @@ import AccountBlock from './account-block';
import Icon from './icon'; import Icon from './icon';
import Loader from './loader'; import Loader from './loader';
export default function GenericAccounts({ onClose = () => {} }) { export default function GenericAccounts({
instance,
excludeRelationshipAttrs = [],
onClose = () => {},
}) {
const { masto, instance: currentInstance } = api();
const isCurrentInstance = instance ? instance === currentInstance : true;
const snapStates = useSnapshot(states); const snapStates = useSnapshot(states);
``;
const [uiState, setUIState] = useState('default'); const [uiState, setUIState] = useState('default');
const [accounts, setAccounts] = useState([]); const [accounts, setAccounts] = useState([]);
const [showMore, setShowMore] = useState(false); const [showMore, setShowMore] = useState(false);
@ -31,6 +40,20 @@ export default function GenericAccounts({ onClose = () => {} }) {
showReactions, showReactions,
} = snapStates.showGenericAccounts; } = snapStates.showGenericAccounts;
const [relationshipsMap, setRelationshipsMap] = useState({});
const loadRelationships = async (accounts) => {
if (!accounts?.length) return;
if (!isCurrentInstance) return;
const relationships = await fetchRelationships(accounts, relationshipsMap);
if (relationships) {
setRelationshipsMap({
...relationshipsMap,
...relationships,
});
}
};
const loadAccounts = (firstLoad) => { const loadAccounts = (firstLoad) => {
if (!fetchAccounts) return; if (!fetchAccounts) return;
if (firstLoad) setAccounts([]); if (firstLoad) setAccounts([]);
@ -40,11 +63,41 @@ export default function GenericAccounts({ onClose = () => {} }) {
const { done, value } = await fetchAccounts(firstLoad); const { done, value } = await fetchAccounts(firstLoad);
if (Array.isArray(value)) { if (Array.isArray(value)) {
if (firstLoad) { if (firstLoad) {
setAccounts(value); const accounts = [];
for (let i = 0; i < value.length; i++) {
const account = value[i];
const theAccount = accounts.find(
(a, j) => a.id === account.id && i !== j,
);
if (!theAccount) {
accounts.push({
_types: [],
...account,
});
} else { } else {
setAccounts((prev) => [...prev, ...value]); theAccount._types.push(...account._types);
}
}
setAccounts(accounts);
} else {
// setAccounts((prev) => [...prev, ...value]);
// Merge accounts by id and _types
setAccounts((prev) => {
const newAccounts = prev;
for (const account of value) {
const theAccount = newAccounts.find((a) => a.id === account.id);
if (!theAccount) {
newAccounts.push(account);
} else {
theAccount._types.push(...account._types);
}
}
return newAccounts;
});
} }
setShowMore(!done); setShowMore(!done);
loadRelationships(value);
} else { } else {
setShowMore(false); setShowMore(false);
} }
@ -60,6 +113,7 @@ export default function GenericAccounts({ onClose = () => {} }) {
useEffect(() => { useEffect(() => {
if (staticAccounts?.length > 0) { if (staticAccounts?.length > 0) {
setAccounts(staticAccounts); setAccounts(staticAccounts);
loadRelationships(staticAccounts);
} else { } else {
loadAccounts(true); loadAccounts(true);
firstLoad.current = false; firstLoad.current = false;
@ -87,8 +141,11 @@ export default function GenericAccounts({ onClose = () => {} }) {
{accounts.length > 0 ? ( {accounts.length > 0 ? (
<> <>
<ul class="accounts-list"> <ul class="accounts-list">
{accounts.map((account) => ( {accounts.map((account) => {
<li key={account.id + (account._types || '')}> const relationship = relationshipsMap[account.id];
const key = `${account.id}-${account._types?.length || ''}`;
return (
<li key={key}>
{showReactions && account._types?.length > 0 && ( {showReactions && account._types?.length > 0 && (
<div class="reactions-block"> <div class="reactions-block">
{account._types.map((type) => ( {account._types.map((type) => (
@ -104,9 +161,17 @@ export default function GenericAccounts({ onClose = () => {} }) {
))} ))}
</div> </div>
)} )}
<AccountBlock account={account} /> <div class="account-relationships">
<AccountBlock
account={account}
showStats
relationship={relationship}
excludeRelationshipAttrs={excludeRelationshipAttrs}
/>
</div>
</li> </li>
))} );
})}
</ul> </ul>
{uiState === 'default' ? ( {uiState === 'default' ? (
showMore ? ( showMore ? (

View file

@ -176,6 +176,10 @@ export default function Modals() {
}} }}
> >
<GenericAccounts <GenericAccounts
instance={snapStates.showGenericAccounts.instance}
excludeRelationshipAttrs={
snapStates.showGenericAccounts.excludeRelationshipAttrs
}
onClose={() => (states.showGenericAccounts = false)} onClose={() => (states.showGenericAccounts = false)}
/> />
</Modal> </Modal>

View file

@ -233,6 +233,7 @@ function NavMenu(props) {
id: 'mute', id: 'mute',
heading: 'Muted users', heading: 'Muted users',
fetchAccounts: fetchMutes, fetchAccounts: fetchMutes,
excludeRelationshipAttrs: ['muting'],
}; };
}} }}
> >
@ -244,6 +245,7 @@ function NavMenu(props) {
id: 'block', id: 'block',
heading: 'Blocked users', heading: 'Blocked users',
fetchAccounts: fetchBlocks, fetchAccounts: fetchBlocks,
excludeRelationshipAttrs: ['blocking'],
}; };
}} }}
> >

View file

@ -158,6 +158,7 @@ function Notification({
heading: genericAccountsHeading, heading: genericAccountsHeading,
accounts: _accounts, accounts: _accounts,
showReactions: type === 'favourite+reblog', showReactions: type === 'favourite+reblog',
excludeRelationshipAttrs: type === 'follow' ? ['followedBy'] : [],
}; };
}; };

View file

@ -88,6 +88,8 @@ const isIOS =
window.ontouchstart !== undefined && window.ontouchstart !== undefined &&
/iPad|iPhone|iPod/.test(navigator.userAgent); /iPad|iPhone|iPod/.test(navigator.userAgent);
const REACTIONS_LIMIT = 80;
function Status({ function Status({
statusID, statusID,
status, status,
@ -380,7 +382,6 @@ function Status({
]); ]);
const [showEdited, setShowEdited] = useState(false); const [showEdited, setShowEdited] = useState(false);
const [showReactions, setShowReactions] = useState(false);
const spoilerContentRef = useTruncated(); const spoilerContentRef = useTruncated();
const contentRef = useTruncated(); const contentRef = useTruncated();
@ -560,6 +561,55 @@ function Status({
(l) => language === l || localeMatch([language], [l]), (l) => language === l || localeMatch([language], [l]),
); );
const reblogIterator = useRef();
const favouriteIterator = useRef();
async function fetchBoostedLikedByAccounts(firstLoad) {
if (firstLoad) {
reblogIterator.current = masto.v1.statuses
.$select(statusID)
.rebloggedBy.list({
limit: REACTIONS_LIMIT,
});
favouriteIterator.current = masto.v1.statuses
.$select(statusID)
.favouritedBy.list({
limit: REACTIONS_LIMIT,
});
}
const [{ value: reblogResults }, { value: favouriteResults }] =
await Promise.allSettled([
reblogIterator.current.next(),
favouriteIterator.current.next(),
]);
if (reblogResults.value?.length || favouriteResults.value?.length) {
const accounts = [];
if (reblogResults.value?.length) {
accounts.push(
...reblogResults.value.map((a) => {
a._types = ['reblog'];
return a;
}),
);
}
if (favouriteResults.value?.length) {
accounts.push(
...favouriteResults.value.map((a) => {
a._types = ['favourite'];
return a;
}),
);
}
return {
value: accounts,
done: reblogResults.done && favouriteResults.done,
};
}
return {
value: [],
done: true,
};
}
const menuInstanceRef = useRef(); const menuInstanceRef = useRef();
const StatusMenuItems = ( const StatusMenuItems = (
<> <>
@ -620,7 +670,16 @@ function Status({
)} )}
{(!isSizeLarge || !!editedAt) && <MenuDivider />} {(!isSizeLarge || !!editedAt) && <MenuDivider />}
{isSizeLarge && ( {isSizeLarge && (
<MenuItem onClick={() => setShowReactions(true)}> <MenuItem
onClick={() => {
states.showGenericAccounts = {
heading: 'Boosted/Liked by…',
fetchAccounts: fetchBoostedLikedByAccounts,
instance,
showReactions: true,
};
}}
>
<Icon icon="react" /> <Icon icon="react" />
<span> <span>
Boosted/Liked by<span class="more-insignificant"></span> Boosted/Liked by<span class="more-insignificant"></span>
@ -1759,22 +1818,6 @@ function Status({
/> />
</Modal> </Modal>
)} )}
{showReactions && (
<Modal
class="light"
onClick={(e) => {
if (e.target === e.currentTarget) {
setShowReactions(false);
}
}}
>
<ReactionsModal
statusID={id}
instance={instance}
onClose={() => setShowReactions(false)}
/>
</Modal>
)}
</article> </article>
); );
} }
@ -2046,160 +2089,6 @@ function EditedAtModal({
); );
} }
const REACTIONS_LIMIT = 80;
function ReactionsModal({ statusID, instance, onClose }) {
const { masto } = api({ instance });
const [uiState, setUIState] = useState('default');
const [accounts, setAccounts] = useState([]);
const [showMore, setShowMore] = useState(false);
const reblogIterator = useRef();
const favouriteIterator = useRef();
async function fetchAccounts(firstLoad) {
setShowMore(false);
setUIState('loading');
(async () => {
try {
if (firstLoad) {
reblogIterator.current = masto.v1.statuses
.$select(statusID)
.rebloggedBy.list({
limit: REACTIONS_LIMIT,
});
favouriteIterator.current = masto.v1.statuses
.$select(statusID)
.favouritedBy.list({
limit: REACTIONS_LIMIT,
});
}
const [{ value: reblogResults }, { value: favouriteResults }] =
await Promise.allSettled([
reblogIterator.current.next(),
favouriteIterator.current.next(),
]);
if (reblogResults.value?.length || favouriteResults.value?.length) {
if (reblogResults.value?.length) {
for (const account of reblogResults.value) {
const theAccount = accounts.find((a) => a.id === account.id);
if (!theAccount) {
accounts.push({
...account,
_types: ['reblog'],
});
} else {
theAccount._types.push('reblog');
}
}
}
if (favouriteResults.value?.length) {
for (const account of favouriteResults.value) {
const theAccount = accounts.find((a) => a.id === account.id);
if (!theAccount) {
accounts.push({
...account,
_types: ['favourite'],
});
} else {
theAccount._types.push('favourite');
}
}
}
setAccounts(accounts);
setShowMore(!reblogResults.done || !favouriteResults.done);
} else {
setShowMore(false);
}
setUIState('default');
} catch (e) {
console.error(e);
setUIState('error');
}
})();
}
useEffect(() => {
fetchAccounts(true);
}, []);
return (
<div id="reactions-container" class="sheet">
{!!onClose && (
<button type="button" class="sheet-close" onClick={onClose}>
<Icon icon="x" />
</button>
)}
<header>
<h2>Boosted/Liked by</h2>
</header>
<main>
{accounts.length > 0 ? (
<>
<ul class="reactions-list">
{accounts.map((account) => {
const { _types } = account;
return (
<li key={account.id + _types}>
<div class="reactions-block">
{_types.map((type) => (
<Icon
icon={
{
reblog: 'rocket',
favourite: 'heart',
}[type]
}
class={`${type}-icon`}
/>
))}
</div>
<AccountBlock account={account} instance={instance} />
</li>
);
})}
</ul>
{uiState === 'default' ? (
showMore ? (
<InView
onChange={(inView) => {
if (inView) {
fetchAccounts();
}
}}
>
<button
type="button"
class="plain block"
onClick={() => fetchAccounts()}
>
Show more&hellip;
</button>
</InView>
) : (
<p class="ui-state insignificant">The end.</p>
)
) : (
uiState === 'loading' && (
<p class="ui-state">
<Loader abrupt />
</p>
)
)}
</>
) : uiState === 'loading' ? (
<p class="ui-state">
<Loader abrupt />
</p>
) : uiState === 'error' ? (
<p class="ui-state">Unable to load accounts</p>
) : (
<p class="ui-state insignificant">No one yet.</p>
)}
</main>
</div>
);
}
function StatusButton({ function StatusButton({
checked, checked,
count, count,

View file

@ -24,8 +24,12 @@
display: flex; display: flex;
padding: 8px 16px; padding: 8px 16px;
gap: 8px; gap: 8px;
align-items: center; /* align-items: center; */
flex-grow: 1; flex-grow: 1;
.account-block {
align-items: flex-start;
}
} }
ul.link-list.hashtag-list { ul.link-list.hashtag-list {

View file

@ -14,6 +14,7 @@ import NavMenu from '../components/nav-menu';
import SearchForm from '../components/search-form'; import SearchForm from '../components/search-form';
import Status from '../components/status'; import Status from '../components/status';
import { api } from '../utils/api'; import { api } from '../utils/api';
import { fetchRelationships } from '../utils/relationships';
import shortenNumber from '../utils/shorten-number'; import shortenNumber from '../utils/shorten-number';
import useTitle from '../utils/useTitle'; import useTitle from '../utils/useTitle';
@ -72,6 +73,18 @@ function Search(props) {
hashtags: setHashtagResults, hashtags: setHashtagResults,
}; };
const [relationshipsMap, setRelationshipsMap] = useState({});
const loadRelationships = async (accounts) => {
if (!accounts?.length) return;
const relationships = await fetchRelationships(accounts, relationshipsMap);
if (relationships) {
setRelationshipsMap({
...relationshipsMap,
...relationships,
});
}
};
function loadResults(firstLoad) { function loadResults(firstLoad) {
if (!firstLoad && !authenticated) { if (!firstLoad && !authenticated) {
// Search results pagination is only available to authenticated users // Search results pagination is only available to authenticated users
@ -119,6 +132,8 @@ function Search(props) {
offsetRef.current = 0; offsetRef.current = 0;
setShowMore(false); setShowMore(false);
} }
loadRelationships(results.accounts);
setUIState('default'); setUIState('default');
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@ -216,6 +231,7 @@ function Search(props) {
account={account} account={account}
instance={instance} instance={instance}
showStats showStats
relationship={relationshipsMap[account.id]}
/> />
</li> </li>
))} ))}

View file

@ -0,0 +1,37 @@
import { api } from './api';
import store from './store';
export async function fetchRelationships(accounts, relationshipsMap = {}) {
if (!accounts?.length) return;
const { masto } = api();
const currentAccount = store.session.get('currentAccount');
const uniqueAccountIds = accounts.reduce((acc, a) => {
// 1. Ignore duplicate accounts
// 2. Ignore accounts that are already inside relationshipsMap
// 3. Ignore currently logged in account
if (
!acc.includes(a.id) &&
!relationshipsMap[a.id] &&
a.id !== currentAccount
) {
acc.push(a.id);
}
return acc;
}, []);
try {
const relationships = await masto.v1.accounts.relationships.fetch({
id: uniqueAccountIds,
});
const newRelationshipsMap = relationships.reduce((acc, r) => {
acc[r.id] = r;
return acc;
}, {});
return newRelationshipsMap;
} catch (e) {
console.error(e);
// It's okay to fail
return null;
}
}