Experimental posting stats for non-following accounts
Also recode+redesign the multiple metadata boxes in account info
This commit is contained in:
parent
b116cbfe8c
commit
9571271d83
2 changed files with 335 additions and 124 deletions
|
@ -139,13 +139,13 @@
|
||||||
/* flex-wrap: wrap; */
|
/* flex-wrap: wrap; */
|
||||||
column-gap: 24px;
|
column-gap: 24px;
|
||||||
row-gap: 8px;
|
row-gap: 8px;
|
||||||
opacity: 0.75;
|
/* opacity: 0.75; */
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
background-color: var(--bg-faded-color);
|
background-color: var(--bg-faded-color);
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border-radius: 16px;
|
/* border-radius: 16px; */
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
overflow-x: auto;
|
overflow-x: auto !important;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
@ -185,11 +185,33 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.account-container .account-metadata-box {
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 16px;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has(+ .account-metadata-box) {
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ .account-metadata-box {
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
border-bottom-left-radius: 16px;
|
||||||
|
border-bottom-right-radius: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.account-container .profile-metadata {
|
.account-container .profile-metadata {
|
||||||
display: flex;
|
display: flex;
|
||||||
/* flex-wrap: wrap; */
|
/* flex-wrap: wrap; */
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
border-radius: 16px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
@ -235,12 +257,11 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.account-container .common-followers p {
|
.account-container .common-followers {
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
color: var(--text-insignificant-color);
|
color: var(--text-insignificant-color);
|
||||||
border-top: 1px solid var(--outline-color);
|
background-color: var(--bg-faded-color);
|
||||||
border-bottom: 1px solid var(--outline-color);
|
padding: 8px 12px;
|
||||||
padding: 8px 0;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,6 +282,74 @@
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes swoosh-bg-image {
|
||||||
|
0% {
|
||||||
|
background-position: -320px 0;
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0 0;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.account-container .posting-stats {
|
||||||
|
font-size: 90%;
|
||||||
|
color: var(--text-insignificant-color);
|
||||||
|
background-color: var(--bg-faded-color);
|
||||||
|
padding: 8px 12px;
|
||||||
|
--size: 8px;
|
||||||
|
--original-color: var(--link-color);
|
||||||
|
|
||||||
|
.posting-stats-bar {
|
||||||
|
height: var(--size);
|
||||||
|
border-radius: var(--size);
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 8px 0;
|
||||||
|
box-shadow: inset 0 0 0 1px var(--outline-color),
|
||||||
|
inset 0 0 0 1.5px var(--bg-blur-color);
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
animation: swoosh-bg-image 0.3s ease-in-out 0.3s both;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
to right,
|
||||||
|
var(--original-color) 0%,
|
||||||
|
var(--original-color) var(--originals-percentage),
|
||||||
|
var(--reply-to-color) var(--originals-percentage),
|
||||||
|
var(--reply-to-color) var(--replies-percentage),
|
||||||
|
var(--reblog-color) var(--replies-percentage),
|
||||||
|
var(--reblog-color) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.posting-stats-legends {
|
||||||
|
font-size: 12px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.posting-stats-legend-item {
|
||||||
|
display: inline-block;
|
||||||
|
width: var(--size);
|
||||||
|
height: var(--size);
|
||||||
|
border-radius: var(--size);
|
||||||
|
background-color: var(--text-insignificant-color);
|
||||||
|
vertical-align: middle;
|
||||||
|
margin: 0 4px 2px;
|
||||||
|
/* border: 1px solid var(--outline-color); */
|
||||||
|
box-shadow: inset 0 0 0 1px var(--outline-color),
|
||||||
|
inset 0 0 0 1.5px var(--bg-blur-color);
|
||||||
|
|
||||||
|
&.posting-stats-legend-item-originals {
|
||||||
|
background-color: var(--original-color);
|
||||||
|
}
|
||||||
|
&.posting-stats-legend-item-replies {
|
||||||
|
background-color: var(--reply-to-color);
|
||||||
|
}
|
||||||
|
&.posting-stats-legend-item-boosts {
|
||||||
|
background-color: var(--reblog-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes shine {
|
@keyframes shine {
|
||||||
0% {
|
0% {
|
||||||
left: -100%;
|
left: -100%;
|
||||||
|
|
|
@ -357,94 +357,99 @@ function AccountInfo({
|
||||||
__html: enhanceContent(note, { emojis }),
|
__html: enhanceContent(note, { emojis }),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{fields?.length > 0 && (
|
<div class="account-metadata-box">
|
||||||
<div class="profile-metadata">
|
{fields?.length > 0 && (
|
||||||
{fields.map(({ name, value, verifiedAt }, i) => (
|
<div class="profile-metadata">
|
||||||
<div
|
{fields.map(({ name, value, verifiedAt }, i) => (
|
||||||
class={`profile-field ${
|
<div
|
||||||
verifiedAt ? 'profile-verified' : ''
|
class={`profile-field ${
|
||||||
}`}
|
verifiedAt ? 'profile-verified' : ''
|
||||||
key={name + i}
|
}`}
|
||||||
>
|
key={name + i}
|
||||||
<b>
|
>
|
||||||
<EmojiText text={name} emojis={emojis} />{' '}
|
<b>
|
||||||
{!!verifiedAt && <Icon icon="check-circle" size="s" />}
|
<EmojiText text={name} emojis={emojis} />{' '}
|
||||||
</b>
|
{!!verifiedAt && (
|
||||||
<p
|
<Icon icon="check-circle" size="s" />
|
||||||
dangerouslySetInnerHTML={{
|
)}
|
||||||
__html: enhanceContent(value, { emojis }),
|
</b>
|
||||||
}}
|
<p
|
||||||
/>
|
dangerouslySetInnerHTML={{
|
||||||
</div>
|
__html: enhanceContent(value, { emojis }),
|
||||||
))}
|
}}
|
||||||
</div>
|
/>
|
||||||
)}
|
</div>
|
||||||
<p class="stats">
|
))}
|
||||||
<LinkOrDiv
|
|
||||||
tabIndex={0}
|
|
||||||
to={accountLink}
|
|
||||||
onClick={() => {
|
|
||||||
states.showAccount = false;
|
|
||||||
states.showGenericAccounts = {
|
|
||||||
heading: 'Followers',
|
|
||||||
fetchAccounts: fetchFollowers,
|
|
||||||
};
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span title={followersCount}>
|
|
||||||
{shortenNumber(followersCount)}
|
|
||||||
</span>{' '}
|
|
||||||
Followers
|
|
||||||
</LinkOrDiv>
|
|
||||||
<LinkOrDiv
|
|
||||||
class="insignificant"
|
|
||||||
tabIndex={0}
|
|
||||||
to={accountLink}
|
|
||||||
onClick={() => {
|
|
||||||
states.showAccount = false;
|
|
||||||
states.showGenericAccounts = {
|
|
||||||
heading: 'Following',
|
|
||||||
fetchAccounts: fetchFollowing,
|
|
||||||
};
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span title={followingCount}>
|
|
||||||
{shortenNumber(followingCount)}
|
|
||||||
</span>{' '}
|
|
||||||
Following
|
|
||||||
<br />
|
|
||||||
</LinkOrDiv>
|
|
||||||
<LinkOrDiv
|
|
||||||
class="insignificant"
|
|
||||||
to={accountLink}
|
|
||||||
onClick={
|
|
||||||
standalone
|
|
||||||
? undefined
|
|
||||||
: () => {
|
|
||||||
hideAllModals();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span title={statusesCount}>
|
|
||||||
{shortenNumber(statusesCount)}
|
|
||||||
</span>{' '}
|
|
||||||
Posts
|
|
||||||
</LinkOrDiv>
|
|
||||||
{!!createdAt && (
|
|
||||||
<div class="insignificant">
|
|
||||||
Joined{' '}
|
|
||||||
<time datetime={createdAt}>
|
|
||||||
{niceDateTime(createdAt, {
|
|
||||||
hideTime: true,
|
|
||||||
})}
|
|
||||||
</time>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</p>
|
<div class="stats">
|
||||||
|
<LinkOrDiv
|
||||||
|
tabIndex={0}
|
||||||
|
to={accountLink}
|
||||||
|
onClick={() => {
|
||||||
|
states.showAccount = false;
|
||||||
|
states.showGenericAccounts = {
|
||||||
|
heading: 'Followers',
|
||||||
|
fetchAccounts: fetchFollowers,
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span title={followersCount}>
|
||||||
|
{shortenNumber(followersCount)}
|
||||||
|
</span>{' '}
|
||||||
|
Followers
|
||||||
|
</LinkOrDiv>
|
||||||
|
<LinkOrDiv
|
||||||
|
class="insignificant"
|
||||||
|
tabIndex={0}
|
||||||
|
to={accountLink}
|
||||||
|
onClick={() => {
|
||||||
|
states.showAccount = false;
|
||||||
|
states.showGenericAccounts = {
|
||||||
|
heading: 'Following',
|
||||||
|
fetchAccounts: fetchFollowing,
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span title={followingCount}>
|
||||||
|
{shortenNumber(followingCount)}
|
||||||
|
</span>{' '}
|
||||||
|
Following
|
||||||
|
<br />
|
||||||
|
</LinkOrDiv>
|
||||||
|
<LinkOrDiv
|
||||||
|
class="insignificant"
|
||||||
|
to={accountLink}
|
||||||
|
onClick={
|
||||||
|
standalone
|
||||||
|
? undefined
|
||||||
|
: () => {
|
||||||
|
hideAllModals();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span title={statusesCount}>
|
||||||
|
{shortenNumber(statusesCount)}
|
||||||
|
</span>{' '}
|
||||||
|
Posts
|
||||||
|
</LinkOrDiv>
|
||||||
|
{!!createdAt && (
|
||||||
|
<div class="insignificant">
|
||||||
|
Joined{' '}
|
||||||
|
<time datetime={createdAt}>
|
||||||
|
{niceDateTime(createdAt, {
|
||||||
|
hideTime: true,
|
||||||
|
})}
|
||||||
|
</time>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<RelatedActions
|
<RelatedActions
|
||||||
info={info}
|
info={info}
|
||||||
instance={instance}
|
instance={instance}
|
||||||
authenticated={authenticated}
|
authenticated={authenticated}
|
||||||
|
standalone={standalone}
|
||||||
/>
|
/>
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
|
@ -454,7 +459,9 @@ function AccountInfo({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function RelatedActions({ info, instance, authenticated }) {
|
const FAMILIAR_FOLLOWERS_LIMIT = 10;
|
||||||
|
|
||||||
|
function RelatedActions({ info, instance, authenticated, standalone }) {
|
||||||
if (!info) return null;
|
if (!info) return null;
|
||||||
const {
|
const {
|
||||||
masto: currentMasto,
|
masto: currentMasto,
|
||||||
|
@ -466,6 +473,7 @@ function RelatedActions({ info, instance, authenticated }) {
|
||||||
const [relationshipUIState, setRelationshipUIState] = useState('default');
|
const [relationshipUIState, setRelationshipUIState] = useState('default');
|
||||||
const [relationship, setRelationship] = useState(null);
|
const [relationship, setRelationship] = useState(null);
|
||||||
const [familiarFollowers, setFamiliarFollowers] = useState([]);
|
const [familiarFollowers, setFamiliarFollowers] = useState([]);
|
||||||
|
const [postingStats, setPostingStats] = useState();
|
||||||
|
|
||||||
const { id, acct, url, username, locked, lastStatusAt, note, fields } = info;
|
const { id, acct, url, username, locked, lastStatusAt, note, fields } = info;
|
||||||
const accountID = useRef(id);
|
const accountID = useRef(id);
|
||||||
|
@ -526,12 +534,11 @@ function RelatedActions({ info, instance, authenticated }) {
|
||||||
|
|
||||||
setRelationshipUIState('loading');
|
setRelationshipUIState('loading');
|
||||||
setFamiliarFollowers([]);
|
setFamiliarFollowers([]);
|
||||||
|
setPostingStats(null);
|
||||||
|
|
||||||
const fetchRelationships = currentMasto.v1.accounts.fetchRelationships([
|
const fetchRelationships = currentMasto.v1.accounts.fetchRelationships([
|
||||||
currentID,
|
currentID,
|
||||||
]);
|
]);
|
||||||
const fetchFamiliarFollowers =
|
|
||||||
currentMasto.v1.accounts.fetchFamiliarFollowers(currentID);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const relationships = await fetchRelationships;
|
const relationships = await fetchRelationships;
|
||||||
|
@ -542,9 +549,55 @@ function RelatedActions({ info, instance, authenticated }) {
|
||||||
|
|
||||||
if (!relationship.following) {
|
if (!relationship.following) {
|
||||||
try {
|
try {
|
||||||
|
const fetchFamiliarFollowers =
|
||||||
|
currentMasto.v1.accounts.fetchFamiliarFollowers(currentID);
|
||||||
|
const fetchStatuses = currentMasto.v1.accounts
|
||||||
|
.listStatuses(currentID, {
|
||||||
|
limit: 20,
|
||||||
|
})
|
||||||
|
.next();
|
||||||
|
|
||||||
const followers = await fetchFamiliarFollowers;
|
const followers = await fetchFamiliarFollowers;
|
||||||
console.log('fetched familiar followers', followers);
|
console.log('fetched familiar followers', followers);
|
||||||
setFamiliarFollowers(followers[0].accounts.slice(0, 10));
|
setFamiliarFollowers(followers[0].accounts);
|
||||||
|
|
||||||
|
if (standalone) return;
|
||||||
|
|
||||||
|
const { value: statuses } = await fetchStatuses;
|
||||||
|
console.log('fetched statuses', statuses);
|
||||||
|
const stats = {
|
||||||
|
total: statuses.length,
|
||||||
|
originals: 0,
|
||||||
|
replies: 0,
|
||||||
|
boosts: 0,
|
||||||
|
};
|
||||||
|
// Categories statuses by type
|
||||||
|
// - Original posts (not replies to others)
|
||||||
|
// - Threads (self-replies + 1st original post)
|
||||||
|
// - Boosts (reblogs)
|
||||||
|
// - Replies (not-self replies)
|
||||||
|
statuses.forEach((status) => {
|
||||||
|
if (status.reblog) {
|
||||||
|
stats.boosts++;
|
||||||
|
} else if (
|
||||||
|
status.inReplyToAccountId !== currentID &&
|
||||||
|
!!status.inReplyToId
|
||||||
|
) {
|
||||||
|
stats.replies++;
|
||||||
|
} else {
|
||||||
|
stats.originals++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Count days since last post
|
||||||
|
stats.daysSinceLastPost = Math.ceil(
|
||||||
|
(Date.now() -
|
||||||
|
new Date(statuses[statuses.length - 1].createdAt)) /
|
||||||
|
86400000,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('posting stats', stats);
|
||||||
|
setPostingStats(stats);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
@ -571,40 +624,109 @@ function RelatedActions({ info, instance, authenticated }) {
|
||||||
const [showTranslatedBio, setShowTranslatedBio] = useState(false);
|
const [showTranslatedBio, setShowTranslatedBio] = useState(false);
|
||||||
const [showAddRemoveLists, setShowAddRemoveLists] = useState(false);
|
const [showAddRemoveLists, setShowAddRemoveLists] = useState(false);
|
||||||
|
|
||||||
|
const hasFamiliarFollowers = familiarFollowers?.length > 0;
|
||||||
|
const hasPostingStats = postingStats?.total >= 3;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
{(hasFamiliarFollowers || hasPostingStats) && (
|
||||||
class="common-followers shazam-container no-animation"
|
<div class="account-metadata-box">
|
||||||
hidden={!familiarFollowers?.length}
|
{hasFamiliarFollowers && (
|
||||||
>
|
<div class="shazam-container">
|
||||||
<div class="shazam-container-inner">
|
<div class="shazam-container-inner">
|
||||||
<p>
|
<p class="common-followers">
|
||||||
Followed by{' '}
|
Followed by{' '}
|
||||||
<span class="ib">
|
<span class="ib">
|
||||||
{familiarFollowers.map((follower) => (
|
{familiarFollowers
|
||||||
<a
|
.slice(0, FAMILIAR_FOLLOWERS_LIMIT)
|
||||||
href={follower.url}
|
.map((follower) => (
|
||||||
rel="noopener noreferrer"
|
<a
|
||||||
onClick={(e) => {
|
href={follower.url}
|
||||||
e.preventDefault();
|
rel="noopener noreferrer"
|
||||||
states.showAccount = {
|
onClick={(e) => {
|
||||||
account: follower,
|
e.preventDefault();
|
||||||
instance,
|
states.showAccount = {
|
||||||
};
|
account: follower,
|
||||||
}}
|
instance,
|
||||||
>
|
};
|
||||||
<Avatar
|
}}
|
||||||
url={follower.avatarStatic}
|
>
|
||||||
size="l"
|
<Avatar
|
||||||
alt={`${follower.displayName} @${follower.acct}`}
|
url={follower.avatarStatic}
|
||||||
squircle={follower?.bot}
|
size="l"
|
||||||
|
alt={`${follower.displayName} @${follower.acct}`}
|
||||||
|
squircle={follower?.bot}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
{familiarFollowers.length > FAMILIAR_FOLLOWERS_LIMIT && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="small plain4"
|
||||||
|
onClick={() => {
|
||||||
|
states.showGenericAccounts = {
|
||||||
|
heading: 'Followed by',
|
||||||
|
accounts: familiarFollowers,
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+{familiarFollowers.length - FAMILIAR_FOLLOWERS_LIMIT}
|
||||||
|
<Icon icon="chevron-down" size="s" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{hasPostingStats && (
|
||||||
|
<div class="shazam-container">
|
||||||
|
<div class="shazam-container-inner">
|
||||||
|
<div class="posting-stats">
|
||||||
|
<div>
|
||||||
|
{postingStats.daysSinceLastPost < 365
|
||||||
|
? `Last ${postingStats.total} posts in the past
|
||||||
|
${postingStats.daysSinceLastPost} day${
|
||||||
|
postingStats.daysSinceLastPost > 1 ? 's' : ''
|
||||||
|
}`
|
||||||
|
: `
|
||||||
|
Last ${postingStats.total} posts in the past year(s)
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="posting-stats-bar"
|
||||||
|
style={{
|
||||||
|
// [originals | replies | boosts]
|
||||||
|
'--originals-percentage': `${
|
||||||
|
(postingStats.originals / postingStats.total) * 100
|
||||||
|
}%`,
|
||||||
|
'--replies-percentage': `${
|
||||||
|
((postingStats.originals + postingStats.replies) /
|
||||||
|
postingStats.total) *
|
||||||
|
100
|
||||||
|
}%`,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</a>
|
<div class="posting-stats-legends">
|
||||||
))}
|
<span class="ib">
|
||||||
</span>
|
<span class="posting-stats-legend-item posting-stats-legend-item-originals" />{' '}
|
||||||
</p>
|
Original
|
||||||
|
</span>{' '}
|
||||||
|
<span class="ib">
|
||||||
|
<span class="posting-stats-legend-item posting-stats-legend-item-replies" />{' '}
|
||||||
|
Replies
|
||||||
|
</span>{' '}
|
||||||
|
<span class="ib">
|
||||||
|
<span class="posting-stats-legend-item posting-stats-legend-item-boosts" />{' '}
|
||||||
|
Boosts
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
<p class="actions">
|
<p class="actions">
|
||||||
<span>
|
<span>
|
||||||
{followedBy ? (
|
{followedBy ? (
|
||||||
|
|
Loading…
Add table
Reference in a new issue