kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Merge remote-tracking branch 'origin/main' into fix-classnames
commit
de2e62746d
|
@ -137,7 +137,6 @@
|
|||
"react-redux": "^9.0.4",
|
||||
"react-router-dom": "^5.3.0",
|
||||
"react-router-dom-v5-compat": "^6.6.2",
|
||||
"react-router-scroll-4": "^1.0.0-beta.2",
|
||||
"react-simple-pull-to-refresh": "^1.3.3",
|
||||
"react-sparklines": "^1.7.0",
|
||||
"react-sticky-box": "^2.0.0",
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { usePrevious } from 'soapbox/hooks';
|
||||
|
||||
export type Location<T> = ReturnType<typeof useLocation<T>>;
|
||||
|
||||
interface IScrollContext {
|
||||
shouldUpdateScroll(prevLocation: Location<any> | undefined, location: Location<any>): boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const ScrollContext: React.FC<IScrollContext> = ({ shouldUpdateScroll, children }) => {
|
||||
const location = useLocation();
|
||||
const prevLocation = usePrevious(location);
|
||||
|
||||
useEffect(() => {
|
||||
if (prevLocation && shouldUpdateScroll(prevLocation, location)) {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}, [location, shouldUpdateScroll]);
|
||||
|
||||
return children;
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { NavLink, useLocation } from 'react-router-dom';
|
||||
|
||||
import { Icon, Text } from './ui';
|
||||
|
||||
|
@ -24,7 +24,9 @@ interface ISidebarNavigationLink {
|
|||
/** Desktop sidebar navigation link. */
|
||||
const SidebarNavigationLink = React.forwardRef((props: ISidebarNavigationLink, ref: React.ForwardedRef<HTMLAnchorElement>): JSX.Element => {
|
||||
const { icon, activeIcon, text, to = '', count, countMax, onClick } = props;
|
||||
const isActive = location.pathname === to;
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const isActive = pathname === to;
|
||||
|
||||
const handleClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
if (onClick) {
|
||||
|
|
|
@ -4,7 +4,7 @@ import throttle from 'lodash/throttle';
|
|||
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
|
||||
import { formatTime, getPointerPosition } from 'soapbox/features/video';
|
||||
|
||||
import Visualizer from './visualizer';
|
||||
|
@ -64,10 +64,11 @@ const Audio: React.FC<IAudio> = (props) => {
|
|||
const [duration, setDuration] = useState<number | undefined>(undefined);
|
||||
const [paused, setPaused] = useState(true);
|
||||
const [muted, setMuted] = useState(false);
|
||||
const [preVolume, setPreVolume] = useState(0);
|
||||
const [volume, setVolume] = useState(0.5);
|
||||
const [dragging, setDragging] = useState(false);
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
const [seekHovered, setSeekHovered] = useState(false);
|
||||
const visualizer = useRef<Visualizer>(new Visualizer(TICK_SIZE));
|
||||
const audioContext = useRef<AudioContext | null>(null);
|
||||
|
||||
|
@ -150,12 +151,20 @@ const Audio: React.FC<IAudio> = (props) => {
|
|||
};
|
||||
|
||||
const toggleMute = () => {
|
||||
const nextMuted = !muted;
|
||||
|
||||
setMuted(nextMuted);
|
||||
|
||||
if (audio.current) {
|
||||
audio.current.muted = nextMuted;
|
||||
const muted = !audio.current.muted;
|
||||
setMuted(muted);
|
||||
audio.current.muted = muted;
|
||||
|
||||
if (muted) {
|
||||
setPreVolume(audio.current.volume);
|
||||
audio.current.volume = 0;
|
||||
setVolume(0);
|
||||
} else {
|
||||
audio.current.volume = preVolume;
|
||||
setVolume(preVolume);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -259,6 +268,14 @@ const Audio: React.FC<IAudio> = (props) => {
|
|||
setHovered(false);
|
||||
};
|
||||
|
||||
const handleSeekEnter = () => {
|
||||
setSeekHovered(true);
|
||||
};
|
||||
|
||||
const handleSeekLeave = () => {
|
||||
setSeekHovered(false);
|
||||
};
|
||||
|
||||
const handleLoadedData = () => {
|
||||
if (audio.current) {
|
||||
setDuration(audio.current.duration);
|
||||
|
@ -438,7 +455,8 @@ const Audio: React.FC<IAudio> = (props) => {
|
|||
|
||||
return (
|
||||
<div
|
||||
className={clsx('audio-player', { editable })}
|
||||
role='menuitem'
|
||||
className={clsx('relative box-border overflow-hidden rounded-[10px] bg-black pb-11', { 'rounded-none h-full': editable })}
|
||||
ref={player}
|
||||
style={{
|
||||
backgroundColor: _getBackgroundColor(),
|
||||
|
@ -446,8 +464,6 @@ const Audio: React.FC<IAudio> = (props) => {
|
|||
width: '100%',
|
||||
height: fullscreen ? '100%' : (height || props.height),
|
||||
}}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
tabIndex={0}
|
||||
onKeyDown={handleKeyDown}
|
||||
onClick={e => e.stopPropagation()}
|
||||
|
@ -466,7 +482,7 @@ const Audio: React.FC<IAudio> = (props) => {
|
|||
<canvas
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
className='audio-player__canvas absolute left-0 top-0 w-full'
|
||||
className='absolute left-0 top-0 w-full'
|
||||
width={width}
|
||||
height={height}
|
||||
ref={canvas}
|
||||
|
@ -490,86 +506,95 @@ const Audio: React.FC<IAudio> = (props) => {
|
|||
/>
|
||||
)}
|
||||
|
||||
<div className='video-player__seek' onMouseDown={handleMouseDown} ref={seek}>
|
||||
<div className='relative h-6 cursor-pointer' onMouseDown={handleMouseDown} onMouseEnter={handleSeekEnter} onMouseLeave={handleSeekLeave} ref={seek}>
|
||||
|
||||
<div className='video-player__seek__buffer' style={{ width: `${buffer}%` }} />
|
||||
<div className='absolute top-0 block h-1 rounded-md bg-white/20' style={{ width: `${buffer}%` }} />
|
||||
|
||||
<div
|
||||
className='video-player__seek__progress'
|
||||
className='absolute top-0 block h-1 rounded-md bg-accent-500'
|
||||
style={{ width: `${progress}%`, backgroundColor: accentColor }}
|
||||
/>
|
||||
|
||||
<span
|
||||
className={clsx('video-player__seek__handle', { active: dragging })}
|
||||
className={clsx('absolute -top-1 z-30 -ml-1.5 size-3 rounded-full bg-accent-500 opacity-0 shadow-[1px_2px_6px_rgba(0,0,0,0.3)] transition-opacity duration-100', { 'opacity-100': dragging || seekHovered })}
|
||||
tabIndex={0}
|
||||
style={{ left: `${progress}%`, backgroundColor: accentColor }}
|
||||
onKeyDown={handleAudioKeyDown}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='video-player__controls active'>
|
||||
<div className='video-player__buttons-bar'>
|
||||
<div className='video-player__buttons left'>
|
||||
<div className={clsx('absolute inset-x-0 bottom-0 z-20 box-border bg-gradient-to-t from-black/70 to-transparent px-[10px] opacity-100 transition-opacity duration-100 ease-linear')}>
|
||||
<div className='my-[-5px] flex justify-between pb-3.5'>
|
||||
<div className='flex w-full flex-auto items-center truncate text-[16px]'>
|
||||
|
||||
<button
|
||||
type='button'
|
||||
title={intl.formatMessage(paused ? messages.play : messages.pause)}
|
||||
aria-label={intl.formatMessage(paused ? messages.play : messages.pause)}
|
||||
className='player-button'
|
||||
className={clsx('inline-block flex-none border-0 bg-transparent px-[6px] py-[5px] text-[16px] text-white/75 opacity-75 outline-none hover:text-white hover:opacity-100 active:text-white active:opacity-100 ')}
|
||||
onClick={togglePlay}
|
||||
>
|
||||
<Icon src={paused ? require('@tabler/icons/outline/player-play.svg') : require('@tabler/icons/outline/player-pause.svg')} />
|
||||
<SvgIcon className='w-5' src={paused ? require('@tabler/icons/outline/player-play.svg') : require('@tabler/icons/outline/player-pause.svg')} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
type='button'
|
||||
title={intl.formatMessage(muted ? messages.unmute : messages.mute)}
|
||||
aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)}
|
||||
className='player-button'
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
className={clsx('inline-block flex-none border-0 bg-transparent px-[6px] py-[5px] text-[16px] text-white/75 opacity-75 outline-none hover:text-white hover:opacity-100 active:text-white active:opacity-100')}
|
||||
onClick={toggleMute}
|
||||
>
|
||||
<Icon src={muted ? require('@tabler/icons/outline/volume-3.svg') : require('@tabler/icons/outline/volume.svg')} />
|
||||
<SvgIcon className='w-5' src={muted ? require('@tabler/icons/outline/volume-3.svg') : require('@tabler/icons/outline/volume.svg')} />
|
||||
</button>
|
||||
|
||||
<div
|
||||
className={clsx('video-player__volume', { active: hovered })}
|
||||
ref={slider}
|
||||
onMouseDown={handleVolumeMouseDown}
|
||||
className={clsx('relative inline-flex h-6 flex-none cursor-pointer overflow-hidden transition-all duration-100 ease-linear', { 'overflow-visible w-[50px] mr-[16px]': hovered })} onMouseDown={handleVolumeMouseDown} ref={slider}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<div
|
||||
className='video-player__volume__current'
|
||||
style={{
|
||||
width: `${volume * 100}%`,
|
||||
backgroundColor: _getAccentColor(),
|
||||
content: '',
|
||||
width: '50px',
|
||||
background: 'rgba(255, 255, 255, 0.35)',
|
||||
borderRadius: '4px',
|
||||
display: 'block',
|
||||
position: 'absolute',
|
||||
height: '4px',
|
||||
left: '0',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className={clsx('absolute left-0 top-1/2 block h-1 -translate-y-1/2 rounded-md bg-accent-500')} style={{ width: `${volume * 100}%` }} />
|
||||
<span
|
||||
className='video-player__volume__handle'
|
||||
className={clsx('absolute left-0 top-1/2 z-30 -ml-1.5 size-3 -translate-y-1/2 rounded-full bg-accent-500 opacity-0 shadow-[1px_2px_6px_rgba(0,0,0,0.3)] transition-opacity duration-100', { 'opacity-100': hovered })}
|
||||
tabIndex={0}
|
||||
style={{ left: `${volume * 100}%`, backgroundColor: _getAccentColor() }}
|
||||
style={{ left: `${volume * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span className='video-player__time'>
|
||||
<span className='video-player__time-current'>{formatTime(Math.floor(currentTime))}</span>
|
||||
<span className='mx-[5px] inline-flex flex-[0_1_auto] overflow-hidden text-ellipsis'>
|
||||
<span className='text-sm font-medium text-white/75'>{formatTime(Math.floor(currentTime))}</span>
|
||||
{getDuration() && (<>
|
||||
<span className='video-player__time-sep'>/</span> {/* eslint-disable-line formatjs/no-literal-string-in-jsx */}
|
||||
<span className='video-player__time-total'>{formatTime(Math.floor(getDuration()))}</span>
|
||||
<span className='mx-1.5 inline-block text-sm font-medium text-white/75'>/</span>{/* eslint-disable-line formatjs/no-literal-string-in-jsx */}
|
||||
<span className='text-sm font-medium text-white/75'>{formatTime(Math.floor(getDuration()))}</span>
|
||||
</>)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className='video-player__buttons right'>
|
||||
<div className='flex min-w-[30px] flex-auto items-center truncate text-[16px]'>
|
||||
<a
|
||||
title={intl.formatMessage(messages.download)}
|
||||
aria-label={intl.formatMessage(messages.download)}
|
||||
className='video-player__download__icon player-button'
|
||||
className={clsx('inline-block flex-none border-0 bg-transparent px-[6px] py-[5px] text-[16px] text-white/75 opacity-75 outline-none hover:text-white hover:opacity-100 active:text-white active:opacity-100 ')}
|
||||
href={src}
|
||||
download
|
||||
target='_blank'
|
||||
>
|
||||
<Icon src={require('@tabler/icons/outline/download.svg')} />
|
||||
<SvgIcon className='w-5' src={require('@tabler/icons/outline/download.svg')} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -15,20 +15,38 @@ const NostrExtensionIndicator: React.FC = () => {
|
|||
dispatch(closeModal());
|
||||
};
|
||||
|
||||
function renderBody(): React.ReactNode {
|
||||
if (window.nostr && window.nostr.nip44) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='nostr_extension.found'
|
||||
defaultMessage='<link>Sign in</link> with browser extension.'
|
||||
values={{
|
||||
link: (node) => <button type='button' className='underline' onClick={onClick}>{node}</button>,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else if (window.nostr) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='nostr_extension.not_supported'
|
||||
defaultMessage='Browser extension not supported. Please upgrade to the latest version.'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='nostr_extension.not_found'
|
||||
defaultMessage='Browser extension not found.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack space={2} className='flex items-center rounded-lg bg-gray-100 p-2 dark:bg-gray-800'>
|
||||
<Text size='xs'>
|
||||
{window.nostr ? (
|
||||
<FormattedMessage
|
||||
id='nostr_extension.found'
|
||||
defaultMessage='<link>Sign in</link> with browser extension.'
|
||||
values={{
|
||||
link: (node) => <button type='button' className='underline' onClick={onClick}>{node}</button>,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage id='nostr_extension.not_found' defaultMessage='Browser extension not found.' />
|
||||
)}
|
||||
{renderBody()}
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
|
|
|
@ -400,7 +400,7 @@ const Video: React.FC<IVideo> = ({
|
|||
const toggleMute = () => {
|
||||
if (video.current) {
|
||||
const muted = !video.current.muted;
|
||||
setMuted(!muted);
|
||||
setMuted(muted);
|
||||
video.current.muted = muted;
|
||||
|
||||
if (muted) {
|
||||
|
|
|
@ -2,12 +2,11 @@ import React, { Suspense, useEffect } from 'react';
|
|||
import { Toaster } from 'react-hot-toast';
|
||||
import { BrowserRouter, Switch, Redirect, Route } from 'react-router-dom';
|
||||
import { CompatRouter } from 'react-router-dom-v5-compat';
|
||||
// @ts-ignore: it doesn't have types
|
||||
import { ScrollContext } from 'react-router-scroll-4';
|
||||
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import * as BuildConfig from 'soapbox/build-config';
|
||||
import LoadingScreen from 'soapbox/components/loading-screen';
|
||||
import { Location, ScrollContext } from 'soapbox/components/scroll-context';
|
||||
import SiteErrorBoundary from 'soapbox/components/site-error-boundary';
|
||||
import {
|
||||
ModalContainer,
|
||||
|
@ -25,6 +24,10 @@ const GdprBanner = React.lazy(() => import('soapbox/components/gdpr-banner'));
|
|||
const EmbeddedStatus = React.lazy(() => import('soapbox/features/embedded-status'));
|
||||
const UI = React.lazy(() => import('soapbox/features/ui'));
|
||||
|
||||
interface LocationState {
|
||||
soapboxModalKey?: string;
|
||||
}
|
||||
|
||||
/** Highest level node with the Redux store. */
|
||||
const SoapboxMount = () => {
|
||||
useCachedLocationHandler();
|
||||
|
@ -51,10 +54,9 @@ const SoapboxMount = () => {
|
|||
|
||||
const { redirectRootNoLogin, gdpr } = soapboxConfig;
|
||||
|
||||
// @ts-ignore: I don't actually know what these should be, lol
|
||||
const shouldUpdateScroll = (prevRouterProps, { location }) => {
|
||||
return !(location.state?.soapboxModalKey && location.state?.soapboxModalKey !== prevRouterProps?.location?.state?.soapboxModalKey);
|
||||
};
|
||||
function shouldUpdateScroll<T extends LocationState>(prev: Location<T> | undefined, location: Location<T>): boolean {
|
||||
return !(location.state?.soapboxModalKey && location.state?.soapboxModalKey !== prev?.state?.soapboxModalKey);
|
||||
}
|
||||
|
||||
return (
|
||||
<SiteErrorBoundary>
|
||||
|
|
|
@ -1167,6 +1167,7 @@
|
|||
"new_group_panel.title": "Create Group",
|
||||
"nostr_extension.found": "<link>Sign in</link> with browser extension.",
|
||||
"nostr_extension.not_found": "Browser extension not found.",
|
||||
"nostr_extension.not_supported": "Browser extension not supported. Please upgrade to the latest version.",
|
||||
"nostr_login.siwe.action": "Log in with extension",
|
||||
"nostr_login.siwe.alt": "Log in with key",
|
||||
"nostr_login.siwe.sign_up": "Sign Up",
|
||||
|
|
|
@ -988,6 +988,7 @@ const getInstanceFeatures = (instance: InstanceV1 | InstanceV2) => {
|
|||
v.software === ICESHRIMP,
|
||||
v.software === MASTODON && gte(v.version, '2.8.0'),
|
||||
v.software === PLEROMA && gte(v.version, '1.0.0'),
|
||||
v.software === DITTO,
|
||||
]),
|
||||
|
||||
/**
|
||||
|
|
23
yarn.lock
23
yarn.lock
|
@ -5516,13 +5516,6 @@ intl-pluralrules@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/intl-pluralrules/-/intl-pluralrules-2.0.1.tgz#de16c3df1e09437635829725e88ea70c9ad79569"
|
||||
integrity sha512-astxTLzIdXPeN0K9Rumi6LfMpm3rvNO0iJE+h/k8Kr/is+wPbRe4ikyDjlLr6VTh/mEfNv8RjN+gu3KwDiuhqg==
|
||||
|
||||
invariant@^2.2.4:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
|
||||
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
|
||||
dependencies:
|
||||
loose-envify "^1.0.0"
|
||||
|
||||
is-array-buffer@^3.0.1, is-array-buffer@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe"
|
||||
|
@ -7363,14 +7356,6 @@ react-router-dom@^5.3.0:
|
|||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-router-scroll-4@^1.0.0-beta.2:
|
||||
version "1.0.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/react-router-scroll-4/-/react-router-scroll-4-1.0.0-beta.2.tgz#d887063ec0f66124aaf450158dd158ff7d3dc279"
|
||||
integrity sha512-K67Dnm75naSBs/WYc2CDNxqU+eE8iA3I0wSCArgGSHb0xR/7AUcgUEXtCxrQYVTogXvjVK60gmwYvOyRQ6fuBA==
|
||||
dependencies:
|
||||
scroll-behavior "^0.9.1"
|
||||
warning "^3.0.0"
|
||||
|
||||
react-router@5.2.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.1.tgz#4d2e4e9d5ae9425091845b8dbc6d9d276239774d"
|
||||
|
@ -7817,14 +7802,6 @@ schema-utils@^4.0.0:
|
|||
ajv-formats "^2.1.1"
|
||||
ajv-keywords "^5.0.0"
|
||||
|
||||
scroll-behavior@^0.9.1:
|
||||
version "0.9.12"
|
||||
resolved "https://registry.yarnpkg.com/scroll-behavior/-/scroll-behavior-0.9.12.tgz#1c22d273ec4ce6cd4714a443fead50227da9424c"
|
||||
integrity sha512-18sirtyq1P/VsBX6O/vgw20Np+ngduFXEMO4/NDFXabdOKBL2kjPVUpz1y0+jm99EWwFJafxf5/tCyMeXt9Xyg==
|
||||
dependencies:
|
||||
dom-helpers "^3.4.0"
|
||||
invariant "^2.2.4"
|
||||
|
||||
semver-compare@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
||||
|
|
Ładowanie…
Reference in New Issue