Alrighty, this is media-view layout
This commit is contained in:
parent
35f7cae01f
commit
b40bbb32c2
11 changed files with 440 additions and 53 deletions
72
src/app.css
72
src/app.css
|
@ -209,6 +209,60 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
|||
.timeline {
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
|
||||
&.timeline-media {
|
||||
--grid-gap: 8px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-auto-rows: fit-content;
|
||||
gap: var(--grid-gap);
|
||||
padding: var(--grid-gap);
|
||||
|
||||
&:not(#columns &) {
|
||||
background-color: var(--bg-faded-color);
|
||||
}
|
||||
|
||||
@media (min-width: 40em) {
|
||||
&:not(#columns &) {
|
||||
--grid-gap: 16px;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
|
||||
@media (min-width: 40em) {
|
||||
width: 95vw;
|
||||
max-width: calc(320px * 3.3);
|
||||
transform: translateX(calc(-50% + var(--main-width) / 2));
|
||||
}
|
||||
}
|
||||
|
||||
#columns & {
|
||||
padding-inline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
border: 0 !important;
|
||||
overflow: visible !important;
|
||||
background-color: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
@supports (grid-template-rows: masonry) {
|
||||
grid-template-rows: masonry;
|
||||
masonry-auto-flow: pack;
|
||||
|
||||
.media-post a {
|
||||
aspect-ratio: revert !important;
|
||||
|
||||
video,
|
||||
img,
|
||||
audio {
|
||||
min-height: 88px; /* for extreme dimensions */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.timeline.grow {
|
||||
/* min-height: 100vh;
|
||||
|
@ -1678,6 +1732,24 @@ body > .szh-menu-container {
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
.szh-menu
|
||||
.szh-menu__item--type-checkbox:not(.szh-menu__item--disabled):not(
|
||||
.szh-menu__item--hover
|
||||
) {
|
||||
.icon {
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
&.szh-menu__item--checked {
|
||||
color: var(--link-color);
|
||||
|
||||
.icon {
|
||||
opacity: 1;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.szh-menu .menu-wrap {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
|
@ -25,6 +25,7 @@ body.cloak,
|
|||
}
|
||||
|
||||
.status :is(img, video, audio),
|
||||
.media-post .media,
|
||||
.avatar,
|
||||
.emoji,
|
||||
.header-banner {
|
||||
|
|
|
@ -102,6 +102,7 @@ export const ICONS = {
|
|||
keyboard: () => import('@iconify-icons/mingcute/keyboard-line'),
|
||||
cloud: () => import('@iconify-icons/mingcute/cloud-line'),
|
||||
month: () => import('@iconify-icons/mingcute/calendar-month-line'),
|
||||
media: () => import('@iconify-icons/mingcute/photo-album-line'),
|
||||
};
|
||||
|
||||
function Icon({
|
||||
|
|
87
src/components/media-post.css
Normal file
87
src/components/media-post.css
Normal file
|
@ -0,0 +1,87 @@
|
|||
.media-post {
|
||||
--item-radius: 16px;
|
||||
position: relative;
|
||||
animation: appear-smooth 1s ease-out;
|
||||
|
||||
&:is(.filtered, .has-spoiler) :is(img, video) {
|
||||
filter: blur(32px);
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: pixelated;
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
&.filtered[data-filtered-text]:before,
|
||||
&.has-spoiler[data-spoiler-text]:before {
|
||||
pointer-events: none;
|
||||
content: attr(data-spoiler-text);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
background-color: var(--bg-blur-color);
|
||||
margin: 8px;
|
||||
padding: 4px 6px;
|
||||
border-radius: calc(var(--item-radius) / 2);
|
||||
font-size: 90%;
|
||||
border: var(--hairline-width) dashed var(--bg-color);
|
||||
|
||||
> * {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.media {
|
||||
border-radius: var(--item-radius);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: block;
|
||||
aspect-ratio: 1 !important;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
content: '';
|
||||
border: 1px solid var(--outline-color);
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
&:not(.media-audio) {
|
||||
background-color: var(--average-color, var(--media-bg-color));
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
--drop-shadow: var(--drop-shadow-color);
|
||||
box-shadow: 0 8px 16px -4px var(--drop-shadow),
|
||||
0 4px 8px var(--drop-shadow);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
--drop-shadow: var(--link-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:active:not(:has(button:active)) {
|
||||
box-shadow: none;
|
||||
filter: brightness(0.8);
|
||||
transform: scale(0.99);
|
||||
}
|
||||
|
||||
video,
|
||||
img,
|
||||
audio {
|
||||
border-radius: 16px;
|
||||
/* object-fit: scale-down; */
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
&:is(:hover, :focus) img {
|
||||
/* Less delay here to make it feel more responsive */
|
||||
animation: position-object 5s ease-in-out 0.3s 5;
|
||||
animation-duration: var(--anim-duration, 5s);
|
||||
}
|
||||
}
|
||||
}
|
126
src/components/media-post.jsx
Normal file
126
src/components/media-post.jsx
Normal file
|
@ -0,0 +1,126 @@
|
|||
import './media-post.css';
|
||||
|
||||
import { memo } from 'preact/compat';
|
||||
import { useSnapshot } from 'valtio';
|
||||
|
||||
import states, { statusKey } from '../utils/states';
|
||||
|
||||
import Media from './media';
|
||||
|
||||
function MediaPost({
|
||||
class: className,
|
||||
statusID,
|
||||
status,
|
||||
instance,
|
||||
parent,
|
||||
allowFilters,
|
||||
onMediaClick,
|
||||
}) {
|
||||
let sKey = statusKey(statusID, instance);
|
||||
const snapStates = useSnapshot(states);
|
||||
if (!status) {
|
||||
status = snapStates.statuses[sKey] || snapStates.statuses[statusID];
|
||||
sKey = statusKey(status?.id, instance);
|
||||
}
|
||||
if (!status) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
account: {
|
||||
acct,
|
||||
avatar,
|
||||
avatarStatic,
|
||||
id: accountId,
|
||||
url: accountURL,
|
||||
displayName,
|
||||
username,
|
||||
emojis: accountEmojis,
|
||||
bot,
|
||||
group,
|
||||
},
|
||||
id,
|
||||
repliesCount,
|
||||
reblogged,
|
||||
reblogsCount,
|
||||
favourited,
|
||||
favouritesCount,
|
||||
bookmarked,
|
||||
poll,
|
||||
muted,
|
||||
sensitive,
|
||||
spoilerText,
|
||||
visibility, // public, unlisted, private, direct
|
||||
language,
|
||||
editedAt,
|
||||
filtered,
|
||||
card,
|
||||
createdAt,
|
||||
inReplyToId,
|
||||
inReplyToAccountId,
|
||||
content,
|
||||
mentions,
|
||||
mediaAttachments,
|
||||
reblog,
|
||||
uri,
|
||||
url,
|
||||
emojis,
|
||||
// Non-API props
|
||||
_deleted,
|
||||
_pinned,
|
||||
_filtered,
|
||||
} = status;
|
||||
|
||||
if (!mediaAttachments?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const debugHover = (e) => {
|
||||
if (e.shiftKey) {
|
||||
console.log({
|
||||
...status,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
console.debug('RENDER Media post', id, status?.account.displayName);
|
||||
|
||||
// const readingExpandSpoilers = useMemo(() => {
|
||||
// const prefs = store.account.get('preferences') || {};
|
||||
// return !!prefs['reading:expand:spoilers'];
|
||||
// }, []);
|
||||
const hasSpoiler = spoilerText || sensitive;
|
||||
|
||||
const Parent = parent || 'div';
|
||||
|
||||
return mediaAttachments.map((media, i) => {
|
||||
const mediaKey = `${sKey}-${media.id}`;
|
||||
return (
|
||||
<Parent
|
||||
onMouseEnter={debugHover}
|
||||
key={mediaKey}
|
||||
data-spoiler-text={
|
||||
spoilerText || (sensitive ? 'Sensitive media' : undefined)
|
||||
}
|
||||
data-filtered-text={_filtered ? 'Filtered' : undefined}
|
||||
class={`
|
||||
media-post
|
||||
${allowFilters && _filtered ? 'filtered' : ''}
|
||||
${hasSpoiler ? 'has-spoiler' : ''}
|
||||
`}
|
||||
>
|
||||
<Media
|
||||
class={className}
|
||||
media={media}
|
||||
lang={language}
|
||||
to={`/${instance}/s/${id}?media-only=${i + 1}`}
|
||||
onClick={
|
||||
onMediaClick ? (e) => onMediaClick(e, i, media, status) : undefined
|
||||
}
|
||||
/>
|
||||
</Parent>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default memo(MediaPost);
|
|
@ -62,6 +62,7 @@ export const isMediaCaptionLong = mem((caption) =>
|
|||
);
|
||||
|
||||
function Media({
|
||||
class: className = '',
|
||||
media,
|
||||
to,
|
||||
lang,
|
||||
|
@ -170,6 +171,9 @@ function Media({
|
|||
const maxAspectHeight =
|
||||
window.innerHeight * (orientation === 'portrait' ? 0.45 : 0.33);
|
||||
const maxHeight = orientation === 'portrait' ? 0 : 160;
|
||||
const averageColorStyle = {
|
||||
'--average-color': rgbAverageColor && `rgb(${rgbAverageColor.join(',')})`,
|
||||
};
|
||||
const mediaStyles =
|
||||
width && height
|
||||
? {
|
||||
|
@ -180,8 +184,11 @@ function Media({
|
|||
(width / height) * Math.max(maxHeight, maxAspectHeight)
|
||||
}px`,
|
||||
aspectRatio: `${width} / ${height}`,
|
||||
...averageColorStyle,
|
||||
}
|
||||
: {};
|
||||
: {
|
||||
...averageColorStyle,
|
||||
};
|
||||
|
||||
const longDesc = isMediaCaptionLong(description);
|
||||
const showInlineDesc =
|
||||
|
@ -233,7 +240,7 @@ function Media({
|
|||
<Figure>
|
||||
<Parent
|
||||
ref={parentRef}
|
||||
class={`media media-image`}
|
||||
class={`media media-image ${className}`}
|
||||
onClick={onClick}
|
||||
data-orientation={orientation}
|
||||
data-has-alt={!showInlineDesc}
|
||||
|
@ -244,6 +251,7 @@ function Media({
|
|||
backgroundSize: imageSmallerThanParent
|
||||
? `${width}px ${height}px`
|
||||
: undefined,
|
||||
...averageColorStyle,
|
||||
}
|
||||
: mediaStyles
|
||||
}
|
||||
|
@ -341,11 +349,13 @@ function Media({
|
|||
return (
|
||||
<Figure>
|
||||
<Parent
|
||||
class={`media media-${isGIF ? 'gif' : 'video'} ${
|
||||
class={`media ${className} media-${isGIF ? 'gif' : 'video'} ${
|
||||
autoGIFAnimate ? 'media-contain' : ''
|
||||
}`}
|
||||
data-orientation={orientation}
|
||||
data-formatted-duration={formattedDuration}
|
||||
data-formatted-duration={
|
||||
!showOriginal ? formattedDuration : undefined
|
||||
}
|
||||
data-label={isGIF && !showOriginal && !autoGIFAnimate ? 'GIF' : ''}
|
||||
data-has-alt={!showInlineDesc}
|
||||
// style={{
|
||||
|
@ -448,8 +458,10 @@ function Media({
|
|||
return (
|
||||
<Figure>
|
||||
<Parent
|
||||
class="media media-audio"
|
||||
data-formatted-duration={formattedDuration}
|
||||
class={`media media-audio ${className}`}
|
||||
data-formatted-duration={
|
||||
!showOriginal ? formattedDuration : undefined
|
||||
}
|
||||
data-has-alt={!showInlineDesc}
|
||||
onClick={onClick}
|
||||
style={!showOriginal && mediaStyles}
|
||||
|
|
|
@ -104,6 +104,11 @@ const TYPE_PARAMS = {
|
|||
placeholder: 'e.g. PixelArt (Max 5, space-separated)',
|
||||
pattern: '[^#]+',
|
||||
},
|
||||
{
|
||||
text: 'Media only',
|
||||
name: 'media',
|
||||
type: 'checkbox',
|
||||
},
|
||||
{
|
||||
text: 'Instance',
|
||||
name: 'instance',
|
||||
|
@ -186,8 +191,10 @@ export const SHORTCUTS_META = {
|
|||
id: 'hashtag',
|
||||
title: ({ hashtag }) => hashtag,
|
||||
subtitle: ({ instance }) => instance || api().instance,
|
||||
path: ({ hashtag, instance }) =>
|
||||
`${instance ? `/${instance}` : ''}/t/${hashtag.split(/\s+/).join('+')}`,
|
||||
path: ({ hashtag, instance, media }) =>
|
||||
`${instance ? `/${instance}` : ''}/t/${hashtag.split(/\s+/).join('+')}${
|
||||
media ? '?media=1' : ''
|
||||
}`,
|
||||
icon: 'hashtag',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -846,7 +846,7 @@
|
|||
object-fit: cover;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.status .media {
|
||||
.media {
|
||||
cursor: pointer;
|
||||
|
||||
&[data-has-alt] {
|
||||
|
@ -885,7 +885,7 @@ body:has(#modal-container .carousel) .status .media img:hover {
|
|||
position: relative;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
.status :is(.media-video, .media-audio) .media-play {
|
||||
:is(.media-video, .media-audio) .media-play {
|
||||
pointer-events: none;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
|
@ -902,10 +902,10 @@ body:has(#modal-container .carousel) .status .media img:hover {
|
|||
border-radius: 70px;
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
.status :is(.media-video, .media-audio):hover:not(:active) .media-play {
|
||||
:is(.media-video, .media-audio):hover:not(:active) .media-play {
|
||||
transform: translate(-50%, -50%) scale(1.1);
|
||||
}
|
||||
.status :is(.media-video, .media-audio)[data-formatted-duration]:after {
|
||||
:is(.media-video, .media-audio)[data-formatted-duration]:after {
|
||||
font-size: 12px;
|
||||
pointer-events: none;
|
||||
content: attr(data-formatted-duration);
|
||||
|
@ -918,10 +918,10 @@ body:has(#modal-container .carousel) .status .media img:hover {
|
|||
border-radius: 4px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
.status .media-audio[data-formatted-duration]:after {
|
||||
.media-audio[data-formatted-duration]:after {
|
||||
content: '♬ ' attr(data-formatted-duration);
|
||||
}
|
||||
.status .media-gif[data-label]:not(:hover):after {
|
||||
.media-gif[data-label]:not(:hover):after {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
pointer-events: none;
|
||||
|
@ -953,12 +953,12 @@ body:has(#modal-container .carousel) .status .media img:hover {
|
|||
.status .media-audio audio {
|
||||
width: 100%;
|
||||
} */
|
||||
.status .media-audio {
|
||||
.media-audio {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: radial-gradient(
|
||||
circle at center center,
|
||||
var(--bg-color),
|
||||
transparent,
|
||||
var(--bg-faded-color)
|
||||
),
|
||||
repeating-radial-gradient(
|
||||
|
|
|
@ -13,6 +13,8 @@ import useScroll from '../utils/useScroll';
|
|||
|
||||
import Icon from './icon';
|
||||
import Link from './link';
|
||||
import Media from './media';
|
||||
import MediaPost from './media-post';
|
||||
import NavMenu from './nav-menu';
|
||||
import Status from './status';
|
||||
|
||||
|
@ -39,6 +41,7 @@ function Timeline({
|
|||
timelineStart,
|
||||
allowFilters,
|
||||
refresh,
|
||||
view,
|
||||
}) {
|
||||
const snapStates = useSnapshot(states);
|
||||
const [items, setItems] = useState([]);
|
||||
|
@ -50,6 +53,7 @@ function Timeline({
|
|||
|
||||
console.debug('RENDER Timeline', id, refresh);
|
||||
|
||||
const allowGrouping = view !== 'media';
|
||||
const loadItems = useDebouncedCallback(
|
||||
(firstLoad) => {
|
||||
setShowNew(false);
|
||||
|
@ -59,10 +63,12 @@ function Timeline({
|
|||
try {
|
||||
let { done, value } = await fetchItems(firstLoad);
|
||||
if (Array.isArray(value)) {
|
||||
if (allowGrouping) {
|
||||
if (boostsCarousel) {
|
||||
value = groupBoosts(value);
|
||||
}
|
||||
value = groupContext(value);
|
||||
}
|
||||
console.log(value);
|
||||
if (firstLoad) {
|
||||
setItems(value);
|
||||
|
@ -210,6 +216,14 @@ function Timeline({
|
|||
}
|
||||
}, [nearReachEnd, showMore]);
|
||||
|
||||
const prevView = useRef(view);
|
||||
useEffect(() => {
|
||||
if (prevView.current !== view) {
|
||||
prevView.current = view;
|
||||
setItems([]);
|
||||
}
|
||||
}, [view]);
|
||||
|
||||
const loadOrCheckUpdates = useCallback(
|
||||
async ({ disableIdleCheck = false } = {}) => {
|
||||
console.log('✨ Load or check updates', {
|
||||
|
@ -346,7 +360,7 @@ function Timeline({
|
|||
)}
|
||||
{!!items.length ? (
|
||||
<>
|
||||
<ul class="timeline">
|
||||
<ul class={`timeline ${view ? `timeline-${view}` : ''}`}>
|
||||
{items.map((status) => (
|
||||
<TimelineItem
|
||||
status={status}
|
||||
|
@ -354,9 +368,12 @@ function Timeline({
|
|||
useItemID={useItemID}
|
||||
allowFilters={allowFilters}
|
||||
key={status.id + status?._pinned}
|
||||
view={view}
|
||||
/>
|
||||
))}
|
||||
{showMore && uiState === 'loading' && (
|
||||
{showMore &&
|
||||
uiState === 'loading' &&
|
||||
(view === 'media' ? null : (
|
||||
<>
|
||||
<li
|
||||
style={{
|
||||
|
@ -373,7 +390,7 @@ function Timeline({
|
|||
<Status skeleton />
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
))}
|
||||
</ul>
|
||||
{uiState === 'default' &&
|
||||
(showMore ? (
|
||||
|
@ -399,11 +416,19 @@ function Timeline({
|
|||
</>
|
||||
) : uiState === 'loading' ? (
|
||||
<ul class="timeline">
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
{Array.from({ length: 5 }).map((_, i) =>
|
||||
view === 'media' ? (
|
||||
<div
|
||||
style={{
|
||||
height: '50vh',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<li key={i}>
|
||||
<Status skeleton />
|
||||
</li>
|
||||
))}
|
||||
),
|
||||
)}
|
||||
</ul>
|
||||
) : (
|
||||
uiState !== 'error' && <p class="ui-state">{emptyText}</p>
|
||||
|
@ -426,7 +451,7 @@ function Timeline({
|
|||
);
|
||||
}
|
||||
|
||||
function TimelineItem({ status, instance, useItemID, allowFilters }) {
|
||||
function TimelineItem({ status, instance, useItemID, allowFilters, view }) {
|
||||
const { id: statusID, reblog, items, type, _pinned } = status;
|
||||
const actualStatusID = reblog?.id || statusID;
|
||||
const url = instance
|
||||
|
@ -531,8 +556,33 @@ function TimelineItem({ status, instance, useItemID, allowFilters }) {
|
|||
);
|
||||
});
|
||||
}
|
||||
|
||||
const itemKey = `timeline-${statusID + _pinned}`;
|
||||
|
||||
if (view === 'media') {
|
||||
return useItemID ? (
|
||||
<MediaPost
|
||||
class="timeline-item"
|
||||
parent="li"
|
||||
key={itemKey}
|
||||
statusID={statusID}
|
||||
instance={instance}
|
||||
allowFilters={allowFilters}
|
||||
/>
|
||||
) : (
|
||||
<MediaPost
|
||||
class="timeline-item"
|
||||
parent="li"
|
||||
key={itemKey}
|
||||
status={status}
|
||||
instance={instance}
|
||||
allowFilters={allowFilters}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={`timeline-${statusID + _pinned}`}>
|
||||
<li key={itemKey}>
|
||||
<Link class="status-link timeline-item" to={url}>
|
||||
{useItemID ? (
|
||||
<Status
|
||||
|
|
|
@ -414,6 +414,7 @@ function AccountStatuses() {
|
|||
errorText="Unable to load posts"
|
||||
fetchItems={fetchAccountStatuses}
|
||||
useItemID
|
||||
view={media ? 'media' : undefined}
|
||||
boostsCarousel={snapStates.settings.boostsCarousel}
|
||||
timelineStart={TimelineStart}
|
||||
refresh={[
|
||||
|
|
|
@ -2,10 +2,11 @@ import {
|
|||
FocusableItem,
|
||||
MenuDivider,
|
||||
MenuGroup,
|
||||
MenuHeader,
|
||||
MenuItem,
|
||||
} from '@szhsin/react-menu';
|
||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||
|
||||
import Icon from '../components/icon';
|
||||
import Menu2 from '../components/menu2';
|
||||
|
@ -25,13 +26,16 @@ const LIMIT = 20;
|
|||
const TAGS_LIMIT_PER_MODE = 4;
|
||||
const TOTAL_TAGS_LIMIT = TAGS_LIMIT_PER_MODE + 1;
|
||||
|
||||
function Hashtags({ columnMode, ...props }) {
|
||||
function Hashtags({ media: mediaView, columnMode, ...props }) {
|
||||
// const navigate = useNavigate();
|
||||
let { hashtag, ...params } = columnMode ? {} : useParams();
|
||||
if (props.hashtag) hashtag = props.hashtag;
|
||||
let hashtags = hashtag.trim().split(/[\s+]+/);
|
||||
hashtags.sort();
|
||||
hashtag = hashtags[0];
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const media = mediaView || !!searchParams.get('media');
|
||||
const linkParams = media ? '?media=1' : '';
|
||||
|
||||
const { masto, instance, authenticated } = api({
|
||||
instance: props?.instance || params.instance,
|
||||
|
@ -60,6 +64,7 @@ function Hashtags({ columnMode, ...props }) {
|
|||
limit: LIMIT,
|
||||
any: hashtags.slice(1),
|
||||
maxId: firstLoad ? undefined : maxID.current,
|
||||
onlyMedia: media,
|
||||
})
|
||||
.next();
|
||||
const { value } = results;
|
||||
|
@ -69,7 +74,9 @@ function Hashtags({ columnMode, ...props }) {
|
|||
}
|
||||
|
||||
value.forEach((item) => {
|
||||
saveStatus(item, instance);
|
||||
saveStatus(item, instance, {
|
||||
skipThreading: media, // If media view, no need to form threads
|
||||
});
|
||||
});
|
||||
|
||||
maxID.current = value[value.length - 1].id;
|
||||
|
@ -136,6 +143,8 @@ function Hashtags({ columnMode, ...props }) {
|
|||
fetchItems={fetchHashtags}
|
||||
checkForUpdates={checkForUpdates}
|
||||
useItemID
|
||||
view={media ? 'media' : undefined}
|
||||
refresh={media}
|
||||
headerEnd={
|
||||
<Menu2
|
||||
portal
|
||||
|
@ -209,6 +218,23 @@ function Hashtags({ columnMode, ...props }) {
|
|||
<MenuDivider />
|
||||
</>
|
||||
)}
|
||||
<MenuHeader className="plain">Filters</MenuHeader>
|
||||
<MenuItem
|
||||
type="checkbox"
|
||||
checked={!!media}
|
||||
onClick={() => {
|
||||
if (media) {
|
||||
searchParams.delete('media');
|
||||
} else {
|
||||
searchParams.set('media', '1');
|
||||
}
|
||||
setSearchParams(searchParams);
|
||||
}}
|
||||
>
|
||||
<Icon icon="check-circle" />{' '}
|
||||
<span class="menu-grow">Media only</span>
|
||||
</MenuItem>
|
||||
<MenuDivider />
|
||||
<FocusableItem className="menu-field" disabled={reachLimit}>
|
||||
{({ ref }) => (
|
||||
<form
|
||||
|
@ -231,7 +257,7 @@ function Hashtags({ columnMode, ...props }) {
|
|||
// );
|
||||
location.hash = instance
|
||||
? `/${instance}/t/${hashtags.join('+')}`
|
||||
: `/t/${hashtags.join('+')}`;
|
||||
: `/t/${hashtags.join('+')}${linkParams}`;
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -267,8 +293,8 @@ function Hashtags({ columnMode, ...props }) {
|
|||
// : `/t/${hashtags.join('+')}`,
|
||||
// );
|
||||
location.hash = instance
|
||||
? `/${instance}/t/${hashtags.join('+')}`
|
||||
: `/t/${hashtags.join('+')}`;
|
||||
? `/${instance}/t/${hashtags.join('+')}${linkParams}`
|
||||
: `/t/${hashtags.join('+')}${linkParams}`;
|
||||
}}
|
||||
>
|
||||
<Icon icon="x" alt="Remove hashtag" class="danger-icon" />
|
||||
|
@ -287,6 +313,7 @@ function Hashtags({ columnMode, ...props }) {
|
|||
type: 'hashtag',
|
||||
hashtag: hashtags.join(' '),
|
||||
instance,
|
||||
media: media ? 'on' : undefined,
|
||||
};
|
||||
// Check if already exists
|
||||
const exists = states.shortcuts.some(
|
||||
|
@ -300,7 +327,8 @@ function Hashtags({ columnMode, ...props }) {
|
|||
.split(/[\s+]+/)
|
||||
.sort()
|
||||
.join(' ') &&
|
||||
(s.instance ? s.instance === shortcut.instance : true),
|
||||
(s.instance ? s.instance === shortcut.instance : true) &&
|
||||
(s.media ? !!s.media === !!shortcut.media : true),
|
||||
);
|
||||
if (exists) {
|
||||
alert('This shortcut already exists');
|
||||
|
@ -324,7 +352,9 @@ function Hashtags({ columnMode, ...props }) {
|
|||
if (newInstance) {
|
||||
newInstance = newInstance.toLowerCase().trim();
|
||||
// navigate(`/${newInstance}/t/${hashtags.join('+')}`);
|
||||
location.hash = `/${newInstance}/t/${hashtags.join('+')}`;
|
||||
location.hash = `/${newInstance}/t/${hashtags.join(
|
||||
'+',
|
||||
)}${linkParams}`;
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
|
Loading…
Add table
Reference in a new issue