diff --git a/app/controllers/api/v1/admin/reports_controller.rb b/app/controllers/api/v1/admin/reports_controller.rb index 9b5beeab67..765996eb3c 100644 --- a/app/controllers/api/v1/admin/reports_controller.rb +++ b/app/controllers/api/v1/admin/reports_controller.rb @@ -16,6 +16,7 @@ class Api::V1::Admin::ReportsController < Api::BaseController FILTER_PARAMS = %i( resolved + unresolved account_id target_account_id ).freeze diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index d5f8ff1ea6..b2fbc6e25f 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -329,7 +329,7 @@ export function uploadCompose(files) { dispatch(showAlert({ message: messages.uploadQuote })); return; } - const uploadLimit = getState().getIn(['server', 'server', 'configuration', 'statuses', 'max_media_attachments']); + const uploadLimit = getState().getIn(['server', 'server', 'item', 'configuration', 'statuses', 'max_media_attachments']); const media = getState().getIn(['compose', 'media_attachments']); const pending = getState().getIn(['compose', 'pending_media_attachments']); const progress = new Array(files.length).fill(0); diff --git a/app/javascript/mastodon/actions/importer/emoji.ts b/app/javascript/mastodon/actions/importer/emoji.ts index 9e06c88f66..e9356ab621 100644 --- a/app/javascript/mastodon/actions/importer/emoji.ts +++ b/app/javascript/mastodon/actions/importer/emoji.ts @@ -1,5 +1,8 @@ import type { ApiCustomEmojiJSON } from '@/mastodon/api_types/custom_emoji'; import { loadCustomEmoji } from '@/mastodon/features/emoji'; +import { emojiLogger } from '@/mastodon/features/emoji/utils'; + +const log = emojiLogger('actions'); export async function importCustomEmoji(emojis: ApiCustomEmojiJSON[]) { if (emojis.length === 0) { @@ -18,5 +21,11 @@ export async function importCustomEmoji(emojis: ApiCustomEmojiJSON[]) { if (existingEmojis.length < emojis.length) { await clearCache('custom'); await loadCustomEmoji(); + + const { reloadCustomEmojis } = + await import('@/mastodon/features/emoji/picker'); + await reloadCustomEmojis(); + + log('Custom emojis updated, reloaded cache and picker data.'); } } diff --git a/app/javascript/mastodon/actions/server.ts b/app/javascript/mastodon/actions/server.ts index 4e4795c123..b301919b23 100644 --- a/app/javascript/mastodon/actions/server.ts +++ b/app/javascript/mastodon/actions/server.ts @@ -16,19 +16,33 @@ export const fetchServer = createDataLoadingThunk( dispatch(importFetchedAccount(instance.contact.account)); } }, + { + condition: (_, { getState }) => !getState().server.server.isLoading, + }, ); export const fetchExtendedDescription = createDataLoadingThunk( 'server/extended_description', () => apiGetExtendedDescription(), + { + condition: (_, { getState }) => + !getState().server.extendedDescription.isLoading, + }, ); export const fetchServerTranslationLanguages = createDataLoadingThunk( 'server/translation_languages', () => apiGetTranslationLanguages(), + { + condition: (_, { getState }) => + !getState().server.translationLanguages.isLoading, + }, ); export const fetchDomainBlocks = createDataLoadingThunk( 'server/domain_blocks', () => apiGetDomainBlocks(), + { + condition: (_, { getState }) => !getState().server.domainBlocks.isLoading, + }, ); diff --git a/app/javascript/mastodon/components/account_header/styles.module.scss b/app/javascript/mastodon/components/account_header/styles.module.scss index 2daf386734..ab751ce170 100644 --- a/app/javascript/mastodon/components/account_header/styles.module.scss +++ b/app/javascript/mastodon/components/account_header/styles.module.scss @@ -480,6 +480,7 @@ $button-fallback-breakpoint: $button-breakpoint + 55px; justify-content: center; align-items: flex-start; margin: 16px 0; + padding: 16px; } .bannerBaseCentered { diff --git a/app/javascript/mastodon/components/hover_card_controller.tsx b/app/javascript/mastodon/components/hover_card_controller.tsx index a0c704a4e7..dd7ff3b3e7 100644 --- a/app/javascript/mastodon/components/hover_card_controller.tsx +++ b/app/javascript/mastodon/components/hover_card_controller.tsx @@ -144,11 +144,16 @@ export const HoverCardController: React.FC = () => { setScrollTimeout(handleScrollEnd, 100); }; - const handleMouseMove = () => { + const handleMouseMove = (e: MouseEvent) => { if (isUsingTouch) { isUsingTouch = false; } + const hasMoved = Math.max(e.movementX, e.movementY) > 0; + if (!hasMoved) { + return; + } + delayEnterTimeout(enterDelay); cancelMoveTimeout(); diff --git a/app/javascript/mastodon/components/scrollable_list/index.jsx b/app/javascript/mastodon/components/scrollable_list/index.jsx index a80fe5581a..e44a827a2f 100644 --- a/app/javascript/mastodon/components/scrollable_list/index.jsx +++ b/app/javascript/mastodon/components/scrollable_list/index.jsx @@ -285,7 +285,7 @@ class ScrollableList extends PureComponent { if (this.props.bindToDocument) { document.removeEventListener('scroll', this.handleScroll); document.removeEventListener('wheel', this.handleWheel, listenerOptions); - } else { + } else if (this.node) { this.node.removeEventListener('scroll', this.handleScroll); this.node.removeEventListener('wheel', this.handleWheel, listenerOptions); } diff --git a/app/javascript/mastodon/components/status/header.tsx b/app/javascript/mastodon/components/status/header.tsx index 65790bb493..1ce5c4a36c 100644 --- a/app/javascript/mastodon/components/status/header.tsx +++ b/app/javascript/mastodon/components/status/header.tsx @@ -20,7 +20,8 @@ export interface StatusHeaderProps { status: Status; account?: Account; avatarSize?: number; - children?: ReactNode; + contentBeforeDate?: ReactNode; + contentAfterDate?: ReactNode; wrapperProps?: HTMLAttributes; displayNameProps?: DisplayNameProps; onHeaderClick?: MouseEventHandler; @@ -33,10 +34,11 @@ export type StatusHeaderRenderFn = (args: StatusHeaderProps) => ReactNode; export const StatusHeader: FC = ({ status, account, - children, className, avatarSize = 48, wrapperProps, + contentBeforeDate, + contentAfterDate, onHeaderClick, }) => { const statusAccount = status.get('account') as Account | undefined; @@ -51,6 +53,14 @@ export const StatusHeader: FC = ({ className={classNames('status__info', className)} /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ > + + + {contentBeforeDate} + = ({ {editedAt && } - - - {children} + {contentAfterDate} ); }; diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx index 4d9ae4fa3f..7e5cccf198 100644 --- a/app/javascript/mastodon/components/status_content.jsx +++ b/app/javascript/mastodon/components/status_content.jsx @@ -69,7 +69,7 @@ class TranslateButton extends PureComponent { } const mapStateToProps = state => ({ - languages: state.server.translationLanguages.items, + languages: state.server.translationLanguages.item, }); class StatusContent extends PureComponent { @@ -187,7 +187,7 @@ class StatusContent extends PureComponent { const renderReadMore = this.props.onClick && status.get('collapsed'); const contentLocale = intl.locale.replace(/[_-].*/, ''); - const targetLanguages = this.props.languages?.get(status.get('language') || 'und'); + const targetLanguages = this.props.languages?.[status.get('language') || 'und']; const renderTranslate = this.props.onTranslate && this.props.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale); const content = statusContent ?? getStatusContent(status); diff --git a/app/javascript/mastodon/components/status_quoted.tsx b/app/javascript/mastodon/components/status_quoted.tsx index 5c9804fb40..b269792a5e 100644 --- a/app/javascript/mastodon/components/status_quoted.tsx +++ b/app/javascript/mastodon/components/status_quoted.tsx @@ -225,17 +225,20 @@ export const QuotedStatus: React.FC = ({ const intl = useIntl(); const headerRenderFn: StatusHeaderRenderFn = useCallback( (props) => ( - - {onQuoteCancel && ( - - )} - + + ) + } + /> ), [intl, onQuoteCancel], ); diff --git a/app/javascript/mastodon/features/account_timeline/components/pinned_statuses.tsx b/app/javascript/mastodon/features/account_timeline/components/pinned_statuses.tsx index 11de336002..c751af83ca 100644 --- a/app/javascript/mastodon/features/account_timeline/components/pinned_statuses.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/pinned_statuses.tsx @@ -22,18 +22,22 @@ export const renderPinnedStatusHeader: StatusHeaderRenderFn = ({ return ; } return ( - - } - label={ - - } - /> - + } + label={ + + } + /> + } + /> ); }; diff --git a/app/javascript/mastodon/features/account_timeline/styles.module.scss b/app/javascript/mastodon/features/account_timeline/styles.module.scss index cd5cef3f29..9773861d22 100644 --- a/app/javascript/mastodon/features/account_timeline/styles.module.scss +++ b/app/javascript/mastodon/features/account_timeline/styles.module.scss @@ -126,4 +126,7 @@ .pinnedBadge { justify-self: end; + + // Allow "click to open post" event to pass through + pointer-events: none; } diff --git a/app/javascript/mastodon/features/collections/components/collection_list_item.tsx b/app/javascript/mastodon/features/collections/components/collection_list_item.tsx index 8169544cb8..c1b32cac27 100644 --- a/app/javascript/mastodon/features/collections/components/collection_list_item.tsx +++ b/app/javascript/mastodon/features/collections/components/collection_list_item.tsx @@ -27,14 +27,14 @@ export const CollectionListItem: React.FC = ({ ...otherProps }) => { const uniqueId = useId(); - const linkId = `${uniqueId}-link`; - const infoId = `${uniqueId}-info`; + const titleId = `${uniqueId}-title`; + const subtitleId = `${uniqueId}-info`; return (
@@ -52,6 +52,8 @@ export const CollectionListItem: React.FC = ({ className={classes.menuButton} /> } + titleId={titleId} + subtitleId={subtitleId} {...otherProps} />
diff --git a/app/javascript/mastodon/features/collections/components/collection_lockup.tsx b/app/javascript/mastodon/features/collections/components/collection_lockup.tsx index e16d49e792..5250ea5504 100644 --- a/app/javascript/mastodon/features/collections/components/collection_lockup.tsx +++ b/app/javascript/mastodon/features/collections/components/collection_lockup.tsx @@ -48,6 +48,8 @@ export interface CollectionLockupProps { sideContent?: React.ReactNode; className?: string; headingLevel?: 'h2' | 'h3' | 'h4'; + titleId?: string; + subtitleId?: string; } export const CollectionLockup: React.FC = ({ @@ -56,6 +58,8 @@ export const CollectionLockup: React.FC = ({ withTimestamp, sideContent, headingLevel = 'h3', + titleId, + subtitleId, className, }) => { const { id, name } = collection; @@ -74,6 +78,7 @@ export const CollectionLockup: React.FC = ({ = ({ withTimestamp={withTimestamp} /> } + subtitleId={subtitleId} > {name} diff --git a/app/javascript/mastodon/features/compose/components/compose_form.jsx b/app/javascript/mastodon/features/compose/components/compose_form.jsx index a646a7efae..2cc1f7915a 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.jsx +++ b/app/javascript/mastodon/features/compose/components/compose_form.jsx @@ -248,7 +248,15 @@ class ComposeForm extends ImmutablePureComponent { const { intl, onPaste, onDrop, autoFocus, withoutNavigation, maxChars, isSubmitting } = this.props; return ( -
+ {!withoutNavigation && } diff --git a/app/javascript/mastodon/features/compose/components/poll_form.jsx b/app/javascript/mastodon/features/compose/components/poll_form.jsx index d59603fe79..abd54a8213 100644 --- a/app/javascript/mastodon/features/compose/components/poll_form.jsx +++ b/app/javascript/mastodon/features/compose/components/poll_form.jsx @@ -58,7 +58,7 @@ const Option = ({ multipleChoice, index, title, autoFocus }) => { const dispatch = useDispatch(); const suggestions = useSelector(state => state.getIn(['compose', 'suggestions'])); const lang = useSelector(state => state.getIn(['compose', 'language'])); - const maxOptions = useSelector(state => state.getIn(['server', 'server', 'configuration', 'polls', 'max_options'])); + const maxOptions = useSelector(state => state.getIn(['server', 'server', 'item', 'configuration', 'polls', 'max_options'])); const handleChange = useCallback(({ target: { value } }) => { dispatch(changePollOption(index, value, maxOptions)); diff --git a/app/javascript/mastodon/features/compose/components/search.tsx b/app/javascript/mastodon/features/compose/components/search.tsx index c2855e8cb5..9c7baf5021 100644 --- a/app/javascript/mastodon/features/compose/components/search.tsx +++ b/app/javascript/mastodon/features/compose/components/search.tsx @@ -547,11 +547,15 @@ export const Search: React.FC<{ const searchOptionsHeading = useId(); return ( - + { const readyAttachmentsSize = state.getIn(['compose', 'media_attachments']).size ?? 0; const pendingAttachmentsSize = state.getIn(['compose', 'pending_media_attachments']).size ?? 0; const attachmentsSize = readyAttachmentsSize + pendingAttachmentsSize; - const isOverLimit = attachmentsSize > state.getIn(['server', 'server', 'configuration', 'statuses', 'max_media_attachments'])-1; + const isOverLimit = attachmentsSize > state.getIn(['server', 'server', 'item', 'configuration', 'statuses', 'max_media_attachments'])-1; const hasVideoOrAudio = state.getIn(['compose', 'media_attachments']).some(m => ['video', 'audio'].includes(m.get('type'))); const hasQuote = !!state.compose.get('quoted_status_id'); diff --git a/app/javascript/mastodon/features/compose/index.tsx b/app/javascript/mastodon/features/compose/index.tsx index 0439606ac2..eb44691997 100644 --- a/app/javascript/mastodon/features/compose/index.tsx +++ b/app/javascript/mastodon/features/compose/index.tsx @@ -87,12 +87,11 @@ const Compose: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => { if (multiColumn) { return ( -
-
@@ -121,7 +143,7 @@ export const ColumnsArea = forwardRef< } return ( -
-
+ ); }); diff --git a/app/javascript/mastodon/features/ui/components/link_footer.module.scss b/app/javascript/mastodon/features/ui/components/link_footer.module.scss index f2b094744e..beb0173ffd 100644 --- a/app/javascript/mastodon/features/ui/components/link_footer.module.scss +++ b/app/javascript/mastodon/features/ui/components/link_footer.module.scss @@ -41,6 +41,10 @@ &:not(:last-child)::after { content: ' · '; + + @supports (content: 'x' / 'y') { + content: ' · ' / ''; + } } } diff --git a/app/javascript/mastodon/features/ui/components/navigation_bar.tsx b/app/javascript/mastodon/features/ui/components/navigation_bar.tsx index ec45e395b2..c3f9373966 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_bar.tsx +++ b/app/javascript/mastodon/features/ui/components/navigation_bar.tsx @@ -31,6 +31,10 @@ export const messages = defineMessages({ defaultMessage: 'Notifications', }, menu: { id: 'tabs_bar.menu', defaultMessage: 'Menu' }, + advancedUiQuickLinks: { + id: 'tabs_bar.quick_links', + defaultMessage: 'Quick links', + }, }); const IconLabelButton: React.FC<{ diff --git a/app/javascript/mastodon/features/ui/containers/status_list_container.js b/app/javascript/mastodon/features/ui/containers/status_list_container.js index 1e21730a00..8abbbefe14 100644 --- a/app/javascript/mastodon/features/ui/containers/status_list_container.js +++ b/app/javascript/mastodon/features/ui/containers/status_list_container.js @@ -11,7 +11,15 @@ import { me } from '@/mastodon/initial_state'; const makeGetStatusIds = (pending = false) => createSelector([ (state, { type }) => state.getIn(['settings', type], ImmutableMap()), - (state, { type }) => state.getIn(['timelines', type, pending ? 'pendingItems' : 'items'], ImmutableList()), + (state, { type, maxItems }) => { + const items = state.getIn(['timelines', type, pending ? 'pendingItems' : 'items'], ImmutableList()); + + if (maxItems) { + return items.take(maxItems); + } + + return items; + }, (state) => state.get('statuses'), ], (columnSettings, statusIds, statuses) => { return statusIds.filter(id => { @@ -41,8 +49,15 @@ const makeMapStateToProps = () => { const getStatusIds = makeGetStatusIds(); const getPendingStatusIds = makeGetStatusIds(true); - const mapStateToProps = (state, { timelineId, initialLoadingState = true }) => ({ - statusIds: getStatusIds(state, { type: timelineId }), + /** + * @param {import('mastodon/store').RootState} state + * @param {Object} props + * @param {string} props.timelineId + * @param {boolean} [props.initialLoadingState] + * @param {number} [props.maxItems] + */ + const mapStateToProps = (state, { timelineId, initialLoadingState = true, maxItems }) => ({ + statusIds: getStatusIds(state, { type: timelineId, maxItems }), lastId: state.getIn(['timelines', timelineId, 'items'])?.last(), isLoading: state.getIn(['timelines', timelineId, 'isLoading'], initialLoadingState), isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false), diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index 4f398b510c..1bf0842cb8 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -29,7 +29,7 @@ import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../act import { clearHeight } from '../../actions/height_cache'; import { fetchServer, fetchServerTranslationLanguages } from '../../actions/server'; import { expandHomeTimeline } from '../../actions/timelines'; -import { initialState, me, owner, singleUserMode, trendsEnabled, landingPage, localLiveFeedAccess, disableHoverCards } from '../../initial_state'; +import { initialState, me, owner, singleUserMode, trendsEnabled, landingPage, localLiveFeedAccess, disableHoverCards, domain } from '../../initial_state'; import BundleColumnError from './components/bundle_column_error'; import { NavigationBar } from './components/navigation_bar'; @@ -88,6 +88,7 @@ import { import { ColumnsContextProvider } from './util/columns_context'; import { focusColumn, getFocusedItemIndex, focusItemSibling, focusFirstItem } from './util/focusUtils'; import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers'; +import { CustomHomepage } from 'mastodon/features/custom_homepage'; // Dummy import, to make sure that ends up in the application bundle. // Without this it ends up in ~8 very commonly used bundles. @@ -105,7 +106,7 @@ const mapStateToProps = state => ({ hasComposingContents: state.getIn(['compose', 'text']).trim().length !== 0 || state.getIn(['compose', 'media_attachments']).size > 0 || state.getIn(['compose', 'poll']) !== null || state.getIn(['compose', 'quoted_status_id']) !== null, canUploadMore: !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) - && state.getIn(['compose', 'media_attachments']).size < state.getIn(['server', 'server', 'configuration', 'statuses', 'max_media_attachments']), + && state.getIn(['compose', 'media_attachments']).size < state.getIn(['server', 'server', 'item', 'configuration', 'statuses', 'max_media_attachments']), isUploadEnabled: state.getIn(['compose', 'isDragDisabled']) !== true, firstLaunch: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION, @@ -177,13 +178,15 @@ class SwitchingColumnsArea extends PureComponent { rootRedirect = '/explore'; } else if (localLiveFeedAccess === 'public' && landingPage === 'local_feed') { rootRedirect = '/public/local'; + } else if (landingPage === 'overview') { + rootRedirect = '/overview'; } else { rootRedirect = '/about'; } return ( - + @@ -262,6 +265,8 @@ class SwitchingColumnsArea extends PureComponent { + + @@ -633,13 +638,18 @@ class UI extends PureComponent { cheat: this.handleDonate, }; + const minimalShell = !this.props.identity.signedIn && landingPage === 'overview'; + return (
- + {!minimalShell && ( + + )} + - + {!minimalShell && } {layout !== 'mobile' && } {!disableHoverCards && } diff --git a/app/javascript/mastodon/hooks/useCustomEmojis.ts b/app/javascript/mastodon/hooks/useCustomEmojis.ts index aeb77620dd..6eb3c17fbf 100644 --- a/app/javascript/mastodon/hooks/useCustomEmojis.ts +++ b/app/javascript/mastodon/hooks/useCustomEmojis.ts @@ -20,7 +20,7 @@ export function useCustomEmojis() { return emojis; } -async function loadEmojisIntoCache() { +export async function loadEmojisIntoCache() { const { loadAllCustomEmoji } = await import('../features/emoji/database'); const emojisRaw = await loadAllCustomEmoji(); if (emojisRaw === null) { diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 057b235576..b97260173b 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -584,6 +584,12 @@ "copy_icon_button.copy_this_text": "Copy link to clipboard", "copypaste.copied": "Copied", "copypaste.copy_to_clipboard": "Copy to clipboard", + "custom_homepage.about": "About", + "custom_homepage.about_this_server": "About this server", + "custom_homepage.administered_by": "Administered by", + "custom_homepage.contact": "Contact:", + "custom_homepage.latest_activity": "Latest activity", + "custom_homepage.these_are_the_latest_posts": "These are the latest 40 posts from accounts on this server.", "directory.federated": "From known fediverse", "directory.local": "From {domain} only", "directory.new_arrivals": "New arrivals", @@ -908,6 +914,7 @@ "navigation_bar.live_feed_local": "Live feed (local)", "navigation_bar.live_feed_public": "Live feed (public)", "navigation_bar.logout": "Logout", + "navigation_bar.main": "Main", "navigation_bar.moderation": "Moderation", "navigation_bar.more": "More", "navigation_bar.mutes": "Muted users", @@ -1305,6 +1312,7 @@ "tabs_bar.menu": "Menu", "tabs_bar.notifications": "Notifications", "tabs_bar.publish": "New Post", + "tabs_bar.quick_links": "Quick links", "tabs_bar.search": "Search", "tag.remove": "Remove", "terms_of_service.effective_as_of": "Effective as of {date}", diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 1ed02feb66..ca4595b977 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -411,7 +411,7 @@ $content-width: 840px; display: flex; } - & > ul { + & > nav > ul { display: none; &.visible { diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 25265433fa..809634e8f5 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1613,7 +1613,6 @@ body > [data-popper-placement] { font-size: 15px; line-height: 22px; height: 40px; - order: 2; flex: 0 0 auto; color: var(--color-text-secondary); } @@ -1666,7 +1665,6 @@ body > [data-popper-placement] { .status__quote-cancel { align-self: self-start; - order: 5; } .status__info { @@ -10778,6 +10776,7 @@ noscript { -webkit-box-orient: vertical; max-height: 2 * 20px; overflow: hidden; + overflow-wrap: anywhere; p { margin-bottom: 0; diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 93fbe2f811..c5009aff1c 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -109,7 +109,7 @@ class Form::AdminSettings REGISTRATION_MODES = %w(open approved none).freeze FEED_ACCESS_MODES = %w(public authenticated disabled).freeze ALTERNATE_FEED_ACCESS_MODES = %w(public authenticated).freeze - LANDING_PAGE = %w(trends about local_feed).freeze + LANDING_PAGE = %w(trends overview local_feed about).freeze attr_accessor(*KEYS) diff --git a/app/models/report_filter.rb b/app/models/report_filter.rb index 9d2b0fb374..dab45007bc 100644 --- a/app/models/report_filter.rb +++ b/app/models/report_filter.rb @@ -3,6 +3,7 @@ class ReportFilter KEYS = %i( resolved + unresolved account_id target_account_id by_target_domain @@ -16,7 +17,7 @@ class ReportFilter end def results - scope = Report.unresolved + scope = status_scope relevant_params.each do |key, value| scope = scope.merge scope_for(key, value) @@ -28,7 +29,7 @@ class ReportFilter private def relevant_params - params.tap do |args| + params.except(:resolved, :unresolved).tap do |args| args.delete(:target_origin) if origin_is_remote_and_domain_present? end end @@ -37,12 +38,20 @@ class ReportFilter params[:target_origin] == 'remote' && params[:by_target_domain].present? end + def status_scope + resolved = params.key?(:resolved) + unresolved = params.key?(:unresolved) + + return Report.all if resolved && unresolved + return Report.resolved if resolved + + Report.unresolved + end + def scope_for(key, value) case key.to_sym when :by_target_domain Report.where(target_account: Account.where(domain: value)) - when :resolved - Report.resolved when :account_id Report.where(account_id: value) when :target_account_id diff --git a/app/views/admin/settings/branding/show.html.haml b/app/views/admin/settings/branding/show.html.haml index 65aae77d5f..9e9653abf1 100644 --- a/app/views/admin/settings/branding/show.html.haml +++ b/app/views/admin/settings/branding/show.html.haml @@ -75,10 +75,11 @@ .fields-row = f.input :landing_page, + as: :radio_buttons, collection: f.object.class::LANDING_PAGE, include_blank: false, - label_method: ->(page) { I18n.t("admin.settings.landing_page.values.#{page}") }, - wrapper: :with_label + label_method: ->(page) { safe_join([I18n.t("admin.settings.landing_page.values.#{page}"), content_tag(:span, I18n.t("admin.settings.landing_page.hints.#{page}_html"), class: 'hint')]) }, + wrapper: :with_block_label .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index 3fb98e5151..4b27f79c06 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -8,7 +8,7 @@ - content_for :content do %a.navigation-skip-link{ href: '#content' }= t('admin.skip_to_content') .admin-wrapper - %nav.sidebar-wrapper + %header.sidebar-wrapper .sidebar-wrapper__inner .sidebar = link_to root_path do @@ -23,7 +23,8 @@ = material_symbol 'menu' = material_symbol 'close' - = render_navigation + %nav + = render_navigation %main.content-wrapper#content .content diff --git a/config/locales/en.yml b/config/locales/en.yml index 2ed8dd1c1a..f47eac1fe1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -956,10 +956,16 @@ en: disabled: Require specific user role public: Everyone landing_page: + hints: + about_html: A page with the description, contact information, rules and other information regarding this server. + local_feed_html: A live feed featuring most recent posts by users on this server. + overview_html: A page showcasing the description of your server alongside the most recent local posts by users on this server. + trends_html: A page featuring what's popular on this server right now. values: - about: About - local_feed: Local feed - trends: Trends + about: About page + local_feed: Local live feed + overview: Overview + trends: Trending registrations: moderation_recommandation: Please make sure you have an adequate and reactive moderation team before you open registrations to everyone! preamble: Control who can create an account on your server. diff --git a/config/routes/web_app.rb b/config/routes/web_app.rb index 514ada9f53..09ac34b848 100644 --- a/config/routes/web_app.rb +++ b/config/routes/web_app.rb @@ -34,4 +34,6 @@ /search /start/(*any) /statuses/(*any) + /overview + /overview/about ).each { |path| get path, to: 'home#index' } diff --git a/package.json b/package.json index e60f612045..79d391d790 100644 --- a/package.json +++ b/package.json @@ -164,7 +164,7 @@ "@vitest/browser-playwright": "^4.1.0", "@vitest/coverage-v8": "^4.1.0", "@vitest/ui": "^4.1.0", - "chromatic": "^16.0.0", + "chromatic": "^17.0.0", "eslint": "^9.39.2", "eslint-import-resolver-typescript": "^4.2.5", "eslint-plugin-formatjs": "^6.0.0", diff --git a/spec/requests/api/v1/admin/reports_spec.rb b/spec/requests/api/v1/admin/reports_spec.rb index 54dd4c9c8c..432e7f47a2 100644 --- a/spec/requests/api/v1/admin/reports_spec.rb +++ b/spec/requests/api/v1/admin/reports_spec.rb @@ -78,6 +78,17 @@ RSpec.describe 'Reports' do end end + context 'with both resolved and unresolved params' do + let(:params) { { resolved: true, unresolved: true } } + let(:scope) { Report.all } + + it 'returns all reports' do + subject + + expect(response.parsed_body).to match_array(expected_response) + end + end + context 'with account_id param' do let(:params) { { account_id: reporter.id } } let(:scope) { Report.unresolved.where(account: reporter) } diff --git a/spec/system/unlogged_spec.rb b/spec/system/unlogged_spec.rb index 26d1bd4542..90db5fb40e 100644 --- a/spec/system/unlogged_spec.rb +++ b/spec/system/unlogged_spec.rb @@ -12,6 +12,6 @@ RSpec.describe 'UnloggedBrowsing', :js, :streaming do it 'loads the home page' do expect(subject).to have_css('div.app-holder') - expect(subject).to have_css('div.columns-area__panels__main') + expect(subject).to have_css('main.columns-area__panels__main') end end diff --git a/yarn.lock b/yarn.lock index f01ab7f7c1..ccbf78c034 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2964,7 +2964,7 @@ __metadata: axios: "npm:^1.4.0" babel-plugin-transform-react-remove-prop-types: "npm:^0.4.24" blurhash: "npm:^2.0.5" - chromatic: "npm:^16.0.0" + chromatic: "npm:^17.0.0" classnames: "npm:^2.3.2" cocoon-js-vanilla: "npm:^1.5.1" color-blend: "npm:^4.0.0" @@ -6830,22 +6830,27 @@ __metadata: languageName: node linkType: hard -"chromatic@npm:^16.0.0": - version: 16.0.0 - resolution: "chromatic@npm:16.0.0" +"chromatic@npm:^17.0.0": + version: 17.0.0 + resolution: "chromatic@npm:17.0.0" + dependencies: + semver: "npm:^7.3.5" peerDependencies: "@chromatic-com/cypress": ^0.*.* || ^1.0.0 "@chromatic-com/playwright": ^0.*.* || ^1.0.0 + "@chromatic-com/vitest": ^0.*.* || ^1.0.0 peerDependenciesMeta: "@chromatic-com/cypress": optional: true "@chromatic-com/playwright": optional: true + "@chromatic-com/vitest": + optional: true bin: - chroma: dist/bin.js - chromatic: dist/bin.js - chromatic-cli: dist/bin.js - checksum: 10c0/ebebbf1c7d57e1ee9863997416c5125aab0a1886dce60fcb0358d34a51e0e1a45edc4635c8f8fb56d9facbcf21cd48014320c550f723b4791da51dde8552ee2b + chroma: dist/bin.cjs + chromatic: dist/bin.cjs + chromatic-cli: dist/bin.cjs + checksum: 10c0/962c86feec17b12757fa3327b7e98abff272a048c03227bb21ecb51c7f1d7ec3589386611ece8e2c413ac4049f26d71e9c9226a5fb7628fdcbb47e87905863a0 languageName: node linkType: hard