2022-12-10 12:14:48 +03:00
|
|
|
import './account.css';
|
|
|
|
|
2023-02-21 09:19:50 +03:00
|
|
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
2023-02-22 08:28:01 +03:00
|
|
|
import { useHotkeys } from 'react-hotkeys-hook';
|
2022-12-10 12:14:48 +03:00
|
|
|
|
2023-02-05 19:17:19 +03:00
|
|
|
import { api } from '../utils/api';
|
2023-01-25 11:25:23 +03:00
|
|
|
import emojifyText from '../utils/emojify-text';
|
2022-12-10 14:16:11 +03:00
|
|
|
import enhanceContent from '../utils/enhance-content';
|
2023-01-31 14:31:25 +03:00
|
|
|
import handleContentLinks from '../utils/handle-content-links';
|
2023-03-01 15:07:22 +03:00
|
|
|
import niceDateTime from '../utils/nice-date-time';
|
2022-12-10 12:14:48 +03:00
|
|
|
import shortenNumber from '../utils/shorten-number';
|
2023-02-02 05:30:16 +03:00
|
|
|
import states, { hideAllModals } from '../utils/states';
|
2022-12-10 14:15:30 +03:00
|
|
|
import store from '../utils/store';
|
2022-12-10 12:14:48 +03:00
|
|
|
|
2023-02-12 14:29:03 +03:00
|
|
|
import AccountBlock from './account-block';
|
2022-12-10 12:14:48 +03:00
|
|
|
import Avatar from './avatar';
|
2022-12-19 12:02:47 +03:00
|
|
|
import Icon from './icon';
|
2023-01-29 18:37:13 +03:00
|
|
|
import Link from './link';
|
2022-12-10 12:14:48 +03:00
|
|
|
|
2023-02-06 11:35:03 +03:00
|
|
|
function Account({ account, instance: propInstance, onClose }) {
|
|
|
|
const { masto, instance, authenticated } = api({ instance: propInstance });
|
2022-12-10 12:14:48 +03:00
|
|
|
const [uiState, setUIState] = useState('default');
|
|
|
|
const isString = typeof account === 'string';
|
|
|
|
const [info, setInfo] = useState(isString ? null : account);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (isString) {
|
|
|
|
setUIState('loading');
|
|
|
|
(async () => {
|
|
|
|
try {
|
2022-12-25 18:28:55 +03:00
|
|
|
const info = await masto.v1.accounts.lookup({
|
2022-12-10 12:14:48 +03:00
|
|
|
acct: account,
|
|
|
|
skip_webfinger: false,
|
|
|
|
});
|
|
|
|
setInfo(info);
|
|
|
|
setUIState('default');
|
|
|
|
} catch (e) {
|
2023-01-22 15:29:48 +03:00
|
|
|
try {
|
|
|
|
const result = await masto.v2.search({
|
|
|
|
q: account,
|
|
|
|
type: 'accounts',
|
|
|
|
limit: 1,
|
2023-02-06 14:54:48 +03:00
|
|
|
resolve: authenticated,
|
2023-01-22 15:29:48 +03:00
|
|
|
});
|
|
|
|
if (result.accounts.length) {
|
|
|
|
setInfo(result.accounts[0]);
|
|
|
|
setUIState('default');
|
|
|
|
return;
|
|
|
|
}
|
2023-02-06 14:54:35 +03:00
|
|
|
setInfo(null);
|
2023-01-22 15:29:48 +03:00
|
|
|
setUIState('error');
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err);
|
2023-02-06 14:54:35 +03:00
|
|
|
setInfo(null);
|
2023-01-22 15:29:48 +03:00
|
|
|
setUIState('error');
|
|
|
|
}
|
2022-12-10 12:14:48 +03:00
|
|
|
}
|
|
|
|
})();
|
2023-01-27 16:36:04 +03:00
|
|
|
} else {
|
|
|
|
setInfo(account);
|
2022-12-10 12:14:48 +03:00
|
|
|
}
|
2023-01-22 15:29:48 +03:00
|
|
|
}, [account]);
|
2022-12-10 12:14:48 +03:00
|
|
|
|
|
|
|
const {
|
|
|
|
acct,
|
|
|
|
avatar,
|
|
|
|
avatarStatic,
|
|
|
|
bot,
|
|
|
|
createdAt,
|
|
|
|
displayName,
|
|
|
|
emojis,
|
|
|
|
fields,
|
|
|
|
followersCount,
|
|
|
|
followingCount,
|
|
|
|
group,
|
|
|
|
header,
|
|
|
|
headerStatic,
|
|
|
|
id,
|
|
|
|
lastStatusAt,
|
|
|
|
locked,
|
|
|
|
note,
|
|
|
|
statusesCount,
|
|
|
|
url,
|
|
|
|
username,
|
|
|
|
} = info || {};
|
|
|
|
|
2023-02-22 08:28:01 +03:00
|
|
|
const escRef = useHotkeys('esc', onClose, [onClose]);
|
|
|
|
|
2022-12-10 12:14:48 +03:00
|
|
|
return (
|
|
|
|
<div
|
2023-02-22 08:28:01 +03:00
|
|
|
ref={escRef}
|
2022-12-10 12:14:48 +03:00
|
|
|
id="account-container"
|
|
|
|
class={`sheet ${uiState === 'loading' ? 'skeleton' : ''}`}
|
|
|
|
>
|
2023-01-26 06:26:24 +03:00
|
|
|
{uiState === 'error' && (
|
|
|
|
<div class="ui-state">
|
|
|
|
<p>Unable to load account.</p>
|
|
|
|
<p>
|
|
|
|
<a href={account} target="_blank">
|
|
|
|
Go to account page <Icon icon="external" />
|
|
|
|
</a>
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
{uiState === 'loading' ? (
|
2022-12-10 12:14:48 +03:00
|
|
|
<>
|
|
|
|
<header>
|
2023-02-12 14:29:03 +03:00
|
|
|
<AccountBlock avatarSize="xxxl" skeleton />
|
2022-12-10 12:14:48 +03:00
|
|
|
</header>
|
2022-12-25 13:01:01 +03:00
|
|
|
<main>
|
|
|
|
<div class="note">
|
|
|
|
<p>████████ ███████</p>
|
|
|
|
<p>███████████████ ███████████████</p>
|
|
|
|
</div>
|
|
|
|
<p class="stats">
|
|
|
|
<span>██ Posts</span>
|
|
|
|
<span>██ Following</span>
|
|
|
|
<span>██ Followers</span>
|
|
|
|
</p>
|
|
|
|
</main>
|
2022-12-10 12:14:48 +03:00
|
|
|
</>
|
|
|
|
) : (
|
2023-01-26 06:26:24 +03:00
|
|
|
info && (
|
|
|
|
<>
|
2023-03-10 12:36:42 +03:00
|
|
|
{header && !/missing\.png$/.test(header) && (
|
|
|
|
<img src={header} alt="" class="header-banner" />
|
|
|
|
)}
|
2023-01-26 06:26:24 +03:00
|
|
|
<header>
|
2023-02-12 14:29:03 +03:00
|
|
|
<AccountBlock
|
|
|
|
account={info}
|
|
|
|
instance={instance}
|
|
|
|
avatarSize="xxxl"
|
|
|
|
external
|
|
|
|
/>
|
2023-01-26 06:26:24 +03:00
|
|
|
</header>
|
|
|
|
<main tabIndex="-1">
|
|
|
|
{bot && (
|
|
|
|
<>
|
|
|
|
<span class="tag">
|
|
|
|
<Icon icon="bot" /> Automated
|
|
|
|
</span>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
<div
|
|
|
|
class="note"
|
2023-02-05 19:17:19 +03:00
|
|
|
onClick={handleContentLinks({
|
|
|
|
instance,
|
|
|
|
})}
|
2023-01-26 06:26:24 +03:00
|
|
|
dangerouslySetInnerHTML={{
|
|
|
|
__html: enhanceContent(note, { emojis }),
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
{fields?.length > 0 && (
|
|
|
|
<div class="profile-metadata">
|
|
|
|
{fields.map(({ name, value, verifiedAt }) => (
|
|
|
|
<div
|
|
|
|
class={`profile-field ${
|
|
|
|
verifiedAt ? 'profile-verified' : ''
|
|
|
|
}`}
|
|
|
|
key={name}
|
|
|
|
>
|
|
|
|
<b>
|
|
|
|
<span
|
|
|
|
dangerouslySetInnerHTML={{
|
|
|
|
__html: emojifyText(name, emojis),
|
|
|
|
}}
|
|
|
|
/>{' '}
|
|
|
|
{!!verifiedAt && <Icon icon="check-circle" size="s" />}
|
|
|
|
</b>
|
|
|
|
<p
|
2023-01-25 11:25:23 +03:00
|
|
|
dangerouslySetInnerHTML={{
|
2023-01-26 06:26:24 +03:00
|
|
|
__html: enhanceContent(value, { emojis }),
|
2023-01-25 11:25:23 +03:00
|
|
|
}}
|
2023-01-26 06:26:24 +03:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
<p class="stats">
|
2023-02-02 05:30:16 +03:00
|
|
|
<Link
|
2023-02-06 14:54:18 +03:00
|
|
|
to={instance ? `/${instance}/a/${id}` : `/a/${id}`}
|
2023-02-02 05:30:16 +03:00
|
|
|
onClick={() => {
|
|
|
|
hideAllModals();
|
|
|
|
}}
|
|
|
|
>
|
2023-01-26 06:26:24 +03:00
|
|
|
Posts
|
2023-01-29 18:37:13 +03:00
|
|
|
<br />
|
|
|
|
<b title={statusesCount}>
|
|
|
|
{shortenNumber(statusesCount)}
|
|
|
|
</b>{' '}
|
|
|
|
</Link>
|
2023-01-26 06:26:24 +03:00
|
|
|
<span>
|
|
|
|
Following
|
2023-01-29 18:37:13 +03:00
|
|
|
<br />
|
|
|
|
<b title={followingCount}>
|
|
|
|
{shortenNumber(followingCount)}
|
|
|
|
</b>{' '}
|
2023-01-26 06:26:24 +03:00
|
|
|
</span>
|
|
|
|
<span>
|
|
|
|
Followers
|
2023-01-29 18:37:13 +03:00
|
|
|
<br />
|
|
|
|
<b title={followersCount}>
|
|
|
|
{shortenNumber(followersCount)}
|
|
|
|
</b>{' '}
|
2023-01-26 06:26:24 +03:00
|
|
|
</span>
|
|
|
|
{!!createdAt && (
|
|
|
|
<span>
|
2023-01-29 18:37:13 +03:00
|
|
|
Joined
|
|
|
|
<br />
|
2023-01-26 06:26:24 +03:00
|
|
|
<b>
|
|
|
|
<time datetime={createdAt}>
|
2023-03-01 15:07:22 +03:00
|
|
|
{niceDateTime(createdAt, {
|
|
|
|
hideTime: true,
|
|
|
|
})}
|
2023-01-26 06:26:24 +03:00
|
|
|
</time>
|
|
|
|
</b>
|
|
|
|
</span>
|
|
|
|
)}
|
|
|
|
</p>
|
2023-02-21 09:19:50 +03:00
|
|
|
<RelatedActions
|
|
|
|
info={info}
|
|
|
|
instance={instance}
|
|
|
|
authenticated={authenticated}
|
|
|
|
/>
|
2023-01-26 06:26:24 +03:00
|
|
|
</main>
|
|
|
|
</>
|
|
|
|
)
|
2022-12-10 12:14:48 +03:00
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
);
|
2022-12-16 08:27:04 +03:00
|
|
|
}
|
|
|
|
|
2023-02-21 09:19:50 +03:00
|
|
|
function RelatedActions({ info, instance, authenticated }) {
|
|
|
|
if (!info) return null;
|
|
|
|
const {
|
|
|
|
masto: currentMasto,
|
|
|
|
instance: currentInstance,
|
|
|
|
authenticated: currentAuthenticated,
|
|
|
|
} = api();
|
|
|
|
const sameInstance = instance === currentInstance;
|
|
|
|
|
|
|
|
const [relationshipUIState, setRelationshipUIState] = useState('default');
|
|
|
|
const [relationship, setRelationship] = useState(null);
|
|
|
|
const [familiarFollowers, setFamiliarFollowers] = useState([]);
|
|
|
|
|
|
|
|
const { id, locked } = info;
|
|
|
|
const accountID = useRef(id);
|
|
|
|
|
|
|
|
const {
|
|
|
|
following,
|
|
|
|
showingReblogs,
|
|
|
|
notifying,
|
|
|
|
followedBy,
|
|
|
|
blocking,
|
|
|
|
blockedBy,
|
|
|
|
muting,
|
|
|
|
mutingNotifications,
|
|
|
|
requested,
|
|
|
|
domainBlocking,
|
|
|
|
endorsed,
|
|
|
|
} = relationship || {};
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (info) {
|
|
|
|
const currentAccount = store.session.get('currentAccount');
|
|
|
|
let currentID;
|
|
|
|
(async () => {
|
|
|
|
if (sameInstance && authenticated) {
|
|
|
|
currentID = id;
|
|
|
|
} else if (!sameInstance && currentAuthenticated) {
|
|
|
|
// Grab this account from my logged-in instance
|
|
|
|
const acctHasInstance = info.acct.includes('@');
|
|
|
|
try {
|
|
|
|
const results = await currentMasto.v2.search({
|
|
|
|
q: acctHasInstance ? info.acct : `${info.username}@${instance}`,
|
|
|
|
type: 'accounts',
|
|
|
|
limit: 1,
|
|
|
|
resolve: true,
|
|
|
|
});
|
|
|
|
console.log('🥏 Fetched account from logged-in instance', results);
|
|
|
|
currentID = results.accounts[0].id;
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!currentID) return;
|
|
|
|
|
|
|
|
if (currentAccount === currentID) {
|
|
|
|
// It's myself!
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
accountID.current = currentID;
|
|
|
|
|
|
|
|
setRelationshipUIState('loading');
|
|
|
|
setFamiliarFollowers([]);
|
|
|
|
|
|
|
|
const fetchRelationships = currentMasto.v1.accounts.fetchRelationships([
|
|
|
|
currentID,
|
|
|
|
]);
|
|
|
|
const fetchFamiliarFollowers =
|
|
|
|
currentMasto.v1.accounts.fetchFamiliarFollowers(currentID);
|
|
|
|
|
|
|
|
try {
|
|
|
|
const relationships = await fetchRelationships;
|
|
|
|
console.log('fetched relationship', relationships);
|
|
|
|
if (relationships.length) {
|
|
|
|
const relationship = relationships[0];
|
|
|
|
setRelationship(relationship);
|
|
|
|
|
|
|
|
if (!relationship.following) {
|
|
|
|
try {
|
|
|
|
const followers = await fetchFamiliarFollowers;
|
|
|
|
console.log('fetched familiar followers', followers);
|
|
|
|
setFamiliarFollowers(followers[0].accounts.slice(0, 10));
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setRelationshipUIState('default');
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
setRelationshipUIState('error');
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
}, [info, authenticated]);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
{familiarFollowers?.length > 0 && (
|
|
|
|
<p class="common-followers">
|
|
|
|
Common followers{' '}
|
|
|
|
<span class="ib">
|
|
|
|
{familiarFollowers.map((follower) => (
|
|
|
|
<a
|
|
|
|
href={follower.url}
|
|
|
|
rel="noopener noreferrer"
|
|
|
|
onClick={(e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
states.showAccount = {
|
|
|
|
account: follower,
|
|
|
|
instance,
|
|
|
|
};
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<Avatar
|
|
|
|
url={follower.avatarStatic}
|
|
|
|
size="l"
|
|
|
|
alt={`${follower.displayName} @${follower.acct}`}
|
|
|
|
/>
|
|
|
|
</a>
|
|
|
|
))}
|
|
|
|
</span>
|
|
|
|
</p>
|
|
|
|
)}
|
|
|
|
<p class="actions">
|
|
|
|
{followedBy ? <span class="tag">Following you</span> : <span />}{' '}
|
|
|
|
{relationshipUIState !== 'loading' && relationship && (
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
class={`${following || requested ? 'light swap' : ''}`}
|
|
|
|
data-swap-state={following || requested ? 'danger' : ''}
|
|
|
|
disabled={relationshipUIState === 'loading'}
|
|
|
|
onClick={() => {
|
|
|
|
setRelationshipUIState('loading');
|
|
|
|
|
|
|
|
(async () => {
|
|
|
|
try {
|
|
|
|
let newRelationship;
|
|
|
|
|
|
|
|
if (following || requested) {
|
|
|
|
const yes = confirm(
|
|
|
|
requested
|
|
|
|
? 'Withdraw follow request?'
|
|
|
|
: `Unfollow @${info.acct || info.username}?`,
|
|
|
|
);
|
|
|
|
|
|
|
|
if (yes) {
|
|
|
|
newRelationship = await currentMasto.v1.accounts.unfollow(
|
|
|
|
accountID.current,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
newRelationship = await currentMasto.v1.accounts.follow(
|
|
|
|
accountID.current,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newRelationship) setRelationship(newRelationship);
|
|
|
|
setRelationshipUIState('default');
|
|
|
|
} catch (e) {
|
|
|
|
alert(e);
|
|
|
|
setRelationshipUIState('error');
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{following ? (
|
|
|
|
<>
|
|
|
|
<span>Following</span>
|
|
|
|
<span>Unfollow…</span>
|
|
|
|
</>
|
|
|
|
) : requested ? (
|
|
|
|
<>
|
|
|
|
<span>Requested</span>
|
|
|
|
<span>Withdraw…</span>
|
|
|
|
</>
|
|
|
|
) : locked ? (
|
|
|
|
<>
|
|
|
|
<Icon icon="lock" /> <span>Follow</span>
|
|
|
|
</>
|
|
|
|
) : (
|
|
|
|
'Follow'
|
|
|
|
)}
|
|
|
|
</button>
|
|
|
|
)}
|
|
|
|
</p>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-12-16 08:27:04 +03:00
|
|
|
export default Account;
|