Status thread page improvements

- Show level 3 comments
- Change header-tap to scroll top to a button instead (prevent accidental scroll top)
- Show avatars in <summary>
- Clean up CSS a bit
pull/49/head
Lim Chee Aun 2023-01-29 01:02:25 +08:00
rodzic ae90b41aae
commit a088b48eb7
3 zmienionych plików z 196 dodań i 82 usunięć

Wyświetl plik

@ -148,17 +148,26 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
border-bottom: none;
}
.timeline.contextual {
--thread-start: 40px;
--line-start: 40px;
--line-width: 3px;
--line-end: calc(var(--line-start) + var(--line-width));
--line-margin-end: 16px;
--line-radius: 10px;
--line-diameter: calc(var(--line-radius) * 2);
--avatar-size: 50px;
--avatar-margin-start: 16px;
--avatar-margin-end: 12px;
}
.timeline.contextual > li {
--width: 3px;
--left: 40px;
--right: calc(var(--left) + var(--width));
background-image: linear-gradient(
to right,
transparent,
transparent var(--left),
var(--comment-line-color) var(--left),
var(--comment-line-color) var(--right),
transparent var(--right),
transparent var(--line-start),
var(--comment-line-color) var(--line-start),
var(--comment-line-color) var(--line-end),
transparent var(--line-end),
transparent
);
background-repeat: no-repeat;
@ -184,41 +193,83 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
> .status-link
+ .replies
> summary {
margin-left: calc(50px + 16px + 12px);
margin-left: calc(
var(--avatar-size) + var(--avatar-margin-start) + var(--avatar-margin-end)
);
}
.timeline.contextual
> li.descendant.thread
> .status-link
+ .replies
.replies
> summary {
margin-left: calc(
var(--avatar-size) + var(--avatar-margin-start) + var(--avatar-margin-end) +
var(--line-margin-end)
);
}
.timeline.contextual
> li.descendant.thread
> .status-link
+ .replies
.status-link {
padding-left: calc(50px + 16px + 12px);
padding-left: calc(
var(--avatar-size) + var(--avatar-margin-start) + var(--avatar-margin-end)
);
}
.timeline.contextual
> li.descendant.thread
> .status-link
+ .replies
.replies
.status-link {
padding-left: calc(
var(--avatar-size) + var(--avatar-margin-start) + var(--avatar-margin-end) +
var(--line-margin-end)
);
}
.timeline.contextual
> li.descendant:not(.thread)
> .status-link
+ .replies
> summary {
margin-left: calc(40px + 16px);
margin-left: calc(var(--thread-start) + var(--line-margin-end));
}
.timeline.contextual
> li.descendant:not(.thread)
> .status-link
+ .replies
.replies
> summary {
margin-left: calc(
var(--thread-start) + var(--line-margin-end) + var(--line-margin-end)
);
}
.timeline.contextual
> li.descendant:not(.thread)
> .status-link
+ .replies
.status-link {
padding-left: calc(40px + 16px);
padding-left: calc(var(--thread-start) + var(--line-margin-end));
}
.timeline.contextual
> li.descendant:not(.thread)
> .status-link
+ .replies
.replies
.status-link {
--line-margin-end: 32px;
}
.timeline.contextual > li.descendant:not(.thread):before {
--radius: 10px;
--diameter: calc(var(--radius) * 2);
content: '';
position: absolute;
top: 10px;
left: 40px;
width: var(--diameter);
height: var(--diameter);
border-radius: var(--radius);
left: var(--line-start);
width: var(--line-diameter);
height: var(--line-diameter);
border-radius: var(--line-radius);
border-style: solid;
border-width: var(--width);
border-width: var(--line-width);
border-color: transparent transparent var(--comment-line-color) transparent;
transform: rotate(45deg);
}
@ -230,7 +281,9 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
font-size: 90%;
}
.timeline.contextual > li.thread > .status-link .replies-link {
margin-left: calc(50px + 16px + 12px);
margin-left: calc(
var(--avatar-size) + var(--avatar-margin-start) + var(--avatar-margin-end)
);
}
.timeline.contextual > li .replies-link * {
vertical-align: middle;
@ -243,7 +296,7 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
padding: 0;
list-style: none;
}
.timeline.contextual > li .replies summary {
.timeline.contextual > li .replies > summary {
padding: 8px 16px;
background-color: var(--bg-faded-color);
display: inline-block;
@ -256,9 +309,16 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
user-select: none;
box-shadow: 0 0 0 2px var(--bg-color);
position: relative;
list-style: none;
}
.timeline.contextual > li .replies summary:active,
.timeline.contextual > li .replies[open] summary {
.timeline.contextual > li .replies > summary > * {
vertical-align: middle;
}
.timeline.contextual > li .replies > summary .avatars {
margin-right: 8px;
}
.timeline.contextual > li .replies > summary:active,
.timeline.contextual > li .replies[open] > summary {
color: var(--text-color);
background-color: var(--comment-line-color);
background-image: linear-gradient(
@ -267,7 +327,7 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
var(--bg-faded-color)
);
}
.timeline.contextual > li .replies[open] summary {
.timeline.contextual > li .replies[open] > summary {
border-bottom-left-radius: 0;
}
.timeline.contextual > li .replies summary[hidden] {
@ -277,43 +337,66 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
position: relative;
}
.timeline.contextual > li .replies li {
--width: 3px;
--left: calc(40px + 16px);
--right: calc(var(--left) + var(--width));
--line-start: calc(var(--thread-start) + var(--line-margin-end));
--line-end: calc(var(--line-start) + var(--line-width));
background-image: linear-gradient(
to right,
transparent,
transparent var(--left),
var(--comment-line-color) var(--left),
var(--comment-line-color) var(--right),
transparent var(--right),
transparent var(--line-start),
var(--comment-line-color) var(--line-start),
var(--comment-line-color) var(--line-end),
transparent var(--line-end),
transparent
);
background-repeat: no-repeat;
}
.timeline.contextual > li .replies .replies li {
--line-start: calc(
var(--thread-start) + var(--line-margin-end) + var(--line-margin-end)
);
}
.timeline.contextual > li.thread .replies li {
--left: calc(50px + 16px + 12px);
--line-start: calc(
var(--avatar-size) + var(--avatar-margin-start) + var(--avatar-margin-end)
);
}
.timeline.contextual > li.thread .replies .replies li {
--line-start: calc(
var(--avatar-size) + var(--avatar-margin-start) + var(--avatar-margin-end) +
var(--line-margin-end)
);
}
.timeline.contextual > li .replies li:last-child {
background-size: 100% 20px;
}
.timeline.contextual > li .replies li:before {
--radius: 10px;
--diameter: calc(var(--radius) * 2);
content: '';
position: absolute;
top: 10px;
left: calc(40px + 16px);
width: var(--diameter);
height: var(--diameter);
border-radius: var(--radius);
left: var(--line-start);
width: var(--line-diameter);
height: var(--line-diameter);
border-radius: var(--line-radius);
border-style: solid;
border-width: var(--width);
border-width: var(--line-width);
border-color: transparent transparent var(--comment-line-color) transparent;
transform: rotate(45deg);
}
.timeline.contextual > li .replies .replies li:before {
--line-start: calc(
var(--thread-start) + var(--line-margin-end) + var(--line-margin-end)
);
}
.timeline.contextual > li.thread .replies li:before {
left: calc(50px + 16px + 12px);
--line-start: calc(
var(--avatar-size) + var(--avatar-margin-start) + var(--avatar-margin-end)
);
}
.timeline.contextual > li.thread .replies .replies li:before {
--line-start: calc(
var(--avatar-size) + var(--avatar-margin-start) + var(--avatar-margin-end) +
var(--line-margin-end)
);
}
.timeline.contextual.loading > li:not(.hero) {
/* opacity: 0.5; */

Wyświetl plik

@ -20,7 +20,6 @@
.hero-heading {
font-size: 16px;
pointer-events: none;
display: inline-block;
margin-bottom: 0.25em;
}

Wyświetl plik

@ -9,6 +9,7 @@ import { InView } from 'react-intersection-observer';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { useSnapshot } from 'valtio';
import Avatar from '../components/avatar';
import Icon from '../components/icon';
import Link from '../components/link';
import Loader from '../components/loader';
@ -24,6 +25,7 @@ import useScroll from '../utils/useScroll';
import useTitle from '../utils/useTitle';
const LIMIT = 40;
const THREAD_LIMIT = 20;
let cachedStatusesMap = {};
function resetScrollPosition(id) {
@ -164,8 +166,16 @@ function StatusPage() {
thread: s.account.id === heroStatus.account.id,
replies: s.__replies?.map((r) => ({
id: r.id,
account: r.account,
repliesCount: r.repliesCount,
content: r.content,
replies: r.__replies?.map((r2) => ({
// Level 3
id: r2.id,
account: r2.account,
repliesCount: r2.repliesCount,
content: r2.content,
})),
})),
})),
];
@ -305,7 +315,7 @@ function StatusPage() {
return statuses.length - limit;
}, [statuses.length, limit]);
const hasManyStatuses = statuses.length > LIMIT;
const hasManyStatuses = statuses.length > THREAD_LIMIT;
const hasDescendants = statuses.some((s) => s.descendant);
const ancestors = statuses.filter((s) => s.ancestor);
@ -405,17 +415,6 @@ function StatusPage() {
>
<header
class={`${heroInView ? 'inview' : ''}`}
onClick={(e) => {
if (
!/^(a|button)$/i.test(e.target.tagName) &&
heroStatusRef.current
) {
heroStatusRef.current.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
}}
onDblClick={(e) => {
// reload statuses
states.reloadStatusPage++;
@ -428,23 +427,34 @@ function StatusPage() {
</div> */}
<h1>
{!heroInView && heroStatus && uiState !== 'loading' ? (
<span class="hero-heading">
{!!heroPointer && (
<>
<Icon
icon={heroPointer === 'down' ? 'arrow-down' : 'arrow-up'}
/>{' '}
</>
)}
<NameText showAvatar account={heroStatus.account} short />{' '}
<span class="insignificant">
&bull;{' '}
<RelativeTime
datetime={heroStatus.createdAt}
format="micro"
<>
<span class="hero-heading">
<NameText showAvatar account={heroStatus.account} short />{' '}
<span class="insignificant">
&bull;{' '}
<RelativeTime
datetime={heroStatus.createdAt}
format="micro"
/>
</span>
</span>{' '}
<button
type="button"
class="ancestors-indicator light small"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
heroStatusRef.current.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}}
>
<Icon
icon={heroPointer === 'down' ? 'arrow-down' : 'arrow-up'}
/>
</span>
</span>
</button>
</>
) : (
<>
Status{' '}
@ -551,24 +561,22 @@ function StatusPage() {
withinContext
size={thread || ancestor ? 'm' : 's'}
/>
{replies?.length > LIMIT && (
{/* {replies?.length > LIMIT && (
<div class="replies-link">
<Icon icon="comment" />{' '}
<span title={replies.length}>
{shortenNumber(replies.length)}
</span>
</div>
)}
)} */}
</Link>
)}
{descendant &&
replies?.length > 0 &&
replies?.length <= LIMIT && (
<SubComments
hasManyStatuses={hasManyStatuses}
replies={replies}
/>
)}
{descendant && replies?.length > 0 && (
<SubComments
hasManyStatuses={hasManyStatuses}
replies={replies}
/>
)}
{uiState === 'loading' &&
isHero &&
!!heroStatus?.repliesCount &&
@ -658,13 +666,31 @@ function SubComments({ hasManyStatuses, replies }) {
isBrief = totalLength < 500;
}
// Get the first 3 accounts, unique by id
const accounts = replies
.map((r) => r.account)
.filter((a, i, arr) => arr.findIndex((b) => b.id === a.id) === i)
.slice(0, 5);
const open = isBrief || !hasManyStatuses;
return (
<details class="replies" open={open}>
<summary hidden={open}>
<span title={replies.length}>{shortenNumber(replies.length)}</span> repl
{replies.length === 1 ? 'y' : 'ies'}
<span class="avatars">
{accounts.map((a) => (
<Avatar
key={a.id}
url={a.avatarStatic}
title={`${a.displayName} @${a.username}`}
/>
))}
</span>
<span>
<span title={replies.length}>{shortenNumber(replies.length)}</span>{' '}
repl
{replies.length === 1 ? 'y' : 'ies'}
</span>
</summary>
<ul>
{replies.map((r) => (
@ -677,15 +703,21 @@ function SubComments({ hasManyStatuses, replies }) {
}}
>
<Status statusID={r.id} withinContext size="s" />
{r.repliesCount > 0 && (
{/* {r.repliesCount > 0 && (
<div class="replies-link">
<Icon icon="comment" />{' '}
<span title={r.repliesCount}>
{shortenNumber(r.repliesCount)}
</span>
</div>
)}
)} */}
</Link>
{r.replies?.length && (
<SubComments
hasManyStatuses={hasManyStatuses}
replies={r.replies}
/>
)}
</li>
))}
</ul>