From 4a94f4127b57176f73e7de136137aa7efe9a0140 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 8 Sep 2021 13:47:48 +0200 Subject: Fix glitch-soc front-end not linking to the provided SOURCE_URL --- app/javascript/flavours/glitch/util/initial_state.js | 2 ++ 1 file changed, 2 insertions(+) (limited to 'app/javascript/flavours/glitch/util') diff --git a/app/javascript/flavours/glitch/util/initial_state.js b/app/javascript/flavours/glitch/util/initial_state.js index 370d982d2..7154e020b 100644 --- a/app/javascript/flavours/glitch/util/initial_state.js +++ b/app/javascript/flavours/glitch/util/initial_state.js @@ -25,6 +25,8 @@ export const maxChars = (initialState && initialState.max_toot_chars) || 500; export const pollLimits = (initialState && initialState.poll_limits); export const invitesEnabled = getMeta('invites_enabled'); export const limitedFederationMode = getMeta('limited_federation_mode'); +export const repository = getMeta('repository'); +export const source_url = getMeta('source_url'); export const version = getMeta('version'); export const mascot = getMeta('mascot'); export const profile_directory = getMeta('profile_directory'); -- cgit From a7f6524c6b052f4d207525c813436ca12c929f00 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 26 Sep 2021 05:46:13 +0200 Subject: [Glitch] Change routing paths to use usernames in web UI Port 52e5c07948c4c91b73062846e1f19ea278ec0e24 to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/actions/accounts.js | 32 ++++++++++ app/javascript/flavours/glitch/actions/compose.js | 5 +- .../flavours/glitch/components/account.js | 4 +- .../flavours/glitch/components/avatar_composite.js | 2 +- .../flavours/glitch/components/display_name.js | 4 +- .../flavours/glitch/components/hashtag.js | 2 +- .../flavours/glitch/components/status.js | 18 ++---- .../glitch/components/status_action_bar.js | 4 +- .../flavours/glitch/components/status_content.js | 6 +- .../flavours/glitch/components/status_header.js | 6 +- .../flavours/glitch/components/status_prepend.js | 2 +- .../flavours/glitch/containers/mastodon.js | 26 +++++++- .../features/account/components/action_bar.js | 6 +- .../glitch/features/account_gallery/index.js | 60 +++++++++++++----- .../features/account_timeline/components/header.js | 6 +- .../account_timeline/components/moved_note.js | 2 +- .../glitch/features/account_timeline/index.js | 64 ++++++++++++++----- .../glitch/features/compose/components/header.js | 6 +- .../features/compose/components/navigation_bar.js | 4 +- .../direct_timeline/components/conversation.js | 2 +- .../features/directory/components/account_card.js | 2 +- .../components/account_authorize.js | 2 +- .../flavours/glitch/features/followers/index.js | 72 +++++++++++++++------- .../flavours/glitch/features/following/index.js | 72 +++++++++++++++------- .../getting_started/components/announcements.js | 6 +- .../glitch/features/getting_started/index.js | 12 ++-- .../flavours/glitch/features/lists/index.js | 2 +- .../features/notifications/components/follow.js | 4 +- .../notifications/components/follow_request.js | 6 +- .../picture_in_picture/components/footer.js | 2 +- .../picture_in_picture/components/header.js | 2 +- .../features/status/components/detailed_status.js | 6 +- .../flavours/glitch/features/status/index.js | 2 +- .../glitch/features/ui/components/boost_modal.js | 2 +- .../glitch/features/ui/components/columns_area.js | 2 +- .../features/ui/components/favourite_modal.js | 2 +- .../glitch/features/ui/components/list_panel.js | 2 +- .../glitch/features/ui/components/media_modal.js | 9 --- .../features/ui/components/navigation_panel.js | 8 +-- .../features/ui/components/onboarding_modal.js | 4 +- .../glitch/features/ui/components/tabs_bar.js | 6 +- .../flavours/glitch/features/ui/index.js | 48 ++++++++------- .../flavours/glitch/reducers/accounts_map.js | 15 +++++ app/javascript/flavours/glitch/reducers/index.js | 2 + app/javascript/flavours/glitch/util/api.js | 26 ++++++-- 45 files changed, 382 insertions(+), 195 deletions(-) create mode 100644 app/javascript/flavours/glitch/reducers/accounts_map.js (limited to 'app/javascript/flavours/glitch/util') diff --git a/app/javascript/flavours/glitch/actions/accounts.js b/app/javascript/flavours/glitch/actions/accounts.js index 912a3d179..0cf64e076 100644 --- a/app/javascript/flavours/glitch/actions/accounts.js +++ b/app/javascript/flavours/glitch/actions/accounts.js @@ -5,6 +5,10 @@ export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS'; export const ACCOUNT_FETCH_FAIL = 'ACCOUNT_FETCH_FAIL'; +export const ACCOUNT_LOOKUP_REQUEST = 'ACCOUNT_LOOKUP_REQUEST'; +export const ACCOUNT_LOOKUP_SUCCESS = 'ACCOUNT_LOOKUP_SUCCESS'; +export const ACCOUNT_LOOKUP_FAIL = 'ACCOUNT_LOOKUP_FAIL'; + export const ACCOUNT_FOLLOW_REQUEST = 'ACCOUNT_FOLLOW_REQUEST'; export const ACCOUNT_FOLLOW_SUCCESS = 'ACCOUNT_FOLLOW_SUCCESS'; export const ACCOUNT_FOLLOW_FAIL = 'ACCOUNT_FOLLOW_FAIL'; @@ -104,6 +108,34 @@ export function fetchAccount(id) { }; }; +export const lookupAccount = acct => (dispatch, getState) => { + dispatch(lookupAccountRequest(acct)); + + api(getState).get('/api/v1/accounts/lookup', { params: { acct } }).then(response => { + dispatch(fetchRelationships([response.data.id])); + dispatch(importFetchedAccount(response.data)); + dispatch(lookupAccountSuccess()); + }).catch(error => { + dispatch(lookupAccountFail(acct, error)); + }); +}; + +export const lookupAccountRequest = (acct) => ({ + type: ACCOUNT_LOOKUP_REQUEST, + acct, +}); + +export const lookupAccountSuccess = () => ({ + type: ACCOUNT_LOOKUP_SUCCESS, +}); + +export const lookupAccountFail = (acct, error) => ({ + type: ACCOUNT_LOOKUP_FAIL, + acct, + error, + skipAlert: true, +}); + export function fetchAccountRequest(id) { return { type: ACCOUNT_FETCH_REQUEST, diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index eebe98626..ac6e7f0c4 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -83,7 +83,7 @@ const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 1); export const ensureComposeIsVisible = (getState, routerHistory) => { if (!getState().getIn(['compose', 'mounted']) && window.innerWidth < COMPOSE_PANEL_BREAKPOINT) { - routerHistory.push('/statuses/new'); + routerHistory.push('/publish'); } }; @@ -176,7 +176,8 @@ export function submitCompose(routerHistory) { 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']), }, }).then(function (response) { - if (routerHistory && routerHistory.location.pathname === '/statuses/new' + if (routerHistory + && routerHistory.location.pathname === '/publish' && window.history.state && !getState().getIn(['compose', 'advanced_options', 'threaded_mode'])) { routerHistory.goBack(); diff --git a/app/javascript/flavours/glitch/components/account.js b/app/javascript/flavours/glitch/components/account.js index 20313535b..396a36ea0 100644 --- a/app/javascript/flavours/glitch/components/account.js +++ b/app/javascript/flavours/glitch/components/account.js @@ -128,7 +128,7 @@ class Account extends ImmutablePureComponent {
- +
{mute_expires_at} diff --git a/app/javascript/flavours/glitch/components/avatar_composite.js b/app/javascript/flavours/glitch/components/avatar_composite.js index 125b51c44..e30dfe68a 100644 --- a/app/javascript/flavours/glitch/components/avatar_composite.js +++ b/app/javascript/flavours/glitch/components/avatar_composite.js @@ -82,7 +82,7 @@ export default class AvatarComposite extends React.PureComponent { this.props.onAccountClick(account.get('id'), e)} + onClick={(e) => this.props.onAccountClick(account.get('acct'), e)} title={`@${account.get('acct')}`} key={account.get('id')} > diff --git a/app/javascript/flavours/glitch/components/display_name.js b/app/javascript/flavours/glitch/components/display_name.js index ad978a2c6..9c7da744e 100644 --- a/app/javascript/flavours/glitch/components/display_name.js +++ b/app/javascript/flavours/glitch/components/display_name.js @@ -61,7 +61,7 @@ export default class DisplayName extends React.PureComponent { onAccountClick(a.get('id'), e)} + onClick={(e) => onAccountClick(a.get('acct'), e)} title={`@${a.get('acct')}`} rel='noopener noreferrer' > @@ -76,7 +76,7 @@ export default class DisplayName extends React.PureComponent { } suffix = ( - onAccountClick(account.get('id'), e)} rel='noopener noreferrer'> + onAccountClick(account.get('acct'), e)} rel='noopener noreferrer'> @{acct} ); diff --git a/app/javascript/flavours/glitch/components/hashtag.js b/app/javascript/flavours/glitch/components/hashtag.js index 24c595ed7..d00c01e77 100644 --- a/app/javascript/flavours/glitch/components/hashtag.js +++ b/app/javascript/flavours/glitch/components/hashtag.js @@ -52,7 +52,7 @@ const Hashtag = ({ hashtag }) => (
#{hashtag.get('name')} diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index b72c2417c..72d59f55a 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -346,7 +346,9 @@ class Status extends ImmutablePureComponent { return; } else { if (destination === undefined) { - destination = `/statuses/${ + destination = `/@${ + status.getIn(['reblog', 'account', 'acct'], status.getIn(['account', 'acct'])) + }/${ status.getIn(['reblog', 'id'], status.get('id')) }`; } @@ -362,16 +364,6 @@ class Status extends ImmutablePureComponent { this.setState({ showMedia: !this.state.showMedia }); } - handleAccountClick = (e) => { - if (this.context.router && e.button === 0) { - const id = e.currentTarget.getAttribute('data-id'); - e.preventDefault(); - let state = {...this.context.router.history.location.state}; - state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; - this.context.router.history.push(`/accounts/${id}`, state); - } - } - handleExpandedToggle = () => { if (this.props.status.get('spoiler_text')) { this.setExpansion(!this.state.isExpanded); @@ -433,13 +425,13 @@ class Status extends ImmutablePureComponent { handleHotkeyOpen = () => { let state = {...this.context.router.history.location.state}; state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; - this.context.router.history.push(`/statuses/${this.props.status.get('id')}`, state); + this.context.router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`, state); } handleHotkeyOpenProfile = () => { let state = {...this.context.router.history.location.state}; state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; - this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state); + this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`, state); } handleHotkeyMoveUp = e => { diff --git a/app/javascript/flavours/glitch/components/status_action_bar.js b/app/javascript/flavours/glitch/components/status_action_bar.js index 206ae74c8..d63c6b142 100644 --- a/app/javascript/flavours/glitch/components/status_action_bar.js +++ b/app/javascript/flavours/glitch/components/status_action_bar.js @@ -148,10 +148,10 @@ class StatusActionBar extends ImmutablePureComponent { handleOpen = () => { let state = {...this.context.router.history.location.state}; if (state.mastodonModalKey) { - this.context.router.history.replace(`/statuses/${this.props.status.get('id')}`, { mastodonBackSteps: (state.mastodonBackSteps || 0) + 1 }); + this.context.router.history.replace(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`, { mastodonBackSteps: (state.mastodonBackSteps || 0) + 1 }); } else { state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; - this.context.router.history.push(`/statuses/${this.props.status.get('id')}`, state); + this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`, state); } } diff --git a/app/javascript/flavours/glitch/components/status_content.js b/app/javascript/flavours/glitch/components/status_content.js index 82d066432..1d32b35e5 100644 --- a/app/javascript/flavours/glitch/components/status_content.js +++ b/app/javascript/flavours/glitch/components/status_content.js @@ -197,7 +197,7 @@ export default class StatusContent extends React.PureComponent { onMentionClick = (mention, e) => { if (this.props.parseClick) { - this.props.parseClick(e, `/accounts/${mention.get('id')}`); + this.props.parseClick(e, `/@${mention.get('acct')}`); } } @@ -205,7 +205,7 @@ export default class StatusContent extends React.PureComponent { hashtag = hashtag.replace(/^#/, ''); if (this.props.parseClick) { - this.props.parseClick(e, `/timelines/tag/${hashtag}`); + this.props.parseClick(e, `/tags/${hashtag}`); } } @@ -277,7 +277,7 @@ export default class StatusContent extends React.PureComponent { const mentionLinks = status.get('mentions').map(item => ( { + handleClick = (acct, e) => { const { parseClick } = this.props; - parseClick(e, `/accounts/${id}`); + parseClick(e, `/@${acct}`); } handleAccountClick = (e) => { const { status } = this.props; - this.handleClick(status.getIn(['account', 'id']), e); + this.handleClick(status.getIn(['account', 'acct']), e); } // Rendering. diff --git a/app/javascript/flavours/glitch/components/status_prepend.js b/app/javascript/flavours/glitch/components/status_prepend.js index af6acdef9..5f8d70c9a 100644 --- a/app/javascript/flavours/glitch/components/status_prepend.js +++ b/app/javascript/flavours/glitch/components/status_prepend.js @@ -17,7 +17,7 @@ export default class StatusPrepend extends React.PureComponent { handleClick = (e) => { const { account, parseClick } = this.props; - parseClick(e, `/accounts/${account.get('id')}`); + parseClick(e, `/${account.get('acct')}`); } Message = () => { diff --git a/app/javascript/flavours/glitch/containers/mastodon.js b/app/javascript/flavours/glitch/containers/mastodon.js index 131303fd3..de8ea8ee2 100644 --- a/app/javascript/flavours/glitch/containers/mastodon.js +++ b/app/javascript/flavours/glitch/containers/mastodon.js @@ -23,14 +23,38 @@ store.dispatch(hydrateAction); // load custom emojis store.dispatch(fetchCustomEmojis()); +const createIdentityContext = state => ({ + signedIn: !!state.meta.me, + accountId: state.meta.me, + accessToken: state.meta.access_token, +}); + export default class Mastodon extends React.PureComponent { static propTypes = { locale: PropTypes.string.isRequired, }; + static childContextTypes = { + identity: PropTypes.shape({ + signedIn: PropTypes.bool.isRequired, + accountId: PropTypes.string, + accessToken: PropTypes.string, + }).isRequired, + }; + + identity = createIdentityContext(initialState); + + getChildContext() { + return { + identity: this.identity, + }; + } + componentDidMount() { - this.disconnect = store.dispatch(connectUserStream()); + if (this.identity.signedIn) { + this.disconnect = store.dispatch(connectUserStream()); + } } componentWillUnmount () { diff --git a/app/javascript/flavours/glitch/features/account/components/action_bar.js b/app/javascript/flavours/glitch/features/account/components/action_bar.js index 2d4cc7f49..23120d57e 100644 --- a/app/javascript/flavours/glitch/features/account/components/action_bar.js +++ b/app/javascript/flavours/glitch/features/account/components/action_bar.js @@ -62,17 +62,17 @@ class ActionBar extends React.PureComponent {
- + - + - + { account.get('followers_count') < 0 ? '-' : } diff --git a/app/javascript/flavours/glitch/features/account_gallery/index.js b/app/javascript/flavours/glitch/features/account_gallery/index.js index 434a47dfc..508f49107 100644 --- a/app/javascript/flavours/glitch/features/account_gallery/index.js +++ b/app/javascript/flavours/glitch/features/account_gallery/index.js @@ -16,13 +16,24 @@ import LoadMore from 'flavours/glitch/components/load_more'; import MissingIndicator from 'flavours/glitch/components/missing_indicator'; import { openModal } from 'flavours/glitch/actions/modal'; -const mapStateToProps = (state, props) => ({ - isAccount: !!state.getIn(['accounts', props.params.accountId]), - attachments: getAccountGallery(state, props.params.accountId), - isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']), - hasMore: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'hasMore']), - suspended: state.getIn(['accounts', props.params.accountId, 'suspended'], false), -}); +const mapStateToProps = (state, { params: { acct } }) => { + const accountId = state.getIn(['accounts_map', acct]); + + if (!accountId) { + return { + isLoading: true, + }; + } + + return { + accountId, + isAccount: !!state.getIn(['accounts', accountId]), + attachments: getAccountGallery(state, accountId), + isLoading: state.getIn(['timelines', `account:${accountId}:media`, 'isLoading']), + hasMore: state.getIn(['timelines', `account:${accountId}:media`, 'hasMore']), + suspended: state.getIn(['accounts', accountId, 'suspended'], false), + }; +}; class LoadMoreMedia extends ImmutablePureComponent { @@ -50,7 +61,10 @@ export default @connect(mapStateToProps) class AccountGallery extends ImmutablePureComponent { static propTypes = { - params: PropTypes.object.isRequired, + params: PropTypes.shape({ + acct: PropTypes.string.isRequired, + }).isRequired, + accountId: PropTypes.string, dispatch: PropTypes.func.isRequired, attachments: ImmutablePropTypes.list.isRequired, isLoading: PropTypes.bool, @@ -64,15 +78,29 @@ class AccountGallery extends ImmutablePureComponent { width: 323, }; + _load () { + const { accountId, dispatch } = this.props; + + dispatch(expandAccountMediaTimeline(accountId)); + } + componentDidMount () { - this.props.dispatch(fetchAccount(this.props.params.accountId)); - this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId)); + const { params: { acct }, accountId, dispatch } = this.props; + + if (accountId) { + this._load(); + } else { + dispatch(lookupAccount(acct)); + } } - componentWillReceiveProps (nextProps) { - if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { - this.props.dispatch(fetchAccount(nextProps.params.accountId)); - this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId)); + componentDidUpdate (prevProps) { + const { params: { acct }, accountId, dispatch } = this.props; + + if (prevProps.accountId !== accountId && accountId) { + this._load(); + } else if (prevProps.params.acct !== acct) { + dispatch(lookupAccount(acct)); } } @@ -96,7 +124,7 @@ class AccountGallery extends ImmutablePureComponent { } handleLoadMore = maxId => { - this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId, { maxId })); + this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, { maxId })); }; handleLoadOlder = e => { @@ -162,7 +190,7 @@ class AccountGallery extends ImmutablePureComponent {
- + {suspended ? (
diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/header.js b/app/javascript/flavours/glitch/features/account_timeline/components/header.js index a6b57d331..e70f011b7 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/header.js +++ b/app/javascript/flavours/glitch/features/account_timeline/components/header.js @@ -128,9 +128,9 @@ export default class Header extends ImmutablePureComponent { {!hideTabs && (
- - - + + +
)}
diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js b/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js index fcaa7b494..308407e94 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js +++ b/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js @@ -23,7 +23,7 @@ export default class MovedNote extends ImmutablePureComponent { e.preventDefault(); let state = {...this.context.router.history.location.state}; state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; - this.context.router.history.push(`/accounts/${this.props.to.get('id')}`, state); + this.context.router.history.push(`/@${this.props.to.get('acct')}`, state); } e.stopPropagation(); diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.js b/app/javascript/flavours/glitch/features/account_timeline/index.js index 0d24980a9..3d2bbb3b7 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/index.js +++ b/app/javascript/flavours/glitch/features/account_timeline/index.js @@ -2,7 +2,7 @@ import React from 'react'; import { connect } from 'react-redux'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; -import { fetchAccount } from 'flavours/glitch/actions/accounts'; +import { lookupAccount, fetchAccount } from 'flavours/glitch/actions/accounts'; import { expandAccountFeaturedTimeline, expandAccountTimeline } from 'flavours/glitch/actions/timelines'; import StatusList from '../../components/status_list'; import LoadingIndicator from '../../components/loading_indicator'; @@ -19,10 +19,19 @@ import TimelineHint from 'flavours/glitch/components/timeline_hint'; const emptyList = ImmutableList(); -const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => { +const mapStateToProps = (state, { params: { acct }, withReplies = false }) => { + const accountId = state.getIn(['accounts_map', acct]); + + if (!accountId) { + return { + isLoading: true, + }; + } + const path = withReplies ? `${accountId}:with_replies` : accountId; return { + accountId, remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])), remoteUrl: state.getIn(['accounts', accountId, 'url']), isAccount: !!state.getIn(['accounts', accountId]), @@ -46,7 +55,10 @@ export default @connect(mapStateToProps) class AccountTimeline extends ImmutablePureComponent { static propTypes = { - params: PropTypes.object.isRequired, + params: PropTypes.shape({ + acct: PropTypes.string.isRequired, + }).isRequired, + accountId: PropTypes.string, dispatch: PropTypes.func.isRequired, statusIds: ImmutablePropTypes.list, featuredStatusIds: ImmutablePropTypes.list, @@ -60,25 +72,47 @@ class AccountTimeline extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - componentWillMount () { - const { params: { accountId }, withReplies } = this.props; + _load () { + const { accountId, withReplies, dispatch } = this.props; - this.props.dispatch(fetchAccount(accountId)); - this.props.dispatch(fetchAccountIdentityProofs(accountId)); + dispatch(fetchAccount(accountId)); + dispatch(fetchAccountIdentityProofs(accountId)); if (!withReplies) { - this.props.dispatch(expandAccountFeaturedTimeline(accountId)); + dispatch(expandAccountFeaturedTimeline(accountId)); + } + dispatch(expandAccountTimeline(accountId, { withReplies })); + } + + componentDidMount () { + const { params: { acct }, accountId, dispatch } = this.props; + + if (accountId) { + this._load(); + } else { + dispatch(lookupAccount(acct)); + } + } + + componentDidUpdate (prevProps) { + const { params: { acct }, accountId, dispatch } = this.props; + + if (prevProps.accountId !== accountId && accountId) { + this._load(); + } else if (prevProps.params.acct !== acct) { + dispatch(lookupAccount(acct)); } - this.props.dispatch(expandAccountTimeline(accountId, { withReplies })); } componentWillReceiveProps (nextProps) { + const { dispatch } = this.props; + if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) { - this.props.dispatch(fetchAccount(nextProps.params.accountId)); - this.props.dispatch(fetchAccountIdentityProofs(nextProps.params.accountId)); + dispatch(fetchAccount(nextProps.params.accountId)); + dispatch(fetchAccountIdentityProofs(nextProps.params.accountId)); if (!nextProps.withReplies) { - this.props.dispatch(expandAccountFeaturedTimeline(nextProps.params.accountId)); + dispatch(expandAccountFeaturedTimeline(nextProps.params.accountId)); } - this.props.dispatch(expandAccountTimeline(nextProps.params.accountId, { withReplies: nextProps.params.withReplies })); + dispatch(expandAccountTimeline(nextProps.params.accountId, { withReplies: nextProps.params.withReplies })); } } @@ -87,7 +121,7 @@ class AccountTimeline extends ImmutablePureComponent { } handleLoadMore = maxId => { - this.props.dispatch(expandAccountTimeline(this.props.params.accountId, { maxId, withReplies: this.props.withReplies })); + this.props.dispatch(expandAccountTimeline(this.props.accountId, { maxId, withReplies: this.props.withReplies })); } setRef = c => { @@ -131,7 +165,7 @@ class AccountTimeline extends ImmutablePureComponent { } + prepend={} alwaysPrepend append={remoteMessage} scrollKey='account_timeline' diff --git a/app/javascript/flavours/glitch/features/compose/components/header.js b/app/javascript/flavours/glitch/features/compose/components/header.js index 5b456b717..c6e4cc36b 100644 --- a/app/javascript/flavours/glitch/features/compose/components/header.js +++ b/app/javascript/flavours/glitch/features/compose/components/header.js @@ -87,7 +87,7 @@ class Header extends ImmutablePureComponent { ))} {renderForColumn('NOTIFICATIONS', ( @@ -106,14 +106,14 @@ class Header extends ImmutablePureComponent { ))} {renderForColumn('PUBLIC', ( ))} - + {this.props.account.get('acct')}
- + @{this.props.account.get('acct')} diff --git a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js index 98b48cd90..9796ded65 100644 --- a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js +++ b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js @@ -163,7 +163,7 @@ class Conversation extends ImmutablePureComponent { menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDelete }); - const names = accounts.map(a => ).reduce((prev, cur) => [prev, ', ', cur]); + const names = accounts.map(a => ).reduce((prev, cur) => [prev, ', ', cur]); const handlers = { reply: this.handleReply, diff --git a/app/javascript/flavours/glitch/features/directory/components/account_card.js b/app/javascript/flavours/glitch/features/directory/components/account_card.js index 9fe84c10b..2a3fd1ecf 100644 --- a/app/javascript/flavours/glitch/features/directory/components/account_card.js +++ b/app/javascript/flavours/glitch/features/directory/components/account_card.js @@ -213,7 +213,7 @@ class AccountCard extends ImmutablePureComponent { diff --git a/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.js b/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.js index eb9f3db7e..cbe7a1032 100644 --- a/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.js +++ b/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.js @@ -30,7 +30,7 @@ class AccountAuthorize extends ImmutablePureComponent { return (
- +
diff --git a/app/javascript/flavours/glitch/features/followers/index.js b/app/javascript/flavours/glitch/features/followers/index.js index 25f7f05b4..21e25b869 100644 --- a/app/javascript/flavours/glitch/features/followers/index.js +++ b/app/javascript/flavours/glitch/features/followers/index.js @@ -5,7 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { debounce } from 'lodash'; import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; import { - fetchAccount, + lookupAccount, fetchFollowers, expandFollowers, } from 'flavours/glitch/actions/accounts'; @@ -19,14 +19,25 @@ import MissingIndicator from 'flavours/glitch/components/missing_indicator'; import ScrollableList from 'flavours/glitch/components/scrollable_list'; import TimelineHint from 'flavours/glitch/components/timeline_hint'; -const mapStateToProps = (state, props) => ({ - remote: !!(state.getIn(['accounts', props.params.accountId, 'acct']) !== state.getIn(['accounts', props.params.accountId, 'username'])), - remoteUrl: state.getIn(['accounts', props.params.accountId, 'url']), - isAccount: !!state.getIn(['accounts', props.params.accountId]), - accountIds: state.getIn(['user_lists', 'followers', props.params.accountId, 'items']), - hasMore: !!state.getIn(['user_lists', 'followers', props.params.accountId, 'next']), - isLoading: state.getIn(['user_lists', 'followers', props.params.accountId, 'isLoading'], true), -}); +const mapStateToProps = (state, { params: { acct } }) => { + const accountId = state.getIn(['accounts_map', acct]); + + if (!accountId) { + return { + isLoading: true, + }; + } + + return { + accountId, + remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])), + remoteUrl: state.getIn(['accounts', accountId, 'url']), + isAccount: !!state.getIn(['accounts', accountId]), + accountIds: state.getIn(['user_lists', 'followers', accountId, 'items']), + hasMore: !!state.getIn(['user_lists', 'followers', accountId, 'next']), + isLoading: state.getIn(['user_lists', 'followers', accountId, 'isLoading'], true), + }; +}; const RemoteHint = ({ url }) => ( } /> @@ -40,7 +51,10 @@ export default @connect(mapStateToProps) class Followers extends ImmutablePureComponent { static propTypes = { - params: PropTypes.object.isRequired, + params: PropTypes.shape({ + acct: PropTypes.string.isRequired, + }).isRequired, + accountId: PropTypes.string, dispatch: PropTypes.func.isRequired, accountIds: ImmutablePropTypes.list, hasMore: PropTypes.bool, @@ -51,32 +65,44 @@ class Followers extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - componentWillMount () { - if (!this.props.accountIds) { - this.props.dispatch(fetchAccount(this.props.params.accountId)); - this.props.dispatch(fetchFollowers(this.props.params.accountId)); - } + _load () { + const { accountId, dispatch } = this.props; + + dispatch(fetchFollowers(accountId)); } - componentWillReceiveProps (nextProps) { - if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { - this.props.dispatch(fetchAccount(nextProps.params.accountId)); - this.props.dispatch(fetchFollowers(nextProps.params.accountId)); + componentDidMount () { + const { params: { acct }, accountId, dispatch } = this.props; + + if (accountId) { + this._load(); + } else { + dispatch(lookupAccount(acct)); } } - handleHeaderClick = () => { - this.column.scrollTop(); + componentDidUpdate (prevProps) { + const { params: { acct }, accountId, dispatch } = this.props; + + if (prevProps.accountId !== accountId && accountId) { + this._load(); + } else if (prevProps.params.acct !== acct) { + dispatch(lookupAccount(acct)); + } } handleLoadMore = debounce(() => { - this.props.dispatch(expandFollowers(this.props.params.accountId)); + this.props.dispatch(expandFollowers(this.props.accountId)); }, 300, { leading: true }); setRef = c => { this.column = c; } + handleHeaderClick = () => { + this.column.scrollTop(); + } + render () { const { accountIds, hasMore, isAccount, multiColumn, isLoading, remote, remoteUrl } = this.props; @@ -115,7 +141,7 @@ class Followers extends ImmutablePureComponent { hasMore={hasMore} isLoading={isLoading} onLoadMore={this.handleLoadMore} - prepend={} + prepend={} alwaysPrepend append={remoteMessage} emptyMessage={emptyMessage} diff --git a/app/javascript/flavours/glitch/features/following/index.js b/app/javascript/flavours/glitch/features/following/index.js index 968829fd5..c1d026d98 100644 --- a/app/javascript/flavours/glitch/features/following/index.js +++ b/app/javascript/flavours/glitch/features/following/index.js @@ -5,7 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { debounce } from 'lodash'; import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; import { - fetchAccount, + lookupAccount, fetchFollowing, expandFollowing, } from 'flavours/glitch/actions/accounts'; @@ -19,14 +19,25 @@ import MissingIndicator from 'flavours/glitch/components/missing_indicator'; import ScrollableList from 'flavours/glitch/components/scrollable_list'; import TimelineHint from 'flavours/glitch/components/timeline_hint'; -const mapStateToProps = (state, props) => ({ - remote: !!(state.getIn(['accounts', props.params.accountId, 'acct']) !== state.getIn(['accounts', props.params.accountId, 'username'])), - remoteUrl: state.getIn(['accounts', props.params.accountId, 'url']), - isAccount: !!state.getIn(['accounts', props.params.accountId]), - accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']), - hasMore: !!state.getIn(['user_lists', 'following', props.params.accountId, 'next']), - isLoading: state.getIn(['user_lists', 'following', props.params.accountId, 'isLoading'], true), -}); +const mapStateToProps = (state, { params: { acct } }) => { + const accountId = state.getIn(['accounts_map', acct]); + + if (!accountId) { + return { + isLoading: true, + }; + } + + return { + accountId, + remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])), + remoteUrl: state.getIn(['accounts', accountId, 'url']), + isAccount: !!state.getIn(['accounts', accountId]), + accountIds: state.getIn(['user_lists', 'following', accountId, 'items']), + hasMore: !!state.getIn(['user_lists', 'following', accountId, 'next']), + isLoading: state.getIn(['user_lists', 'following', accountId, 'isLoading'], true), + }; +}; const RemoteHint = ({ url }) => ( } /> @@ -40,7 +51,10 @@ export default @connect(mapStateToProps) class Following extends ImmutablePureComponent { static propTypes = { - params: PropTypes.object.isRequired, + params: PropTypes.shape({ + acct: PropTypes.string.isRequired, + }).isRequired, + accountId: PropTypes.string, dispatch: PropTypes.func.isRequired, accountIds: ImmutablePropTypes.list, hasMore: PropTypes.bool, @@ -51,32 +65,44 @@ class Following extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - componentWillMount () { - if (!this.props.accountIds) { - this.props.dispatch(fetchAccount(this.props.params.accountId)); - this.props.dispatch(fetchFollowing(this.props.params.accountId)); - } + _load () { + const { accountId, dispatch } = this.props; + + dispatch(fetchFollowing(accountId)); } - componentWillReceiveProps (nextProps) { - if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { - this.props.dispatch(fetchAccount(nextProps.params.accountId)); - this.props.dispatch(fetchFollowing(nextProps.params.accountId)); + componentDidMount () { + const { params: { acct }, accountId, dispatch } = this.props; + + if (accountId) { + this._load(); + } else { + dispatch(lookupAccount(acct)); } } - handleHeaderClick = () => { - this.column.scrollTop(); + componentDidUpdate (prevProps) { + const { params: { acct }, accountId, dispatch } = this.props; + + if (prevProps.accountId !== accountId && accountId) { + this._load(); + } else if (prevProps.params.acct !== acct) { + dispatch(lookupAccount(acct)); + } } handleLoadMore = debounce(() => { - this.props.dispatch(expandFollowing(this.props.params.accountId)); + this.props.dispatch(expandFollowing(this.props.accountId)); }, 300, { leading: true }); setRef = c => { this.column = c; } + handleHeaderClick = () => { + this.column.scrollTop(); + } + render () { const { accountIds, hasMore, isAccount, multiColumn, isLoading, remote, remoteUrl } = this.props; @@ -115,7 +141,7 @@ class Following extends ImmutablePureComponent { hasMore={hasMore} isLoading={isLoading} onLoadMore={this.handleLoadMore} - prepend={} + prepend={} alwaysPrepend append={remoteMessage} emptyMessage={emptyMessage} diff --git a/app/javascript/flavours/glitch/features/getting_started/components/announcements.js b/app/javascript/flavours/glitch/features/getting_started/components/announcements.js index 4eebe83ef..2f6d2de5c 100644 --- a/app/javascript/flavours/glitch/features/getting_started/components/announcements.js +++ b/app/javascript/flavours/glitch/features/getting_started/components/announcements.js @@ -87,7 +87,7 @@ class Content extends ImmutablePureComponent { onMentionClick = (mention, e) => { if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); - this.context.router.history.push(`/accounts/${mention.get('id')}`); + this.context.router.history.push(`/@${mention.get('acct')}`); } } @@ -96,14 +96,14 @@ class Content extends ImmutablePureComponent { if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); - this.context.router.history.push(`/timelines/tag/${hashtag}`); + this.context.router.history.push(`/tags/${hashtag}`); } } onStatusClick = (status, e) => { if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); - this.context.router.history.push(`/statuses/${status.get('id')}`); + this.context.router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); } } diff --git a/app/javascript/flavours/glitch/features/getting_started/index.js b/app/javascript/flavours/glitch/features/getting_started/index.js index b4549fdf8..56750fd88 100644 --- a/app/javascript/flavours/glitch/features/getting_started/index.js +++ b/app/javascript/flavours/glitch/features/getting_started/index.js @@ -107,7 +107,7 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2); const { fetchFollowRequests, multiColumn } = this.props; if (!multiColumn && window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) { - this.context.router.history.replace('/timelines/home'); + this.context.router.history.replace('/home'); return; } @@ -122,7 +122,7 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2); if (multiColumn) { if (!columns.find(item => item.get('id') === 'HOME')) { - navItems.push(); + navItems.push(); } if (!columns.find(item => item.get('id') === 'NOTIFICATIONS')) { @@ -130,16 +130,16 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2); } if (!columns.find(item => item.get('id') === 'COMMUNITY')) { - navItems.push(); + navItems.push(); } if (!columns.find(item => item.get('id') === 'PUBLIC')) { - navItems.push(); + navItems.push(); } } if (!multiColumn || !columns.find(item => item.get('id') === 'DIRECT')) { - navItems.push(); + navItems.push(); } if (!multiColumn || !columns.find(item => item.get('id') === 'BOOKMARKS')) { @@ -160,7 +160,7 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
{lists.filter(list => !columns.find(item => item.get('id') === 'LIST' && item.getIn(['params', 'id']) === list.get('id'))).map(list => - + )}
, ]); diff --git a/app/javascript/flavours/glitch/features/lists/index.js b/app/javascript/flavours/glitch/features/lists/index.js index e384f301b..b92389d82 100644 --- a/app/javascript/flavours/glitch/features/lists/index.js +++ b/app/javascript/flavours/glitch/features/lists/index.js @@ -73,7 +73,7 @@ class Lists extends ImmutablePureComponent { bindToDocument={!multiColumn} > {lists.map(list => - , + , )} diff --git a/app/javascript/flavours/glitch/features/notifications/components/follow.js b/app/javascript/flavours/glitch/features/notifications/components/follow.js index 0d3162fc9..b8fad19d0 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/follow.js +++ b/app/javascript/flavours/glitch/features/notifications/components/follow.js @@ -39,7 +39,7 @@ export default class NotificationFollow extends ImmutablePureComponent { handleOpenProfile = () => { const { notification } = this.props; - this.context.router.history.push(`/accounts/${notification.getIn(['account', 'id'])}`); + this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`); } handleMention = e => { @@ -70,7 +70,7 @@ export default class NotificationFollow extends ImmutablePureComponent { className='notification__display-name' href={account.get('url')} title={account.get('acct')} - to={`/accounts/${account.get('id')}`} + to={`/@${account.get('acct')}`} dangerouslySetInnerHTML={{ __html: displayName }} /> ); diff --git a/app/javascript/flavours/glitch/features/notifications/components/follow_request.js b/app/javascript/flavours/glitch/features/notifications/components/follow_request.js index f351c1035..69b92a06f 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/follow_request.js +++ b/app/javascript/flavours/glitch/features/notifications/components/follow_request.js @@ -45,7 +45,7 @@ class FollowRequest extends ImmutablePureComponent { handleOpenProfile = () => { const { notification } = this.props; - this.context.router.history.push(`/accounts/${notification.getIn(['account', 'id'])}`); + this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`); } handleMention = e => { @@ -89,7 +89,7 @@ class FollowRequest extends ImmutablePureComponent { className='notification__display-name' href={account.get('url')} title={account.get('acct')} - to={`/accounts/${account.get('id')}`} + to={`/@${account.get('acct')}`} dangerouslySetInnerHTML={{ __html: displayName }} /> ); @@ -111,7 +111,7 @@ class FollowRequest extends ImmutablePureComponent {
- +
diff --git a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js index 98d1f40b2..e01d277a1 100644 --- a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js +++ b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js @@ -122,7 +122,7 @@ class Footer extends ImmutablePureComponent { onClose(); } - router.history.push(`/statuses/${status.get('id')}`); + router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); } render () { diff --git a/app/javascript/flavours/glitch/features/picture_in_picture/components/header.js b/app/javascript/flavours/glitch/features/picture_in_picture/components/header.js index 28526ca88..26f2da374 100644 --- a/app/javascript/flavours/glitch/features/picture_in_picture/components/header.js +++ b/app/javascript/flavours/glitch/features/picture_in_picture/components/header.js @@ -34,7 +34,7 @@ class Header extends ImmutablePureComponent { return (
- + diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js index 416a4ca13..a1f981484 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js @@ -51,7 +51,7 @@ export default class DetailedStatus extends ImmutablePureComponent { e.preventDefault(); let state = {...this.context.router.history.location.state}; state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; - this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state); + this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`, state); } e.stopPropagation(); @@ -216,7 +216,7 @@ export default class DetailedStatus extends ImmutablePureComponent { reblogLink = ( ยท - + @@ -240,7 +240,7 @@ export default class DetailedStatus extends ImmutablePureComponent { if (this.context.router) { favouriteLink = ( - + diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js index 9dbba4772..bddf1a2ef 100644 --- a/app/javascript/flavours/glitch/features/status/index.js +++ b/app/javascript/flavours/glitch/features/status/index.js @@ -405,7 +405,7 @@ class Status extends ImmutablePureComponent { handleHotkeyOpenProfile = () => { let state = {...this.context.router.history.location.state}; state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; - this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state); + this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`, state); } handleMoveUp = id => { diff --git a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js index c4af25599..c23aec5ee 100644 --- a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js @@ -69,7 +69,7 @@ class BoostModal extends ImmutablePureComponent { this.props.onClose(); let state = {...this.context.router.history.location.state}; state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; - this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state); + this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`, state); } } diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.js b/app/javascript/flavours/glitch/features/ui/components/columns_area.js index d4e0bedac..5ab8f249d 100644 --- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js +++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.js @@ -216,7 +216,7 @@ class ColumnsArea extends ImmutablePureComponent { const columnIndex = getIndex(this.context.router.history.location.pathname); if (singleColumn) { - const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : ; + const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : ; const content = columnIndex !== -1 ? ( diff --git a/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js index ea1d7876e..9b7f9d1fb 100644 --- a/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js @@ -49,7 +49,7 @@ class FavouriteModal extends ImmutablePureComponent { this.props.onClose(); let state = {...this.context.router.history.location.state}; state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; - this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state); + this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`, state); } } diff --git a/app/javascript/flavours/glitch/features/ui/components/list_panel.js b/app/javascript/flavours/glitch/features/ui/components/list_panel.js index 354e35027..e61234283 100644 --- a/app/javascript/flavours/glitch/features/ui/components/list_panel.js +++ b/app/javascript/flavours/glitch/features/ui/components/list_panel.js @@ -46,7 +46,7 @@ class ListPanel extends ImmutablePureComponent {
{lists.map(list => ( - {list.get('title')} + {list.get('title')} ))}
); diff --git a/app/javascript/flavours/glitch/features/ui/components/media_modal.js b/app/javascript/flavours/glitch/features/ui/components/media_modal.js index a8cbb837e..6974aab26 100644 --- a/app/javascript/flavours/glitch/features/ui/components/media_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/media_modal.js @@ -112,15 +112,6 @@ class MediaModal extends ImmutablePureComponent { })); }; - handleStatusClick = e => { - if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - this.context.router.history.push(`/statuses/${this.props.statusId}`); - } - - this._sendBackgroundColor(); - } - componentDidUpdate (prevProps, prevState) { if (prevState.index !== this.state.index) { this._sendBackgroundColor(); diff --git a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js index 50e7d5c48..2dcd535ca 100644 --- a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js +++ b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js @@ -11,12 +11,12 @@ import TrendsContainer from 'flavours/glitch/features/getting_started/containers const NavigationPanel = ({ onOpenSettings }) => (
- + - - - + + + {profile_directory && } diff --git a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js index e0872927a..e7be62ad8 100644 --- a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js @@ -79,7 +79,7 @@ const PageThree = ({ intl, myAccount }) => (
-

#illustration, introductions: #introductions }} />

+

#illustration, introductions: #introductions }} />

); @@ -130,7 +130,7 @@ const PageSix = ({ admin, domain }) => { if (admin) { adminSection = (

- @{admin.get('acct')} }} /> + @{admin.get('acct')} }} />
}} />

diff --git a/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js b/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js index a67405215..55cc84f5e 100644 --- a/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js +++ b/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js @@ -8,10 +8,10 @@ import Icon from 'flavours/glitch/components/icon'; import NotificationsCounterIcon from './notifications_counter_icon'; export const links = [ - , + , , - , - , + , + , , , ]; diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js index ad063f01b..4683f11cd 100644 --- a/app/javascript/flavours/glitch/features/ui/index.js +++ b/app/javascript/flavours/glitch/features/ui/index.js @@ -78,6 +78,7 @@ const mapStateToProps = state => ({ hicolorPrivacyIcons: state.getIn(['local_settings', 'hicolor_privacy_icons']), moved: state.getIn(['accounts', me, 'moved']) && state.getIn(['accounts', state.getIn(['accounts', me, 'moved'])]), firstLaunch: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION, + username: state.getIn(['accounts', me, 'username']), }); const keyMap = { @@ -190,7 +191,7 @@ class SwitchingColumnsArea extends React.PureComponent { render () { const { children, navbarUnder } = this.props; const singleColumn = this.state.mobile; - const redirect = singleColumn ? : ; + const redirect = singleColumn ? : ; return ( @@ -198,32 +199,32 @@ class SwitchingColumnsArea extends React.PureComponent { {redirect} - - - - - - + + + + + + + + - - - - - - - - - - + + + + + + + + @@ -265,6 +266,7 @@ class UI extends React.Component { showFaviconBadge: PropTypes.bool, moved: PropTypes.map, firstLaunch: PropTypes.bool, + username: PropTypes.string, }; state = { @@ -517,7 +519,7 @@ class UI extends React.Component { } handleHotkeyGoToHome = () => { - this.props.history.push('/timelines/home'); + this.props.history.push('/home'); } handleHotkeyGoToNotifications = () => { @@ -525,15 +527,15 @@ class UI extends React.Component { } handleHotkeyGoToLocal = () => { - this.props.history.push('/timelines/public/local'); + this.props.history.push('/public/local'); } handleHotkeyGoToFederated = () => { - this.props.history.push('/timelines/public'); + this.props.history.push('/public'); } handleHotkeyGoToDirect = () => { - this.props.history.push('/timelines/direct'); + this.props.history.push('/conversations'); } handleHotkeyGoToStart = () => { @@ -549,7 +551,7 @@ class UI extends React.Component { } handleHotkeyGoToProfile = () => { - this.props.history.push(`/accounts/${me}`); + this.props.history.push(`/@${this.props.username}`); } handleHotkeyGoToBlocked = () => { @@ -616,7 +618,7 @@ class UI extends React.Component { id='moved_to_warning' defaultMessage='This account is marked as moved to {moved_to_link}, and may thus not accept new follows.' values={{ moved_to_link: ( - + @{moved.get('acct')} )}} diff --git a/app/javascript/flavours/glitch/reducers/accounts_map.js b/app/javascript/flavours/glitch/reducers/accounts_map.js new file mode 100644 index 000000000..e0d42e9cd --- /dev/null +++ b/app/javascript/flavours/glitch/reducers/accounts_map.js @@ -0,0 +1,15 @@ +import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from '../actions/importer'; +import { Map as ImmutableMap } from 'immutable'; + +const initialState = ImmutableMap(); + +export default function accountsMap(state = initialState, action) { + switch(action.type) { + case ACCOUNT_IMPORT: + return state.set(action.account.acct, action.account.id); + case ACCOUNTS_IMPORT: + return state.withMutations(map => action.accounts.forEach(account => map.set(account.acct, account.id))); + default: + return state; + } +}; diff --git a/app/javascript/flavours/glitch/reducers/index.js b/app/javascript/flavours/glitch/reducers/index.js index c452e834c..7d7fe6fd3 100644 --- a/app/javascript/flavours/glitch/reducers/index.js +++ b/app/javascript/flavours/glitch/reducers/index.js @@ -40,6 +40,7 @@ import announcements from './announcements'; import markers from './markers'; import account_notes from './account_notes'; import picture_in_picture from './picture_in_picture'; +import accounts_map from './accounts_map'; const reducers = { announcements, @@ -54,6 +55,7 @@ const reducers = { status_lists, accounts, accounts_counters, + accounts_map, statuses, relationships, settings, diff --git a/app/javascript/flavours/glitch/util/api.js b/app/javascript/flavours/glitch/util/api.js index c59a24518..90d8465ef 100644 --- a/app/javascript/flavours/glitch/util/api.js +++ b/app/javascript/flavours/glitch/util/api.js @@ -12,21 +12,35 @@ export const getLinks = response => { return LinkHeader.parse(value); }; -let csrfHeader = {}; +const csrfHeader = {}; -function setCSRFHeader() { +const setCSRFHeader = () => { const csrfToken = document.querySelector('meta[name=csrf-token]'); + if (csrfToken) { csrfHeader['X-CSRF-Token'] = csrfToken.content; } -} +}; ready(setCSRFHeader); +const authorizationHeaderFromState = getState => { + const accessToken = getState && getState().getIn(['meta', 'access_token'], ''); + + if (!accessToken) { + return {}; + } + + return { + 'Authorization': `Bearer ${accessToken}`, + }; +}; + export default getState => axios.create({ - headers: Object.assign(csrfHeader, getState ? { - 'Authorization': `Bearer ${getState().getIn(['meta', 'access_token'], '')}`, - } : {}), + headers: { + ...csrfHeader, + ...authorizationHeaderFromState(getState), + }, transformResponse: [function (data) { try { -- cgit From 7aec1bc30862b81de8dcb43b61f8fdd13c935ecd Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 14 Oct 2021 20:44:59 +0200 Subject: [Glitch] Add graphs and retention metrics to admin dashboard (#16829) Port 07341e7aa60fe7c7d4f298136af99276820940e7 to glitch-soc Signed-off-by: Claire --- app/javascript/core/admin.js | 20 --- .../flavours/glitch/components/admin/Counter.js | 115 ++++++++++++ .../flavours/glitch/components/admin/Dimension.js | 92 ++++++++++ .../flavours/glitch/components/admin/Retention.js | 141 +++++++++++++++ .../flavours/glitch/components/admin/Trends.js | 73 ++++++++ .../flavours/glitch/components/hashtag.js | 61 +++---- .../flavours/glitch/components/skeleton.js | 11 ++ .../flavours/glitch/containers/admin_component.js | 26 +++ .../flavours/glitch/containers/media_container.js | 2 +- .../features/compose/components/search_results.js | 2 +- .../features/getting_started/components/trends.js | 2 +- app/javascript/flavours/glitch/packs/admin.js | 24 +++ app/javascript/flavours/glitch/styles/admin.scss | 193 ++++++++++++++++++++- .../flavours/glitch/styles/components/search.scss | 53 +++++- .../flavours/glitch/styles/dashboard.scss | 57 ++++-- app/javascript/flavours/glitch/theme.yml | 2 +- app/javascript/flavours/glitch/util/numbers.js | 8 + app/javascript/flavours/vanilla/theme.yml | 2 +- app/javascript/packs/admin.js | 24 +++ 19 files changed, 838 insertions(+), 70 deletions(-) create mode 100644 app/javascript/flavours/glitch/components/admin/Counter.js create mode 100644 app/javascript/flavours/glitch/components/admin/Dimension.js create mode 100644 app/javascript/flavours/glitch/components/admin/Retention.js create mode 100644 app/javascript/flavours/glitch/components/admin/Trends.js create mode 100644 app/javascript/flavours/glitch/components/skeleton.js create mode 100644 app/javascript/flavours/glitch/containers/admin_component.js create mode 100644 app/javascript/flavours/glitch/packs/admin.js create mode 100644 app/javascript/packs/admin.js (limited to 'app/javascript/flavours/glitch/util') diff --git a/app/javascript/core/admin.js b/app/javascript/core/admin.js index 8817c09b6..d2db89ca7 100644 --- a/app/javascript/core/admin.js +++ b/app/javascript/core/admin.js @@ -101,24 +101,4 @@ ready(() => { const registrationMode = document.getElementById('form_admin_settings_registrations_mode'); if (registrationMode) onChangeRegistrationMode(registrationMode); - - const React = require('react'); - const ReactDOM = require('react-dom'); - - [].forEach.call(document.querySelectorAll('[data-admin-component]'), element => { - const componentName = element.getAttribute('data-admin-component'); - const { locale, ...componentProps } = JSON.parse(element.getAttribute('data-props')); - - import('../mastodon/containers/admin_component').then(({ default: AdminComponent }) => { - return import('../mastodon/components/admin/' + componentName).then(({ default: Component }) => { - ReactDOM.render(( - - - - ), element); - }); - }).catch(error => { - console.error(error); - }); - }); }); diff --git a/app/javascript/flavours/glitch/components/admin/Counter.js b/app/javascript/flavours/glitch/components/admin/Counter.js new file mode 100644 index 000000000..39ef216bd --- /dev/null +++ b/app/javascript/flavours/glitch/components/admin/Counter.js @@ -0,0 +1,115 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import api from 'flavours/glitch/util/api'; +import { FormattedNumber } from 'react-intl'; +import { Sparklines, SparklinesCurve } from 'react-sparklines'; +import classNames from 'classnames'; +import Skeleton from 'flavours/glitch/components/skeleton'; + +const percIncrease = (a, b) => { + let percent; + + if (b !== 0) { + if (a !== 0) { + percent = (b - a) / a; + } else { + percent = 1; + } + } else if (b === 0 && a === 0) { + percent = 0; + } else { + percent = - 1; + } + + return percent; +}; + +export default class Counter extends React.PureComponent { + + static propTypes = { + measure: PropTypes.string.isRequired, + start_at: PropTypes.string.isRequired, + end_at: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + href: PropTypes.string, + }; + + state = { + loading: true, + data: null, + }; + + componentDidMount () { + const { measure, start_at, end_at } = this.props; + + api().post('/api/v1/admin/measures', { keys: [measure], start_at, end_at }).then(res => { + this.setState({ + loading: false, + data: res.data, + }); + }).catch(err => { + console.error(err); + }); + } + + render () { + const { label, href } = this.props; + const { loading, data } = this.state; + + let content; + + if (loading) { + content = ( + + + + + ); + } else { + const measure = data[0]; + const percentChange = percIncrease(measure.previous_total * 1, measure.total * 1); + + content = ( + + + 0, negative: percentChange < 0 })}>{percentChange > 0 && '+'} + + ); + } + + const inner = ( + +
+ {content} +
+ +
+ {label} +
+ +
+ {!loading && ( + x.value * 1)}> + + + )} +
+
+ ); + + if (href) { + return ( + + {inner} + + ); + } else { + return ( +
+ {inner} +
+ ); + } + } + +} diff --git a/app/javascript/flavours/glitch/components/admin/Dimension.js b/app/javascript/flavours/glitch/components/admin/Dimension.js new file mode 100644 index 000000000..b4fbf86c8 --- /dev/null +++ b/app/javascript/flavours/glitch/components/admin/Dimension.js @@ -0,0 +1,92 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import api from 'flavours/glitch/util/api'; +import { FormattedNumber } from 'react-intl'; +import { roundTo10 } from 'flavours/glitch/util/numbers'; +import Skeleton from 'flavours/glitch/components/skeleton'; + +export default class Dimension extends React.PureComponent { + + static propTypes = { + dimension: PropTypes.string.isRequired, + start_at: PropTypes.string.isRequired, + end_at: PropTypes.string.isRequired, + limit: PropTypes.number.isRequired, + label: PropTypes.string.isRequired, + }; + + state = { + loading: true, + data: null, + }; + + componentDidMount () { + const { start_at, end_at, dimension, limit } = this.props; + + api().post('/api/v1/admin/dimensions', { keys: [dimension], start_at, end_at, limit }).then(res => { + this.setState({ + loading: false, + data: res.data, + }); + }).catch(err => { + console.error(err); + }); + } + + render () { + const { label, limit } = this.props; + const { loading, data } = this.state; + + let content; + + if (loading) { + content = ( + + + {Array.from(Array(limit)).map((_, i) => ( + + + + + + ))} + +
+ + + +
+ ); + } else { + const sum = data[0].data.reduce((sum, cur) => sum + (cur.value * 1), 0); + + content = ( + + + {data[0].data.map(item => ( + + + + + + ))} + +
+ + {item.human_key} + + {typeof item.human_value !== 'undefined' ? item.human_value : } +
+ ); + } + + return ( +
+

{label}

+ + {content} +
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/components/admin/Retention.js b/app/javascript/flavours/glitch/components/admin/Retention.js new file mode 100644 index 000000000..8295362a4 --- /dev/null +++ b/app/javascript/flavours/glitch/components/admin/Retention.js @@ -0,0 +1,141 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import api from 'flavours/glitch/util/api'; +import { FormattedMessage, FormattedNumber, FormattedDate } from 'react-intl'; +import classNames from 'classnames'; +import { roundTo10 } from 'flavours/glitch/util/numbers'; + +const dateForCohort = cohort => { + switch(cohort.frequency) { + case 'day': + return ; + default: + return ; + } +}; + +export default class Retention extends React.PureComponent { + + static propTypes = { + start_at: PropTypes.string, + end_at: PropTypes.string, + frequency: PropTypes.string, + }; + + state = { + loading: true, + data: null, + }; + + componentDidMount () { + const { start_at, end_at, frequency } = this.props; + + api().post('/api/v1/admin/retention', { start_at, end_at, frequency }).then(res => { + this.setState({ + loading: false, + data: res.data, + }); + }).catch(err => { + console.error(err); + }); + } + + render () { + const { loading, data } = this.state; + + let content; + + if (loading) { + content = ; + } else { + content = ( + + + + + + + + {data[0].data.slice(1).map((retention, i) => ( + + ))} + + + + + + + + {data[0].data.slice(1).map((retention, i) => { + const average = data.reduce((sum, cohort, k) => cohort.data[i + 1] ? sum + (cohort.data[i + 1].percent - sum)/(k + 1) : sum, 0); + + return ( + + ); + })} + + + + + {data.slice(0, -1).map(cohort => ( + + + + + + {cohort.data.slice(1).map(retention => ( + + ))} + + ))} + +
+
+ +
+
+
+ +
+
+
+ {i + 1} +
+
+
+ +
+
+
+ sum + ((cohort.data[0].value * 1) - sum) / (i + 1), 0)} maximumFractionDigits={0} /> +
+
+
+ +
+
+
+ {dateForCohort(cohort)} +
+
+
+ +
+
+
+ +
+
+ ); + } + + return ( +
+

+ + {content} +
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/components/admin/Trends.js b/app/javascript/flavours/glitch/components/admin/Trends.js new file mode 100644 index 000000000..d7c4eb72c --- /dev/null +++ b/app/javascript/flavours/glitch/components/admin/Trends.js @@ -0,0 +1,73 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import api from 'flavours/glitch/util/api'; +import { FormattedMessage } from 'react-intl'; +import classNames from 'classnames'; +import Hashtag from 'flavours/glitch/components/hashtag'; + +export default class Trends extends React.PureComponent { + + static propTypes = { + limit: PropTypes.number.isRequired, + }; + + state = { + loading: true, + data: null, + }; + + componentDidMount () { + const { limit } = this.props; + + api().get('/api/v1/admin/trends', { params: { limit } }).then(res => { + this.setState({ + loading: false, + data: res.data, + }); + }).catch(err => { + console.error(err); + }); + } + + render () { + const { limit } = this.props; + const { loading, data } = this.state; + + let content; + + if (loading) { + content = ( +
+ {Array.from(Array(limit)).map((_, i) => ( + + ))} +
+ ); + } else { + content = ( +
+ {data.map(hashtag => ( + day.uses)} + className={classNames(hashtag.requires_review && 'trends__item--requires-review', !hashtag.trendable && !hashtag.requires_review && 'trends__item--disabled')} + /> + ))} +
+ ); + } + + return ( +
+

+ + {content} +
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/components/hashtag.js b/app/javascript/flavours/glitch/components/hashtag.js index d00c01e77..769185a2b 100644 --- a/app/javascript/flavours/glitch/components/hashtag.js +++ b/app/javascript/flavours/glitch/components/hashtag.js @@ -6,6 +6,8 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Permalink from './permalink'; import ShortNumber from 'flavours/glitch/components/short_number'; +import Skeleton from 'flavours/glitch/components/skeleton'; +import classNames from 'classnames'; class SilentErrorBoundary extends React.Component { @@ -47,45 +49,38 @@ const accountsCountRenderer = (displayNumber, pluralReady) => ( /> ); -const Hashtag = ({ hashtag }) => ( -
+export const ImmutableHashtag = ({ hashtag }) => ( + day.get('uses')).toArray()} + /> +); + +ImmutableHashtag.propTypes = { + hashtag: ImmutablePropTypes.map.isRequired, +}; + +const Hashtag = ({ name, href, to, people, uses, history, className }) => ( +
- - #{hashtag.get('name')} + + {name ? #{name} : } - + {typeof people !== 'undefined' ? : }
- + {typeof uses !== 'undefined' ? : }
- day.get('uses')) - .toArray()} - > + 0)}> @@ -94,7 +89,13 @@ const Hashtag = ({ hashtag }) => ( ); Hashtag.propTypes = { - hashtag: ImmutablePropTypes.map.isRequired, + name: PropTypes.string, + href: PropTypes.string, + to: PropTypes.string, + people: PropTypes.number, + uses: PropTypes.number, + history: PropTypes.arrayOf(PropTypes.number), + className: PropTypes.string, }; export default Hashtag; diff --git a/app/javascript/flavours/glitch/components/skeleton.js b/app/javascript/flavours/glitch/components/skeleton.js new file mode 100644 index 000000000..09093e99c --- /dev/null +++ b/app/javascript/flavours/glitch/components/skeleton.js @@ -0,0 +1,11 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const Skeleton = ({ width, height }) => ; + +Skeleton.propTypes = { + width: PropTypes.number, + height: PropTypes.number, +}; + +export default Skeleton; diff --git a/app/javascript/flavours/glitch/containers/admin_component.js b/app/javascript/flavours/glitch/containers/admin_component.js new file mode 100644 index 000000000..64dabac8b --- /dev/null +++ b/app/javascript/flavours/glitch/containers/admin_component.js @@ -0,0 +1,26 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { IntlProvider, addLocaleData } from 'react-intl'; +import { getLocale } from 'mastodon/locales'; + +const { localeData, messages } = getLocale(); +addLocaleData(localeData); + +export default class AdminComponent extends React.PureComponent { + + static propTypes = { + locale: PropTypes.string.isRequired, + children: PropTypes.node.isRequired, + }; + + render () { + const { locale, children } = this.props; + + return ( + + {children} + + ); + } + +} diff --git a/app/javascript/flavours/glitch/containers/media_container.js b/app/javascript/flavours/glitch/containers/media_container.js index 8657b8064..1ddbc706b 100644 --- a/app/javascript/flavours/glitch/containers/media_container.js +++ b/app/javascript/flavours/glitch/containers/media_container.js @@ -7,7 +7,7 @@ import { getLocale } from 'mastodon/locales'; import { getScrollbarWidth } from 'flavours/glitch/util/scrollbar'; import MediaGallery from 'flavours/glitch/components/media_gallery'; import Poll from 'flavours/glitch/components/poll'; -import Hashtag from 'flavours/glitch/components/hashtag'; +import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag'; import ModalRoot from 'flavours/glitch/components/modal_root'; import MediaModal from 'flavours/glitch/features/ui/components/media_modal'; import Video from 'flavours/glitch/features/video'; diff --git a/app/javascript/flavours/glitch/features/compose/components/search_results.js b/app/javascript/flavours/glitch/features/compose/components/search_results.js index 9a76e5418..cbc1f35e5 100644 --- a/app/javascript/flavours/glitch/features/compose/components/search_results.js +++ b/app/javascript/flavours/glitch/features/compose/components/search_results.js @@ -5,7 +5,7 @@ import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import AccountContainer from 'flavours/glitch/containers/account_container'; import StatusContainer from 'flavours/glitch/containers/status_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import Hashtag from 'flavours/glitch/components/hashtag'; +import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag'; import Icon from 'flavours/glitch/components/icon'; import { searchEnabled } from 'flavours/glitch/util/initial_state'; import LoadMore from 'flavours/glitch/components/load_more'; diff --git a/app/javascript/flavours/glitch/features/getting_started/components/trends.js b/app/javascript/flavours/glitch/features/getting_started/components/trends.js index 0734ec72b..ce4d94c64 100644 --- a/app/javascript/flavours/glitch/features/getting_started/components/trends.js +++ b/app/javascript/flavours/glitch/features/getting_started/components/trends.js @@ -2,7 +2,7 @@ import React from 'react'; import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import Hashtag from 'flavours/glitch/components/hashtag'; +import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag'; import { FormattedMessage } from 'react-intl'; export default class Trends extends ImmutablePureComponent { diff --git a/app/javascript/flavours/glitch/packs/admin.js b/app/javascript/flavours/glitch/packs/admin.js new file mode 100644 index 000000000..4c09ddb05 --- /dev/null +++ b/app/javascript/flavours/glitch/packs/admin.js @@ -0,0 +1,24 @@ +import 'packs/public-path'; +import ready from 'flavours/glitch/util/ready'; + +ready(() => { + const React = require('react'); + const ReactDOM = require('react-dom'); + + [].forEach.call(document.querySelectorAll('[data-admin-component]'), element => { + const componentName = element.getAttribute('data-admin-component'); + const { locale, ...componentProps } = JSON.parse(element.getAttribute('data-props')); + + import('flavours/glitch/containers/admin_component').then(({ default: AdminComponent }) => { + return import('flavours/glitch/components/admin/' + componentName).then(({ default: Component }) => { + ReactDOM.render(( + + + + ), element); + }); + }).catch(error => { + console.error(error); + }); + }); +}); diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss index 4801a4644..24618c29f 100644 --- a/app/javascript/flavours/glitch/styles/admin.scss +++ b/app/javascript/flavours/glitch/styles/admin.scss @@ -1,3 +1,5 @@ +@use "sass:math"; + $no-columns-breakpoint: 600px; $sidebar-width: 240px; $content-width: 840px; @@ -925,10 +927,197 @@ a.name-tag, } } +.dashboard__counters.admin-account-counters { + margin-top: 10px; +} + .account-badges { margin: -2px 0; } -.dashboard__counters.admin-account-counters { - margin-top: 10px; +.retention { + &__table { + &__number { + color: $secondary-text-color; + padding: 10px; + } + + &__date { + white-space: nowrap; + padding: 10px 0; + text-align: left; + min-width: 120px; + + &.retention__table__average { + font-weight: 700; + } + } + + &__size { + text-align: center; + padding: 10px; + } + + &__label { + font-weight: 700; + color: $darker-text-color; + } + + &__box { + box-sizing: border-box; + background: $ui-highlight-color; + padding: 10px; + font-weight: 500; + color: $primary-text-color; + width: 52px; + margin: 1px; + + @for $i from 0 through 10 { + &--#{10 * $i} { + background-color: rgba($ui-highlight-color, 1 * (math.div(max(1, $i), 10))); + } + } + } + } +} + +.sparkline { + display: block; + text-decoration: none; + background: lighten($ui-base-color, 4%); + border-radius: 4px; + padding: 0; + position: relative; + padding-bottom: 55px + 20px; + overflow: hidden; + + &__value { + display: flex; + line-height: 33px; + align-items: flex-end; + padding: 20px; + padding-bottom: 10px; + + &__total { + display: block; + margin-right: 10px; + font-weight: 500; + font-size: 28px; + color: $primary-text-color; + } + + &__change { + display: block; + font-weight: 500; + font-size: 18px; + color: $darker-text-color; + margin-bottom: -3px; + + &.positive { + color: $valid-value-color; + } + + &.negative { + color: $error-value-color; + } + } + } + + &__label { + padding: 0 20px; + padding-bottom: 10px; + text-transform: uppercase; + color: $darker-text-color; + font-weight: 500; + } + + &__graph { + position: absolute; + bottom: 0; + + svg { + display: block; + margin: 0; + } + + path:first-child { + fill: rgba($highlight-text-color, 0.25) !important; + fill-opacity: 1 !important; + } + + path:last-child { + stroke: lighten($highlight-text-color, 6%) !important; + fill: none !important; + } + } +} + +a.sparkline { + &:hover, + &:focus, + &:active { + background: lighten($ui-base-color, 6%); + } +} + +.skeleton { + background-color: lighten($ui-base-color, 8%); + background-image: linear-gradient(90deg, lighten($ui-base-color, 8%), lighten($ui-base-color, 12%), lighten($ui-base-color, 8%)); + background-size: 200px 100%; + background-repeat: no-repeat; + border-radius: 4px; + display: inline-block; + line-height: 1; + width: 100%; + animation: skeleton 1.2s ease-in-out infinite; +} + +@keyframes skeleton { + 0% { + background-position: -200px 0; + } + + 100% { + background-position: calc(200px + 100%) 0; + } +} + +.dimension { + table { + width: 100%; + } + + &__item { + border-bottom: 1px solid lighten($ui-base-color, 4%); + + &__key { + font-weight: 500; + padding: 11px 10px; + } + + &__value { + text-align: right; + color: $darker-text-color; + padding: 11px 10px; + } + + &__indicator { + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + background: $ui-highlight-color; + margin-right: 10px; + + @for $i from 0 through 10 { + &--#{10 * $i} { + background-color: rgba($ui-highlight-color, 1 * (math.div(max(1, $i), 10))); + } + } + } + + &:last-child { + border-bottom: 0; + } + } } diff --git a/app/javascript/flavours/glitch/styles/components/search.scss b/app/javascript/flavours/glitch/styles/components/search.scss index 929769130..f7415368b 100644 --- a/app/javascript/flavours/glitch/styles/components/search.scss +++ b/app/javascript/flavours/glitch/styles/components/search.scss @@ -171,7 +171,6 @@ &__current { flex: 0 0 auto; font-size: 24px; - line-height: 36px; font-weight: 500; text-align: right; padding-right: 15px; @@ -193,5 +192,57 @@ fill: none !important; } } + + &--requires-review { + .trends__item__name { + color: $gold-star; + + a { + color: $gold-star; + } + } + + .trends__item__current { + color: $gold-star; + } + + .trends__item__sparkline { + path:first-child { + fill: rgba($gold-star, 0.25) !important; + } + + path:last-child { + stroke: lighten($gold-star, 6%) !important; + } + } + } + + &--disabled { + .trends__item__name { + color: lighten($ui-base-color, 12%); + + a { + color: lighten($ui-base-color, 12%); + } + } + + .trends__item__current { + color: lighten($ui-base-color, 12%); + } + + .trends__item__sparkline { + path:first-child { + fill: rgba(lighten($ui-base-color, 12%), 0.25) !important; + } + + path:last-child { + stroke: lighten(lighten($ui-base-color, 12%), 6%) !important; + } + } + } + } + + &--compact &__item { + padding: 10px; } } diff --git a/app/javascript/flavours/glitch/styles/dashboard.scss b/app/javascript/flavours/glitch/styles/dashboard.scss index c0944d417..cad5a105b 100644 --- a/app/javascript/flavours/glitch/styles/dashboard.scss +++ b/app/javascript/flavours/glitch/styles/dashboard.scss @@ -56,23 +56,56 @@ } } -.dashboard__widgets { - display: flex; - flex-wrap: wrap; - margin: 0 -5px; +.dashboard { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr); + grid-gap: 10px; - & > div { - flex: 0 0 33.333%; - margin-bottom: 20px; + &__item { + &--span-double-column { + grid-column: span 2; + } - & > div { - padding: 0 5px; + &--span-double-row { + grid-row: span 2; + } + + h4 { + padding-top: 20px; } } - a:not(.name-tag) { - color: $ui-secondary-color; - font-weight: 500; + &__quick-access { + display: flex; + align-items: baseline; + border-radius: 4px; + background: $ui-highlight-color; + color: $primary-text-color; + transition: all 100ms ease-in; + font-size: 14px; + padding: 0 16px; + line-height: 36px; + height: 36px; text-decoration: none; + margin-bottom: 4px; + + &:active, + &:focus, + &:hover { + background-color: lighten($ui-highlight-color, 10%); + transition: all 200ms ease-out; + } + + span { + flex: 1 1 auto; + } + + .fa { + flex: 0 0 auto; + } + + strong { + font-weight: 700; + } } } diff --git a/app/javascript/flavours/glitch/theme.yml b/app/javascript/flavours/glitch/theme.yml index 2a98e4c29..ee2b699b2 100644 --- a/app/javascript/flavours/glitch/theme.yml +++ b/app/javascript/flavours/glitch/theme.yml @@ -1,7 +1,7 @@ # (REQUIRED) The location of the pack files. pack: about: packs/about.js - admin: packs/public.js + admin: packs/admin.js auth: packs/public.js common: filename: packs/common.js diff --git a/app/javascript/flavours/glitch/util/numbers.js b/app/javascript/flavours/glitch/util/numbers.js index 6f2505cae..6ef563ad8 100644 --- a/app/javascript/flavours/glitch/util/numbers.js +++ b/app/javascript/flavours/glitch/util/numbers.js @@ -69,3 +69,11 @@ export function pluralReady(sourceNumber, division) { return Math.trunc(sourceNumber / closestScale) * closestScale; } + +/** + * @param {number} num + * @returns {number} + */ +export function roundTo10(num) { + return Math.round(num * 0.1) / 0.1; +} diff --git a/app/javascript/flavours/vanilla/theme.yml b/app/javascript/flavours/vanilla/theme.yml index 74e9fb1b5..3263fd7d4 100644 --- a/app/javascript/flavours/vanilla/theme.yml +++ b/app/javascript/flavours/vanilla/theme.yml @@ -1,7 +1,7 @@ # (REQUIRED) The location of the pack files inside `pack_directory`. pack: about: about.js - admin: public.js + admin: admin.js auth: public.js common: filename: common.js diff --git a/app/javascript/packs/admin.js b/app/javascript/packs/admin.js new file mode 100644 index 000000000..599015000 --- /dev/null +++ b/app/javascript/packs/admin.js @@ -0,0 +1,24 @@ +import './public-path'; +import ready from '../mastodon/ready'; + +ready(() => { + const React = require('react'); + const ReactDOM = require('react-dom'); + + [].forEach.call(document.querySelectorAll('[data-admin-component]'), element => { + const componentName = element.getAttribute('data-admin-component'); + const { locale, ...componentProps } = JSON.parse(element.getAttribute('data-props')); + + import('../mastodon/containers/admin_component').then(({ default: AdminComponent }) => { + return import('../mastodon/components/admin/' + componentName).then(({ default: Component }) => { + ReactDOM.render(( + + + + ), element); + }); + }).catch(error => { + console.error(error); + }); + }); +}); -- cgit From 69208ef6ff16860a885eb389b3d14afd93531252 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 17 Jan 2022 09:41:33 +0100 Subject: [Glitch] Add notifications for statuses deleted by moderators Port front-end changes from 14f436c457560862fafabd753eb314c8b8a8e674 to glitch-soc Signed-off-by: Claire --- .../components/admin/ReportReasonSelector.js | 159 ++++++++++ app/javascript/flavours/glitch/styles/admin.scss | 328 ++++++++++++++++++++- app/javascript/flavours/glitch/styles/polls.scss | 15 + .../flavours/glitch/util/backend_links.js | 2 +- 4 files changed, 488 insertions(+), 16 deletions(-) create mode 100644 app/javascript/flavours/glitch/components/admin/ReportReasonSelector.js (limited to 'app/javascript/flavours/glitch/util') diff --git a/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.js b/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.js new file mode 100644 index 000000000..0f2a4fe36 --- /dev/null +++ b/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.js @@ -0,0 +1,159 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import api from 'flavours/glitch/util/api'; +import { injectIntl, defineMessages } from 'react-intl'; +import classNames from 'classnames'; + +const messages = defineMessages({ + other: { id: 'report.categories.other', defaultMessage: 'Other' }, + spam: { id: 'report.categories.spam', defaultMessage: 'Spam' }, + violation: { id: 'report.categories.violation', defaultMessage: 'Content violates one or more server rules' }, +}); + +class Category extends React.PureComponent { + + static propTypes = { + id: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + selected: PropTypes.bool, + disabled: PropTypes.bool, + onSelect: PropTypes.func, + children: PropTypes.node, + }; + + handleClick = () => { + const { id, disabled, onSelect } = this.props; + + if (!disabled) { + onSelect(id); + } + }; + + render () { + const { id, text, disabled, selected, children } = this.props; + + return ( +
+ {selected && } + +
+ + {text} +
+ + {(selected && children) && ( +
+ {children} +
+ )} +
+ ); + } + +} + +class Rule extends React.PureComponent { + + static propTypes = { + id: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + selected: PropTypes.bool, + disabled: PropTypes.bool, + onToggle: PropTypes.func, + }; + + handleClick = () => { + const { id, disabled, onToggle } = this.props; + + if (!disabled) { + onToggle(id); + } + }; + + render () { + const { id, text, disabled, selected } = this.props; + + return ( +
+ + {selected && } + {text} +
+ ); + } + +} + +export default @injectIntl +class ReportReasonSelector extends React.PureComponent { + + static propTypes = { + id: PropTypes.string.isRequired, + category: PropTypes.string.isRequired, + rule_ids: PropTypes.arrayOf(PropTypes.string), + disabled: PropTypes.bool, + intl: PropTypes.object.isRequired, + }; + + state = { + category: this.props.category, + rule_ids: this.props.rule_ids || [], + rules: [], + }; + + componentDidMount() { + api().get('/api/v1/instance').then(res => { + this.setState({ + rules: res.data.rules, + }); + }).catch(err => { + console.error(err); + }); + } + + _save = () => { + const { id, disabled } = this.props; + const { category, rule_ids } = this.state; + + if (disabled) { + return; + } + + api().put(`/api/v1/admin/reports/${id}`, { + category, + rule_ids, + }).catch(err => { + console.error(err); + }); + }; + + handleSelect = id => { + this.setState({ category: id }, () => this._save()); + }; + + handleToggle = id => { + const { rule_ids } = this.state; + + if (rule_ids.includes(id)) { + this.setState({ rule_ids: rule_ids.filter(x => x !== id ) }, () => this._save()); + } else { + this.setState({ rule_ids: [...rule_ids, id] }, () => this._save()); + } + }; + + render () { + const { disabled, intl } = this.props; + const { rules, category, rule_ids } = this.state; + + return ( +
+ + + + {rules.map(rule => )} + +
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss index 8fd556c73..92061585a 100644 --- a/app/javascript/flavours/glitch/styles/admin.scss +++ b/app/javascript/flavours/glitch/styles/admin.scss @@ -595,39 +595,44 @@ body, .log-entry { line-height: 20px; - padding: 15px 0; + padding: 15px; + padding-left: 15px * 2 + 40px; background: $ui-base-color; - border-bottom: 1px solid lighten($ui-base-color, 4%); + border-bottom: 1px solid darken($ui-base-color, 8%); + position: relative; + + &:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } &:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; border-bottom: 0; } + &:hover { + background: lighten($ui-base-color, 4%); + } + &__header { - display: flex; - justify-content: flex-start; - align-items: center; color: $darker-text-color; font-size: 14px; - padding: 0 10px; } &__avatar { - margin-right: 10px; + position: absolute; + left: 15px; + top: 15px; .avatar { - display: block; - margin: 0; - border-radius: 50%; + border-radius: 4px; width: 40px; height: 40px; } } - &__content { - max-width: calc(100% - 90px); - } - &__title { word-wrap: break-word; } @@ -643,6 +648,14 @@ body, text-decoration: none; font-weight: 500; } + + a { + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } } a.name-tag, @@ -671,8 +684,9 @@ a.inline-name-tag, a.name-tag, .name-tag { - display: flex; + display: inline-flex; align-items: center; + vertical-align: top; .avatar { display: block; @@ -1130,3 +1144,287 @@ a.sparkline { } } } + +.report-reason-selector { + border-radius: 4px; + background: $ui-base-color; + margin-bottom: 20px; + + &__category { + cursor: pointer; + border-bottom: 1px solid darken($ui-base-color, 8%); + + &:last-child { + border-bottom: 0; + } + + &__label { + padding: 15px; + } + + &__rules { + margin-left: 30px; + } + } + + &__rule { + cursor: pointer; + padding: 15px; + } +} + +.report-header { + display: grid; + grid-gap: 15px; + grid-template-columns: minmax(0, 1fr) 300px; + + &__details { + &__item { + border-bottom: 1px solid lighten($ui-base-color, 8%); + padding: 15px 0; + + &:last-child { + border-bottom: 0; + } + + &__header { + font-weight: 600; + padding: 4px 0; + } + } + + &--horizontal { + display: grid; + grid-auto-columns: minmax(0, 1fr); + grid-auto-flow: column; + + .report-header__details__item { + border-bottom: 0; + } + } + } +} + +.account-card { + background: $ui-base-color; + border-radius: 4px; + + &__header { + padding: 4px; + border-radius: 4px; + height: 128px; + + img { + display: block; + margin: 0; + width: 100%; + height: 100%; + object-fit: cover; + background: darken($ui-base-color, 8%); + } + } + + &__title { + margin-top: -25px; + display: flex; + align-items: flex-end; + + &__avatar { + padding: 15px; + + img { + display: block; + margin: 0; + width: 56px; + height: 56px; + background: darken($ui-base-color, 8%); + border-radius: 8px; + } + } + + .display-name { + color: $darker-text-color; + padding-bottom: 15px; + font-size: 15px; + + bdi { + display: block; + color: $primary-text-color; + font-weight: 500; + } + } + } + + &__bio { + padding: 0 15px; + overflow: hidden; + text-overflow: ellipsis; + word-wrap: break-word; + max-height: 18px * 2; + position: relative; + + &::after { + display: block; + content: ""; + width: 50px; + height: 18px; + position: absolute; + bottom: 0; + right: 15px; + background: linear-gradient(to left, $ui-base-color, transparent); + pointer-events: none; + } + } + + &__actions { + display: flex; + align-items: center; + padding-top: 10px; + + &__button { + flex: 0 0 auto; + padding: 0 15px; + } + } + + &__counters { + flex: 1 1 auto; + display: grid; + grid-auto-columns: minmax(0, 1fr); + grid-auto-flow: column; + + &__item { + padding: 15px; + text-align: center; + color: $primary-text-color; + font-weight: 600; + font-size: 15px; + + small { + display: block; + color: $darker-text-color; + font-weight: 400; + font-size: 13px; + } + } + } +} + +.report-notes { + margin-bottom: 20px; + + &__item { + background: $ui-base-color; + position: relative; + padding: 15px; + padding-left: 15px * 2 + 40px; + border-bottom: 1px solid darken($ui-base-color, 8%); + + &:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } + + &:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom: 0; + } + + &:hover { + background-color: lighten($ui-base-color, 4%); + } + + &__avatar { + position: absolute; + left: 15px; + top: 15px; + border-radius: 4px; + width: 40px; + height: 40px; + } + + &__header { + color: $darker-text-color; + font-size: 15px; + line-height: 20px; + margin-bottom: 4px; + + .username a { + color: $primary-text-color; + font-weight: 500; + text-decoration: none; + margin-right: 5px; + + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } + + time { + margin-left: 5px; + vertical-align: baseline; + } + } + + &__content { + font-size: 15px; + line-height: 20px; + word-wrap: break-word; + font-weight: 400; + color: $primary-text-color; + + p { + margin-bottom: 20px; + white-space: pre-wrap; + unicode-bidi: plaintext; + + &:last-child { + margin-bottom: 0; + } + } + } + + &__actions { + position: absolute; + top: 15px; + right: 15px; + text-align: right; + } + } +} + +.report-actions { + border: 1px solid darken($ui-base-color, 8%); + + &__item { + display: flex; + align-items: center; + line-height: 18px; + border-bottom: 1px solid darken($ui-base-color, 8%); + + &:last-child { + border-bottom: 0; + } + + &__button { + flex: 0 0 auto; + width: 100px; + padding: 15px; + padding-right: 0; + + .button { + display: block; + width: 100%; + } + } + + &__description { + padding: 15px; + font-size: 14px; + color: $dark-text-color; + } + } +} diff --git a/app/javascript/flavours/glitch/styles/polls.scss b/app/javascript/flavours/glitch/styles/polls.scss index 5fc41ed9e..a2cdecf06 100644 --- a/app/javascript/flavours/glitch/styles/polls.scss +++ b/app/javascript/flavours/glitch/styles/polls.scss @@ -150,6 +150,21 @@ &:active { outline: 0 !important; } + + &.disabled { + border-color: $dark-text-color; + + &.active { + background: $dark-text-color; + } + + &:active, + &:focus, + &:hover { + border-color: $dark-text-color; + border-width: 1px; + } + } } &__number { diff --git a/app/javascript/flavours/glitch/util/backend_links.js b/app/javascript/flavours/glitch/util/backend_links.js index 0fb378cc1..2e5111a7f 100644 --- a/app/javascript/flavours/glitch/util/backend_links.js +++ b/app/javascript/flavours/glitch/util/backend_links.js @@ -3,7 +3,7 @@ export const profileLink = '/settings/profile'; export const signOutLink = '/auth/sign_out'; export const termsLink = '/terms'; export const accountAdminLink = (id) => `/admin/accounts/${id}`; -export const statusAdminLink = (account_id, status_id) => `/admin/accounts/${account_id}/statuses/${status_id}`; +export const statusAdminLink = (account_id, status_id) => `/admin/accounts/${account_id}/statuses?id=${status_id}`; export const filterEditLink = (id) => `/filters/${id}/edit`; export const relationshipsLink = '/relationships'; export const securityLink = '/auth/edit'; -- cgit