diff --git a/app/soapbox/actions/accounts.js b/app/soapbox/actions/accounts.js index 0aabbb42b..16ce44677 100644 --- a/app/soapbox/actions/accounts.js +++ b/app/soapbox/actions/accounts.js @@ -148,8 +148,16 @@ export function fetchAccountByUsername(username) { const instance = state.get('instance'); const features = getFeatures(instance); + const me = state.get('me'); - if (features.accountByUsername) { + if (!me && features.accountLookup) { + dispatch(accountLookup(username)).then(account => { + dispatch(fetchAccountSuccess(account)); + }).catch(error => { + dispatch(fetchAccountFail(null, error)); + dispatch(importErrorWhileFetchingAccountByUsername(username)); + }); + } else if (features.accountByUsername) { api(getState).get(`/api/v1/accounts/${username}`).then(response => { dispatch(fetchRelationships([response.data.id])); dispatch(importFetchedAccount(response.data)); diff --git a/app/soapbox/actions/interactions.js b/app/soapbox/actions/interactions.js index c292abaac..c67789117 100644 --- a/app/soapbox/actions/interactions.js +++ b/app/soapbox/actions/interactions.js @@ -48,6 +48,10 @@ export const UNBOOKMARK_REQUEST = 'UNBOOKMARKED_REQUEST'; export const UNBOOKMARK_SUCCESS = 'UNBOOKMARKED_SUCCESS'; export const UNBOOKMARK_FAIL = 'UNBOOKMARKED_FAIL'; +export const REMOTE_INTERACTION_REQUEST = 'REMOTE_INTERACTION_REQUEST'; +export const REMOTE_INTERACTION_SUCCESS = 'REMOTE_INTERACTION_SUCCESS'; +export const REMOTE_INTERACTION_FAIL = 'REMOTE_INTERACTION_FAIL'; + const messages = defineMessages({ bookmarkAdded: { id: 'status.bookmarked', defaultMessage: 'Bookmark added.' }, bookmarkRemoved: { id: 'status.unbookmarked', defaultMessage: 'Bookmark removed.' }, @@ -475,3 +479,46 @@ export function unpinFail(status, error) { skipLoading: true, }; } + +export function remoteInteraction(ap_id, profile) { + return (dispatch, getState) => { + dispatch(remoteInteractionRequest(ap_id, profile)); + + return api(getState).post('/api/v1/pleroma/remote_interaction', { ap_id, profile }).then(({ data }) => { + if (data.error) throw new Error(data.error); + + dispatch(remoteInteractionSuccess(ap_id, profile, data.url)); + + return data.url; + }).catch(error => { + dispatch(remoteInteractionFail(ap_id, profile, error)); + throw error; + }); + }; +} + +export function remoteInteractionRequest(ap_id, profile) { + return { + type: REMOTE_INTERACTION_REQUEST, + ap_id, + profile, + }; +} + +export function remoteInteractionSuccess(ap_id, profile, url) { + return { + type: REMOTE_INTERACTION_SUCCESS, + ap_id, + profile, + url, + }; +} + +export function remoteInteractionFail(ap_id, profile, error) { + return { + type: REMOTE_INTERACTION_FAIL, + ap_id, + profile, + error, + }; +} diff --git a/app/soapbox/components/poll.js b/app/soapbox/components/poll.js index 7962ee779..a5b8b038e 100644 --- a/app/soapbox/components/poll.js +++ b/app/soapbox/components/poll.js @@ -34,6 +34,7 @@ class Poll extends ImmutablePureComponent { dispatch: PropTypes.func, disabled: PropTypes.bool, me: SoapboxPropTypes.me, + status: PropTypes.string, }; state = { @@ -81,7 +82,11 @@ class Poll extends ImmutablePureComponent { }; openUnauthorizedModal = () => { - this.props.dispatch(openModal('UNAUTHORIZED')); + const { dispatch, status } = this.props; + dispatch(openModal('UNAUTHORIZED', { + action: 'POLL_VOTE', + ap_id: status, + })); } handleRefresh = () => { diff --git a/app/soapbox/components/profile_hover_card.js b/app/soapbox/components/profile_hover_card.js index c9d1024e7..879bf92e3 100644 --- a/app/soapbox/components/profile_hover_card.js +++ b/app/soapbox/components/profile_hover_card.js @@ -52,6 +52,7 @@ export const ProfileHoverCard = ({ visible }) => { const [popperElement, setPopperElement] = useState(null); + const me = useSelector(state => state.get('me')); const accountId = useSelector(state => state.getIn(['profile_hover_card', 'accountId'])); const account = useSelector(state => accountId && getAccount(state, accountId)); const targetRef = useSelector(state => state.getIn(['profile_hover_card', 'ref', 'current'])); @@ -65,7 +66,7 @@ export const ProfileHoverCard = ({ visible }) => { if (!account) return null; const accountBio = { __html: account.get('note_emojified') }; - const followedBy = account.getIn(['relationship', 'followed_by']); + const followedBy = me !== account.get('id') && account.getIn(['relationship', 'followed_by']); return (
diff --git a/app/soapbox/components/status_action_bar.js b/app/soapbox/components/status_action_bar.js index d4356c097..d552a82b8 100644 --- a/app/soapbox/components/status_action_bar.js +++ b/app/soapbox/components/status_action_bar.js @@ -117,11 +117,11 @@ class StatusActionBar extends ImmutablePureComponent { ] handleReplyClick = () => { - const { me } = this.props; + const { me, onReply, onOpenUnauthorizedModal, status } = this.props; if (me) { - this.props.onReply(this.props.status, this.context.router.history); + onReply(status, this.context.router.history); } else { - this.props.onOpenUnauthorizedModal(); + onOpenUnauthorizedModal('REPLY'); } } @@ -167,22 +167,22 @@ class StatusActionBar extends ImmutablePureComponent { handleReactClick = emoji => { return e => { - const { me, status } = this.props; + const { me, dispatch, onOpenUnauthorizedModal, status } = this.props; if (me) { - this.props.dispatch(simpleEmojiReact(status, emoji)); + dispatch(simpleEmojiReact(status, emoji)); } else { - this.props.onOpenUnauthorizedModal(); + onOpenUnauthorizedModal('FAVOURITE'); } this.setState({ emojiSelectorVisible: false }); }; } handleFavouriteClick = () => { - const { me } = this.props; + const { me, onFavourite, onOpenUnauthorizedModal, status } = this.props; if (me) { - this.props.onFavourite(this.props.status); + onFavourite(status); } else { - this.props.onOpenUnauthorizedModal(); + onOpenUnauthorizedModal('FAVOURITE'); } } @@ -191,11 +191,11 @@ class StatusActionBar extends ImmutablePureComponent { } handleReblogClick = e => { - const { me } = this.props; + const { me, onReblog, onOpenUnauthorizedModal, status } = this.props; if (me) { - this.props.onReblog(this.props.status, e); + onReblog(status, e); } else { - this.props.onOpenUnauthorizedModal(); + onOpenUnauthorizedModal('REBLOG'); } } @@ -599,10 +599,13 @@ const mapStateToProps = state => { }; }; -const mapDispatchToProps = (dispatch) => ({ +const mapDispatchToProps = (dispatch, { status }) => ({ dispatch, - onOpenUnauthorizedModal() { - dispatch(openModal('UNAUTHORIZED')); + onOpenUnauthorizedModal(action) { + dispatch(openModal('UNAUTHORIZED', { + action, + ap_id: status.get('url'), + })); }, }); diff --git a/app/soapbox/components/status_content.js b/app/soapbox/components/status_content.js index 508e2de1f..efbe81b5d 100644 --- a/app/soapbox/components/status_content.js +++ b/app/soapbox/components/status_content.js @@ -242,7 +242,7 @@ class StatusContent extends React.PureComponent {
- {!hidden && !!status.get('poll') && } + {!hidden && !!status.get('poll') && }
); } else if (this.props.onClick) { @@ -265,7 +265,7 @@ class StatusContent extends React.PureComponent { } if (status.get('poll')) { - output.push(); + output.push(); } return output; @@ -285,7 +285,7 @@ class StatusContent extends React.PureComponent { ]; if (status.get('poll')) { - output.push(); + output.push(); } return output; diff --git a/app/soapbox/containers/poll_container.js b/app/soapbox/containers/poll_container.js index dc35964f5..ad9672120 100644 --- a/app/soapbox/containers/poll_container.js +++ b/app/soapbox/containers/poll_container.js @@ -6,4 +6,5 @@ const mapStateToProps = (state, { pollId }) => ({ me: state.get('me'), }); + export default connect(mapStateToProps)(Poll); diff --git a/app/soapbox/containers/status_container.js b/app/soapbox/containers/status_container.js index 9c5b27415..40697a922 100644 --- a/app/soapbox/containers/status_container.js +++ b/app/soapbox/containers/status_container.js @@ -44,10 +44,10 @@ const messages = defineMessages({ deleteHeading: { id: 'confirmations.delete.heading', defaultMessage: 'Delete' }, deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this post?' }, redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' }, - redraftHeading: { id: 'confirmations.redraft.heading', defaultMessage: 'Delete & redraft' }, redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this post and re-draft it? Favorites and reposts will be lost, and replies to the original post will be orphaned.' }, blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' }, replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, + redraftHeading: { id: 'confirmations.redraft.heading', defaultMessage: 'Delete & redraft' }, replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' }, }); @@ -68,178 +68,180 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -const mapDispatchToProps = (dispatch, { intl }) => ({ - - onReply(status, router) { - dispatch((_, getState) => { - const state = getState(); - if (state.getIn(['compose', 'text']).trim().length !== 0) { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.replyMessage), - confirm: intl.formatMessage(messages.replyConfirm), - onConfirm: () => dispatch(replyCompose(status, router)), - })); - } else { - dispatch(replyCompose(status, router)); - } - }); - }, - - onModalReblog(status) { +const mapDispatchToProps = (dispatch, { intl }) => { + function onModalReblog(status) { if (status.get('reblogged')) { dispatch(unreblog(status)); } else { dispatch(reblog(status)); } - }, + } - onReblog(status, e) { - dispatch((_, getState) => { - const boostModal = getSettings(getState()).get('boostModal'); - if (e.shiftKey || !boostModal) { - this.onModalReblog(status); + return { + onReply(status, router) { + dispatch((_, getState) => { + const state = getState(); + if (state.getIn(['compose', 'text']).trim().length !== 0) { + dispatch(openModal('CONFIRM', { + message: intl.formatMessage(messages.replyMessage), + confirm: intl.formatMessage(messages.replyConfirm), + onConfirm: () => dispatch(replyCompose(status, router)), + })); + } else { + dispatch(replyCompose(status, router)); + } + }); + }, + + onModalReblog, + + onReblog(status, e) { + dispatch((_, getState) => { + const boostModal = getSettings(getState()).get('boostModal'); + if (e.shiftKey || !boostModal) { + this.onModalReblog(status); + } else { + dispatch(openModal('BOOST', { status, onReblog: onModalReblog })); + } + }); + }, + + onFavourite(status) { + if (status.get('favourited')) { + dispatch(unfavourite(status)); } else { - dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog })); + dispatch(favourite(status)); } - }); - }, + }, - onFavourite(status) { - if (status.get('favourited')) { - dispatch(unfavourite(status)); - } else { - dispatch(favourite(status)); - } - }, - - onBookmark(status) { - if (status.get('bookmarked')) { - dispatch(unbookmark(intl, status)); - } else { - dispatch(bookmark(intl, status)); - } - }, - - onPin(status) { - if (status.get('pinned')) { - dispatch(unpin(status)); - } else { - dispatch(pin(status)); - } - }, - - onEmbed(status) { - dispatch(openModal('EMBED', { - url: status.get('url'), - onError: error => dispatch(showAlertForError(error)), - })); - }, - - onDelete(status, history, withRedraft = false) { - dispatch((_, getState) => { - const deleteModal = getSettings(getState()).get('deleteModal'); - if (!deleteModal) { - dispatch(deleteStatus(status.get('id'), history, withRedraft)); + onBookmark(status) { + if (status.get('bookmarked')) { + dispatch(unbookmark(intl, status)); } else { - dispatch(openModal('CONFIRM', { - icon: withRedraft ? require('@tabler/icons/icons/edit.svg') : require('@tabler/icons/icons/trash.svg'), - heading: intl.formatMessage(withRedraft ? messages.redraftHeading : messages.deleteHeading), - message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage), - confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm), - onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)), - })); + dispatch(bookmark(intl, status)); } - }); - }, + }, - onDirect(account, router) { - dispatch(directCompose(account, router)); - }, + onPin(status) { + if (status.get('pinned')) { + dispatch(unpin(status)); + } else { + dispatch(pin(status)); + } + }, - onChat(account, router) { - dispatch(launchChat(account.get('id'), router)); - }, + onEmbed(status) { + dispatch(openModal('EMBED', { + url: status.get('url'), + onError: error => dispatch(showAlertForError(error)), + })); + }, - onMention(account, router) { - dispatch(mentionCompose(account, router)); - }, + onDelete(status, history, withRedraft = false) { + dispatch((_, getState) => { + const deleteModal = getSettings(getState()).get('deleteModal'); + if (!deleteModal) { + dispatch(deleteStatus(status.get('id'), history, withRedraft)); + } else { + dispatch(openModal('CONFIRM', { + icon: withRedraft ? require('@tabler/icons/icons/edit.svg') : require('@tabler/icons/icons/trash.svg'), + heading: intl.formatMessage(withRedraft ? messages.redraftHeading : messages.deleteHeading), + message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage), + confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm), + onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)), + })); + } + }); + }, - onOpenMedia(media, index) { - dispatch(openModal('MEDIA', { media, index })); - }, + onDirect(account, router) { + dispatch(directCompose(account, router)); + }, - onOpenVideo(media, time) { - dispatch(openModal('VIDEO', { media, time })); - }, + onChat(account, router) { + dispatch(launchChat(account.get('id'), router)); + }, - onOpenAudio(media, time) { - dispatch(openModal('AUDIO', { media, time })); - }, + onMention(account, router) { + dispatch(mentionCompose(account, router)); + }, - onBlock(status) { - const account = status.get('account'); - dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/icons/ban.svg'), - heading: , - message: @{account.get('acct')} }} />, - confirm: intl.formatMessage(messages.blockConfirm), - onConfirm: () => dispatch(blockAccount(account.get('id'))), - secondary: intl.formatMessage(messages.blockAndReport), - onSecondary: () => { - dispatch(blockAccount(account.get('id'))); - dispatch(initReport(account, status)); - }, - })); - }, + onOpenMedia(media, index) { + dispatch(openModal('MEDIA', { media, index })); + }, - onReport(status) { - dispatch(initReport(status.get('account'), status)); - }, + onOpenVideo(media, time) { + dispatch(openModal('VIDEO', { media, time })); + }, - onMute(account) { - dispatch(initMuteModal(account)); - }, + onOpenAudio(media, time) { + dispatch(openModal('AUDIO', { media, time })); + }, - onMuteConversation(status) { - if (status.get('muted')) { - dispatch(unmuteStatus(status.get('id'))); - } else { - dispatch(muteStatus(status.get('id'))); - } - }, + onBlock(status) { + const account = status.get('account'); + dispatch(openModal('CONFIRM', { + icon: require('@tabler/icons/icons/ban.svg'), + heading: , + message: @{account.get('acct')} }} />, + confirm: intl.formatMessage(messages.blockConfirm), + onConfirm: () => dispatch(blockAccount(account.get('id'))), + secondary: intl.formatMessage(messages.blockAndReport), + onSecondary: () => { + dispatch(blockAccount(account.get('id'))); + dispatch(initReport(account, status)); + }, + })); + }, - onToggleHidden(status) { - if (status.get('hidden')) { - dispatch(revealStatus(status.get('id'))); - } else { - dispatch(hideStatus(status.get('id'))); - } - }, + onReport(status) { + dispatch(initReport(status.get('account'), status)); + }, - onGroupRemoveAccount(groupId, accountId) { - dispatch(createRemovedAccount(groupId, accountId)); - }, + onMute(account) { + dispatch(initMuteModal(account)); + }, - onGroupRemoveStatus(groupId, statusId) { - dispatch(groupRemoveStatus(groupId, statusId)); - }, + onMuteConversation(status) { + if (status.get('muted')) { + dispatch(unmuteStatus(status.get('id'))); + } else { + dispatch(muteStatus(status.get('id'))); + } + }, - onDeactivateUser(status) { - dispatch(deactivateUserModal(intl, status.getIn(['account', 'id']))); - }, + onToggleHidden(status) { + if (status.get('hidden')) { + dispatch(revealStatus(status.get('id'))); + } else { + dispatch(hideStatus(status.get('id'))); + } + }, - onDeleteUser(status) { - dispatch(deleteUserModal(intl, status.getIn(['account', 'id']))); - }, + onGroupRemoveAccount(groupId, accountId) { + dispatch(createRemovedAccount(groupId, accountId)); + }, - onDeleteStatus(status) { - dispatch(deleteStatusModal(intl, status.get('id'))); - }, + onGroupRemoveStatus(groupId, statusId) { + dispatch(groupRemoveStatus(groupId, statusId)); + }, - onToggleStatusSensitivity(status) { - dispatch(toggleStatusSensitivityModal(intl, status.get('id'), status.get('sensitive'))); - }, + onDeactivateUser(status) { + dispatch(deactivateUserModal(intl, status.getIn(['account', 'id']))); + }, -}); + onDeleteUser(status) { + dispatch(deleteUserModal(intl, status.getIn(['account', 'id']))); + }, + + onDeleteStatus(status) { + dispatch(deleteStatusModal(intl, status.get('id'))); + }, + + onToggleStatusSensitivity(status) { + dispatch(toggleStatusSensitivityModal(intl, status.get('id'), status.get('sensitive'))); + }, + }; +}; export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status)); diff --git a/app/soapbox/features/account/components/header.js b/app/soapbox/features/account/components/header.js index a27271888..66830bc6c 100644 --- a/app/soapbox/features/account/components/header.js +++ b/app/soapbox/features/account/components/header.js @@ -356,7 +356,7 @@ class Header extends ImmutablePureComponent { }); } - if (account.get('id') !== me && isLocal(account)) { + if (account.get('id') !== me && isLocal(account) && isAdmin(meAccount)) { if (isAdmin(account)) { menu.push({ text: intl.formatMessage(messages.demoteToModerator, { name: account.get('username') }), @@ -407,7 +407,7 @@ class Header extends ImmutablePureComponent { }); } - if (features.suggestionsV2) { + if (features.suggestionsV2 && isAdmin(meAccount)) { if (account.getIn(['pleroma', 'is_suggested'])) { menu.push({ text: intl.formatMessage(messages.unsuggestUser, { name: account.get('username') }), diff --git a/app/soapbox/features/account_gallery/index.js b/app/soapbox/features/account_gallery/index.js index 953c18add..9a4bedf54 100644 --- a/app/soapbox/features/account_gallery/index.js +++ b/app/soapbox/features/account_gallery/index.js @@ -8,7 +8,7 @@ import { } from 'soapbox/actions/accounts'; import { expandAccountMediaTimeline } from '../../actions/timelines'; import LoadingIndicator from 'soapbox/components/loading_indicator'; -import Column from '../ui/components/column'; +import Column from 'soapbox/components/column'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { getAccountGallery, findAccountByUsername } from 'soapbox/selectors'; import MediaItem from './components/media_item'; @@ -17,6 +17,7 @@ import MissingIndicator from 'soapbox/components/missing_indicator'; import { openModal } from 'soapbox/actions/modal'; import { NavLink } from 'react-router-dom'; import { FormattedMessage } from 'react-intl'; +import SubNavigation from 'soapbox/components/sub_navigation'; const mapStateToProps = (state, { params, withReplies = false }) => { const username = params.username || ''; @@ -186,6 +187,7 @@ class AccountGallery extends ImmutablePureComponent { return ( +
diff --git a/app/soapbox/features/admin/user_index.js b/app/soapbox/features/admin/user_index.js index 4406a0aad..53f3c2ecb 100644 --- a/app/soapbox/features/admin/user_index.js +++ b/app/soapbox/features/admin/user_index.js @@ -12,6 +12,7 @@ import { SimpleForm, TextInput } from 'soapbox/features/forms'; import { Set as ImmutableSet, OrderedSet as ImmutableOrderedSet, is } from 'immutable'; const messages = defineMessages({ + heading: { id: 'column.admin.users', defaultMessage: 'Users' }, empty: { id: 'admin.user_index.empty', defaultMessage: 'No users found.' }, searchPlaceholder: { id: 'admin.user_index.search_input_placeholder', defaultMessage: 'Who are you looking for?' }, }); @@ -100,7 +101,7 @@ class UserIndex extends ImmutablePureComponent { const showLoading = isLoading && accountIds.isEmpty(); return ( - + { const getAccount = makeGetAccount(); const mapStateToProps = (state, { id }) => ({ + me: state.get('me'), account: getAccount(state, id), autoPlayGif: getSettings(state).get('autoPlayGif'), }); @@ -30,15 +32,24 @@ export default @injectIntl class AccountCard extends ImmutablePureComponent { static propTypes = { + me: SoapboxPropTypes.me, account: ImmutablePropTypes.map.isRequired, autoPlayGif: PropTypes.bool, }; render() { - const { account, autoPlayGif } = this.props; + const { account, autoPlayGif, me } = this.props; + + const followedBy = me !== account.get('id') && account.getIn(['relationship', 'followed_by']); return (
+ {followedBy && +
+ + + +
}
diff --git a/app/soapbox/features/favourited_statuses/index.js b/app/soapbox/features/favourited_statuses/index.js index 5dd5b9f67..e70f24634 100644 --- a/app/soapbox/features/favourited_statuses/index.js +++ b/app/soapbox/features/favourited_statuses/index.js @@ -5,7 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { fetchFavouritedStatuses, expandFavouritedStatuses, fetchAccountFavouritedStatuses, expandAccountFavouritedStatuses } from '../../actions/favourites'; import Column from '../ui/components/column'; import StatusList from '../../components/status_list'; -import { injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { debounce } from 'lodash'; import MissingIndicator from 'soapbox/components/missing_indicator'; @@ -13,6 +13,10 @@ import { fetchAccount, fetchAccountByUsername } from '../../actions/accounts'; import LoadingIndicator from '../../components/loading_indicator'; import { findAccountByUsername } from 'soapbox/selectors'; +const messages = defineMessages({ + heading: { id: 'column.favourited_statuses', defaultMessage: 'Liked posts' }, +}); + const mapStateToProps = (state, { params }) => { const username = params.username || ''; const me = state.get('me'); @@ -102,7 +106,7 @@ class Favourites extends ImmutablePureComponent { }, 300, { leading: true }) render() { - const { statusIds, isLoading, hasMore, isMyAccount, isAccount, accountId, unavailable } = this.props; + const { intl, statusIds, isLoading, hasMore, isMyAccount, isAccount, accountId, unavailable } = this.props; if (!isMyAccount && !isAccount && accountId !== -1) { return ( @@ -135,7 +139,7 @@ class Favourites extends ImmutablePureComponent { : ; return ( - + - - - - {filters.map((filter, i) => ( -
-
-
- - {filter.get('phrase')} -
-
- - - {filter.get('context').map((context, i) => ( - {context} - ))} - -
-
- - - {filter.get('irreversible') ? - : - - } - {filter.get('whole_word') && - - } - -
-
-
- - -
-
- ))} -
-
+ - - + + {filters.map((filter, i) => ( +
+
+
+ + {filter.get('phrase')} +
+
+ + + {filter.get('context').map((context, i) => ( + {context} + ))} + +
+
+ + + {filter.get('irreversible') ? + : + + } + {filter.get('whole_word') && + + } + +
+
+
+ + +
+
+ ))} +
); } diff --git a/app/soapbox/features/followers/index.js b/app/soapbox/features/followers/index.js index 85b258e03..ef760cef5 100644 --- a/app/soapbox/features/followers/index.js +++ b/app/soapbox/features/followers/index.js @@ -11,7 +11,7 @@ import { expandFollowers, fetchAccountByUsername, } from '../../actions/accounts'; -import { FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import AccountContainer from '../../containers/account_container'; import Column from '../ui/components/column'; import ScrollableList from '../../components/scrollable_list'; @@ -19,6 +19,10 @@ import MissingIndicator from 'soapbox/components/missing_indicator'; import { getFollowDifference } from 'soapbox/utils/accounts'; import { findAccountByUsername } from 'soapbox/selectors'; +const messages = defineMessages({ + heading: { id: 'column.followers', defaultMessage: 'Followers' }, +}); + const mapStateToProps = (state, { params, withReplies = false }) => { const username = params.username || ''; const me = state.get('me'); @@ -47,9 +51,11 @@ const mapStateToProps = (state, { params, withReplies = false }) => { }; export default @connect(mapStateToProps) +@injectIntl class Followers extends ImmutablePureComponent { static propTypes = { + intl: PropTypes.object.isRequired, params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, accountIds: ImmutablePropTypes.orderedSet, @@ -85,7 +91,7 @@ class Followers extends ImmutablePureComponent { }, 300, { leading: true }); render() { - const { accountIds, hasMore, diffCount, isAccount, accountId, unavailable } = this.props; + const { intl, accountIds, hasMore, diffCount, isAccount, accountId, unavailable } = this.props; if (!isAccount && accountId !== -1) { return ( @@ -114,7 +120,7 @@ class Followers extends ImmutablePureComponent { } return ( - + { const username = params.username || ''; const me = state.get('me'); @@ -47,9 +51,11 @@ const mapStateToProps = (state, { params, withReplies = false }) => { }; export default @connect(mapStateToProps) +@injectIntl class Following extends ImmutablePureComponent { static propTypes = { + intl: PropTypes.object.isRequired, params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, accountIds: ImmutablePropTypes.orderedSet, @@ -85,7 +91,7 @@ class Following extends ImmutablePureComponent { }, 300, { leading: true }); render() { - const { accountIds, hasMore, isAccount, diffCount, accountId, unavailable } = this.props; + const { intl, accountIds, hasMore, isAccount, diffCount, accountId, unavailable } = this.props; if (!isAccount && accountId !== -1) { return ( @@ -114,7 +120,7 @@ class Following extends ImmutablePureComponent { } return ( - + +

diff --git a/app/soapbox/features/list_editor/index.js b/app/soapbox/features/list_editor/index.js index c9c732586..1e07ce853 100644 --- a/app/soapbox/features/list_editor/index.js +++ b/app/soapbox/features/list_editor/index.js @@ -70,7 +70,7 @@ class ListEditor extends ImmutablePureComponent {

-
+
diff --git a/app/soapbox/features/pinned_statuses/index.js b/app/soapbox/features/pinned_statuses/index.js index bfa57417e..5221d5c9b 100644 --- a/app/soapbox/features/pinned_statuses/index.js +++ b/app/soapbox/features/pinned_statuses/index.js @@ -5,10 +5,14 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { fetchPinnedStatuses } from '../../actions/pin_statuses'; import Column from '../ui/components/column'; import StatusList from '../../components/status_list'; -import { injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import MissingIndicator from 'soapbox/components/missing_indicator'; +const messages = defineMessages({ + heading: { id: 'column.pins', defaultMessage: 'Pinned posts' }, +}); + const mapStateToProps = (state, { params }) => { const username = params.username || ''; const me = state.get('me'); @@ -37,7 +41,7 @@ class PinnedStatuses extends ImmutablePureComponent { } render() { - const { statusIds, hasMore, isMyAccount } = this.props; + const { intl, statusIds, hasMore, isMyAccount } = this.props; if (!isMyAccount) { return ( @@ -48,7 +52,7 @@ class PinnedStatuses extends ImmutablePureComponent { } return ( - + { const getStatus = makeGetStatus(); const status = getStatus(state, { @@ -35,6 +39,7 @@ const mapStateToProps = (state, props) => { }; export default @connect(mapStateToProps) +@injectIntl class Reactions extends ImmutablePureComponent { static contextTypes = { @@ -78,14 +83,11 @@ class Reactions extends ImmutablePureComponent { }; render() { - const { params, reactions, accounts, status } = this.props; - const { username, statusId } = params; - - const back = `/@${username}/posts/${statusId}`; + const { intl, params, reactions, accounts, status } = this.props; if (!accounts) { return ( - + ); @@ -93,7 +95,7 @@ class Reactions extends ImmutablePureComponent { if (!status) { return ( - + ); @@ -102,7 +104,7 @@ class Reactions extends ImmutablePureComponent { const emptyMessage = ; return ( - + { reactions.size > 0 && (
diff --git a/app/soapbox/features/status/components/action_bar.js b/app/soapbox/features/status/components/action_bar.js index 3a6da7475..a3f42adb9 100644 --- a/app/soapbox/features/status/components/action_bar.js +++ b/app/soapbox/features/status/components/action_bar.js @@ -66,9 +66,12 @@ const mapStateToProps = state => { }; }; -const mapDispatchToProps = (dispatch) => ({ - onOpenUnauthorizedModal() { - dispatch(openModal('UNAUTHORIZED')); +const mapDispatchToProps = (dispatch, { status }) => ({ + onOpenUnauthorizedModal(action) { + dispatch(openModal('UNAUTHORIZED', { + action, + ap_id: status.get('url'), + })); }, }); @@ -121,20 +124,20 @@ class ActionBar extends React.PureComponent { } handleReplyClick = () => { - const { me } = this.props; + const { me, onReply, onOpenUnauthorizedModal } = this.props; if (me) { - this.props.onReply(this.props.status); + onReply(this.props.status); } else { - this.props.onOpenUnauthorizedModal(); + onOpenUnauthorizedModal('REPLY'); } } handleReblogClick = (e) => { - const { me } = this.props; + const { me, onReblog, onOpenUnauthorizedModal, status } = this.props; if (me) { - this.props.onReblog(this.props.status, e); + onReblog(status, e); } else { - this.props.onOpenUnauthorizedModal(); + onOpenUnauthorizedModal('REBLOG'); } } @@ -143,11 +146,11 @@ class ActionBar extends React.PureComponent { } handleFavouriteClick = () => { - const { me } = this.props; + const { me, onFavourite, onOpenUnauthorizedModal } = this.props; if (me) { - this.props.onFavourite(this.props.status); + onFavourite(status); } else { - this.props.onOpenUnauthorizedModal(); + onOpenUnauthorizedModal('FAVOURITE'); } } @@ -184,11 +187,11 @@ class ActionBar extends React.PureComponent { handleReactClick = emoji => { return e => { - const { me } = this.props; + const { me, onEmojiReact, onOpenUnauthorizedModal, status } = this.props; if (me) { - this.props.onEmojiReact(this.props.status, emoji); + onEmojiReact(status, emoji); } else { - this.props.onOpenUnauthorizedModal(); + onOpenUnauthorizedModal('FAVOURITE'); } this.setState({ emojiSelectorVisible: false, emojiSelectorFocused: false }); }; diff --git a/app/soapbox/features/ui/components/action_button.js b/app/soapbox/features/ui/components/action_button.js index e55b41142..152f6f467 100644 --- a/app/soapbox/features/ui/components/action_button.js +++ b/app/soapbox/features/ui/components/action_button.js @@ -13,6 +13,8 @@ import { blockAccount, unblockAccount, } from 'soapbox/actions/accounts'; +import { openModal } from 'soapbox/actions/modal'; +import { getFeatures } from 'soapbox/utils/features'; const messages = defineMessages({ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, @@ -26,8 +28,11 @@ const messages = defineMessages({ const mapStateToProps = state => { const me = state.get('me'); + const instance = state.get('instance'); + return { me, + features: getFeatures(instance), }; }; @@ -47,6 +52,14 @@ const mapDispatchToProps = (dispatch) => ({ dispatch(blockAccount(account.get('id'))); } }, + + onOpenUnauthorizedModal(account) { + dispatch(openModal('UNAUTHORIZED', { + action: 'FOLLOW', + account: account.get('id'), + ap_id: account.get('url'), + })); + }, }); export default @connect(mapStateToProps, mapDispatchToProps) @@ -57,8 +70,10 @@ class ActionButton extends ImmutablePureComponent { account: ImmutablePropTypes.map.isRequired, onFollow: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired, + onOpenUnauthorizedModal: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, small: PropTypes.bool, + features: PropTypes.object.isRequired, }; static defaultProps = { @@ -81,12 +96,26 @@ class ActionButton extends ImmutablePureComponent { this.props.onBlock(this.props.account); } + handleRemoteFollow = () => { + this.props.onOpenUnauthorizedModal(this.props.account); + } + render() { - const { account, intl, me, small } = this.props; + const { account, intl, me, small, features } = this.props; const empty = <>; if (!me) { // Remote follow + if (features.remoteInteractionsAPI) { + return (); + } + return (
diff --git a/app/soapbox/features/ui/components/column_forbidden.js b/app/soapbox/features/ui/components/column_forbidden.js new file mode 100644 index 000000000..d57d2f547 --- /dev/null +++ b/app/soapbox/features/ui/components/column_forbidden.js @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { defineMessages, injectIntl } from 'react-intl'; + +import Column from './column'; +import ColumnHeader from './column_header'; + +const messages = defineMessages({ + title: { id: 'column_forbidden.title', defaultMessage: 'Forbidden' }, + body: { id: 'column_forbidden.body', defaultMessage: 'You do not have permission to access this page.' }, +}); + +class ColumnForbidden extends React.PureComponent { + + static propTypes = { + intl: PropTypes.object.isRequired, + } + + render() { + const { intl: { formatMessage } } = this.props; + + return ( + + +
+ {formatMessage(messages.body)} +
+
+ ); + } + +} + +export default injectIntl(ColumnForbidden); diff --git a/app/soapbox/features/ui/components/unauthorized_modal.js b/app/soapbox/features/ui/components/unauthorized_modal.js index 0465a9af6..0e1429d8d 100644 --- a/app/soapbox/features/ui/components/unauthorized_modal.js +++ b/app/soapbox/features/ui/components/unauthorized_modal.js @@ -5,32 +5,150 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { Link } from 'react-router-dom'; import ImmutablePureComponent from 'react-immutable-pure-component'; import IconButton from 'soapbox/components/icon_button'; +import snackbar from 'soapbox/actions/snackbar'; +import { remoteInteraction } from 'soapbox/actions/interactions'; +import { getFeatures } from 'soapbox/utils/features'; const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, + accountPlaceholder: { id: 'remote_interaction.account_placeholder', defaultMessage: 'Enter your username@domain you want to act from' }, + userNotFoundError: { id: 'remote_interaction.user_not_found_error', defaultMessage: 'Couldn\'t find given user' }, }); -const mapStateToProps = state => { - const me = state.get('me'); +const mapStateToProps = (state, props) => { + const instance = state.get('instance'); + const features = getFeatures(instance); + + if (props.action !== 'FOLLOW') { + return { + features, + siteTitle: state.getIn(['instance', 'title']), + remoteInteractionsAPI: features.remoteInteractionsAPI, + }; + } + + const userName = state.getIn(['accounts', props.account, 'display_name']); + return { - account: state.getIn(['accounts', me]), + features, siteTitle: state.getIn(['instance', 'title']), + userName, + remoteInteractionsAPI: features.remoteInteractionsAPI, }; }; +const mapDispatchToProps = dispatch => ({ + dispatch, + onRemoteInteraction(ap_id, account) { + return dispatch(remoteInteraction(ap_id, account)); + }, +}); + class UnauthorizedModal extends ImmutablePureComponent { static propTypes = { intl: PropTypes.object.isRequired, + features: PropTypes.object.isRequired, onClose: PropTypes.func.isRequired, + onRemoteInteraction: PropTypes.func.isRequired, + userName: PropTypes.string, }; + state = { + account: '', + }; + + onAccountChange = e => { + this.setState({ account: e.target.value }); + } + onClickClose = () => { this.props.onClose('UNAUTHORIZED'); }; + onClickProceed = e => { + e.preventDefault(); + + const { intl, ap_id, dispatch, onClose, onRemoteInteraction } = this.props; + const { account } = this.state; + + onRemoteInteraction(ap_id, account) + .then(url => { + window.open(url, '_new', 'noopener,noreferrer'); + onClose('UNAUTHORIZED'); + }) + .catch(error => { + if (error.message === 'Couldn\'t find user') { + dispatch(snackbar.error(intl.formatMessage(messages.userNotFoundError))); + } + }); + } + + renderRemoteInteractions() { + const { intl, siteTitle, userName, action } = this.props; + const { account } = this.state; + + let header; + let button; + + if (action === 'FOLLOW') { + header = ; + button = ; + } else if (action === 'REPLY') { + header = ; + button = ; + } else if (action === 'REBLOG') { + header = ; + button = ; + } else if (action === 'FAVOURITE') { + header = ; + button = ; + } else if (action === 'POLL_VOTE') { + header = ; + button = ; + } + + return ( +
+
+

{header}

+ +
+
+ + + + +
+ + + +
+

+ + + + + + +
+
+ ); + } + render() { - const { intl, siteTitle } = this.props; + const { intl, features, siteTitle } = this.props; + + if (features.remoteInteractionsAPI && features.federating) return this.renderRemoteInteractions(); return (
@@ -61,4 +179,4 @@ class UnauthorizedModal extends ImmutablePureComponent { } -export default injectIntl(connect(mapStateToProps)(UnauthorizedModal)); +export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(UnauthorizedModal)); diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index 49e4c26d7..85b376d54 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -317,14 +317,14 @@ class SwitchingColumnsArea extends React.PureComponent { - + - - - - - + + + + + diff --git a/app/soapbox/features/ui/util/react_router_helpers.js b/app/soapbox/features/ui/util/react_router_helpers.js index 4a636a738..af0b40800 100644 --- a/app/soapbox/features/ui/util/react_router_helpers.js +++ b/app/soapbox/features/ui/util/react_router_helpers.js @@ -1,16 +1,20 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; import { Redirect, Route } from 'react-router-dom'; import ColumnsAreaContainer from '../containers/columns_area_container'; import ColumnLoading from '../components/column_loading'; +import ColumnForbidden from '../components/column_forbidden'; import BundleColumnError from '../components/bundle_column_error'; import BundleContainer from '../containers/bundle_container'; +import { isStaff, isAdmin } from 'soapbox/utils/accounts'; const mapStateToProps = state => { + const me = state.get('me'); + return { - me: state.get('me'), + account: state.getIn(['accounts', me]), }; }; @@ -22,8 +26,10 @@ class WrappedRoute extends React.Component { content: PropTypes.node, componentParams: PropTypes.object, layout: PropTypes.object, + account: ImmutablePropTypes.map, publicRoute: PropTypes.bool, - me: SoapboxPropTypes.me, + staffOnly: PropTypes.bool, + adminOnly: PropTypes.bool, }; static defaultProps = { @@ -72,6 +78,14 @@ class WrappedRoute extends React.Component { ); } + renderForbidden = () => { + return ( + + + + ); + } + renderError = (props) => { return ( @@ -80,16 +94,26 @@ class WrappedRoute extends React.Component { ); } - render() { - const { component: Component, content, publicRoute, me, ...rest } = this.props; + loginRedirect = () => { + const actualUrl = encodeURIComponent(`${this.props.computedMatch.url}${this.props.location.search}`); // eslint-disable-line react/prop-types + return ; + } - if (!publicRoute && me === false) { - const actualUrl = encodeURIComponent(`${this.props.computedMatch.url}${this.props.location.search}`); // eslint-disable-line react/prop-types - return ; - // return { - // window.location.href = `/auth/sign_in?redirect_uri=${actualUrl}`; - // return null; - // }}/> + render() { + const { component: Component, content, account, publicRoute, staffOnly, adminOnly, ...rest } = this.props; + + const authorized = [ + account || publicRoute, + staffOnly ? account && isStaff(account) : true, + adminOnly ? account && isAdmin(account) : true, + ].every(c => c); + + if (!authorized) { + if (!account) { + return this.loginRedirect(); + } else { + return this.renderForbidden(); + } } return ; diff --git a/app/soapbox/locales/pl.json b/app/soapbox/locales/pl.json index a907d1219..5837f9e0b 100644 --- a/app/soapbox/locales/pl.json +++ b/app/soapbox/locales/pl.json @@ -740,6 +740,18 @@ "remote_instance.federation_panel.some_restrictions_message": "{siteTitle} nakłada pewne ograniczenia na {host}.", "remote_instance.pin_host": "Przypnij {instance}", "remote_instance.unpin_host": "Odepnij {instance}", + "remote_interaction.account_placeholder": "Wprowadź nazwę@domenę użytkownika, z którego chcesz wykonać działanie", + "remote_interaction.favourite": "Przejdź do polubienia", + "remote_interaction.favourite_title": "Polub wpis zdalnie", + "remote_interaction.follow": "Przejdź do obserwacji", + "remote_interaction.follow_title": "Obserwuj {user} zdalnie", + "remote_interaction.poll_vote": "Przejdź do ankiety", + "remote_interaction.poll_vote_title": "Zagłosuj w ankiecie zdalnie", + "remote_interaction.reblog": "Przejdź do wpisu", + "remote_interaction.reblog_title": "Udostępnij wpis zdalnie", + "remote_interaction.reply": "Przejdź do odpowiedzi", + "remote_interaction.reply_title": "Odpowiedz na wpis zdalnie", + "remote_interaction.user_not_found_error": "Nie można odnaleźć podanego użytkownika", "remote_timeline.filter_message": "Przeglądasz oś czasu {instance}", "reply_indicator.cancel": "Anuluj", "report.block": "Zablokuj {target}", diff --git a/app/soapbox/utils/features.js b/app/soapbox/utils/features.js index 6447f53f7..124df7d2d 100644 --- a/app/soapbox/utils/features.js +++ b/app/soapbox/utils/features.js @@ -74,6 +74,7 @@ export const getFeatures = createSelector([ v.software === MASTODON && gte(v.compatVersion, '3.4.0'), v.software === PLEROMA && gte(v.version, '2.4.50'), ]), + remoteInteractionsAPI: v.software === PLEROMA && gte(v.version, '2.4.50'), }; }); diff --git a/app/styles/components/columns.scss b/app/styles/components/columns.scss index 61bde2361..6670d41ea 100644 --- a/app/styles/components/columns.scss +++ b/app/styles/components/columns.scss @@ -956,7 +956,7 @@ // ScrollableList .slist .item-list > article:first-child, // ScrollableList placeholders - .slist .item-list .slist__placeholder > .material-status:first-child, + .slist .item-list .slist__placeholder:first-child > .material-status:first-child, .slist.slist--flex .item-list > .material-status:first-child, // Thread .material-status:not(.material-status + .material-status) { diff --git a/app/styles/components/detailed-status.scss b/app/styles/components/detailed-status.scss index 6215a1c95..c6fcaa747 100644 --- a/app/styles/components/detailed-status.scss +++ b/app/styles/components/detailed-status.scss @@ -185,17 +185,29 @@ } } - &__descendants .thread__status:first-child { + &__descendants &__status:first-child { margin-top: 10px; + + .status__wrapper--filtered { + margin-top: -10px; + } } &__status--focused:first-child, &__ancestors &__status:first-child { margin-top: 10px; + + .status__wrapper--filtered { + margin-top: -10px; + } } &__descendants &__status:last-child { margin-bottom: 10px; + + .status__wrapper--filtered { + margin-bottom: -10px; + } } &__connector { diff --git a/app/styles/components/directory.scss b/app/styles/components/directory.scss index 80fe759cb..62f6c7172 100644 --- a/app/styles/components/directory.scss +++ b/app/styles/components/directory.scss @@ -39,6 +39,13 @@ overflow: hidden; position: relative; + &__info { + z-index: 1; + position: absolute; + top: 10px; + left: 10px; + } + &__action-button { z-index: 1; position: absolute; diff --git a/app/styles/components/list-forms.scss b/app/styles/components/list-forms.scss index bc784fd31..354d170ec 100644 --- a/app/styles/components/list-forms.scss +++ b/app/styles/components/list-forms.scss @@ -14,6 +14,10 @@ border-radius: 8px 8px 0 0; } + &__content { + padding: 0; + } + &__accounts { background: var(--background-color); overflow-y: auto; @@ -33,7 +37,7 @@ .search { display: flex; flex-direction: row; - margin: 10px 0; + margin: 10px; > label { flex: 1 1; diff --git a/app/styles/components/modal.scss b/app/styles/components/modal.scss index b2e796e80..fd8550470 100644 --- a/app/styles/components/modal.scss +++ b/app/styles/components/modal.scss @@ -702,6 +702,12 @@ right: 15px; height: 20px; width: 20px; + + .svg-icon { + color: var(--primary-text-color); + height: 20px; + width: 20px; + } } } @@ -854,6 +860,59 @@ } } +.remote-interaction-modal { + &__content { + display: flex; + flex-direction: column; + // align-items: center; + row-gap: 10px; + padding: 10px; + + .unauthorized-modal-content__button { + margin: 0 auto; + } + } + + &__fields { + display: flex; + flex-direction: column; + gap: 10px; + width: 100%; + + .button { + width: auto; + margin: 0; + text-transform: none; + overflow: unset; + } + } + + &__divider { + display: flex; + align-items: center; + gap: 10px; + margin: 0 -10px; + + &::before, + &::after { + content: ""; + flex: 1; + border-bottom: 1px solid hsla(var(--primary-text-color_hsl), 0.2); + } + } + + @media screen and (max-width: 895px) { + margin: 0; + border-radius: 6px; + height: unset !important; + width: 440px !important; + } + + @media screen and (max-width: 480px) { + width: 330px !important; + } +} + .focal-point-modal { max-width: 80vw; max-height: 80vh; diff --git a/app/styles/components/status.scss b/app/styles/components/status.scss index a1eb66295..39fd6b49f 100644 --- a/app/styles/components/status.scss +++ b/app/styles/components/status.scss @@ -127,7 +127,7 @@ } .status__wrapper--filtered { - color: var(--primary-text-color); + color: var(--primary-text-color--faint); border: 0; font-size: inherit; text-align: center; @@ -137,7 +137,6 @@ box-sizing: border-box; width: 100%; clear: both; - border-bottom: 1px solid var(--brand-color--med); } .status__prepend-icon-wrapper { diff --git a/app/styles/emoji_picker.scss b/app/styles/emoji_picker.scss index 6398aeb32..8e6fe8777 100644 --- a/app/styles/emoji_picker.scss +++ b/app/styles/emoji_picker.scss @@ -169,7 +169,7 @@ } &:hover::before { - z-index: 0; + z-index: -1; content: ""; position: absolute; top: 0; diff --git a/app/styles/forms.scss b/app/styles/forms.scss index eebe8ee02..e62ec0b11 100644 --- a/app/styles/forms.scss +++ b/app/styles/forms.scss @@ -496,7 +496,7 @@ code { &::after { display: flex; align-items: center; - font-family: "Font Awesome 5 Free"; + font-family: 'Font Awesome 5 Free'; content: ""; position: absolute; right: 12px; @@ -504,6 +504,7 @@ code { padding-left: 12px; pointer-events: none; margin-top: 8px; + font-weight: 900; } } diff --git a/app/styles/ui.scss b/app/styles/ui.scss index 7e8d8018c..f29c89ba0 100644 --- a/app/styles/ui.scss +++ b/app/styles/ui.scss @@ -246,6 +246,10 @@ } } +article:last-child > .domain { + border-bottom: none; +} + .domain__wrapper { display: flex; } diff --git a/package.json b/package.json index 5f4529840..3c3ce850d 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "detect-passive-events": "^2.0.0", "dotenv": "^8.0.0", "emoji-datasource": "5.0.0", - "emoji-mart": "^3.0.1", + "emoji-mart": "npm:emoji-mart-lazyload", "entities": "^3.0.1", "es6-symbol": "^3.1.1", "escape-html": "^1.0.3", @@ -90,7 +90,7 @@ "http-link-header": "^1.0.2", "immutable": "^4.0.0-rc.14", "imports-loader": "^1.0.0", - "intersection-observer": "^0.11.0", + "intersection-observer": "^0.12.0", "intl": "^1.2.5", "intl-messageformat": "^9.0.0", "intl-messageformat-parser": "^6.0.0", diff --git a/yarn.lock b/yarn.lock index 53753d04a..d867807f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3598,12 +3598,13 @@ emoji-datasource@5.0.0: resolved "https://registry.yarnpkg.com/emoji-datasource/-/emoji-datasource-5.0.0.tgz#1522fdba3c52223a1cf5a1c1fc282935400eaa06" integrity sha512-LuvLWFnxznTH++GytEzpzOPUo1SB+6CUFqIlVETJJ3x9fpyMCKFfyqberbhMLOpT1qcNe+km+zoyBeUSC3u5Rw== -emoji-mart@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-3.0.1.tgz#9ce86706e02aea0506345f98464814a662ca54c6" - integrity sha512-sxpmMKxqLvcscu6mFn9ITHeZNkGzIvD0BSNFE/LJESPbCA8s1jM6bCDPjWbV31xHq7JXaxgpHxLB54RCbBZSlg== +"emoji-mart@npm:emoji-mart-lazyload": + version "3.0.1-j" + resolved "https://registry.yarnpkg.com/emoji-mart-lazyload/-/emoji-mart-lazyload-3.0.1-j.tgz#87a90d30b79d9145ece078d53e3e683c1a10ce9c" + integrity sha512-0wKF7MR0/iAeCIoiBLY+JjXCugycTgYRC2SL0y9/bjNSQlbeMdzILmPQJAufU/mgLFDUitOvjxLDhOZ9yxZ48g== dependencies: "@babel/runtime" "^7.0.0" + intersection-observer "^0.12.0" prop-types "^15.6.0" emoji-regex@^8.0.0: @@ -4951,10 +4952,10 @@ interpret@^2.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== -intersection-observer@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.11.0.tgz#f4ea067070326f68393ee161cc0a2ca4c0040c6f" - integrity sha512-KZArj2QVnmdud9zTpKf279m2bbGfG+4/kn16UU0NL3pTVl52ZHiJ9IRNSsnn6jaHrL9EGLFM5eWjTx2fz/+zoQ== +intersection-observer@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.12.0.tgz#6c84628f67ce8698e5f9ccf857d97718745837aa" + integrity sha512-2Vkz8z46Dv401zTWudDGwO7KiGHNDkMv417T5ItcNYfmvHR/1qCTVBO9vwH8zZmQ0WkA/1ARwpysR9bsnop4NQ== intl-messageformat-parser@6.1.2: version "6.1.2"