mirror of
https://github.com/cheeaun/phanpy.git
synced 2025-03-14 12:18:30 +03:00
Add all the relationships
This commit is contained in:
parent
c16532d4c2
commit
8ce720f305
14 changed files with 350 additions and 242 deletions
|
@ -1578,6 +1578,13 @@ body:has(.media-modal-container + .status-deck) .media-post-link {
|
|||
.tag.danger {
|
||||
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 */
|
||||
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
gap: 8px;
|
||||
color: var(--text-color);
|
||||
text-decoration: none;
|
||||
|
||||
.account-block-acct {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.account-block:hover b {
|
||||
text-decoration: underline;
|
||||
|
@ -13,44 +17,54 @@
|
|||
color: var(--bg-faded-color);
|
||||
}
|
||||
|
||||
.account-block .short-desc {
|
||||
max-height: 1.2em; /* just in case clamping ain't working */
|
||||
}
|
||||
.account-block .short-desc,
|
||||
.account-block .short-desc > * {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
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 {
|
||||
color: var(--green-color);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
align-items: baseline;
|
||||
gap: 2px;
|
||||
}
|
||||
.account-block .verified-field .icon {
|
||||
}
|
||||
.account-block .verified-field .invisible {
|
||||
display: none;
|
||||
|
||||
* {
|
||||
-webkit-box-orient: vertical;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
line-clamp: 1;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
a {
|
||||
pointer-events: none;
|
||||
color: color-mix(
|
||||
in lch,
|
||||
var(--green-color) 20%,
|
||||
var(--text-insignificant-color) 80%
|
||||
) !important;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: var(--green-color);
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.invisible {
|
||||
display: none;
|
||||
}
|
||||
.ellipsis:after {
|
||||
content: '…';
|
||||
}
|
||||
}
|
||||
|
||||
.account-block .account-block-stats {
|
||||
line-height: 1.25;
|
||||
margin-top: 2px;
|
||||
font-size: 0.9em;
|
||||
color: var(--text-insignificant-color);
|
||||
}
|
||||
.account-block .account-block-stats a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
column-gap: 4px;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import './account-block.css';
|
|||
// import { useNavigate } from 'react-router-dom';
|
||||
import enhanceContent from '../utils/enhance-content';
|
||||
import niceDateTime from '../utils/nice-date-time';
|
||||
import shortenNumber from '../utils/shorten-number';
|
||||
import states from '../utils/states';
|
||||
|
||||
import Avatar from './avatar';
|
||||
|
@ -22,6 +23,8 @@ function AccountBlock({
|
|||
showStats = false,
|
||||
accountInstance,
|
||||
hideDisplayName = false,
|
||||
relationship = {},
|
||||
excludeRelationshipAttrs = [],
|
||||
}) {
|
||||
if (skeleton) {
|
||||
return (
|
||||
|
@ -53,6 +56,7 @@ function AccountBlock({
|
|||
fields,
|
||||
note,
|
||||
group,
|
||||
followersCount,
|
||||
} = account;
|
||||
let [_, acct1, acct2] = acct.match(/([^@]+)(@.+)/i) || [, acct];
|
||||
if (accountInstance) {
|
||||
|
@ -61,6 +65,17 @@ function AccountBlock({
|
|||
|
||||
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 (
|
||||
<a
|
||||
class="account-block"
|
||||
|
@ -97,9 +112,8 @@ function AccountBlock({
|
|||
) : (
|
||||
<b>{username}</b>
|
||||
)}
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
)}{' '}
|
||||
<span class="account-block-acct">
|
||||
@{acct1}
|
||||
<wbr />
|
||||
|
@ -124,28 +138,44 @@ function AccountBlock({
|
|||
)}
|
||||
{showStats && (
|
||||
<div class="account-block-stats">
|
||||
<div
|
||||
class="short-desc"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: enhanceContent(note, { emojis }),
|
||||
}}
|
||||
/>
|
||||
{bot && (
|
||||
<>
|
||||
<span class="tag">
|
||||
<span class="tag collapsed">
|
||||
<Icon icon="bot" /> Automated
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{!!group && (
|
||||
<>
|
||||
<span class="tag">
|
||||
<span class="tag collapsed">
|
||||
<Icon icon="group" /> Group
|
||||
</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 && (
|
||||
<span class="verified-field ib">
|
||||
<span class="verified-field">
|
||||
<Icon icon="check-circle" size="s" />{' '}
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
|
|
|
@ -177,6 +177,7 @@
|
|||
}
|
||||
|
||||
.account-container .account-block .account-block-acct {
|
||||
display: block;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
|
|
|
@ -604,6 +604,8 @@ function AccountInfo({
|
|||
states.showGenericAccounts = {
|
||||
heading: 'Followers',
|
||||
fetchAccounts: fetchFollowers,
|
||||
instance,
|
||||
excludeRelationshipAttrs: ['followedBy'],
|
||||
};
|
||||
}, 0);
|
||||
}}
|
||||
|
@ -637,6 +639,8 @@ function AccountInfo({
|
|||
states.showGenericAccounts = {
|
||||
heading: 'Following',
|
||||
fetchAccounts: fetchFollowing,
|
||||
instance,
|
||||
excludeRelationshipAttrs: ['following'],
|
||||
};
|
||||
}, 0);
|
||||
}}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#generic-accounts-container {
|
||||
.accounts-list {
|
||||
--list-gap: 16px;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 8px 0;
|
||||
|
@ -7,29 +8,46 @@
|
|||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
column-gap: 1.5em;
|
||||
row-gap: 16px;
|
||||
row-gap: var(--list-gap);
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-basis: 16em;
|
||||
align-items: center;
|
||||
/* align-items: center; */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
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 {
|
||||
font-size: 80%;
|
||||
font-size: 0.9em;
|
||||
color: var(--text-insignificant-color);
|
||||
display: block;
|
||||
/* display: block; */
|
||||
}
|
||||
}
|
||||
|
||||
.reactions-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-self: center;
|
||||
/* align-self: center; */
|
||||
|
||||
.favourite-icon {
|
||||
color: var(--favourite-color);
|
||||
|
@ -38,5 +56,21 @@
|
|||
.reblog-icon {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import { useEffect, useRef, useState } from 'preact/hooks';
|
|||
import { InView } from 'react-intersection-observer';
|
||||
import { useSnapshot } from 'valtio';
|
||||
|
||||
import { api } from '../utils/api';
|
||||
import { fetchRelationships } from '../utils/relationships';
|
||||
import states from '../utils/states';
|
||||
import useLocationChange from '../utils/useLocationChange';
|
||||
|
||||
|
@ -11,8 +13,15 @@ import AccountBlock from './account-block';
|
|||
import Icon from './icon';
|
||||
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 [uiState, setUIState] = useState('default');
|
||||
const [accounts, setAccounts] = useState([]);
|
||||
const [showMore, setShowMore] = useState(false);
|
||||
|
@ -31,6 +40,20 @@ export default function GenericAccounts({ onClose = () => {} }) {
|
|||
showReactions,
|
||||
} = 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) => {
|
||||
if (!fetchAccounts) return;
|
||||
if (firstLoad) setAccounts([]);
|
||||
|
@ -40,11 +63,41 @@ export default function GenericAccounts({ onClose = () => {} }) {
|
|||
const { done, value } = await fetchAccounts(firstLoad);
|
||||
if (Array.isArray(value)) {
|
||||
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 {
|
||||
theAccount._types.push(...account._types);
|
||||
}
|
||||
}
|
||||
setAccounts(accounts);
|
||||
} else {
|
||||
setAccounts((prev) => [...prev, ...value]);
|
||||
// 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);
|
||||
|
||||
loadRelationships(value);
|
||||
} else {
|
||||
setShowMore(false);
|
||||
}
|
||||
|
@ -60,6 +113,7 @@ export default function GenericAccounts({ onClose = () => {} }) {
|
|||
useEffect(() => {
|
||||
if (staticAccounts?.length > 0) {
|
||||
setAccounts(staticAccounts);
|
||||
loadRelationships(staticAccounts);
|
||||
} else {
|
||||
loadAccounts(true);
|
||||
firstLoad.current = false;
|
||||
|
@ -87,26 +141,37 @@ export default function GenericAccounts({ onClose = () => {} }) {
|
|||
{accounts.length > 0 ? (
|
||||
<>
|
||||
<ul class="accounts-list">
|
||||
{accounts.map((account) => (
|
||||
<li key={account.id + (account._types || '')}>
|
||||
{showReactions && account._types?.length > 0 && (
|
||||
<div class="reactions-block">
|
||||
{account._types.map((type) => (
|
||||
<Icon
|
||||
icon={
|
||||
{
|
||||
reblog: 'rocket',
|
||||
favourite: 'heart',
|
||||
}[type]
|
||||
}
|
||||
class={`${type}-icon`}
|
||||
/>
|
||||
))}
|
||||
{accounts.map((account) => {
|
||||
const relationship = relationshipsMap[account.id];
|
||||
const key = `${account.id}-${account._types?.length || ''}`;
|
||||
return (
|
||||
<li key={key}>
|
||||
{showReactions && account._types?.length > 0 && (
|
||||
<div class="reactions-block">
|
||||
{account._types.map((type) => (
|
||||
<Icon
|
||||
icon={
|
||||
{
|
||||
reblog: 'rocket',
|
||||
favourite: 'heart',
|
||||
}[type]
|
||||
}
|
||||
class={`${type}-icon`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div class="account-relationships">
|
||||
<AccountBlock
|
||||
account={account}
|
||||
showStats
|
||||
relationship={relationship}
|
||||
excludeRelationshipAttrs={excludeRelationshipAttrs}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<AccountBlock account={account} />
|
||||
</li>
|
||||
))}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
{uiState === 'default' ? (
|
||||
showMore ? (
|
||||
|
|
|
@ -176,6 +176,10 @@ export default function Modals() {
|
|||
}}
|
||||
>
|
||||
<GenericAccounts
|
||||
instance={snapStates.showGenericAccounts.instance}
|
||||
excludeRelationshipAttrs={
|
||||
snapStates.showGenericAccounts.excludeRelationshipAttrs
|
||||
}
|
||||
onClose={() => (states.showGenericAccounts = false)}
|
||||
/>
|
||||
</Modal>
|
||||
|
|
|
@ -233,6 +233,7 @@ function NavMenu(props) {
|
|||
id: 'mute',
|
||||
heading: 'Muted users',
|
||||
fetchAccounts: fetchMutes,
|
||||
excludeRelationshipAttrs: ['muting'],
|
||||
};
|
||||
}}
|
||||
>
|
||||
|
@ -244,6 +245,7 @@ function NavMenu(props) {
|
|||
id: 'block',
|
||||
heading: 'Blocked users',
|
||||
fetchAccounts: fetchBlocks,
|
||||
excludeRelationshipAttrs: ['blocking'],
|
||||
};
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -158,6 +158,7 @@ function Notification({
|
|||
heading: genericAccountsHeading,
|
||||
accounts: _accounts,
|
||||
showReactions: type === 'favourite+reblog',
|
||||
excludeRelationshipAttrs: type === 'follow' ? ['followedBy'] : [],
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -88,6 +88,8 @@ const isIOS =
|
|||
window.ontouchstart !== undefined &&
|
||||
/iPad|iPhone|iPod/.test(navigator.userAgent);
|
||||
|
||||
const REACTIONS_LIMIT = 80;
|
||||
|
||||
function Status({
|
||||
statusID,
|
||||
status,
|
||||
|
@ -380,7 +382,6 @@ function Status({
|
|||
]);
|
||||
|
||||
const [showEdited, setShowEdited] = useState(false);
|
||||
const [showReactions, setShowReactions] = useState(false);
|
||||
|
||||
const spoilerContentRef = useTruncated();
|
||||
const contentRef = useTruncated();
|
||||
|
@ -560,6 +561,55 @@ function Status({
|
|||
(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 StatusMenuItems = (
|
||||
<>
|
||||
|
@ -620,7 +670,16 @@ function Status({
|
|||
)}
|
||||
{(!isSizeLarge || !!editedAt) && <MenuDivider />}
|
||||
{isSizeLarge && (
|
||||
<MenuItem onClick={() => setShowReactions(true)}>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
states.showGenericAccounts = {
|
||||
heading: 'Boosted/Liked by…',
|
||||
fetchAccounts: fetchBoostedLikedByAccounts,
|
||||
instance,
|
||||
showReactions: true,
|
||||
};
|
||||
}}
|
||||
>
|
||||
<Icon icon="react" />
|
||||
<span>
|
||||
Boosted/Liked by<span class="more-insignificant">…</span>
|
||||
|
@ -1759,22 +1818,6 @@ function Status({
|
|||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{showReactions && (
|
||||
<Modal
|
||||
class="light"
|
||||
onClick={(e) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
setShowReactions(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ReactionsModal
|
||||
statusID={id}
|
||||
instance={instance}
|
||||
onClose={() => setShowReactions(false)}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
</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…
|
||||
</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({
|
||||
checked,
|
||||
count,
|
||||
|
|
|
@ -24,8 +24,12 @@
|
|||
display: flex;
|
||||
padding: 8px 16px;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
/* align-items: center; */
|
||||
flex-grow: 1;
|
||||
|
||||
.account-block {
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
ul.link-list.hashtag-list {
|
||||
|
|
|
@ -14,6 +14,7 @@ import NavMenu from '../components/nav-menu';
|
|||
import SearchForm from '../components/search-form';
|
||||
import Status from '../components/status';
|
||||
import { api } from '../utils/api';
|
||||
import { fetchRelationships } from '../utils/relationships';
|
||||
import shortenNumber from '../utils/shorten-number';
|
||||
import useTitle from '../utils/useTitle';
|
||||
|
||||
|
@ -72,6 +73,18 @@ function Search(props) {
|
|||
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) {
|
||||
if (!firstLoad && !authenticated) {
|
||||
// Search results pagination is only available to authenticated users
|
||||
|
@ -119,6 +132,8 @@ function Search(props) {
|
|||
offsetRef.current = 0;
|
||||
setShowMore(false);
|
||||
}
|
||||
loadRelationships(results.accounts);
|
||||
|
||||
setUIState('default');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
@ -216,6 +231,7 @@ function Search(props) {
|
|||
account={account}
|
||||
instance={instance}
|
||||
showStats
|
||||
relationship={relationshipsMap[account.id]}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
|
|
37
src/utils/relationships.js
Normal file
37
src/utils/relationships.js
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue