Compare commits
1 Commits
main
...
feature-cu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01f9e3cd8a |
75
app/javascript/mastodon/features/custom_homepage/about.tsx
Normal file
75
app/javascript/mastodon/features/custom_homepage/about.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { fetchExtendedDescription } from 'mastodon/actions/server';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import { Skeleton } from 'mastodon/components/skeleton';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
const Placeholder = () => (
|
||||
<div className={classes.placeholder}>
|
||||
<Skeleton width='100%' />
|
||||
<Skeleton width='100%' />
|
||||
<Skeleton width='100%' />
|
||||
</div>
|
||||
);
|
||||
|
||||
export const About = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const server = useAppSelector((state) => state.server.server);
|
||||
const extendedDescription = useAppSelector(
|
||||
(state) => state.server.extendedDescription,
|
||||
);
|
||||
|
||||
const accountId = server.item?.contact.account?.id ?? '';
|
||||
const isLoading = extendedDescription.isLoading;
|
||||
const hasContent = (extendedDescription.item?.content.length ?? 0) > 0;
|
||||
const content = extendedDescription.item?.content ?? '';
|
||||
|
||||
useEffect(() => {
|
||||
void dispatch(fetchExtendedDescription());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classes.block}>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id='custom_homepage.administered_by'
|
||||
defaultMessage='Administered by'
|
||||
/>
|
||||
</h2>
|
||||
<Account id={accountId} size={36} minimal />
|
||||
</div>
|
||||
|
||||
<div className={classes.block}>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id='custom_homepage.about_this_server'
|
||||
defaultMessage='About this server'
|
||||
/>
|
||||
</h2>
|
||||
{isLoading ? (
|
||||
<Placeholder />
|
||||
) : hasContent ? (
|
||||
<div
|
||||
className='prose'
|
||||
dangerouslySetInnerHTML={{ __html: content }}
|
||||
/>
|
||||
) : (
|
||||
<div className='prose'>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='about.not_available'
|
||||
defaultMessage='This information has not been made available on this server.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
76
app/javascript/mastodon/features/custom_homepage/index.tsx
Normal file
76
app/javascript/mastodon/features/custom_homepage/index.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Route, Switch, useRouteMatch } from 'react-router-dom';
|
||||
|
||||
import { Helmet } from '@unhead/react/helmet';
|
||||
|
||||
import { fetchServer } from 'mastodon/actions/server';
|
||||
import { ServerHeroImage } from 'mastodon/components/server_hero_image';
|
||||
import { TabLink, TabList } from 'mastodon/components/tab_list';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { About } from './about';
|
||||
import { LatestActivity } from './latest_activity';
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'custom_homepage.title', defaultMessage: 'Mastodon' },
|
||||
});
|
||||
|
||||
export const CustomHomepage: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const server = useAppSelector((state) => state.server.server);
|
||||
const { path } = useRouteMatch();
|
||||
|
||||
useEffect(() => {
|
||||
void dispatch(fetchServer());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<div className={classes.page}>
|
||||
<ServerHeroImage
|
||||
alt={server.item?.thumbnail.description ?? ''}
|
||||
blurhash={server.item?.thumbnail.blurhash ?? ''}
|
||||
src={server.item?.thumbnail.url ?? ''}
|
||||
srcSet={Object.keys(server.item?.thumbnail.versions ?? {})
|
||||
.map(
|
||||
(key) =>
|
||||
`${server.item?.thumbnail.versions?.[key]} ${key.replace('@', '')}`,
|
||||
)
|
||||
.join(', ')}
|
||||
className={classes.header}
|
||||
/>
|
||||
|
||||
<div className={classes.topSection}>
|
||||
<h1>{server.item?.domain}</h1>
|
||||
<p>{server.item?.description}</p>
|
||||
</div>
|
||||
|
||||
<TabList>
|
||||
<TabLink to={path} exact>
|
||||
<FormattedMessage
|
||||
id='custom_homepage.latest_activity'
|
||||
defaultMessage='Latest activity'
|
||||
/>
|
||||
</TabLink>
|
||||
|
||||
<TabLink to={`${path}/about`} exact>
|
||||
<FormattedMessage id='custom_homepage.about' defaultMessage='About' />
|
||||
</TabLink>
|
||||
</TabList>
|
||||
|
||||
<Switch>
|
||||
<Route path={path} exact component={LatestActivity} />
|
||||
<Route path={`${path}/about`} exact component={About} />
|
||||
</Switch>
|
||||
|
||||
<Helmet>
|
||||
<title>{intl.formatMessage(messages.title)}</title>
|
||||
<meta name='robots' content='all' />
|
||||
</Helmet>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { expandCommunityTimeline } from 'mastodon/actions/timelines';
|
||||
import StatusListContainer from 'mastodon/features/ui/containers/status_list_container';
|
||||
import { useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
export const LatestActivity = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
void dispatch(expandCommunityTimeline());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<StatusListContainer
|
||||
prepend={
|
||||
<div className={classes.banner}>
|
||||
<FormattedMessage
|
||||
id='custom_homepage.these_are_the_latest_posts'
|
||||
defaultMessage='These are the latest 40 posts from accounts on this server.'
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
scrollKey='custom_homepage'
|
||||
timelineId='community'
|
||||
bindToDocument
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
.page {
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--color-border-primary);
|
||||
background: var(--color-background-primary);
|
||||
min-height: 100%;
|
||||
|
||||
:global(.item-list) article:last-child :global(.status) {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
aspect-ratio: 40/21;
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
|
||||
.banner {
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
background: var(--color-bg-brand-softest);
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin: 16px;
|
||||
margin-bottom: 0;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.topSection {
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
color: var(--color-text-primary);
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
line-height: 30px;
|
||||
letter-spacing: -0.12px;
|
||||
}
|
||||
|
||||
p {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.block {
|
||||
padding: 16px;
|
||||
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 22.4px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
:global(.account) {
|
||||
border: 1px solid var(--color-border-primary);
|
||||
padding: 18px 16px;
|
||||
border-radius: 12px;
|
||||
|
||||
--avatar-border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
padding: 4px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
:global(.skeleton) {
|
||||
height: 40px;
|
||||
border-radius: 12px;
|
||||
background: var(--color-bg-overlay-highlight);
|
||||
}
|
||||
}
|
||||
@@ -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 <Status /> ends up in the application bundle.
|
||||
// Without this it ends up in ~8 very commonly used bundles.
|
||||
@@ -177,6 +178,8 @@ 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';
|
||||
}
|
||||
@@ -262,6 +265,8 @@ class SwitchingColumnsArea extends PureComponent {
|
||||
<WrappedRoute path='/followed_tags' component={FollowedTags} content={children} />
|
||||
<WrappedRoute path='/mutes' component={Mutes} content={children} />
|
||||
<WrappedRoute path='/lists' component={Lists} content={children} />
|
||||
|
||||
<Route path='/overview' component={CustomHomepage} />
|
||||
<Route component={BundleColumnError} />
|
||||
</WrappedSwitch>
|
||||
</ColumnsArea>
|
||||
|
||||
@@ -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.latest_activity": "Latest activity",
|
||||
"custom_homepage.these_are_the_latest_posts": "These are the latest 40 posts from accounts on this server.",
|
||||
"custom_homepage.title": "Mastodon",
|
||||
"directory.federated": "From known fediverse",
|
||||
"directory.local": "From {domain} only",
|
||||
"directory.new_arrivals": "New arrivals",
|
||||
|
||||
@@ -94,7 +94,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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -33,4 +33,6 @@
|
||||
/search
|
||||
/start/(*any)
|
||||
/statuses/(*any)
|
||||
/overview
|
||||
/overview/about
|
||||
).each { |path| get path, to: 'home#index' }
|
||||
|
||||
Reference in New Issue
Block a user