Early implementation of media-first UI experience

pull/493/head
Lim Chee Aun 2024-04-11 17:18:17 +08:00
rodzic 6e73728e2b
commit a0d2037007
5 zmienionych plików z 682 dodań i 230 usunięć

Wyświetl plik

@ -301,6 +301,34 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
}
}
}
.deck-container-media-first {
.timeline {
> li:not(.timeline-item-carousel, .timeline-item-container) {
&:has(.status-media-first) {
width: fit-content;
background-color: transparent !important;
border: 0 !important;
box-shadow: none !important;
max-width: min(480px, 100%);
margin-inline: auto !important;
&:has(.skeleton) {
width: 100%;
}
}
&:has(.media[data-orientation='landscape']) {
max-width: 100%;
}
}
.status-link:has(.status-media-first):hover {
background-color: transparent;
}
}
}
.timeline.grow {
/* min-height: 100vh;
min-height: 100dvh; */

Wyświetl plik

@ -618,6 +618,7 @@
~ *:not(
.content.truncated,
.media-container,
.media-first-container,
.card,
.media-figure-multiple,
.spoiler-media-button
@ -638,6 +639,7 @@
~ *:not(
.media-container,
.media-first-container,
.card,
.media-figure-multiple,
.spoiler-media-button
@ -708,11 +710,12 @@
}
}
~ :is(.media-container, .media-figure-multiple) .media {
~ :is(.media-container, .media-first-container, .media-figure-multiple)
.media {
background-image: radial-gradient(
circle at 50% 50%,
var(--average-color, var(--bg-faded-color)),
var(--bg-color) 20em
var(--bg-color) 25em
);
> *:not(.media-play, .alt-badge) {
@ -1316,6 +1319,227 @@ body:has(#modal-container .carousel) .status .media img:hover {
background-blend-mode: multiply;
}
.status.skeleton .media-first-container {
min-height: 3em;
background-color: var(--outline-color);
}
.status-media-first {
.meta-name {
opacity: 0.65;
transition: opacity 0.5s ease-in-out;
b + i {
opacity: 0;
transition: opacity 0.5s ease-in-out;
}
}
:is(:hover, :focus) > & .meta-name {
opacity: 1;
b + i {
opacity: 0.5;
}
}
.media-first-spoiler-content {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
transition: opacity 0.5s ease-in-out;
opacity: 0.5;
}
&:hover .media-first-spoiler-content {
opacity: 1;
}
.media-first-spoiler-button {
display: inline-flex !important;
}
.media-first-container {
margin-top: 8px;
display: flex;
max-height: 80vh;
overflow-x: auto;
overflow-y: hidden;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
user-select: none;
margin-inline: -16px;
position: relative;
scrollbar-width: none;
/* border: var(--hairline-width) solid var(--outline-color);
border-inline-width: 0;
background-color: var(--bg-faded-color); */
@media (min-width: 40em) {
margin-inline: 0;
/* border-radius: 4px; */
border-inline-width: var(--hairline-width);
}
&::-webkit-scrollbar {
display: none;
}
> .media-first-item {
scroll-snap-align: center;
scroll-snap-stop: always;
flex-shrink: 0;
display: flex;
width: 100%;
align-items: center;
justify-content: center;
&:not(:only-child) {
background-color: var(--bg-blur-color);
box-shadow: inset 0 0 0 var(--hairline-width) var(--outline-color);
}
.media {
/* background-color: var(--average-color, var(--bg-faded-color)); */
width: var(--width);
max-width: 100%;
max-height: 100%;
min-height: var(--min-dimension);
/* max-height: min(var(--height), 80vh); */
&:active {
transform: none;
}
img,
video {
object-fit: scale-down;
animation: none;
&:not([data-loaded='true']) {
background-color: var(--bg-color);
}
}
}
}
.media-carousel-controls {
flex-shrink: 0;
width: 100%;
position: sticky;
right: 0;
left: 0;
pointer-events: none;
display: flex;
justify-content: space-between;
}
.carousel-indexer {
z-index: 1;
position: absolute;
top: 8px;
right: 8px;
color: var(--media-fg-color);
background-color: var(--media-bg-color);
padding: 2px 8px;
border-radius: 16px;
font-size: 0.8em;
font-variant-numeric: tabular-nums;
opacity: 0.6;
transition: opacity 1.5s ease-in-out;
border: var(--hairline-width) solid var(--media-outline-color);
}
.media-carousel-button {
display: flex;
flex-shrink: 0;
padding-inline: 8px;
margin-block: 3em;
pointer-events: auto;
cursor: pointer;
align-items: center;
justify-content: center;
}
.carousel-button {
@media (pointer: coarse) {
display: none;
}
+ .carousel-button {
left: auto;
right: 8px;
}
}
@media (hover: hover) and (pointer: fine) {
.carousel-button {
filter: opacity(0);
}
&:hover .carousel-button {
filter: opacity(1);
}
}
}
:is(:hover, :focus) > & .carousel-indexer {
opacity: 0;
}
.media-carousel-dots {
pointer-events: none;
display: flex;
gap: 5px;
justify-content: center;
margin-top: 8px;
padding: 8px;
.carousel-dot {
display: inline-block;
width: 5px;
height: 5px;
border-radius: 50%;
background-color: var(--text-color);
transition: all 0.3s ease-in-out;
opacity: 0.3;
&.active {
opacity: 1;
background-color: var(--text-color);
transform: scale(1.5);
}
}
}
.media-first-content {
margin-top: 8px;
height: 1.75em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.9em;
mask-image: linear-gradient(to bottom, black 1.5em, transparent 1.75em);
opacity: 0.5;
transition: opacity 0.5s ease-in-out;
@media (min-width: 40em) {
margin-inline: 16px;
}
* {
text-align: center;
/* Brute force ellipsis */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap !important;
pointer-events: none;
}
a {
filter: grayscale(0.5);
}
}
:is(:hover, :focus) > & .media-first-content {
opacity: 1;
}
}
.status:not(.large) .hashtag-stuffing {
opacity: 0.75;
transition: opacity 0.2s ease-in-out;

Wyświetl plik

@ -169,15 +169,19 @@ function Status({
allowContextMenu,
showActionsBar,
showReplyParent,
mediaFirst,
}) {
if (skeleton) {
return (
<div class="status skeleton">
<Avatar size="xxl" />
<div class={`status skeleton ${mediaFirst ? 'status-media-first' : ''}`}>
{!mediaFirst && <Avatar size="xxl" />}
<div class="container">
<div class="meta"> </div>
<div class="meta">
{(size === 's' || mediaFirst) && <Avatar size="m" />}
</div>
<div class="content-container">
<div class="content">
{mediaFirst && <div class="media-first-container" />}
<div class={`content ${mediaFirst ? 'media-first-content' : ''}`}>
<p> </p>
</div>
</div>
@ -247,6 +251,10 @@ function Status({
emojiReactions,
} = status;
// if (!mediaAttachments?.length) mediaFirst = false;
const hasMediaAttachments = !!mediaAttachments?.length;
if (mediaFirst && hasMediaAttachments) size = 's';
const currentAccount = useMemo(() => {
return store.session.get('currentAccount');
}, []);
@ -354,6 +362,7 @@ function Status({
size={size}
contentTextWeight={contentTextWeight}
readOnly={readOnly}
mediaFirst={mediaFirst}
/>
</div>
);
@ -378,6 +387,7 @@ function Status({
contentTextWeight={contentTextWeight}
readOnly={readOnly}
enableCommentHint
mediaFirst={mediaFirst}
/>
</div>
);
@ -411,6 +421,7 @@ function Status({
contentTextWeight={contentTextWeight}
readOnly={readOnly}
enableCommentHint
mediaFirst={mediaFirst}
/>
</div>
);
@ -848,56 +859,62 @@ function Status({
</MenuItem>
</>
)}
{(enableTranslate || !language || differentLanguage) && <MenuDivider />}
{enableTranslate ? (
<div class={supportsTTS ? 'menu-horizontal' : ''}>
<MenuItem
disabled={forceTranslate}
onClick={() => {
setForceTranslate(true);
}}
>
<Icon icon="translate" />
<span>Translate</span>
</MenuItem>
{supportsTTS && (
<MenuItem
onClick={() => {
const postText = getPostText(status);
if (postText) {
speak(postText, language);
}
}}
>
<Icon icon="speak" />
<span>Speak</span>
</MenuItem>
{!mediaFirst && (
<>
{(enableTranslate || !language || differentLanguage) && (
<MenuDivider />
)}
</div>
) : (
(!language || differentLanguage) && (
<div class={supportsTTS ? 'menu-horizontal' : ''}>
<MenuLink
to={`${instance ? `/${instance}` : ''}/s/${id}?translate=1`}
>
<Icon icon="translate" />
<span>Translate</span>
</MenuLink>
{supportsTTS && (
{enableTranslate ? (
<div class={supportsTTS ? 'menu-horizontal' : ''}>
<MenuItem
disabled={forceTranslate}
onClick={() => {
const postText = getPostText(status);
if (postText) {
speak(postText, language);
}
setForceTranslate(true);
}}
>
<Icon icon="speak" />
<span>Speak</span>
<Icon icon="translate" />
<span>Translate</span>
</MenuItem>
)}
</div>
)
{supportsTTS && (
<MenuItem
onClick={() => {
const postText = getPostText(status);
if (postText) {
speak(postText, language);
}
}}
>
<Icon icon="speak" />
<span>Speak</span>
</MenuItem>
)}
</div>
) : (
(!language || differentLanguage) && (
<div class={supportsTTS ? 'menu-horizontal' : ''}>
<MenuLink
to={`${instance ? `/${instance}` : ''}/s/${id}?translate=1`}
>
<Icon icon="translate" />
<span>Translate</span>
</MenuLink>
{supportsTTS && (
<MenuItem
onClick={() => {
const postText = getPostText(status);
if (postText) {
speak(postText, language);
}
}}
>
<Icon icon="speak" />
<span>Speak</span>
</MenuItem>
)}
</div>
)
)}
</>
)}
{((!isSizeLarge && sameInstance) ||
enableTranslate ||
@ -1384,7 +1401,7 @@ function Status({
}[size]
} ${_deleted ? 'status-deleted' : ''} ${quoted ? 'status-card' : ''} ${
isContextMenuOpen ? 'status-menu-open' : ''
}`}
} ${mediaFirst && hasMediaAttachments ? 'status-media-first' : ''}`}
onMouseEnter={debugHover}
onContextMenu={(e) => {
if (!showContextMenu) return;
@ -1712,188 +1729,253 @@ function Status({
}
}
>
{!!spoilerText && (
{mediaFirst && hasMediaAttachments ? (
<>
<div
class="content spoiler-content"
lang={language}
dir="auto"
ref={spoilerContentRef}
data-read-more={readMoreText}
>
<p>
<EmojiText text={spoilerText} emojis={emojis} />
</p>
</div>
{readingExpandSpoilers || previewMode ? (
<div class="spoiler-divider">
<Icon icon="eye-open" /> Content warning
{(!!spoilerText || !!sensitive) && !readingExpandSpoilers && (
<>
{!!spoilerText && (
<span
class="spoiler-content media-first-spoiler-content"
lang={language}
dir="auto"
ref={spoilerContentRef}
data-read-more={readMoreText}
>
<EmojiText text={spoilerText} emojis={emojis} />{' '}
</span>
)}
<button
class={`light spoiler-button media-first-spoiler-button ${
showSpoiler ? 'spoiling' : ''
}`}
type="button"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (showSpoiler) {
delete states.spoilers[id];
if (!readingExpandSpoilers) {
delete states.spoilersMedia[id];
}
} else {
states.spoilers[id] = true;
if (!readingExpandSpoilers) {
states.spoilersMedia[id] = true;
}
}
}}
>
<Icon icon={showSpoiler ? 'eye-open' : 'eye-close'} />{' '}
{showSpoiler ? 'Show less' : 'Show content'}
</button>
</>
)}
<MediaFirstContainer
mediaAttachments={mediaAttachments}
language={language}
postID={id}
instance={instance}
/>
{!!content && (
<div class="media-first-content content" ref={contentRef}>
<PostContent
post={status}
instance={instance}
previewMode={previewMode}
/>
</div>
) : (
<button
class={`light spoiler-button ${
showSpoiler ? 'spoiling' : ''
}`}
type="button"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (showSpoiler) {
delete states.spoilers[id];
if (!readingExpandSpoilers) {
delete states.spoilersMedia[id];
}
} else {
states.spoilers[id] = true;
if (!readingExpandSpoilers) {
states.spoilersMedia[id] = true;
}
}
}}
>
<Icon icon={showSpoiler ? 'eye-open' : 'eye-close'} />{' '}
{showSpoiler ? 'Show less' : 'Show content'}
</button>
)}
</>
)}
{!!content && (
<div
class="content"
ref={contentRef}
data-read-more={readMoreText}
>
<PostContent
post={status}
instance={instance}
previewMode={previewMode}
/>
<QuoteStatuses id={id} instance={instance} level={quoted} />
</div>
)}
{!!poll && (
<Poll
lang={language}
poll={poll}
readOnly={readOnly || !sameInstance || !authenticated}
onUpdate={(newPoll) => {
states.statuses[sKey].poll = newPoll;
}}
refresh={() => {
return masto.v1.polls
.$select(poll.id)
.fetch()
.then((pollResponse) => {
states.statuses[sKey].poll = pollResponse;
})
.catch((e) => {}); // Silently fail
}}
votePoll={(choices) => {
return masto.v1.polls
.$select(poll.id)
.votes.create({
choices,
})
.then((pollResponse) => {
states.statuses[sKey].poll = pollResponse;
})
.catch((e) => {}); // Silently fail
}}
/>
)}
{(((enableTranslate || inlineTranslate) &&
!!content.trim() &&
!!getHTMLText(emojifyText(content, emojis)) &&
differentLanguage) ||
forceTranslate) && (
<TranslationBlock
forceTranslate={forceTranslate || inlineTranslate}
mini={!isSizeLarge && !withinContext}
sourceLanguage={language}
text={getPostText(status)}
/>
)}
{!previewMode &&
sensitive &&
!!mediaAttachments.length &&
readingExpandMedia !== 'show_all' && (
<button
class={`plain spoiler-media-button ${
showSpoilerMedia ? 'spoiling' : ''
}`}
type="button"
hidden={!readingExpandSpoilers && !!spoilerText}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (showSpoilerMedia) {
delete states.spoilersMedia[id];
} else {
states.spoilersMedia[id] = true;
}
}}
>
<Icon icon={showSpoilerMedia ? 'eye-open' : 'eye-close'} />{' '}
{showSpoilerMedia ? 'Show less' : 'Show media'}
</button>
)}
{!!mediaAttachments.length && (
<MultipleMediaFigure
lang={language}
enabled={showMultipleMediaCaptions}
captionChildren={captionChildren}
>
<div
ref={mediaContainerRef}
class={`media-container media-eq${mediaAttachments.length} ${
mediaAttachments.length > 2 ? 'media-gt2' : ''
} ${mediaAttachments.length > 4 ? 'media-gt4' : ''}`}
>
{displayedMediaAttachments.map((media, i) => (
<Media
key={media.id}
media={media}
autoAnimate={isSizeLarge}
showCaption={mediaAttachments.length === 1}
allowLongerCaption={
!content && mediaAttachments.length === 1
}
) : (
<>
{!!spoilerText && (
<>
<div
class="content spoiler-content"
lang={language}
altIndex={
showMultipleMediaCaptions &&
!!media.description &&
i + 1
}
to={`/${instance}/s/${id}?${
withinContext ? 'media' : 'media-only'
}=${i + 1}`}
onClick={
onMediaClick
? (e) => {
onMediaClick(e, i, media, status);
dir="auto"
ref={spoilerContentRef}
data-read-more={readMoreText}
>
<p>
<EmojiText text={spoilerText} emojis={emojis} />
</p>
</div>
{readingExpandSpoilers || previewMode ? (
<div class="spoiler-divider">
<Icon icon="eye-open" /> Content warning
</div>
) : (
<button
class={`light spoiler-button ${
showSpoiler ? 'spoiling' : ''
}`}
type="button"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (showSpoiler) {
delete states.spoilers[id];
if (!readingExpandSpoilers) {
delete states.spoilersMedia[id];
}
: undefined
}
} else {
states.spoilers[id] = true;
if (!readingExpandSpoilers) {
states.spoilersMedia[id] = true;
}
}
}}
>
<Icon icon={showSpoiler ? 'eye-open' : 'eye-close'} />{' '}
{showSpoiler ? 'Show less' : 'Show content'}
</button>
)}
</>
)}
{!!content && (
<div
class="content"
ref={contentRef}
data-read-more={readMoreText}
>
<PostContent
post={status}
instance={instance}
previewMode={previewMode}
/>
))}
</div>
</MultipleMediaFigure>
<QuoteStatuses id={id} instance={instance} level={quoted} />
</div>
)}
{!!poll && (
<Poll
lang={language}
poll={poll}
readOnly={readOnly || !sameInstance || !authenticated}
onUpdate={(newPoll) => {
states.statuses[sKey].poll = newPoll;
}}
refresh={() => {
return masto.v1.polls
.$select(poll.id)
.fetch()
.then((pollResponse) => {
states.statuses[sKey].poll = pollResponse;
})
.catch((e) => {}); // Silently fail
}}
votePoll={(choices) => {
return masto.v1.polls
.$select(poll.id)
.votes.create({
choices,
})
.then((pollResponse) => {
states.statuses[sKey].poll = pollResponse;
})
.catch((e) => {}); // Silently fail
}}
/>
)}
{(((enableTranslate || inlineTranslate) &&
!!content.trim() &&
!!getHTMLText(emojifyText(content, emojis)) &&
differentLanguage) ||
forceTranslate) && (
<TranslationBlock
forceTranslate={forceTranslate || inlineTranslate}
mini={!isSizeLarge && !withinContext}
sourceLanguage={language}
text={getPostText(status)}
/>
)}
{!previewMode &&
sensitive &&
!!mediaAttachments.length &&
readingExpandMedia !== 'show_all' && (
<button
class={`plain spoiler-media-button ${
showSpoilerMedia ? 'spoiling' : ''
}`}
type="button"
hidden={!readingExpandSpoilers && !!spoilerText}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (showSpoilerMedia) {
delete states.spoilersMedia[id];
} else {
states.spoilersMedia[id] = true;
}
}}
>
<Icon
icon={showSpoilerMedia ? 'eye-open' : 'eye-close'}
/>{' '}
{showSpoilerMedia ? 'Show less' : 'Show media'}
</button>
)}
{!!mediaAttachments.length && (
<MultipleMediaFigure
lang={language}
enabled={showMultipleMediaCaptions}
captionChildren={captionChildren}
>
<div
ref={mediaContainerRef}
class={`media-container media-eq${
mediaAttachments.length
} ${mediaAttachments.length > 2 ? 'media-gt2' : ''} ${
mediaAttachments.length > 4 ? 'media-gt4' : ''
}`}
>
{displayedMediaAttachments.map((media, i) => (
<Media
key={media.id}
media={media}
autoAnimate={isSizeLarge}
showCaption={mediaAttachments.length === 1}
allowLongerCaption={
!content && mediaAttachments.length === 1
}
lang={language}
altIndex={
showMultipleMediaCaptions &&
!!media.description &&
i + 1
}
to={`/${instance}/s/${id}?${
withinContext ? 'media' : 'media-only'
}=${i + 1}`}
onClick={
onMediaClick
? (e) => {
onMediaClick(e, i, media, status);
}
: undefined
}
/>
))}
</div>
</MultipleMediaFigure>
)}
{!!card &&
/^https/i.test(card?.url) &&
!sensitive &&
!spoilerText &&
!poll &&
!mediaAttachments.length &&
!snapStates.statusQuotes[sKey] && (
<Card
card={card}
selfReferential={
card?.url === status.url || card?.url === status.uri
}
instance={currentInstance}
/>
)}
</>
)}
{!!card &&
/^https/i.test(card?.url) &&
!sensitive &&
!spoilerText &&
!poll &&
!mediaAttachments.length &&
!snapStates.statusQuotes[sKey] && (
<Card
card={card}
selfReferential={
card?.url === status.url || card?.url === status.uri
}
instance={currentInstance}
/>
)}
</div>
{!isSizeLarge && showCommentCount && (
<div class="content-comment-hint insignificant">
@ -2171,6 +2253,101 @@ function MultipleMediaFigure(props) {
);
}
function MediaFirstContainer(props) {
const { mediaAttachments, language, postID, instance } = props;
const moreThanOne = mediaAttachments.length > 1;
const carouselRef = useRef();
const [currentIndex, setCurrentIndex] = useState(0);
useEffect(() => {
let handleScroll = () => {
const { clientWidth, scrollLeft } = carouselRef.current;
const index = Math.round(scrollLeft / clientWidth);
setCurrentIndex(index);
};
if (carouselRef.current) {
carouselRef.current.addEventListener('scroll', handleScroll, {
passive: true,
});
}
return () => {
if (carouselRef.current) {
carouselRef.current.removeEventListener('scroll', handleScroll);
}
};
}, []);
return (
<>
<div class="media-first-container" ref={carouselRef}>
{mediaAttachments.map((media, i) => (
<div class="media-first-item" key={media.id}>
<Media
media={media}
lang={language}
to={`/${instance}/s/${postID}?media-only=${i + 1}`}
/>
</div>
))}
{moreThanOne && (
<div class="media-carousel-controls">
<div class="carousel-indexer">
{currentIndex + 1}/{mediaAttachments.length}
</div>
<label class="media-carousel-button">
<button
type="button"
class="carousel-button"
hidden={currentIndex === 0}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
carouselRef.current.focus();
carouselRef.current.scrollTo({
left: carouselRef.current.clientWidth * (currentIndex - 1),
behavior: 'smooth',
});
}}
>
<Icon icon="arrow-left" />
</button>
</label>
<label class="media-carousel-button">
<button
type="button"
class="carousel-button"
hidden={currentIndex === mediaAttachments.length - 1}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
carouselRef.current.focus();
carouselRef.current.scrollTo({
left: carouselRef.current.clientWidth * (currentIndex + 1),
behavior: 'smooth',
});
}}
>
<Icon icon="arrow-right" />
</button>
</label>
</div>
)}
</div>
{moreThanOne && (
<div class="media-carousel-dots">
{mediaAttachments.map((media, i) => (
<span
key={media.id}
class={`carousel-dot ${i === currentIndex ? 'active' : ''}`}
/>
))}
</div>
)}
</>
);
}
function Card({ card, selfReferential, instance }) {
const snapStates = useSnapshot(states);
const {

Wyświetl plik

@ -1,5 +1,11 @@
import { memo } from 'preact/compat';
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'preact/hooks';
import { useHotkeys } from 'react-hotkeys-hook';
import { InView } from 'react-intersection-observer';
import { useDebouncedCallback } from 'use-debounce';
@ -9,6 +15,7 @@ import FilterContext from '../utils/filter-context';
import { filteredItems, isFiltered } from '../utils/filters';
import states, { statusKey } from '../utils/states';
import statusPeek from '../utils/status-peek';
import { isMediaFirstInstance } from '../utils/store-utils';
import { groupBoosts, groupContext } from '../utils/timeline-utils';
import useInterval from '../utils/useInterval';
import usePageVisibility from '../utils/usePageVisibility';
@ -59,6 +66,8 @@ function Timeline({
console.debug('RENDER Timeline', id, refresh);
const mediaFirst = useMemo(() => isMediaFirstInstance(), []);
const allowGrouping = view !== 'media';
const loadItems = useDebouncedCallback(
(firstLoad) => {
@ -355,7 +364,9 @@ function Timeline({
<FilterContext.Provider value={filterContext}>
<div
id={`${id}-page`}
class="deck-container"
class={`deck-container ${
mediaFirst ? 'deck-container-media-first' : ''
}`}
ref={(node) => {
scrollableRef.current = node;
jRef.current = node;
@ -432,6 +443,7 @@ function Timeline({
view={view}
showFollowedTags={showFollowedTags}
showReplyParent={showReplyParent}
mediaFirst={mediaFirst}
/>
))}
{showMore &&
@ -443,14 +455,14 @@ function Timeline({
height: '20vh',
}}
>
<Status skeleton />
<Status skeleton mediaFirst={mediaFirst} />
</li>
<li
style={{
height: '25vh',
}}
>
<Status skeleton />
<Status skeleton mediaFirst={mediaFirst} />
</li>
</>
))}
@ -490,7 +502,7 @@ function Timeline({
/>
) : (
<li key={i}>
<Status skeleton />
<Status skeleton mediaFirst={mediaFirst} />
</li>
),
)}
@ -525,6 +537,7 @@ const TimelineItem = memo(
view,
showFollowedTags,
showReplyParent,
mediaFirst,
}) => {
console.debug('RENDER TimelineItem', status.id);
const { id: statusID, reblog, items, type, _pinned } = status;
@ -533,6 +546,7 @@ const TimelineItem = memo(
const url = instance
? `/${instance}/s/${actualStatusID}`
: `/s/${actualStatusID}`;
if (items) {
const fItems = filteredItems(items, filterContext);
let title = '';
@ -585,6 +599,7 @@ const TimelineItem = memo(
contentTextWeight
enableCommentHint
// allowFilters={allowFilters}
mediaFirst={mediaFirst}
/>
) : (
<Status
@ -594,6 +609,7 @@ const TimelineItem = memo(
contentTextWeight
enableCommentHint
// allowFilters={allowFilters}
mediaFirst={mediaFirst}
/>
)}
</Link>
@ -689,6 +705,7 @@ const TimelineItem = memo(
showFollowedTags={showFollowedTags}
showReplyParent={showReplyParent}
// allowFilters={allowFilters}
mediaFirst={mediaFirst}
/>
) : (
<Status
@ -698,6 +715,7 @@ const TimelineItem = memo(
showFollowedTags={showFollowedTags}
showReplyParent={showReplyParent}
// allowFilters={allowFilters}
mediaFirst={mediaFirst}
/>
)}
</Link>

Wyświetl plik

@ -126,3 +126,8 @@ export function getCurrentInstanceConfiguration() {
const instance = getCurrentInstance();
return getInstanceConfiguration(instance);
}
export function isMediaFirstInstance() {
const instance = getCurrentInstance();
return /pixelfed/i.test(instance?.version);
}