diff options
| author | 2025-04-18 17:36:26 +0200 | |
|---|---|---|
| committer | 2025-04-18 17:36:26 +0200 | |
| commit | 2fce02780876d5e4c4f32e0ca341c43a5d8b21a8 (patch) | |
| tree | 409adbe897de49e6ae2ca0dedbec4bfd044e10fd /web/source | |
| parent | [chore] Don't push `latest` Docker image on prerelease (#4017) (diff) | |
| download | gotosocial-2fce02780876d5e4c4f32e0ca341c43a5d8b21a8.tar.xz | |
[feature/frontend] Hide "engagement" stats, edits, and other info under a little drop down to unclutter status info bar (#4021)
* dick about with stats a bit
* more dicking abuot
* lil tweaks
* more about-dicking
* weee
* comments
* fixie uppie
Diffstat (limited to 'web/source')
| -rw-r--r-- | web/source/css/_media-wrapper.css | 3 | ||||
| -rw-r--r-- | web/source/css/status.css | 166 | ||||
| -rw-r--r-- | web/source/css/thread.css | 13 | ||||
| -rw-r--r-- | web/source/frontend/index.js | 22 | ||||
| -rw-r--r-- | web/source/settings/components/status.tsx | 113 |
5 files changed, 250 insertions, 67 deletions
diff --git a/web/source/css/_media-wrapper.css b/web/source/css/_media-wrapper.css index 561ae1ed3..b8541df4b 100644 --- a/web/source/css/_media-wrapper.css +++ b/web/source/css/_media-wrapper.css @@ -29,7 +29,6 @@ border-radius: $br; position: relative; overflow: hidden; - z-index: 2; img { width: 100%; @@ -59,8 +58,8 @@ position: absolute; height: 100%; width: 100%; - z-index: 3; overflow: hidden; + z-index: 1; display: grid; padding: 1rem; diff --git a/web/source/css/status.css b/web/source/css/status.css index 91665cd45..81ee7601a 100644 --- a/web/source/css/status.css +++ b/web/source/css/status.css @@ -28,8 +28,6 @@ padding-top: 0.75rem; a { - position: relative; - z-index: 1; color: inherit; text-decoration: none; } @@ -109,11 +107,6 @@ gap: 0.5rem; } - .text-spoiler > summary, .text { - position: relative; - z-index: 2; - } - .text-spoiler > summary { list-style: none; display: flex; @@ -193,7 +186,6 @@ .poll { background-color: $gray2; - z-index: 2; display: flex; flex-direction: column; @@ -260,59 +252,150 @@ display: flex; gap: 1rem; - .stats-grouping { + .stats-grouping, + .stats-more-info-content { display: flex; flex-wrap: wrap; - column-gap: 1rem; + } - .edited-at { - font-size: smaller; - } + .stats-grouping { + column-gap: 1rem; + row-gap: 0.25rem; } .stats-item { display: flex; gap: 0.4rem; + width: fit-content; } - .stats-item.published-at { - text-decoration: underline; + details.stats-more-info { + margin-left: auto; + + & > summary { + display: flex; + + /* + Make it easy to touch. + */ + width: 3rem; + height: 2rem; + margin: -0.25rem -0.5rem; + + /* + Remove details/summary + arrow and use our own. + */ + list-style: none; + &::-webkit-details-marker { + display: none; /* Safari */ + } + + /* + Don't display the + "hide" button initially. + */ + i.hide { + display: none; + } + + /* + Normalize fa + icon alignment. + */ + align-items: center; + i.fa { + text-align: center; + } + + cursor: pointer; + border-radius: $br-inner; + &:focus-visible { + outline: $button-focus-outline; + } + + &:hover { + outline: 0.1rem solid $fg-reduced; + } + } + + @keyframes fade-in { + 0% {opacity: 0} + 100% {opacity: 1} + } + + &[open] { + .stats-more-info-content { + animation: fade-in .1s; + } + + & > summary i.show { + display: none; + } + + & > summary i.hide { + display: block; + } + } } - .stats-item:not(.published-at):not(.edited-at) { - z-index: 1; + .stats-more-info-content { + position: absolute; + right: 0; + z-index: 2; + + flex-direction: column; + max-width: 100%; + row-gap: 0.5rem; + + background: $status-info-bg; + padding: 0.5rem 0.75rem; + border: $boxshadow-border; + box-shadow: $boxshadow; + + opacity: 1; + + .stats-grouping { + width: 100%; + justify-content: space-between; + } + } + + .stats-item.published-at dd a { + time.dt-published { + text-decoration: underline; + } + + &:focus-visible { + outline: 0; + time.dt-published { + outline: $link-focus-outline; + outline-offset: -0.25rem; + } + } + } + + .stats-item:not(.published-at):not(.edit-timeline) { user-select: none; } - .language { - margin-left: auto; + .stats-item.edit-timeline { + flex-direction: column; + width: 100%; + border-top: $boxshadow-border; + padding-top: 0.4rem; + + dd { + display: flex; + align-items: center; + gap: 0.4rem; + } } } grid-column: span 3; } - .status-link { - top: 0; - right: 0; - bottom: 0; - left: 0; - overflow: hidden; - text-indent: 100%; - white-space: nowrap; - - position: absolute; - z-index: 0; - - &:focus-visible { - /* - Inset focus to compensate for themes where - statuses have a really thick border. - */ - outline-offset: -0.25rem; - } - } - &:first-child { /* top left, top right */ border-top-left-radius: $br; @@ -327,7 +410,8 @@ &.expanded { background: $status-focus-bg; - .status-info { + .status-info, + .status-info .status-stats .stats-more-info-content { background: $status-focus-info-bg; } } diff --git a/web/source/css/thread.css b/web/source/css/thread.css index c67c95d4e..75dda550b 100644 --- a/web/source/css/thread.css +++ b/web/source/css/thread.css @@ -79,9 +79,18 @@ &.indent-3, &.indent-4, &.indent-5 { - .status-link { - margin-left: -0.5rem; + /* + Show a stripey line to the left of + indented statuses for better legibility. + */ + &::before { + content: ""; + position: absolute; + left: 0; + top: 0; + height: 100%; border-left: 0.15rem dashed $border-accent; + margin-left: -0.5rem; } } diff --git a/web/source/frontend/index.js b/web/source/frontend/index.js index 860d6d10a..da158ed77 100644 --- a/web/source/frontend/index.js +++ b/web/source/frontend/index.js @@ -338,3 +338,25 @@ Array.from(document.getElementsByTagName('time')).forEach(timeTag => { timeTag.textContent = dateTimeFormat.format(date); } }); + +// When clicking anywhere that's not an open +// stats-info-more-content details dropdown, +// close that open dropdown. +document.body.addEventListener("click", (e) => { + const openStats = document.querySelector("details.stats-more-info[open]"); + if (!openStats) { + // No open stats + // details element. + return; + } + + if (openStats.contains(e.target)) { + // Click is within stats + // element, leave it alone. + return; + } + + // Click was outside of + // stats elements, close it. + openStats.removeAttribute("open"); +}); diff --git a/web/source/settings/components/status.tsx b/web/source/settings/components/status.tsx index a5b85f214..9d0dfa2b4 100644 --- a/web/source/settings/components/status.tsx +++ b/web/source/settings/components/status.tsx @@ -17,7 +17,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -import React, { useState } from "react"; +import React, { useMemo, useState } from "react"; import { useVerifyCredentialsQuery } from "../lib/query/login"; import { MediaAttachment, Status as StatusType } from "../lib/types/status"; import sanitize from "sanitize-html"; @@ -68,15 +68,6 @@ export function Status({ status }: { status: StatusType }) { <StatusHeader status={status} /> <StatusBody status={status} /> <StatusFooter status={status} /> - <a - href={status.url} - target="_blank" - className="status-link" - data-nosnippet - title="Open this status (opens in new tab)" - > - Open this status (opens in new tab) - </a> </article> ); } @@ -266,25 +257,103 @@ function StatusMediaEntry({ media }: { media: MediaAttachment }) { ); } +function useVisibilityIcon(visibility: string): string { + return useMemo(() => { + switch (true) { + case visibility === "direct": + return "fa-envelope"; + case visibility === "followers_only": + return "fa-lock"; + case visibility === "unlisted": + return "fa-unlock"; + case visibility === "public": + return "fa-globe"; + default: + return "fa-question"; + } + }, [visibility]); +} + function StatusFooter({ status }: { status: StatusType }) { + const visibilityIcon = useVisibilityIcon(status.visibility); return ( <aside className="status-info"> - <dl className="status-stats"> - <div className="stats-grouping"> + <div className="status-stats"> + <dl className="stats-grouping text-cutoff"> <div className="stats-item published-at text-cutoff"> <dt className="sr-only">Published</dt> - <dd> - <time dateTime={status.created_at}> - { new Date(status.created_at).toLocaleString() } - </time> + <dd className="text-cutoff"> + <a + href={status.url} + className="u-url text-cutoff" + > + <time + className="dt-published text-cutoff" + dateTime={status.created_at} + > + {new Date(status.created_at).toLocaleString(undefined, { + year: 'numeric', + month: 'short', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + hour12: false + })} + </time> + </a> </dd> </div> - </div> - <div className="stats-item language"> - <dt className="sr-only">Language</dt> - <dd>{status.language}</dd> - </div> - </dl> + <div className="stats-grouping"> + <div className="stats-item visibility-level" title={status.visibility}> + <dt className="sr-only">Visibility</dt> + <dd> + <i className={`fa ${visibilityIcon}`} aria-hidden="true"></i> + <span className="sr-only">{status.visibility}</span> + </dd> + </div> + </div> + </dl> + <details className="stats-more-info"> + <summary title="More info"> + <i className="fa fa-fw fa-info" aria-hidden="true"></i> + <span className="sr-only">More info</span> + <i className="fa fa-fw fa-chevron-right show" aria-hidden="true"></i> + <i className="fa fa-fw fa-chevron-down hide" aria-hidden="true"></i> + </summary> + <dl className="stats-more-info-content"> + <div className="stats-grouping"> + <div className="stats-item" title="Language"> + <dt> + <span className="sr-only">Language</span> + <i className="fa fa-language" aria-hidden="true"></i> + </dt> + <dd>{status.language}</dd> + </div> + <div className="stats-item" title="Replies"> + <dt> + <span className="sr-only">Replies</span> + <i className="fa fa-reply-all" aria-hidden="true"></i> + </dt> + <dd>{status.replies_count}</dd> + </div> + <div className="stats-item" title="Faves"> + <dt> + <span className="sr-only">Favourites</span> + <i className="fa fa-star" aria-hidden="true"></i> + </dt> + <dd>{status.favourites_count}</dd> + </div> + <div className="stats-item" title="Boosts"> + <dt> + <span className="sr-only">Reblogs</span> + <i className="fa fa-retweet" aria-hidden="true"></i> + </dt> + <dd>{status.reblogs_count}</dd> + </div> + </div> + </dl> + </details> + </div> </aside> ); } |
