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
|
@ -139,13 +139,13 @@
|
|||
/* flex-wrap: wrap; */
|
||||
column-gap: 24px;
|
||||
row-gap: 8px;
|
||||
opacity: 0.75;
|
||||
/* opacity: 0.75; */
|
||||
font-size: 90%;
|
||||
background-color: var(--bg-faded-color);
|
||||
padding: 12px;
|
||||
border-radius: 16px;
|
||||
/* border-radius: 16px; */
|
||||
line-height: 1.25;
|
||||
overflow-x: auto;
|
||||
overflow-x: auto !important;
|
||||
justify-content: flex-start;
|
||||
position: relative;
|
||||
|
||||
|
@ -185,11 +185,33 @@
|
|||
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 {
|
||||
display: flex;
|
||||
/* flex-wrap: wrap; */
|
||||
gap: 2px;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
@ -235,12 +257,11 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.account-container .common-followers p {
|
||||
.account-container .common-followers {
|
||||
font-size: 90%;
|
||||
color: var(--text-insignificant-color);
|
||||
border-top: 1px solid var(--outline-color);
|
||||
border-bottom: 1px solid var(--outline-color);
|
||||
padding: 8px 0;
|
||||
background-color: var(--bg-faded-color);
|
||||
padding: 8px 12px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
@ -261,6 +282,74 @@
|
|||
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 {
|
||||
0% {
|
||||
left: -100%;
|
||||
|
|
|
@ -357,6 +357,7 @@ function AccountInfo({
|
|||
__html: enhanceContent(note, { emojis }),
|
||||
}}
|
||||
/>
|
||||
<div class="account-metadata-box">
|
||||
{fields?.length > 0 && (
|
||||
<div class="profile-metadata">
|
||||
{fields.map(({ name, value, verifiedAt }, i) => (
|
||||
|
@ -368,7 +369,9 @@ function AccountInfo({
|
|||
>
|
||||
<b>
|
||||
<EmojiText text={name} emojis={emojis} />{' '}
|
||||
{!!verifiedAt && <Icon icon="check-circle" size="s" />}
|
||||
{!!verifiedAt && (
|
||||
<Icon icon="check-circle" size="s" />
|
||||
)}
|
||||
</b>
|
||||
<p
|
||||
dangerouslySetInnerHTML={{
|
||||
|
@ -379,7 +382,7 @@ function AccountInfo({
|
|||
))}
|
||||
</div>
|
||||
)}
|
||||
<p class="stats">
|
||||
<div class="stats">
|
||||
<LinkOrDiv
|
||||
tabIndex={0}
|
||||
to={accountLink}
|
||||
|
@ -440,11 +443,13 @@ function AccountInfo({
|
|||
</time>
|
||||
</div>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<RelatedActions
|
||||
info={info}
|
||||
instance={instance}
|
||||
authenticated={authenticated}
|
||||
standalone={standalone}
|
||||
/>
|
||||
</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;
|
||||
const {
|
||||
masto: currentMasto,
|
||||
|
@ -466,6 +473,7 @@ function RelatedActions({ info, instance, authenticated }) {
|
|||
const [relationshipUIState, setRelationshipUIState] = useState('default');
|
||||
const [relationship, setRelationship] = useState(null);
|
||||
const [familiarFollowers, setFamiliarFollowers] = useState([]);
|
||||
const [postingStats, setPostingStats] = useState();
|
||||
|
||||
const { id, acct, url, username, locked, lastStatusAt, note, fields } = info;
|
||||
const accountID = useRef(id);
|
||||
|
@ -526,12 +534,11 @@ function RelatedActions({ info, instance, authenticated }) {
|
|||
|
||||
setRelationshipUIState('loading');
|
||||
setFamiliarFollowers([]);
|
||||
setPostingStats(null);
|
||||
|
||||
const fetchRelationships = currentMasto.v1.accounts.fetchRelationships([
|
||||
currentID,
|
||||
]);
|
||||
const fetchFamiliarFollowers =
|
||||
currentMasto.v1.accounts.fetchFamiliarFollowers(currentID);
|
||||
|
||||
try {
|
||||
const relationships = await fetchRelationships;
|
||||
|
@ -542,9 +549,55 @@ function RelatedActions({ info, instance, authenticated }) {
|
|||
|
||||
if (!relationship.following) {
|
||||
try {
|
||||
const fetchFamiliarFollowers =
|
||||
currentMasto.v1.accounts.fetchFamiliarFollowers(currentID);
|
||||
const fetchStatuses = currentMasto.v1.accounts
|
||||
.listStatuses(currentID, {
|
||||
limit: 20,
|
||||
})
|
||||
.next();
|
||||
|
||||
const followers = await fetchFamiliarFollowers;
|
||||
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) {
|
||||
console.error(e);
|
||||
}
|
||||
|
@ -571,17 +624,22 @@ function RelatedActions({ info, instance, authenticated }) {
|
|||
const [showTranslatedBio, setShowTranslatedBio] = useState(false);
|
||||
const [showAddRemoveLists, setShowAddRemoveLists] = useState(false);
|
||||
|
||||
const hasFamiliarFollowers = familiarFollowers?.length > 0;
|
||||
const hasPostingStats = postingStats?.total >= 3;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
class="common-followers shazam-container no-animation"
|
||||
hidden={!familiarFollowers?.length}
|
||||
>
|
||||
{(hasFamiliarFollowers || hasPostingStats) && (
|
||||
<div class="account-metadata-box">
|
||||
{hasFamiliarFollowers && (
|
||||
<div class="shazam-container">
|
||||
<div class="shazam-container-inner">
|
||||
<p>
|
||||
<p class="common-followers">
|
||||
Followed by{' '}
|
||||
<span class="ib">
|
||||
{familiarFollowers.map((follower) => (
|
||||
{familiarFollowers
|
||||
.slice(0, FAMILIAR_FOLLOWERS_LIMIT)
|
||||
.map((follower) => (
|
||||
<a
|
||||
href={follower.url}
|
||||
rel="noopener noreferrer"
|
||||
|
@ -601,10 +659,74 @@ function RelatedActions({ info, instance, authenticated }) {
|
|||
/>
|
||||
</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
|
||||
}%`,
|
||||
}}
|
||||
/>
|
||||
<div class="posting-stats-legends">
|
||||
<span class="ib">
|
||||
<span class="posting-stats-legend-item posting-stats-legend-item-originals" />{' '}
|
||||
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>
|
||||
)}
|
||||
<p class="actions">
|
||||
<span>
|
||||
{followedBy ? (
|
||||
|
|
Loading…
Reference in a new issue