diff options
author | Reverite <github@reverite.sh> | 2019-09-30 16:38:09 -0700 |
---|---|---|
committer | Reverite <github@reverite.sh> | 2019-09-30 16:38:09 -0700 |
commit | 01001ba118d7adcd4966c893cf95db32fe76c273 (patch) | |
tree | fc8b580c8f0a3c1e14a8ca20e3bea5bd7acb4ff1 /app/javascript/flavours/glitch | |
parent | 1e75b3d874973dd71ea74b158ebeb501d9ac68a3 (diff) | |
parent | 65db9df011a3e13efa79f4e56343aa69bdad7716 (diff) |
Merge branch 'glitch' into production
Diffstat (limited to 'app/javascript/flavours/glitch')
50 files changed, 1091 insertions, 421 deletions
diff --git a/app/javascript/flavours/glitch/actions/blocks.js b/app/javascript/flavours/glitch/actions/blocks.js index 498ce519f..adae9d83c 100644 --- a/app/javascript/flavours/glitch/actions/blocks.js +++ b/app/javascript/flavours/glitch/actions/blocks.js @@ -1,6 +1,7 @@ import api, { getLinks } from 'flavours/glitch/util/api'; import { fetchRelationships } from './accounts'; import { importFetchedAccounts } from './importer'; +import { openModal } from './modal'; export const BLOCKS_FETCH_REQUEST = 'BLOCKS_FETCH_REQUEST'; export const BLOCKS_FETCH_SUCCESS = 'BLOCKS_FETCH_SUCCESS'; @@ -10,6 +11,8 @@ export const BLOCKS_EXPAND_REQUEST = 'BLOCKS_EXPAND_REQUEST'; export const BLOCKS_EXPAND_SUCCESS = 'BLOCKS_EXPAND_SUCCESS'; export const BLOCKS_EXPAND_FAIL = 'BLOCKS_EXPAND_FAIL'; +export const BLOCKS_INIT_MODAL = 'BLOCKS_INIT_MODAL'; + export function fetchBlocks() { return (dispatch, getState) => { dispatch(fetchBlocksRequest()); @@ -83,3 +86,14 @@ export function expandBlocksFail(error) { error, }; }; + +export function initBlockModal(account) { + return dispatch => { + dispatch({ + type: BLOCKS_INIT_MODAL, + account, + }); + + dispatch(openModal('BLOCK')); + }; +} diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index e1da03745..69cf65b5a 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -261,7 +261,7 @@ export function uploadCompose(files) { progress[i] = loaded; dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total)); }, - }).then(({ data }) => dispatch(uploadComposeSuccess(data))); + }).then(({ data }) => dispatch(uploadComposeSuccess(data, f))); }).catch(error => dispatch(uploadComposeFail(error))); }; }; @@ -316,10 +316,11 @@ export function uploadComposeProgress(loaded, total) { }; }; -export function uploadComposeSuccess(media) { +export function uploadComposeSuccess(media, file) { return { type: COMPOSE_UPLOAD_SUCCESS, media: media, + file: file, skipLoading: true, }; }; diff --git a/app/javascript/flavours/glitch/actions/conversations.js b/app/javascript/flavours/glitch/actions/conversations.js index 856f8f10f..e5c85c65d 100644 --- a/app/javascript/flavours/glitch/actions/conversations.js +++ b/app/javascript/flavours/glitch/actions/conversations.js @@ -15,6 +15,10 @@ export const CONVERSATIONS_UPDATE = 'CONVERSATIONS_UPDATE'; export const CONVERSATIONS_READ = 'CONVERSATIONS_READ'; +export const CONVERSATIONS_DELETE_REQUEST = 'CONVERSATIONS_DELETE_REQUEST'; +export const CONVERSATIONS_DELETE_SUCCESS = 'CONVERSATIONS_DELETE_SUCCESS'; +export const CONVERSATIONS_DELETE_FAIL = 'CONVERSATIONS_DELETE_FAIL'; + export const mountConversations = () => ({ type: CONVERSATIONS_MOUNT, }); @@ -82,3 +86,27 @@ export const updateConversations = conversation => dispatch => { conversation, }); }; + +export const deleteConversation = conversationId => (dispatch, getState) => { + dispatch(deleteConversationRequest(conversationId)); + + api(getState).delete(`/api/v1/conversations/${conversationId}`) + .then(() => dispatch(deleteConversationSuccess(conversationId))) + .catch(error => dispatch(deleteConversationFail(conversationId, error))); +}; + +export const deleteConversationRequest = id => ({ + type: CONVERSATIONS_DELETE_REQUEST, + id, +}); + +export const deleteConversationSuccess = id => ({ + type: CONVERSATIONS_DELETE_SUCCESS, + id, +}); + +export const deleteConversationFail = (id, error) => ({ + type: CONVERSATIONS_DELETE_FAIL, + id, + error, +}); diff --git a/app/javascript/flavours/glitch/actions/importer/normalizer.js b/app/javascript/flavours/glitch/actions/importer/normalizer.js index 52d85c059..b35c4d7bd 100644 --- a/app/javascript/flavours/glitch/actions/importer/normalizer.js +++ b/app/javascript/flavours/glitch/actions/importer/normalizer.js @@ -71,8 +71,9 @@ export function normalizePoll(poll) { const emojiMap = makeEmojiMap(normalPoll); - normalPoll.options = poll.options.map(option => ({ + normalPoll.options = poll.options.map((option, index) => ({ ...option, + voted: poll.own_votes && poll.own_votes.includes(index), title_emojified: emojify(escapeTextContentForBrowser(option.title), emojiMap), })); diff --git a/app/javascript/flavours/glitch/components/avatar_composite.js b/app/javascript/flavours/glitch/components/avatar_composite.js index c52df043a..125b51c44 100644 --- a/app/javascript/flavours/glitch/components/avatar_composite.js +++ b/app/javascript/flavours/glitch/components/avatar_composite.js @@ -35,35 +35,35 @@ export default class AvatarComposite extends React.PureComponent { if (size === 2) { if (index === 0) { - right = '2px'; + right = '1px'; } else { - left = '2px'; + left = '1px'; } } else if (size === 3) { if (index === 0) { - right = '2px'; + right = '1px'; } else if (index > 0) { - left = '2px'; + left = '1px'; } if (index === 1) { - bottom = '2px'; + bottom = '1px'; } else if (index > 1) { - top = '2px'; + top = '1px'; } } else if (size === 4) { if (index === 0 || index === 2) { - right = '2px'; + right = '1px'; } if (index === 1 || index === 3) { - left = '2px'; + left = '1px'; } if (index < 2) { - bottom = '2px'; + bottom = '1px'; } else { - top = '2px'; + top = '1px'; } } @@ -96,7 +96,13 @@ export default class AvatarComposite extends React.PureComponent { return ( <div className='account__avatar-composite' style={{ width: `${size}px`, height: `${size}px` }}> - {accounts.take(4).map((account, i) => this.renderItem(account, accounts.size, i))} + {accounts.take(4).map((account, i) => this.renderItem(account, Math.min(accounts.size, 4), i))} + + {accounts.size > 4 && ( + <span className='account__avatar-composite__label'> + +{accounts.size - 4} + </span> + )} </div> ); } diff --git a/app/javascript/flavours/glitch/components/column_back_button_slim.js b/app/javascript/flavours/glitch/components/column_back_button_slim.js index b57e3a057..faa0c23a8 100644 --- a/app/javascript/flavours/glitch/components/column_back_button_slim.js +++ b/app/javascript/flavours/glitch/components/column_back_button_slim.js @@ -1,7 +1,7 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; -import Icon from 'mastodon/components/icon'; +import Icon from 'flavours/glitch/components/icon'; export default class ColumnBackButtonSlim extends React.PureComponent { diff --git a/app/javascript/flavours/glitch/components/poll.js b/app/javascript/flavours/glitch/components/poll.js index 905aa54c1..a7f9a97da 100644 --- a/app/javascript/flavours/glitch/components/poll.js +++ b/app/javascript/flavours/glitch/components/poll.js @@ -10,9 +10,11 @@ import spring from 'react-motion/lib/spring'; import escapeTextContentForBrowser from 'escape-html'; import emojify from 'flavours/glitch/util/emoji'; import RelativeTimestamp from './relative_timestamp'; +import Icon from 'flavours/glitch/components/icon'; const messages = defineMessages({ closed: { id: 'poll.closed', defaultMessage: 'Closed' }, + voted: { id: 'poll.voted', defaultMessage: 'You voted for this answer', description: 'Tooltip of the "voted" checkmark in polls' }, }); const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => { @@ -99,10 +101,12 @@ class Poll extends ImmutablePureComponent { }; renderOption (option, optionIndex, showResults) { - const { poll, disabled } = this.props; - const percent = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100; - const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count')); - const active = !!this.state.selected[`${optionIndex}`]; + const { poll, disabled, intl } = this.props; + const pollVotesCount = poll.get('voters_count') || poll.get('votes_count'); + const percent = pollVotesCount === 0 ? 0 : (option.get('votes_count') / pollVotesCount) * 100; + const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') >= other.get('votes_count')); + const active = !!this.state.selected[`${optionIndex}`]; + const voted = option.get('voted') || (poll.get('own_votes') && poll.get('own_votes').includes(optionIndex)); let titleEmojified = option.get('title_emojified'); if (!titleEmojified) { @@ -131,7 +135,10 @@ class Poll extends ImmutablePureComponent { /> {!showResults && <span className={classNames('poll__input', { checkbox: poll.get('multiple'), active })} />} - {showResults && <span className='poll__number'>{Math.round(percent)}%</span>} + {showResults && <span className='poll__number'> + {!!voted && <Icon id='check' className='poll__vote__mark' title={intl.formatMessage(messages.voted)} />} + {Math.round(percent)}% + </span>} <span dangerouslySetInnerHTML={{ __html: titleEmojified }} /> </label> @@ -151,6 +158,14 @@ class Poll extends ImmutablePureComponent { const showResults = poll.get('voted') || expired; const disabled = this.props.disabled || Object.entries(this.state.selected).every(item => !item); + let votesCount = null; + + if (poll.get('voters_count') !== null && poll.get('voters_count') !== undefined) { + votesCount = <FormattedMessage id='poll.total_people' defaultMessage='{count, plural, one {# person} other {# people}}' values={{ count: poll.get('voters_count') }} />; + } else { + votesCount = <FormattedMessage id='poll.total_votes' defaultMessage='{count, plural, one {# vote} other {# votes}}' values={{ count: poll.get('votes_count') }} />; + } + return ( <div className='poll'> <ul> @@ -160,7 +175,7 @@ class Poll extends ImmutablePureComponent { <div className='poll__footer'> {!showResults && <button className='button button-secondary' disabled={disabled} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>} {showResults && !this.props.disabled && <span><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </span>} - <FormattedMessage id='poll.total_votes' defaultMessage='{count, plural, one {# vote} other {# votes}}' values={{ count: poll.get('votes_count') }} /> + {votesCount} {poll.get('expires_at') && <span> · {timeRemaining}</span>} </div> </div> diff --git a/app/javascript/flavours/glitch/containers/status_container.js b/app/javascript/flavours/glitch/containers/status_container.js index 15eb4f85f..4c3555dea 100644 --- a/app/javascript/flavours/glitch/containers/status_container.js +++ b/app/javascript/flavours/glitch/containers/status_container.js @@ -1,4 +1,3 @@ -import React from 'react'; import { connect } from 'react-redux'; import Status from 'flavours/glitch/components/status'; import { List as ImmutableList } from 'immutable'; @@ -18,9 +17,9 @@ import { pin, unpin, } from 'flavours/glitch/actions/interactions'; -import { blockAccount } from 'flavours/glitch/actions/accounts'; import { muteStatus, unmuteStatus, deleteStatus } from 'flavours/glitch/actions/statuses'; import { initMuteModal } from 'flavours/glitch/actions/mutes'; +import { initBlockModal } from 'flavours/glitch/actions/blocks'; import { initReport } from 'flavours/glitch/actions/reports'; import { openModal } from 'flavours/glitch/actions/modal'; import { changeLocalSetting } from 'flavours/glitch/actions/local_settings'; @@ -37,10 +36,8 @@ const messages = defineMessages({ deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' }, redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.' }, - blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' }, replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, 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' }, unfilterConfirm: { id: 'confirmations.unfilter.confirm', defaultMessage: 'Show' }, author: { id: 'confirmations.unfilter.author', defaultMessage: 'Author' }, matchingFilters: { id: 'confirmations.unfilter.filters', defaultMessage: 'Matching {count, plural, one {filter} other {filters}}' }, @@ -83,6 +80,7 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ onReply (status, router) { dispatch((_, getState) => { let state = getState(); + if (state.getIn(['local_settings', 'confirm_before_clearing_draft']) && state.getIn(['compose', 'text']).trim().length !== 0) { dispatch(openModal('CONFIRM', { message: intl.formatMessage(messages.replyMessage), @@ -186,16 +184,7 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ onBlock (status) { const account = status.get('account'); - dispatch(openModal('CONFIRM', { - message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, - 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)); - }, - })); + dispatch(initBlockModal(account)); }, onUnfilter (status, onConfirm) { diff --git a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js index 787a36658..fff5e097f 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js +++ b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js @@ -5,7 +5,6 @@ import Header from '../components/header'; import { followAccount, unfollowAccount, - blockAccount, unblockAccount, unmuteAccount, pinAccount, @@ -16,6 +15,7 @@ import { directCompose } from 'flavours/glitch/actions/compose'; import { initMuteModal } from 'flavours/glitch/actions/mutes'; +import { initBlockModal } from 'flavours/glitch/actions/blocks'; import { initReport } from 'flavours/glitch/actions/reports'; import { openModal } from 'flavours/glitch/actions/modal'; import { blockDomain, unblockDomain } from 'flavours/glitch/actions/domain_blocks'; @@ -25,9 +25,7 @@ import { List as ImmutableList } from 'immutable'; const messages = defineMessages({ unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' }, - blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' }, blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' }, - blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' }, }); const makeMapStateToProps = () => { @@ -64,16 +62,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ if (account.getIn(['relationship', 'blocking'])) { dispatch(unblockAccount(account.get('id'))); } else { - dispatch(openModal('CONFIRM', { - message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, - 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)); - }, - })); + dispatch(initBlockModal(account)); } }, 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 9ddeabe75..17487b202 100644 --- a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js +++ b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js @@ -2,9 +2,28 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import StatusContainer from 'flavours/glitch/containers/status_container'; +import StatusContent from 'flavours/glitch/components/status_content'; +import AttachmentList from 'flavours/glitch/components/attachment_list'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; +import AvatarComposite from 'flavours/glitch/components/avatar_composite'; +import Permalink from 'flavours/glitch/components/permalink'; +import IconButton from 'flavours/glitch/components/icon_button'; +import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; +import { HotKeys } from 'react-hotkeys'; -export default class Conversation extends ImmutablePureComponent { +const messages = defineMessages({ + more: { id: 'status.more', defaultMessage: 'More' }, + open: { id: 'conversation.open', defaultMessage: 'View conversation' }, + reply: { id: 'status.reply', defaultMessage: 'Reply' }, + markAsRead: { id: 'conversation.mark_as_read', defaultMessage: 'Mark as read' }, + delete: { id: 'conversation.delete', defaultMessage: 'Delete conversation' }, + muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' }, + unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' }, +}); + +export default @injectIntl +class Conversation extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object, @@ -13,25 +32,61 @@ export default class Conversation extends ImmutablePureComponent { static propTypes = { conversationId: PropTypes.string.isRequired, accounts: ImmutablePropTypes.list.isRequired, - lastStatusId: PropTypes.string, + lastStatus: ImmutablePropTypes.map, unread:PropTypes.bool.isRequired, onMoveUp: PropTypes.func, onMoveDown: PropTypes.func, markRead: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + state = { + isExpanded: undefined, }; + parseClick = (e, destination) => { + const { router } = this.context; + const { lastStatus, unread, markRead } = this.props; + if (!router) return; + + if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey)) { + if (destination === undefined) { + if (unread) { + markRead(); + } + destination = `/statuses/${lastStatus.get('id')}`; + } + let state = {...router.history.location.state}; + state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; + router.history.push(destination, state); + e.preventDefault(); + } + } + handleClick = () => { if (!this.context.router) { return; } - const { lastStatusId, unread, markRead } = this.props; + const { lastStatus, unread, markRead } = this.props; if (unread) { markRead(); } - this.context.router.history.push(`/statuses/${lastStatusId}`); + this.context.router.history.push(`/statuses/${lastStatus.get('id')}`); + } + + handleMarkAsRead = () => { + this.props.markRead(); + } + + handleReply = () => { + this.props.reply(this.props.lastStatus, this.context.router.history); + } + + handleDelete = () => { + this.props.delete(); } handleHotkeyMoveUp = () => { @@ -42,22 +97,94 @@ export default class Conversation extends ImmutablePureComponent { this.props.onMoveDown(this.props.conversationId); } + handleConversationMute = () => { + this.props.onMute(this.props.lastStatus); + } + + handleShowMore = () => { + if (this.props.lastStatus.get('spoiler_text')) { + this.setExpansion(!this.state.isExpanded); + } + }; + + setExpansion = value => { + this.setState({ isExpanded: value }); + } + render () { - const { accounts, lastStatusId, unread } = this.props; + const { accounts, lastStatus, unread, intl } = this.props; + const { isExpanded } = this.state; - if (lastStatusId === null) { + if (lastStatus === null) { return null; } + const menu = [ + { text: intl.formatMessage(messages.open), action: this.handleClick }, + null, + ]; + + menu.push({ text: intl.formatMessage(lastStatus.get('muted') ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMute }); + + if (unread) { + menu.push({ text: intl.formatMessage(messages.markAsRead), action: this.handleMarkAsRead }); + menu.push(null); + } + + menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDelete }); + + const names = accounts.map(a => <Permalink to={`/accounts/${a.get('id')}`} href={a.get('url')} key={a.get('id')} title={a.get('acct')}><bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi></Permalink>).reduce((prev, cur) => [prev, ', ', cur]); + + const handlers = { + reply: this.handleReply, + open: this.handleClick, + moveUp: this.handleHotkeyMoveUp, + moveDown: this.handleHotkeyMoveDown, + toggleHidden: this.handleShowMore, + }; + + let media = null; + if (lastStatus.get('media_attachments').size > 0) { + media = <AttachmentList compact media={lastStatus.get('media_attachments')} />; + } + return ( - <StatusContainer - id={lastStatusId} - unread={unread} - otherAccounts={accounts} - onMoveUp={this.handleHotkeyMoveUp} - onMoveDown={this.handleHotkeyMoveDown} - onClick={this.handleClick} - /> + <HotKeys handlers={handlers}> + <div className='conversation focusable muted' tabIndex='0'> + <div className='conversation__avatar'> + <AvatarComposite accounts={accounts} size={48} /> + </div> + + <div className='conversation__content'> + <div className='conversation__content__info'> + <div className='conversation__content__relative-time'> + <RelativeTimestamp timestamp={lastStatus.get('created_at')} /> + </div> + + <div className='conversation__content__names'> + <FormattedMessage id='conversation.with' defaultMessage='With {names}' values={{ names: <span>{names}</span> }} /> + </div> + </div> + + <StatusContent + status={lastStatus} + parseClick={this.parseClick} + expanded={isExpanded} + onExpandedToggle={this.handleShowMore} + collapsable + media={media} + /> + + <div className='status__action-bar'> + <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.reply)} icon='reply' onClick={this.handleReply} /> + + <div className='status__action-bar-dropdown'> + <DropdownMenuContainer status={lastStatus} items={menu} icon='ellipsis-h' size={18} direction='right' title={intl.formatMessage(messages.more)} /> + </div> + </div> + </div> + </div> + </HotKeys> ); } diff --git a/app/javascript/flavours/glitch/features/direct_timeline/containers/conversation_container.js b/app/javascript/flavours/glitch/features/direct_timeline/containers/conversation_container.js index bd6f6bfb0..b15ce9f0f 100644 --- a/app/javascript/flavours/glitch/features/direct_timeline/containers/conversation_container.js +++ b/app/javascript/flavours/glitch/features/direct_timeline/containers/conversation_container.js @@ -1,19 +1,74 @@ import { connect } from 'react-redux'; import Conversation from '../components/conversation'; -import { markConversationRead } from '../../../actions/conversations'; +import { markConversationRead, deleteConversation } from 'flavours/glitch/actions/conversations'; +import { makeGetStatus } from 'flavours/glitch/selectors'; +import { replyCompose } from 'flavours/glitch/actions/compose'; +import { openModal } from 'flavours/glitch/actions/modal'; +import { muteStatus, unmuteStatus, hideStatus, revealStatus } from 'flavours/glitch/actions/statuses'; +import { defineMessages, injectIntl } from 'react-intl'; -const mapStateToProps = (state, { conversationId }) => { - const conversation = state.getIn(['conversations', 'items']).find(x => x.get('id') === conversationId); +const messages = defineMessages({ + replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, + replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, +}); + +const mapStateToProps = () => { + const getStatus = makeGetStatus(); + + return (state, { conversationId }) => { + const conversation = state.getIn(['conversations', 'items']).find(x => x.get('id') === conversationId); + const lastStatusId = conversation.get('last_status', null); - return { - accounts: conversation.get('accounts').map(accountId => state.getIn(['accounts', accountId], null)), - unread: conversation.get('unread'), - lastStatusId: conversation.get('last_status', null), + return { + accounts: conversation.get('accounts').map(accountId => state.getIn(['accounts', accountId], null)), + unread: conversation.get('unread'), + lastStatus: lastStatusId && getStatus(state, { id: lastStatusId }), + }; }; }; -const mapDispatchToProps = (dispatch, { conversationId }) => ({ - markRead: () => dispatch(markConversationRead(conversationId)), +const mapDispatchToProps = (dispatch, { intl, conversationId }) => ({ + + markRead () { + dispatch(markConversationRead(conversationId)); + }, + + reply (status, router) { + dispatch((_, getState) => { + let 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)); + } + }); + }, + + delete () { + dispatch(deleteConversation(conversationId)); + }, + + onMute (status) { + if (status.get('muted')) { + dispatch(unmuteStatus(status.get('id'))); + } else { + dispatch(muteStatus(status.get('id'))); + } + }, + + onToggleHidden (status) { + if (status.get('hidden')) { + dispatch(revealStatus(status.get('id'))); + } else { + dispatch(hideStatus(status.get('id'))); + } + }, + }); -export default connect(mapStateToProps, mapDispatchToProps)(Conversation); +export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(Conversation)); diff --git a/app/javascript/flavours/glitch/features/favourites/index.js b/app/javascript/flavours/glitch/features/favourites/index.js index 5c33c8677..7afadf12e 100644 --- a/app/javascript/flavours/glitch/features/favourites/index.js +++ b/app/javascript/flavours/glitch/features/favourites/index.js @@ -31,7 +31,9 @@ class Favourites extends ImmutablePureComponent { }; componentWillMount () { - this.props.dispatch(fetchFavourites(this.props.params.statusId)); + if (!this.props.accountIds) { + this.props.dispatch(fetchFavourites(this.props.params.statusId)); + } } componentWillReceiveProps (nextProps) { diff --git a/app/javascript/flavours/glitch/features/followers/index.js b/app/javascript/flavours/glitch/features/followers/index.js index b12efa774..2bd0e6e2f 100644 --- a/app/javascript/flavours/glitch/features/followers/index.js +++ b/app/javascript/flavours/glitch/features/followers/index.js @@ -36,8 +36,10 @@ class Followers extends ImmutablePureComponent { }; componentWillMount () { - this.props.dispatch(fetchAccount(this.props.params.accountId)); - this.props.dispatch(fetchFollowers(this.props.params.accountId)); + if (!this.props.accountIds) { + this.props.dispatch(fetchAccount(this.props.params.accountId)); + this.props.dispatch(fetchFollowers(this.props.params.accountId)); + } } componentWillReceiveProps (nextProps) { diff --git a/app/javascript/flavours/glitch/features/following/index.js b/app/javascript/flavours/glitch/features/following/index.js index 9ea008e61..f03da0c94 100644 --- a/app/javascript/flavours/glitch/features/following/index.js +++ b/app/javascript/flavours/glitch/features/following/index.js @@ -36,8 +36,10 @@ class Following extends ImmutablePureComponent { }; componentWillMount () { - this.props.dispatch(fetchAccount(this.props.params.accountId)); - this.props.dispatch(fetchFollowing(this.props.params.accountId)); + if (!this.props.accountIds) { + this.props.dispatch(fetchAccount(this.props.params.accountId)); + this.props.dispatch(fetchFollowing(this.props.params.accountId)); + } } componentWillReceiveProps (nextProps) { diff --git a/app/javascript/flavours/glitch/features/getting_started/index.js b/app/javascript/flavours/glitch/features/getting_started/index.js index 68b5209dc..a5095fbd9 100644 --- a/app/javascript/flavours/glitch/features/getting_started/index.js +++ b/app/javascript/flavours/glitch/features/getting_started/index.js @@ -104,16 +104,14 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2); } componentDidMount () { - const { myAccount, fetchFollowRequests, multiColumn } = this.props; + const { fetchFollowRequests, multiColumn } = this.props; if (!multiColumn && window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) { this.context.router.history.replace('/timelines/home'); return; } - if (myAccount.get('locked')) { - fetchFollowRequests(); - } + fetchFollowRequests(); } render () { @@ -148,7 +146,7 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2); navItems.push(<ColumnLink key='5' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} to='/bookmarks' />); } - if (myAccount.get('locked')) { + if (myAccount.get('locked') || unreadFollowRequests > 0) { navItems.push(<ColumnLink key='6' icon='user-plus' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />); } diff --git a/app/javascript/flavours/glitch/features/notifications/components/filter_bar.js b/app/javascript/flavours/glitch/features/notifications/components/filter_bar.js index 356ca4721..6118305d6 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/filter_bar.js +++ b/app/javascript/flavours/glitch/features/notifications/components/filter_bar.js @@ -64,7 +64,7 @@ class FilterBar extends React.PureComponent { onClick={this.onClick('mention')} title={intl.formatMessage(tooltips.mentions)} > - <Icon id='at' fixedWidth /> + <Icon id='reply-all' fixedWidth /> </button> <button className={selectedFilter === 'favourite' ? 'active' : ''} diff --git a/app/javascript/flavours/glitch/features/reblogs/index.js b/app/javascript/flavours/glitch/features/reblogs/index.js index 1fc26b0d7..a8e9db7f5 100644 --- a/app/javascript/flavours/glitch/features/reblogs/index.js +++ b/app/javascript/flavours/glitch/features/reblogs/index.js @@ -31,7 +31,9 @@ class Reblogs extends ImmutablePureComponent { }; componentWillMount () { - this.props.dispatch(fetchReblogs(this.props.params.statusId)); + if (!this.props.accountIds) { + this.props.dispatch(fetchReblogs(this.props.params.statusId)); + } } componentWillReceiveProps(nextProps) { diff --git a/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js b/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js index e6c390537..e71803328 100644 --- a/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js +++ b/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js @@ -1,4 +1,3 @@ -import React from 'react'; import { connect } from 'react-redux'; import DetailedStatus from '../components/detailed_status'; import { makeGetStatus } from 'flavours/glitch/selectors'; @@ -15,7 +14,6 @@ import { pin, unpin, } from 'flavours/glitch/actions/interactions'; -import { blockAccount } from 'flavours/glitch/actions/accounts'; import { muteStatus, unmuteStatus, @@ -24,9 +22,10 @@ import { revealStatus, } from 'flavours/glitch/actions/statuses'; import { initMuteModal } from 'flavours/glitch/actions/mutes'; +import { initBlockModal } from 'flavours/glitch/actions/blocks'; import { initReport } from 'flavours/glitch/actions/reports'; import { openModal } from 'flavours/glitch/actions/modal'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import { boostModal, deleteModal } from 'flavours/glitch/util/initial_state'; import { showAlertForError } from 'flavours/glitch/actions/alerts'; @@ -35,10 +34,8 @@ const messages = defineMessages({ deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' }, redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts 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' }, 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' }, }); const makeMapStateToProps = () => { @@ -139,16 +136,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ onBlock (status) { const account = status.get('account'); - dispatch(openModal('CONFIRM', { - message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, - 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)); - }, - })); + dispatch(initBlockModal(account)); }, onReport (status) { diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js index e91ab5f3a..dd17823ad 100644 --- a/app/javascript/flavours/glitch/features/status/index.js +++ b/app/javascript/flavours/glitch/features/status/index.js @@ -26,9 +26,9 @@ import { directCompose, } from 'flavours/glitch/actions/compose'; import { changeLocalSetting } from 'flavours/glitch/actions/local_settings'; -import { blockAccount } from 'flavours/glitch/actions/accounts'; import { muteStatus, unmuteStatus, deleteStatus } from 'flavours/glitch/actions/statuses'; import { initMuteModal } from 'flavours/glitch/actions/mutes'; +import { initBlockModal } from 'flavours/glitch/actions/blocks'; import { initReport } from 'flavours/glitch/actions/reports'; import { makeGetStatus } from 'flavours/glitch/selectors'; import { ScrollContainer } from 'react-router-scroll-4'; @@ -36,7 +36,7 @@ import ColumnBackButton from 'flavours/glitch/components/column_back_button'; import ColumnHeader from '../../components/column_header'; import StatusContainer from 'flavours/glitch/containers/status_container'; import { openModal } from 'flavours/glitch/actions/modal'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { HotKeys } from 'react-hotkeys'; import { boostModal, favouriteModal, deleteModal } from 'flavours/glitch/util/initial_state'; @@ -50,13 +50,11 @@ const messages = defineMessages({ deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' }, redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.' }, - blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' }, revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' }, hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' }, detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' }, replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, 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' }, tootHeading: { id: 'column.toot', defaultMessage: 'Toots and replies' }, }); @@ -339,19 +337,9 @@ class Status extends ImmutablePureComponent { } handleBlockClick = (status) => { - const { dispatch, intl } = this.props; + const { dispatch } = this.props; const account = status.get('account'); - - dispatch(openModal('CONFIRM', { - message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, - 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)); - }, - })); + dispatch(initBlockModal(account)); } handleReport = (status) => { diff --git a/app/javascript/flavours/glitch/features/ui/components/block_modal.js b/app/javascript/flavours/glitch/features/ui/components/block_modal.js new file mode 100644 index 000000000..a07baeaa6 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/block_modal.js @@ -0,0 +1,103 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { injectIntl, FormattedMessage } from 'react-intl'; +import { makeGetAccount } from '../../../selectors'; +import Button from '../../../components/button'; +import { closeModal } from '../../../actions/modal'; +import { blockAccount } from '../../../actions/accounts'; +import { initReport } from '../../../actions/reports'; + + +const makeMapStateToProps = () => { + const getAccount = makeGetAccount(); + + const mapStateToProps = state => ({ + account: getAccount(state, state.getIn(['blocks', 'new', 'account_id'])), + }); + + return mapStateToProps; +}; + +const mapDispatchToProps = dispatch => { + return { + onConfirm(account) { + dispatch(blockAccount(account.get('id'))); + }, + + onBlockAndReport(account) { + dispatch(blockAccount(account.get('id'))); + dispatch(initReport(account)); + }, + + onClose() { + dispatch(closeModal()); + }, + }; +}; + +export default @connect(makeMapStateToProps, mapDispatchToProps) +@injectIntl +class BlockModal extends React.PureComponent { + + static propTypes = { + account: PropTypes.object.isRequired, + onClose: PropTypes.func.isRequired, + onBlockAndReport: PropTypes.func.isRequired, + onConfirm: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + componentDidMount() { + this.button.focus(); + } + + handleClick = () => { + this.props.onClose(); + this.props.onConfirm(this.props.account); + } + + handleSecondary = () => { + this.props.onClose(); + this.props.onBlockAndReport(this.props.account); + } + + handleCancel = () => { + this.props.onClose(); + } + + setRef = (c) => { + this.button = c; + } + + render () { + const { account } = this.props; + + return ( + <div className='modal-root__modal block-modal'> + <div className='block-modal__container'> + <p> + <FormattedMessage + id='confirmations.block.message' + defaultMessage='Are you sure you want to block {name}?' + values={{ name: <strong>@{account.get('acct')}</strong> }} + /> + </p> + </div> + + <div className='block-modal__action-bar'> + <Button onClick={this.handleCancel} className='block-modal__cancel-button'> + <FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' /> + </Button> + <Button onClick={this.handleSecondary} className='confirmation-modal__secondary-button'> + <FormattedMessage id='confirmations.block.block_and_report' defaultMessage='Block & Report' /> + </Button> + <Button onClick={this.handleClick} ref={this.setRef}> + <FormattedMessage id='confirmations.block.confirm' defaultMessage='Block' /> + </Button> + </div> + </div> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js b/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js index 8bded391a..d5c9e66ae 100644 --- a/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js @@ -173,7 +173,17 @@ class FocalPointModal extends ImmutablePureComponent { langPath: `${assetHost}/ocr/lang-data`, }); - worker.recognize(media.get('url')) + let media_url = media.get('file'); + + if (window.URL && URL.createObjectURL) { + try { + media_url = URL.createObjectURL(media.get('file')); + } catch (error) { + console.error(error); + } + } + + worker.recognize(media_url) .progress(({ progress }) => this.setState({ progress })) .finally(() => worker.terminate()) .then(({ text }) => this.setState({ description: removeExtraLineBreaks(text), dirty: true, detecting: false })) diff --git a/app/javascript/flavours/glitch/features/ui/components/modal_root.js b/app/javascript/flavours/glitch/features/ui/components/modal_root.js index 303e05db6..0941ce9c8 100644 --- a/app/javascript/flavours/glitch/features/ui/components/modal_root.js +++ b/app/javascript/flavours/glitch/features/ui/components/modal_root.js @@ -15,6 +15,7 @@ import FocalPointModal from './focal_point_modal'; import { OnboardingModal, MuteModal, + BlockModal, ReportModal, SettingsModal, EmbedModal, @@ -32,6 +33,7 @@ const MODAL_COMPONENTS = { 'DOODLE': () => Promise.resolve({ default: DoodleModal }), 'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }), 'MUTE': MuteModal, + 'BLOCK': BlockModal, 'REPORT': ReportModal, 'SETTINGS': SettingsModal, 'ACTIONS': () => Promise.resolve({ default: ActionsModal }), diff --git a/app/javascript/flavours/glitch/features/ui/components/mute_modal.js b/app/javascript/flavours/glitch/features/ui/components/mute_modal.js index 3492eca69..dec6413c3 100644 --- a/app/javascript/flavours/glitch/features/ui/components/mute_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/mute_modal.js @@ -11,7 +11,6 @@ import { toggleHideNotifications } from 'flavours/glitch/actions/mutes'; const mapStateToProps = state => { return { - isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']), account: state.getIn(['mutes', 'new', 'account']), notifications: state.getIn(['mutes', 'new', 'notifications']), }; @@ -38,7 +37,6 @@ export default @connect(mapStateToProps, mapDispatchToProps) class MuteModal extends React.PureComponent { static propTypes = { - isSubmitting: PropTypes.bool.isRequired, account: PropTypes.object.isRequired, notifications: PropTypes.bool.isRequired, onClose: PropTypes.func.isRequired, @@ -81,11 +79,16 @@ class MuteModal extends React.PureComponent { values={{ name: <strong>@{account.get('acct')}</strong> }} /> </p> - <div> - <label htmlFor='mute-modal__hide-notifications-checkbox'> + <p className='mute-modal__explanation'> + <FormattedMessage + id='confirmations.mute.explanation' + defaultMessage='This will hide posts from them and posts mentioning them, but it will still allow them to see your posts follow you.' + /> + </p> + <div className='setting-toggle'> + <Toggle id='mute-modal__hide-notifications-checkbox' checked={notifications} onChange={this.toggleNotifications} /> + <label className='setting-toggle__label' htmlFor='mute-modal__hide-notifications-checkbox'> <FormattedMessage id='mute_modal.hide_notifications' defaultMessage='Hide notifications from this user?' /> - {' '} - <Toggle id='mute-modal__hide-notifications-checkbox' checked={notifications} onChange={this.toggleNotifications} /> </label> </div> </div> diff --git a/app/javascript/flavours/glitch/packs/public.js b/app/javascript/flavours/glitch/packs/public.js index 019de2167..5a15830df 100644 --- a/app/javascript/flavours/glitch/packs/public.js +++ b/app/javascript/flavours/glitch/packs/public.js @@ -114,6 +114,16 @@ function main() { this.parentElement.parentElement.nextElementSibling.classList.toggle('hidden'); }); }); + + delegate(document, '.sidebar__toggle__icon', 'click', () => { + const target = document.querySelector('.sidebar ul'); + + if (target.style.display === 'block') { + target.style.display = 'none'; + } else { + target.style.display = 'block'; + } + }); } loadPolyfills().then(main).catch(error => { diff --git a/app/javascript/flavours/glitch/packs/settings.js b/app/javascript/flavours/glitch/packs/settings.js new file mode 100644 index 000000000..b32f38226 --- /dev/null +++ b/app/javascript/flavours/glitch/packs/settings.js @@ -0,0 +1,20 @@ +import loadPolyfills from 'flavours/glitch/util/load_polyfills'; +import ready from 'flavours/glitch/util/ready'; + +function main() { + const { delegate } = require('rails-ujs'); + + delegate(document, '.sidebar__toggle__icon', 'click', () => { + const target = document.querySelector('.sidebar ul'); + + if (target.style.display === 'block') { + target.style.display = 'none'; + } else { + target.style.display = 'block'; + } + }); +} + +loadPolyfills().then(main).catch(error => { + console.error(error); +}); diff --git a/app/javascript/flavours/glitch/reducers/blocks.js b/app/javascript/flavours/glitch/reducers/blocks.js new file mode 100644 index 000000000..1b6507163 --- /dev/null +++ b/app/javascript/flavours/glitch/reducers/blocks.js @@ -0,0 +1,22 @@ +import Immutable from 'immutable'; + +import { + BLOCKS_INIT_MODAL, +} from '../actions/blocks'; + +const initialState = Immutable.Map({ + new: Immutable.Map({ + account_id: null, + }), +}); + +export default function mutes(state = initialState, action) { + switch (action.type) { + case BLOCKS_INIT_MODAL: + return state.withMutations((state) => { + state.setIn(['new', 'account_id'], action.account.get('id')); + }); + default: + return state; + } +} diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js index adad205c0..17ce5de3c 100644 --- a/app/javascript/flavours/glitch/reducers/compose.js +++ b/app/javascript/flavours/glitch/reducers/compose.js @@ -190,10 +190,13 @@ function continueThread (state, status) { }); } -function appendMedia(state, media) { +function appendMedia(state, media, file) { const prevSize = state.get('media_attachments').size; return state.withMutations(map => { + if (media.get('type') === 'image') { + media = media.set('file', file); + } map.update('media_attachments', list => list.push(media)); map.set('is_uploading', false); map.set('resetFileKey', Math.floor((Math.random() * 0x10000))); @@ -422,7 +425,7 @@ export default function compose(state = initialState, action) { case COMPOSE_UPLOAD_REQUEST: return state.set('is_uploading', true); case COMPOSE_UPLOAD_SUCCESS: - return appendMedia(state, fromJS(action.media)); + return appendMedia(state, fromJS(action.media), action.file); case COMPOSE_UPLOAD_FAIL: return state.set('is_uploading', false); case COMPOSE_UPLOAD_UNDO: diff --git a/app/javascript/flavours/glitch/reducers/index.js b/app/javascript/flavours/glitch/reducers/index.js index b03590194..7dbca3a29 100644 --- a/app/javascript/flavours/glitch/reducers/index.js +++ b/app/javascript/flavours/glitch/reducers/index.js @@ -16,6 +16,7 @@ import local_settings from './local_settings'; import push_notifications from './push_notifications'; import status_lists from './status_lists'; import mutes from './mutes'; +import blocks from './blocks'; import reports from './reports'; import contexts from './contexts'; import compose from './compose'; @@ -53,6 +54,7 @@ const reducers = { local_settings, push_notifications, mutes, + blocks, reports, contexts, compose, diff --git a/app/javascript/flavours/glitch/reducers/mutes.js b/app/javascript/flavours/glitch/reducers/mutes.js index 8f52a7704..7111bb710 100644 --- a/app/javascript/flavours/glitch/reducers/mutes.js +++ b/app/javascript/flavours/glitch/reducers/mutes.js @@ -7,7 +7,6 @@ import { const initialState = Immutable.Map({ new: Immutable.Map({ - isSubmitting: false, account: null, notifications: true, }), @@ -17,7 +16,6 @@ export default function mutes(state = initialState, action) { switch (action.type) { case MUTES_INIT_MODAL: return state.withMutations((state) => { - state.setIn(['new', 'isSubmitting'], false); state.setIn(['new', 'account'], action.account); state.setIn(['new', 'notifications'], true); }); diff --git a/app/javascript/flavours/glitch/reducers/notifications.js b/app/javascript/flavours/glitch/reducers/notifications.js index 8dc7a4aba..8d5c6785c 100644 --- a/app/javascript/flavours/glitch/reducers/notifications.js +++ b/app/javascript/flavours/glitch/reducers/notifications.js @@ -52,7 +52,7 @@ const notificationToMap = (state, notification) => ImmutableMap({ const normalizeNotification = (state, notification, usePendingItems) => { const top = !shouldCountUnreadNotifications(state); - if (usePendingItems || !top || !state.get('pendingItems').isEmpty()) { + if (usePendingItems || !state.get('pendingItems').isEmpty()) { return state.update('pendingItems', list => list.unshift(notificationToMap(state, notification))).update('unread', unread => unread + 1); } @@ -82,7 +82,7 @@ const expandNormalizedNotifications = (state, notifications, next, isLoadingRece return state.withMutations(mutable => { if (!items.isEmpty()) { - usePendingItems = isLoadingRecent && (usePendingItems || !mutable.get('top') || !mutable.get('pendingItems').isEmpty()); + usePendingItems = isLoadingRecent && (usePendingItems || !mutable.get('pendingItems').isEmpty()); mutable.update(usePendingItems ? 'pendingItems' : 'items', list => { const lastIndex = 1 + list.findLastIndex( diff --git a/app/javascript/flavours/glitch/reducers/timelines.js b/app/javascript/flavours/glitch/reducers/timelines.js index e6bef18e9..df88a6c23 100644 --- a/app/javascript/flavours/glitch/reducers/timelines.js +++ b/app/javascript/flavours/glitch/reducers/timelines.js @@ -40,7 +40,8 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, is if (timeline.endsWith(':pinned')) { mMap.set('items', statuses.map(status => status.get('id'))); } else if (!statuses.isEmpty()) { - usePendingItems = isLoadingRecent && (usePendingItems || !mMap.get('top') || !mMap.get('pendingItems').isEmpty()); + usePendingItems = isLoadingRecent && (usePendingItems || !mMap.get('pendingItems').isEmpty()); + mMap.update(usePendingItems ? 'pendingItems' : 'items', ImmutableList(), oldIds => { const newIds = statuses.map(status => status.get('id')); const lastIndex = oldIds.findLastIndex(id => id !== null && compareId(id, newIds.last()) >= 0) + 1; @@ -62,7 +63,7 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, is const updateTimeline = (state, timeline, status, usePendingItems) => { const top = state.getIn([timeline, 'top']); - if (usePendingItems || !top || !state.getIn([timeline, 'pendingItems']).isEmpty()) { + if (usePendingItems || !state.getIn([timeline, 'pendingItems']).isEmpty()) { if (state.getIn([timeline, 'pendingItems'], ImmutableList()).includes(status.get('id')) || state.getIn([timeline, 'items'], ImmutableList()).includes(status.get('id'))) { return state; } diff --git a/app/javascript/flavours/glitch/styles/_mixins.scss b/app/javascript/flavours/glitch/styles/_mixins.scss index d542b1083..088b41e76 100644 --- a/app/javascript/flavours/glitch/styles/_mixins.scss +++ b/app/javascript/flavours/glitch/styles/_mixins.scss @@ -62,24 +62,6 @@ color: $darker-text-color; font-size: 14px; margin: 0; - - &::-moz-focus-inner { - border: 0; - } - - &::-moz-focus-inner, - &:focus, - &:active { - outline: 0 !important; - } - - &:focus { - background: lighten($ui-base-color, 4%); - } - - @media screen and (max-width: 600px) { - font-size: 16px; - } } @mixin search-popout() { diff --git a/app/javascript/flavours/glitch/styles/about.scss b/app/javascript/flavours/glitch/styles/about.scss index 0e910693d..7c129674d 100644 --- a/app/javascript/flavours/glitch/styles/about.scss +++ b/app/javascript/flavours/glitch/styles/about.scss @@ -17,109 +17,102 @@ $small-breakpoint: 960px; .rich-formatting { font-family: $font-sans-serif, sans-serif; - font-size: 16px; + font-size: 14px; font-weight: 400; - font-size: 16px; - line-height: 30px; + line-height: 1.7; + word-wrap: break-word; color: $darker-text-color; - padding-right: 10px; a { color: $highlight-text-color; text-decoration: underline; + + &:hover, + &:focus, + &:active { + text-decoration: none; + } } p, li { - font-family: $font-sans-serif, sans-serif; - font-size: 16px; - font-weight: 400; - font-size: 16px; - line-height: 30px; - margin-bottom: 12px; color: $darker-text-color; + } - a { - color: $highlight-text-color; - text-decoration: underline; - } + p { + margin-top: 0; + margin-bottom: .85em; &:last-child { margin-bottom: 0; } } - strong, - em { + strong { font-weight: 700; - color: lighten($darker-text-color, 10%); + color: $secondary-text-color; } - h1 { - font-family: $font-display, sans-serif; - font-size: 26px; - line-height: 30px; - font-weight: 500; - margin-bottom: 20px; + em { + font-style: italic; color: $secondary-text-color; + } - small { - font-family: $font-sans-serif, sans-serif; - display: block; - font-size: 18px; - font-weight: 400; - color: lighten($darker-text-color, 10%); - } + code { + font-size: 0.85em; + background: darken($ui-base-color, 8%); + border-radius: 4px; + padding: 0.2em 0.3em; } - h2 { + h1, + h2, + h3, + h4, + h5, + h6 { font-family: $font-display, sans-serif; - font-size: 22px; - line-height: 26px; + margin-top: 1.275em; + margin-bottom: .85em; font-weight: 500; - margin-bottom: 20px; color: $secondary-text-color; } + h1 { + font-size: 2em; + } + + h2 { + font-size: 1.75em; + } + h3 { - font-family: $font-display, sans-serif; - font-size: 18px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; + font-size: 1.5em; } h4 { - font-family: $font-display, sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; + font-size: 1.25em; } - h5 { - font-family: $font-display, sans-serif; - font-size: 14px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; + h5, + h6 { + font-size: 1em; } - h6 { - font-family: $font-display, sans-serif; - font-size: 12px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; + ul { + list-style: disc; + } + + ol { + list-style: decimal; } ul, ol { - margin-left: 20px; + margin: 0; + padding: 0; + padding-left: 2em; + margin-bottom: 0.85em; &[type='a'] { list-style-type: lower-alpha; @@ -130,31 +123,63 @@ $small-breakpoint: 960px; } } - ul { - list-style: disc; - } - - ol { - list-style: decimal; - } - - li > ol, - li > ul { - margin-top: 6px; - } - hr { width: 100%; height: 0; border: 0; - border-bottom: 1px solid rgba($ui-base-lighter-color, .6); - margin: 20px 0; + border-bottom: 1px solid lighten($ui-base-color, 4%); + margin: 1.7em 0; &.spacer { height: 1px; border: 0; } } + + table { + width: 100%; + border-collapse: collapse; + break-inside: auto; + margin-top: 24px; + margin-bottom: 32px; + + thead tr, + tbody tr { + break-after: auto; + break-inside: avoid; + border-bottom: 1px solid lighten($ui-base-color, 4%); + font-size: 1em; + line-height: 1.625; + font-weight: 400; + text-align: left; + color: $darker-text-color; + } + + thead tr { + border-bottom-width: 2px; + line-height: 1.5; + font-weight: 500; + color: $dark-text-color; + } + + th, + td { + padding: 8px; + align-self: start; + align-items: start; + + &.nowrap { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 25%; + } + } + } + + & > :first-child { + margin-top: 0; + } } .information-board { @@ -418,7 +443,7 @@ $small-breakpoint: 960px; } &__call-to-action { - background: darken($ui-base-color, 4%); + background: $ui-base-color; border-radius: 4px; padding: 25px 40px; overflow: hidden; diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss index 089ae68c0..1d25d0129 100644 --- a/app/javascript/flavours/glitch/styles/admin.scss +++ b/app/javascript/flavours/glitch/styles/admin.scss @@ -5,21 +5,66 @@ $content-width: 840px; .admin-wrapper { display: flex; justify-content: center; - height: 100%; + width: 100%; + min-height: 100vh; .sidebar-wrapper { - flex: 1 1 $sidebar-width; - height: 100%; - background: $ui-base-color; - display: flex; - justify-content: flex-end; + min-height: 100vh; + overflow: hidden; + pointer-events: none; + flex: 1 1 auto; + + &__inner { + display: flex; + justify-content: flex-end; + background: $ui-base-color; + height: 100%; + } } .sidebar { width: $sidebar-width; - height: 100%; padding: 0; - overflow-y: auto; + pointer-events: auto; + + &__toggle { + display: none; + background: lighten($ui-base-color, 8%); + height: 48px; + + &__logo { + flex: 1 1 auto; + + a { + display: inline-block; + padding: 15px; + } + + svg { + fill: $primary-text-color; + height: 20px; + position: relative; + bottom: -2px; + } + } + + &__icon { + display: block; + color: $darker-text-color; + text-decoration: none; + flex: 0 0 auto; + font-size: 20px; + padding: 15px; + } + + a { + &:hover, + &:focus, + &:active { + background: lighten($ui-base-color, 12%); + } + } + } .logo { display: block; @@ -52,6 +97,9 @@ $content-width: 840px; transition: all 200ms linear; transition-property: color, background-color; border-radius: 4px 0 0 4px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; i.fa { margin-right: 5px; @@ -99,12 +147,30 @@ $content-width: 840px; } .content-wrapper { - flex: 2 1 $content-width; - overflow: auto; + box-sizing: border-box; + width: 100%; + max-width: $content-width; + flex: 1 1 auto; + } + + @media screen and (max-width: $content-width + $sidebar-width) { + .sidebar-wrapper--empty { + display: none; + } + + .sidebar-wrapper { + width: $sidebar-width; + flex: 0 0 auto; + } + } + + @media screen and (max-width: $no-columns-breakpoint) { + .sidebar-wrapper { + width: 100%; + } } .content { - max-width: $content-width; padding: 20px 15px; padding-top: 60px; padding-left: 25px; @@ -123,6 +189,12 @@ $content-width: 840px; padding-bottom: 40px; border-bottom: 1px solid lighten($ui-base-color, 8%); margin-bottom: 40px; + + @media screen and (max-width: $no-columns-breakpoint) { + border-bottom: 0; + padding-bottom: 0; + font-weight: 700; + } } h3 { @@ -147,7 +219,7 @@ $content-width: 840px; font-size: 16px; color: $secondary-text-color; line-height: 28px; - font-weight: 400; + font-weight: 500; } .fields-group h6 { @@ -176,7 +248,7 @@ $content-width: 840px; & > p { font-size: 14px; - line-height: 18px; + line-height: 21px; color: $secondary-text-color; margin-bottom: 20px; @@ -204,49 +276,86 @@ $content-width: 840px; border: 0; } } - - .muted-hint { - color: $darker-text-color; - - a { - color: $highlight-text-color; - } - } - - .positive-hint { - color: $valid-value-color; - font-weight: 500; - } - - .negative-hint { - color: $error-value-color; - font-weight: 500; - } - - .neutral-hint { - color: $dark-text-color; - font-weight: 500; - } } @media screen and (max-width: $no-columns-breakpoint) { display: block; - overflow-y: auto; - -webkit-overflow-scrolling: touch; - .sidebar-wrapper, - .content-wrapper { - flex: 0 0 auto; - height: auto; - overflow: initial; + .sidebar-wrapper { + min-height: 0; } .sidebar { width: 100%; padding: 0; height: auto; + + &__toggle { + display: flex; + } + + & > ul { + display: none; + } + + ul a, + ul ul a { + border-radius: 0; + border-bottom: 1px solid lighten($ui-base-color, 4%); + transition: none; + + &:hover { + transition: none; + } + } + + ul ul { + border-radius: 0; + } + + ul .simple-navigation-active-leaf a { + border-bottom-color: $ui-highlight-color; + } + } + } +} + +hr.spacer { + width: 100%; + border: 0; + margin: 20px 0; + height: 1px; +} + +body, +.admin-wrapper .content { + .muted-hint { + color: $darker-text-color; + + a { + color: $highlight-text-color; } } + + .positive-hint { + color: $valid-value-color; + font-weight: 500; + } + + .negative-hint { + color: $error-value-color; + font-weight: 500; + } + + .neutral-hint { + color: $dark-text-color; + font-weight: 500; + } + + .warning-hint { + color: $gold-star; + font-weight: 500; + } } .filters { @@ -255,10 +364,10 @@ $content-width: 840px; .filter-subset { flex: 0 0 auto; - margin: 0 40px 10px 0; + margin: 0 40px 20px 0; &:last-child { - margin-bottom: 20px; + margin-bottom: 30px; } ul { diff --git a/app/javascript/flavours/glitch/styles/basics.scss b/app/javascript/flavours/glitch/styles/basics.scss index 4de3955a6..64e543b78 100644 --- a/app/javascript/flavours/glitch/styles/basics.scss +++ b/app/javascript/flavours/glitch/styles/basics.scss @@ -74,9 +74,6 @@ body { &.admin { background: darken($ui-base-color, 4%); - position: fixed; - width: 100%; - height: 100%; padding: 0; } diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss index dc49e083c..5be4da48a 100644 --- a/app/javascript/flavours/glitch/styles/components/accounts.scss +++ b/app/javascript/flavours/glitch/styles/components/accounts.scss @@ -50,6 +50,8 @@ &-composite { @include avatar-radius; overflow: hidden; + position: relative; + cursor: default; & div { @include avatar-radius; @@ -57,6 +59,18 @@ position: relative; box-sizing: border-box; } + + &__label { + display: block; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: $primary-text-color; + text-shadow: 1px 1px 2px $base-shadow-color; + font-weight: 700; + font-size: 15px; + } } } @@ -245,6 +259,28 @@ .column-select { &__control { @include search-input(); + + &::placeholder { + color: lighten($darker-text-color, 4%); + } + + &::-moz-focus-inner { + border: 0; + } + + &::-moz-focus-inner, + &:focus, + &:active { + outline: 0 !important; + } + + &:focus { + background: lighten($ui-base-color, 4%); + } + + @media screen and (max-width: 600px) { + font-size: 16px; + } } &__placeholder { diff --git a/app/javascript/flavours/glitch/styles/components/composer.scss b/app/javascript/flavours/glitch/styles/components/composer.scss index 656615f4f..436974919 100644 --- a/app/javascript/flavours/glitch/styles/components/composer.scss +++ b/app/javascript/flavours/glitch/styles/components/composer.scss @@ -44,6 +44,10 @@ font-family: inherit; resize: vertical; + &::placeholder { + color: $dark-text-color; + } + &:focus { outline: 0 } @include single-column('screen and (max-width: 630px)') { font-size: 16px } } @@ -263,6 +267,10 @@ resize: none; scrollbar-color: initial; + &::placeholder { + color: $dark-text-color; + } + &::-webkit-scrollbar { all: unset; } diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss index 97c525565..848ef78df 100644 --- a/app/javascript/flavours/glitch/styles/components/index.scss +++ b/app/javascript/flavours/glitch/styles/components/index.scss @@ -1433,49 +1433,68 @@ height: 1em; } -.layout-toggle { +.conversation { display: flex; + border-bottom: 1px solid lighten($ui-base-color, 8%); padding: 5px; + padding-bottom: 0; - button { - box-sizing: border-box; - flex: 0 0 50%; - background: transparent; - padding: 5px; - border: 0; - position: relative; + &:focus { + background: lighten($ui-base-color, 2%); + outline: 0; + } - &:hover, - &:focus, - &:active { - svg path:first-child { - fill: lighten($ui-base-color, 16%); - } - } + &__avatar { + flex: 0 0 auto; + padding: 10px; + padding-top: 12px; } - svg { - width: 100%; - height: auto; + &__content { + flex: 1 1 auto; + padding: 10px 5px; + padding-right: 15px; + word-break: break-all; + overflow: hidden; - path:first-child { - fill: lighten($ui-base-color, 12%); + &__info { + overflow: hidden; + display: flex; + flex-direction: row-reverse; + justify-content: space-between; } - path:last-child { - fill: darken($ui-base-color, 14%); + &__relative-time { + font-size: 15px; + color: $darker-text-color; + padding-left: 15px; } - } - &__active { - color: $ui-highlight-color; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background: lighten($ui-base-color, 12%); - border-radius: 50%; - padding: 0.35rem; + &__names { + color: $darker-text-color; + font-size: 15px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 4px; + flex-basis: 170px; + flex-shrink: 1000; + + a { + color: $primary-text-color; + text-decoration: none; + + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } + } + + .status__content { + margin: 0; + } } } diff --git a/app/javascript/flavours/glitch/styles/components/modal.scss b/app/javascript/flavours/glitch/styles/components/modal.scss index ec32c9114..4f3e5babf 100644 --- a/app/javascript/flavours/glitch/styles/components/modal.scss +++ b/app/javascript/flavours/glitch/styles/components/modal.scss @@ -405,7 +405,8 @@ .confirmation-modal, .report-modal, .actions-modal, -.mute-modal { +.mute-modal, +.block-modal { background: lighten($ui-secondary-color, 8%); color: $inverted-text-color; border-radius: 8px; @@ -465,7 +466,8 @@ .boost-modal__action-bar, .favourite-modal__action-bar, .confirmation-modal__action-bar, -.mute-modal__action-bar { +.mute-modal__action-bar, +.block-modal__action-bar { display: flex; justify-content: space-between; background: $ui-secondary-color; @@ -495,11 +497,13 @@ font-size: 14px; } -.mute-modal { +.mute-modal, +.block-modal { line-height: 24px; } -.mute-modal .react-toggle { +.mute-modal .react-toggle, +.block-modal .react-toggle { vertical-align: middle; } @@ -712,27 +716,29 @@ } .confirmation-modal__action-bar, -.mute-modal__action-bar { - .confirmation-modal__secondary-button, - .confirmation-modal__cancel-button, - .mute-modal__cancel-button { - background-color: transparent; - color: $lighter-text-color; - font-size: 14px; - font-weight: 500; - - &:hover, - &:focus, - &:active { - color: darken($lighter-text-color, 4%); - } - } - +.mute-modal__action-bar, +.block-modal__action-bar { .confirmation-modal__secondary-button { flex-shrink: 1; } } +.confirmation-modal__secondary-button, +.confirmation-modal__cancel-button, +.mute-modal__cancel-button, +.block-modal__cancel-button { + background-color: transparent; + color: $lighter-text-color; + font-size: 14px; + font-weight: 500; + + &:hover, + &:focus, + &:active { + color: darken($lighter-text-color, 4%); + } +} + .confirmation-modal__do_not_ask_again { padding-left: 20px; padding-right: 20px; @@ -747,10 +753,10 @@ .confirmation-modal__container, .mute-modal__container, +.block-modal__container, .report-modal__target { padding: 30px; font-size: 16px; - text-align: center; strong { font-weight: 500; @@ -763,6 +769,31 @@ } } +.confirmation-modal__container, +.report-modal__target { + text-align: center; +} + +.block-modal, +.mute-modal { + &__explanation { + margin-top: 20px; + } + + .setting-toggle { + margin-top: 20px; + margin-bottom: 24px; + display: flex; + align-items: center; + + &__label { + color: $inverted-text-color; + margin: 0; + margin-left: 8px; + } + } +} + .report-modal__target { padding: 15px; diff --git a/app/javascript/flavours/glitch/styles/components/search.scss b/app/javascript/flavours/glitch/styles/components/search.scss index c3ea47eb0..30d69d05c 100644 --- a/app/javascript/flavours/glitch/styles/components/search.scss +++ b/app/javascript/flavours/glitch/styles/components/search.scss @@ -10,6 +10,28 @@ padding-right: 30px; line-height: 18px; font-size: 16px; + + &::placeholder { + color: lighten($darker-text-color, 4%); + } + + &::-moz-focus-inner { + border: 0; + } + + &::-moz-focus-inner, + &:focus, + &:active { + outline: 0 !important; + } + + &:focus { + background: lighten($ui-base-color, 4%); + } + + @media screen and (max-width: 600px) { + font-size: 16px; + } } .search__icon { diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss index 24ab71969..ae89ac0a8 100644 --- a/app/javascript/flavours/glitch/styles/components/status.scss +++ b/app/javascript/flavours/glitch/styles/components/status.scss @@ -673,6 +673,7 @@ a.status__display-name, } .muted { + .status__content, .status__content p, .status__content a, .status__content__text { diff --git a/app/javascript/flavours/glitch/styles/containers.scss b/app/javascript/flavours/glitch/styles/containers.scss index 17455ca58..6a48ff354 100644 --- a/app/javascript/flavours/glitch/styles/containers.scss +++ b/app/javascript/flavours/glitch/styles/containers.scss @@ -143,6 +143,63 @@ grid-row: 3; } + @media screen and (max-width: $no-gap-breakpoint) { + grid-gap: 0; + grid-template-columns: minmax(0, 100%); + + .column-0 { + grid-column: 1; + } + + .column-1 { + grid-column: 1; + grid-row: 3; + } + + .column-2 { + grid-column: 1; + grid-row: 2; + } + + .column-3 { + grid-column: 1; + grid-row: 4; + } + } +} + +.grid-4 { + display: grid; + grid-gap: 10px; + grid-template-columns: repeat(4, minmax(0, 1fr)); + grid-auto-columns: 25%; + grid-auto-rows: max-content; + + .column-0 { + grid-column: 1 / 5; + grid-row: 1; + } + + .column-1 { + grid-column: 1 / 4; + grid-row: 2; + } + + .column-2 { + grid-column: 4; + grid-row: 2; + } + + .column-3 { + grid-column: 2 / 5; + grid-row: 3; + } + + .column-4 { + grid-column: 1; + grid-row: 3; + } + .landing-page__call-to-action { min-height: 100%; } @@ -192,6 +249,11 @@ .column-3 { grid-column: 1; + grid-row: 5; + } + + .column-4 { + grid-column: 1; grid-row: 4; } } diff --git a/app/javascript/flavours/glitch/styles/forms.scss b/app/javascript/flavours/glitch/styles/forms.scss index dae29a003..747c5309d 100644 --- a/app/javascript/flavours/glitch/styles/forms.scss +++ b/app/javascript/flavours/glitch/styles/forms.scss @@ -245,6 +245,10 @@ code { &-6 { max-width: 50%; } + + .actions { + margin-top: 27px; + } } .fields-group:last-child, @@ -300,6 +304,13 @@ code { } } + .input.static .label_input__wrapper { + font-size: 16px; + padding: 10px; + border: 1px solid $dark-text-color; + border-radius: 4px; + } + input[type=text], input[type=number], input[type=email], @@ -318,6 +329,10 @@ code { border-radius: 4px; padding: 10px; + &::placeholder { + color: lighten($darker-text-color, 4%); + } + &:invalid { box-shadow: none; } diff --git a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss index 4c2b76a21..5c7fa87da 100644 --- a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss +++ b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss @@ -226,6 +226,7 @@ .boost-modal, .confirmation-modal, .mute-modal, +.block-modal, .report-modal, .embed-modal, .error-modal, @@ -236,6 +237,7 @@ .boost-modal__action-bar, .confirmation-modal__action-bar, .mute-modal__action-bar, +.block-modal__action-bar, .onboarding-modal__paginator, .error-modal__footer { background: darken($ui-base-color, 6%); diff --git a/app/javascript/flavours/glitch/styles/polls.scss b/app/javascript/flavours/glitch/styles/polls.scss index 06f60408d..95d8e510c 100644 --- a/app/javascript/flavours/glitch/styles/polls.scss +++ b/app/javascript/flavours/glitch/styles/polls.scss @@ -102,13 +102,19 @@ &__number { display: inline-block; - width: 36px; + width: 52px; font-weight: 700; padding: 0 10px; + padding-left: 8px; text-align: right; margin-top: auto; margin-bottom: auto; - flex: 0 0 36px; + flex: 0 0 52px; + } + + &__vote__mark { + float: left; + line-height: 18px; } &__footer { diff --git a/app/javascript/flavours/glitch/styles/tables.scss b/app/javascript/flavours/glitch/styles/tables.scss index 669f72787..b84f6a708 100644 --- a/app/javascript/flavours/glitch/styles/tables.scss +++ b/app/javascript/flavours/glitch/styles/tables.scss @@ -288,70 +288,3 @@ a.table-action-link { } } } - -.blocks-table { - width: 100%; - max-width: 100%; - border-spacing: 0; - border-collapse: collapse; - table-layout: fixed; - border: 1px solid darken($ui-base-color, 8%); - - thead { - border: 1px solid darken($ui-base-color, 8%); - background: darken($ui-base-color, 4%); - font-weight: 500; - - th.severity-column { - width: 120px; - } - - th.button-column { - width: 23px; - } - } - - tbody > tr { - border: 1px solid darken($ui-base-color, 8%); - border-bottom: 0; - background: darken($ui-base-color, 4%); - - &:hover { - background: darken($ui-base-color, 2%); - } - - &.even { - background: $ui-base-color; - - &:hover { - background: lighten($ui-base-color, 2%); - } - } - - &.rationale { - background: lighten($ui-base-color, 4%); - border-top: 0; - - &:hover { - background: lighten($ui-base-color, 6%); - } - - &.hidden { - display: none; - } - } - - td:first-child { - overflow: hidden; - text-overflow: ellipsis; - } - } - - th, - td { - padding: 8px; - line-height: 18px; - vertical-align: top; - text-align: left; - } -} diff --git a/app/javascript/flavours/glitch/styles/widgets.scss b/app/javascript/flavours/glitch/styles/widgets.scss index 9e9c6eb58..a6f7fc0be 100644 --- a/app/javascript/flavours/glitch/styles/widgets.scss +++ b/app/javascript/flavours/glitch/styles/widgets.scss @@ -128,41 +128,43 @@ margin-bottom: 10px; } -.contact-widget, -.landing-page__information.contact-widget { - box-sizing: border-box; - padding: 20px; - min-height: 100%; - border-radius: 4px; - background: $ui-base-color; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); -} - .contact-widget { + min-height: 100%; font-size: 15px; color: $darker-text-color; line-height: 20px; word-wrap: break-word; font-weight: 400; + padding: 0; - strong { - font-weight: 500; + h4 { + padding: 10px; + text-transform: uppercase; + font-weight: 700; + font-size: 13px; + color: $darker-text-color; } - p { - margin-bottom: 10px; - - &:last-child { - margin-bottom: 0; - } + .account { + border-bottom: 0; + padding: 10px 0; + padding-top: 5px; } - &__mail { - margin-top: 10px; + & > a { + display: inline-block; + padding: 10px; + padding-top: 0; + color: $darker-text-color; + text-decoration: none; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; - a { - color: $primary-text-color; - text-decoration: none; + &:hover, + &:focus, + &:active { + text-decoration: underline; } } } @@ -557,3 +559,38 @@ $fluid-breakpoint: $maximum-width + 20px; } } } + +.table-of-contents { + background: darken($ui-base-color, 4%); + min-height: 100%; + font-size: 14px; + border-radius: 4px; + + li a { + display: block; + font-weight: 500; + padding: 15px; + overflow: hidden; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-decoration: none; + color: $primary-text-color; + border-bottom: 1px solid lighten($ui-base-color, 4%); + + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } + + li:last-child a { + border-bottom: 0; + } + + li ul { + padding-left: 20px; + border-bottom: 1px solid lighten($ui-base-color, 4%); + } +} diff --git a/app/javascript/flavours/glitch/theme.yml b/app/javascript/flavours/glitch/theme.yml index 06e26ade2..0fd627f19 100644 --- a/app/javascript/flavours/glitch/theme.yml +++ b/app/javascript/flavours/glitch/theme.yml @@ -18,7 +18,7 @@ pack: mailer: modal: public: packs/public.js - settings: + settings: packs/settings.js share: packs/share.js # (OPTIONAL) The directory which contains localization files for diff --git a/app/javascript/flavours/glitch/util/async-components.js b/app/javascript/flavours/glitch/util/async-components.js index 6c0acdb27..26255bbb7 100644 --- a/app/javascript/flavours/glitch/util/async-components.js +++ b/app/javascript/flavours/glitch/util/async-components.js @@ -122,6 +122,10 @@ export function MuteModal () { return import(/* webpackChunkName: "flavours/glitch/async/mute_modal" */'flavours/glitch/features/ui/components/mute_modal'); } +export function BlockModal () { + return import(/* webpackChunkName: "flavours/glitch/async/block_modal" */'flavours/glitch/features/ui/components/block_modal'); +} + export function ReportModal () { return import(/* webpackChunkName: "flavours/glitch/async/report_modal" */'flavours/glitch/features/ui/components/report_modal'); } diff --git a/app/javascript/flavours/glitch/util/emoji/index.js b/app/javascript/flavours/glitch/util/emoji/index.js index b2d13cc95..e1a244127 100644 --- a/app/javascript/flavours/glitch/util/emoji/index.js +++ b/app/javascript/flavours/glitch/util/emoji/index.js @@ -100,4 +100,4 @@ export const buildCustomEmojis = (customEmojis) => { return emojis; }; -export const categoriesFromEmojis = customEmojis => customEmojis.reduce((set, emoji) => set.add(emoji.get('category') ? `custom-${emoji.get('category')}` : 'custom'), new Set()); +export const categoriesFromEmojis = customEmojis => customEmojis.reduce((set, emoji) => set.add(emoji.get('category') ? `custom-${emoji.get('category')}` : 'custom'), new Set(['custom'])); |