diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 68f1d86944..149b955353 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -36,7 +36,6 @@ Rails/OutputSafety: Style/FetchEnvVar: Exclude: - 'config/initializers/2_limited_federation_mode.rb' - - 'config/initializers/3_omniauth.rb' - 'config/initializers/paperclip.rb' - 'lib/tasks/repo.rake' diff --git a/app/javascript/flavours/glitch/components/featured_carousel.tsx b/app/javascript/flavours/glitch/components/featured_carousel.tsx index 9c1efb48c4..120797f7c5 100644 --- a/app/javascript/flavours/glitch/components/featured_carousel.tsx +++ b/app/javascript/flavours/glitch/components/featured_carousel.tsx @@ -1,5 +1,12 @@ import type { ComponentPropsWithRef } from 'react'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { + useCallback, + useEffect, + useLayoutEffect, + useRef, + useState, + useId, +} from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; @@ -11,11 +18,14 @@ import { animated, useSpring } from '@react-spring/web'; import { useDrag } from '@use-gesture/react'; import { expandAccountFeaturedTimeline } from '@/flavours/glitch/actions/timelines'; +import { Icon } from '@/flavours/glitch/components/icon'; import { IconButton } from '@/flavours/glitch/components/icon_button'; import StatusContainer from '@/flavours/glitch/containers/status_container'; +import { usePrevious } from '@/flavours/glitch/hooks/usePrevious'; import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store'; import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; +import PushPinIcon from '@/material-icons/400-24px/push_pin.svg?react'; const messages = defineMessages({ previous: { id: 'featured_carousel.previous', defaultMessage: 'Previous' }, @@ -31,6 +41,7 @@ export const FeaturedCarousel: React.FC<{ tagged?: string; }> = ({ accountId, tagged }) => { const intl = useIntl(); + const accessibilityId = useId(); // Load pinned statuses const dispatch = useAppDispatch(); @@ -74,6 +85,7 @@ export const FeaturedCarousel: React.FC<{ const [currentSlideHeight, setCurrentSlideHeight] = useState( wrapperRef.current?.scrollHeight ?? 0, ); + const previousSlideHeight = usePrevious(currentSlideHeight); const observerRef = useRef( new ResizeObserver(() => { handleSlideChange(0); @@ -82,8 +94,10 @@ export const FeaturedCarousel: React.FC<{ const wrapperStyles = useSpring({ x: `-${slideIndex * 100}%`, height: currentSlideHeight, + // Don't animate from zero to the height of the initial slide + immediate: !previousSlideHeight, }); - useEffect(() => { + useLayoutEffect(() => { // Update slide height when the component mounts if (currentSlideHeight === 0) { handleSlideChange(0); @@ -110,11 +124,15 @@ export const FeaturedCarousel: React.FC<{ className='featured-carousel' {...bind()} aria-roledescription='carousel' - aria-labelledby='featured-carousel-title' + aria-labelledby={`${accessibilityId}-title`} role='region' >
-

+ (value: T): T | undefined { + const ref = useRef(); + + useEffect(() => { + ref.current = value; + }, [value]); + + return ref.current; +} diff --git a/app/javascript/flavours/glitch/styles/components.scss b/app/javascript/flavours/glitch/styles/components.scss index b5743debab..5654b7964f 100644 --- a/app/javascript/flavours/glitch/styles/components.scss +++ b/app/javascript/flavours/glitch/styles/components.scss @@ -11397,5 +11397,13 @@ noscript { font-size: 12px; font-weight: 500; text-transform: uppercase; + display: flex; + align-items: center; + gap: 4px; + + .icon { + width: 16px; + height: 16px; + } } } diff --git a/app/javascript/mastodon/components/featured_carousel.tsx b/app/javascript/mastodon/components/featured_carousel.tsx index efdf275fcb..195331ef9f 100644 --- a/app/javascript/mastodon/components/featured_carousel.tsx +++ b/app/javascript/mastodon/components/featured_carousel.tsx @@ -1,5 +1,12 @@ import type { ComponentPropsWithRef } from 'react'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { + useCallback, + useEffect, + useLayoutEffect, + useRef, + useState, + useId, +} from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; @@ -11,11 +18,14 @@ import { animated, useSpring } from '@react-spring/web'; import { useDrag } from '@use-gesture/react'; import { expandAccountFeaturedTimeline } from '@/mastodon/actions/timelines'; +import { Icon } from '@/mastodon/components/icon'; import { IconButton } from '@/mastodon/components/icon_button'; import StatusContainer from '@/mastodon/containers/status_container'; +import { usePrevious } from '@/mastodon/hooks/usePrevious'; import { useAppDispatch, useAppSelector } from '@/mastodon/store'; import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; +import PushPinIcon from '@/material-icons/400-24px/push_pin.svg?react'; const messages = defineMessages({ previous: { id: 'featured_carousel.previous', defaultMessage: 'Previous' }, @@ -31,6 +41,7 @@ export const FeaturedCarousel: React.FC<{ tagged?: string; }> = ({ accountId, tagged }) => { const intl = useIntl(); + const accessibilityId = useId(); // Load pinned statuses const dispatch = useAppDispatch(); @@ -74,6 +85,7 @@ export const FeaturedCarousel: React.FC<{ const [currentSlideHeight, setCurrentSlideHeight] = useState( wrapperRef.current?.scrollHeight ?? 0, ); + const previousSlideHeight = usePrevious(currentSlideHeight); const observerRef = useRef( new ResizeObserver(() => { handleSlideChange(0); @@ -82,8 +94,10 @@ export const FeaturedCarousel: React.FC<{ const wrapperStyles = useSpring({ x: `-${slideIndex * 100}%`, height: currentSlideHeight, + // Don't animate from zero to the height of the initial slide + immediate: !previousSlideHeight, }); - useEffect(() => { + useLayoutEffect(() => { // Update slide height when the component mounts if (currentSlideHeight === 0) { handleSlideChange(0); @@ -110,11 +124,15 @@ export const FeaturedCarousel: React.FC<{ className='featured-carousel' {...bind()} aria-roledescription='carousel' - aria-labelledby='featured-carousel-title' + aria-labelledby={`${accessibilityId}-title`} role='region' >
-

+ (value: T): T | undefined { + const ref = useRef(); + + useEffect(() => { + ref.current = value; + }, [value]); + + return ref.current; +} diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index a520bfa08f..cbedc53a01 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -11074,5 +11074,13 @@ noscript { font-size: 12px; font-weight: 500; text-transform: uppercase; + display: flex; + align-items: center; + gap: 4px; + + .icon { + width: 16px; + height: 16px; + } } } diff --git a/config/initializers/3_omniauth.rb b/config/initializers/3_omniauth.rb index 0f8378ee14..607d9c15ba 100644 --- a/config/initializers/3_omniauth.rb +++ b/config/initializers/3_omniauth.rb @@ -12,7 +12,7 @@ Devise.setup do |config| # CAS strategy if ENV['CAS_ENABLED'] == 'true' cas_options = {} - cas_options[:display_name] = ENV['CAS_DISPLAY_NAME'] + cas_options[:display_name] = ENV.fetch('CAS_DISPLAY_NAME', nil) cas_options[:url] = ENV['CAS_URL'] if ENV['CAS_URL'] cas_options[:host] = ENV['CAS_HOST'] if ENV['CAS_HOST'] cas_options[:port] = ENV['CAS_PORT'] if ENV['CAS_PORT'] @@ -41,7 +41,7 @@ Devise.setup do |config| # SAML strategy if ENV['SAML_ENABLED'] == 'true' saml_options = {} - saml_options[:display_name] = ENV['SAML_DISPLAY_NAME'] + saml_options[:display_name] = ENV.fetch('SAML_DISPLAY_NAME', nil) saml_options[:assertion_consumer_service_url] = ENV['SAML_ACS_URL'] if ENV['SAML_ACS_URL'] saml_options[:issuer] = ENV['SAML_ISSUER'] if ENV['SAML_ISSUER'] saml_options[:idp_sso_target_url] = ENV['SAML_IDP_SSO_TARGET_URL'] if ENV['SAML_IDP_SSO_TARGET_URL'] @@ -73,7 +73,7 @@ Devise.setup do |config| # OpenID Connect Strategy if ENV['OIDC_ENABLED'] == 'true' oidc_options = {} - oidc_options[:display_name] = ENV['OIDC_DISPLAY_NAME'] # OPTIONAL + oidc_options[:display_name] = ENV.fetch('OIDC_DISPLAY_NAME', nil) # OPTIONAL oidc_options[:issuer] = ENV['OIDC_ISSUER'] if ENV['OIDC_ISSUER'] # NEED oidc_options[:discovery] = ENV['OIDC_DISCOVERY'] == 'true' if ENV['OIDC_DISCOVERY'] # OPTIONAL (default: false) oidc_options[:client_auth_method] = ENV['OIDC_CLIENT_AUTH_METHOD'] if ENV['OIDC_CLIENT_AUTH_METHOD'] # OPTIONAL (default: basic)