From 54314de976ab31d2253cae28e67af20cca64e1c2 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun <cheeaun@gmail.com> Date: Fri, 5 Jan 2024 19:15:22 +0800 Subject: [PATCH] Experiment unlinked replies (again) But still show link to the post's "thread" --- src/app.css | 90 ++++++++++++++++++++++---- src/index.css | 1 + src/pages/status.jsx | 151 +++++++++++++++++++++++++++---------------- 3 files changed, 176 insertions(+), 66 deletions(-) diff --git a/src/app.css b/src/app.css index c311881b..baf91efd 100644 --- a/src/app.css +++ b/src/app.css @@ -547,9 +547,10 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) { list-style: none; } .timeline.contextual > li .replies > .replies-summary { - padding: 8px; + --summary-padding: 8px; + padding: var(--summary-padding); background-color: var(--bg-faded-color); - display: inline-block; + display: inline-flex; border-radius: 8px; cursor: pointer; text-transform: uppercase; @@ -559,12 +560,67 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) { box-shadow: 0 0 0 2px var(--bg-color); position: relative; list-style: none; - white-space: nowrap; + gap: 8px; + align-items: center; + margin-right: calc(44px + 8px); b { font-weight: 500; color: var(--text-color); } + + .avatars { + flex-shrink: 0; + transition: opacity 0.3s ease; + + .avatar { + transition: transform 0.3s ease; + + &:not(:first-child) { + margin: 0 0 0 -4px; + } + } + } + + .replies-counts { + /* flex-grow: 1; */ + + > * { + display: inline-block; + } + } + + .replies-summary-chevron { + transition: transform 0.3s ease; + } + + .replies-parent-link { + position: absolute; + right: 4px; + height: 100%; + z-index: 2; + font-size: 16px; + font-weight: bold; + align-self: stretch; + text-decoration: none; + display: flex; + align-items: center; + padding: var(--summary-padding) calc(var(--summary-padding) * 2); + transform: translateX(100%); + margin: calc(-1 * var(--summary-padding)) calc(-1 * var(--summary-padding)) + calc(-1 * var(--summary-padding)) 0; + border-radius: 8px; + background-color: var(--link-bg-color); + + &:is(:hover, :focus) { + color: var(--link-text-color); + box-shadow: inset 0 0 0 2px var(--link-faded-color); + } + + &:active { + background-color: var(--link-faded-color); + } + } } .timeline.contextual > li .replies > .replies-summary::-webkit-details-marker { display: none; @@ -572,14 +628,14 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) { .timeline.contextual > li .replies > .replies-summary > * { vertical-align: middle; } -.timeline.contextual > li .replies > .replies-summary .avatars { - margin-right: 8px; - - > *:not(:first-child) { - margin: 0 0 0 -4px; - } -} -.timeline.contextual > li .replies > .replies-summary:active, +.timeline.contextual + > li + .replies + > .replies-summary + .timeline.contextual + > li + .replies + > .replies-summary:active, .timeline.contextual > li .replies[open] > .replies-summary { color: var(--text-color); background-color: var(--comment-line-color); @@ -591,6 +647,18 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) { } .timeline.contextual > li .replies[open] > .replies-summary { border-bottom-left-radius: 0; + + .avatars { + opacity: 0.5; + + .avatar { + transform: rotate(-15deg); + } + } + + .replies-summary-chevron { + transform: rotate(180deg); + } } .timeline.contextual > li .replies .replies-summary[hidden] { display: none; diff --git a/src/index.css b/src/index.css index 1cd4d7b2..5da6f219 100644 --- a/src/index.css +++ b/src/index.css @@ -32,6 +32,7 @@ --text-color: #1c1e21; --text-insignificant-color: #1c1e2199; --link-color: var(--blue-color); + --link-bg-color: #4169e122; --link-light-color: #4169e199; --link-faded-color: #4169e155; --link-bg-hover-color: #f0f2f599; diff --git a/src/pages/status.jsx b/src/pages/status.jsx index 69612e4f..aa425abb 100644 --- a/src/pages/status.jsx +++ b/src/pages/status.jsx @@ -184,6 +184,15 @@ function StatusPage(params) { ); } +function StatusParent(props) { + const { linkable, to, onClick, ...restProps } = props; + return linkable ? ( + <Link class="status-link" to={to} onClick={onClick} {...restProps} /> + ) : ( + <div class="status-focus" tabIndex={0} {...restProps} /> + ); +} + function StatusThread({ id, closeLink = '/', instance: propInstance }) { const [searchParams, setSearchParams] = useSearchParams(); const mediaParam = searchParams.get('media'); @@ -705,24 +714,8 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) { weight, } = status; const isHero = statusID === id; - // const StatusParent = useCallback( - // (props) => - // isThread || thread || ancestor ? ( - // <Link - // class="status-link" - // to={ - // instance ? `/${instance}/s/${statusID}` : `/s/${statusID}` - // } - // onClick={() => { - // resetScrollPosition(statusID); - // }} - // {...props} - // /> - // ) : ( - // <div class="status-focus" tabIndex={0} {...props} /> - // ), - // [isThread, thread], - // ); + const isLinkable = isThread || ancestor; + return ( <li key={statusID} @@ -808,25 +801,42 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) { )} </> ) : ( - // <StatusParent> - <Link - class="status-link" + <StatusParent + linkable={isLinkable} to={instance ? `/${instance}/s/${statusID}` : `/s/${statusID}`} onClick={() => { resetScrollPosition(statusID); }} > - <InView - skip={i !== 0 || !ancestor} - threshold={0.5} - onChange={(inView) => { - queueMicrotask(() => { - requestAnimationFrame(() => { - setReachTopPost(inView); + {/* <Link + class="status-link" + to={instance ? `/${instance}/s/${statusID}` : `/s/${statusID}`} + onClick={() => { + resetScrollPosition(statusID); + }} + > */} + {i === 0 && ancestor ? ( + <InView + threshold={0.5} + onChange={(inView) => { + queueMicrotask(() => { + requestAnimationFrame(() => { + setReachTopPost(inView); + }); }); - }); - }} - > + }} + > + <Status + statusID={statusID} + instance={instance} + withinContext + size={thread || ancestor ? 'm' : 's'} + enableTranslate + onMediaClick={handleMediaClick} + onStatusLinkClick={handleStatusLinkClick} + /> + </InView> + ) : ( <Status statusID={statusID} instance={instance} @@ -836,7 +846,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) { onMediaClick={handleMediaClick} onStatusLinkClick={handleStatusLinkClick} /> - </InView> + )} {ancestor && repliesCount > 1 && ( <div class="replies-link"> <Icon icon="comment2" />{' '} @@ -853,8 +863,8 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) { </span> </div> )} */} - {/* </StatusParent> */} - </Link> + </StatusParent> + // </Link> )} {descendant && replies?.length > 0 && ( <SubComments @@ -864,6 +874,10 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) { level={1} accWeight={weight} openAll={totalDescendants.current < SUBCOMMENTS_OPEN_ALL_LIMIT} + parentLink={{ + to: instance ? `/${instance}/s/${statusID}` : `/s/${statusID}`, + onClick: () => resetScrollPosition(statusID), + }} /> )} {uiState === 'loading' && @@ -932,6 +946,11 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) { return ids.map((id) => statusKey(id, instance)); }, [showMore, statuses, limit, instance]); + const statusesList = useMemo( + () => statuses.slice(0, limit).map(renderStatus), + [statuses, limit, renderStatus], + ); + return ( <div tabIndex="-1" @@ -1176,7 +1195,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) { uiState === 'loading' ? 'loading' : '' }`} > - {statuses.slice(0, limit).map(renderStatus)} + {statusesList} {showMore > 0 && ( <li> <button @@ -1244,6 +1263,7 @@ function SubComments({ level, accWeight, openAll, + parentLink, }) { const [searchParams, setSearchParams] = useSearchParams(); @@ -1330,34 +1350,49 @@ function SubComments({ /> ))} </span> - <b> - <span title={replies.length}>{shortenNumber(replies.length)}</span>{' '} - repl - {replies.length === 1 ? 'y' : 'ies'} - </b> - {!sameCount && totalComments > 1 && ( - <> - {' '} - ·{' '} - <span> - <span title={totalComments}>{shortenNumber(totalComments)}</span>{' '} - comment - {totalComments === 1 ? '' : 's'} - </span> - </> + <span class="replies-counts"> + <b> + <span title={replies.length}>{shortenNumber(replies.length)}</span>{' '} + repl + {replies.length === 1 ? 'y' : 'ies'} + </b> + {!sameCount && totalComments > 1 && ( + <> + {' '} + ·{' '} + <span> + <span title={totalComments}> + {shortenNumber(totalComments)} + </span>{' '} + comment + {totalComments === 1 ? '' : 's'} + </span> + </> + )} + </span> + <Icon icon="chevron-down" class="replies-summary-chevron" /> + {!!parentLink && ( + <Link + class="replies-parent-link" + to={parentLink.to} + onClick={parentLink.onClick} + title="View post with its replies" + > + » + </Link> )} </summary> <ul> {replies.map((r) => ( <li key={r.id}> - <Link + {/* <Link class="status-link" to={instance ? `/${instance}/s/${r.id}` : `/s/${r.id}`} onClick={() => { resetScrollPosition(r.id); }} - > - {/* <div class="status-focus" tabIndex={0}> */} + > */} + <div class="status-focus" tabIndex={0}> <Status statusID={r.id} instance={instance} @@ -1374,8 +1409,8 @@ function SubComments({ </span> </div> )} - {/* </div> */} - </Link> + </div> + {/* </Link> */} {r.replies?.length && ( <SubComments instance={instance} @@ -1383,6 +1418,12 @@ function SubComments({ level={level + 1} accWeight={!open ? r.weight : totalWeight} openAll={openAll} + parentLink={{ + to: instance ? `/${instance}/s/${r.id}` : `/s/${r.id}`, + onClick: () => { + resetScrollPosition(r.id); + }, + }} /> )} </li>