Report modal style fix, chat updates, statusserializer revert, display name truncation
This commit is contained in:
mgabdev 2020-12-20 12:27:24 -05:00
parent 7ec426e3d8
commit 67eb9d5890
49 changed files with 369 additions and 158 deletions

View File

@ -9,7 +9,6 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
@statuses = load_statuses
render json: @statuses,
each_serializer: REST::StatusSerializer,
account_id: params[:account_id],
relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
end

View File

@ -25,6 +25,7 @@ class Api::V1::ChatConversationController < Api::BaseController
# check if chat blocked
# check if allow anyone to message then create with approved:true
# unique account id, participants
chat_conversation_account = find_or_create_conversation
render json: chat_conversation_account, each_serializer: REST::ChatConversationAccountSerializer
end
@ -67,7 +68,6 @@ class Api::V1::ChatConversationController < Api::BaseController
else
@expires_at = nil
end
puts "tilly @expires_at: " + @expires_at.inspect
@chat_conversation_account.chat_message_expiration_policy = @expires_at
@chat_conversation_account.save!
render json: @chat_conversation_account, serializer: REST::ChatConversationAccountSerializer

View File

@ -13,7 +13,7 @@ class Api::V1::Timelines::GroupController < Api::BaseController
if current_user
render json: @statuses,
each_serializer: REST::StatusSerializer,
group_id: params[:id],
group_id: params[:id], # : todo :
relationships: StatusRelationshipsPresenter.new(@statuses, current_user.account_id, group_id: @group.id)
else
render json: @statuses, each_serializer: REST::StatusSerializer

View File

@ -10,7 +10,7 @@ class Api::V1::Timelines::PreviewCardController < Api::BaseController
def show
render json: @statuses,
each_serializer: REST::StatusSerializer,
preview_card_id: params[:id],
preview_card_id: params[:id], # : todo :
relationships: StatusRelationshipsPresenter.new(@statuses, current_user.account_id)
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class Api::Web::ChatSettingsController < Api::Web::BaseController
before_action :require_user!
def update
setting.data = params[:data]
setting.save!
# todo validate all data objects
render_empty_success
end
private
def setting
@_setting ||= ::Web::Setting.where(user: current_user).first_or_initialize(user: current_user)
end
end

View File

@ -162,7 +162,6 @@ export const followAccount = (id, reblogs = true) => (dispatch, getState) => {
dispatch(followAccountRequest(id, locked))
api(getState).post(`/api/v1/accounts/${id}/follow`, { reblogs }).then((response) => {
console.log("response:", response)
dispatch(followAccountSuccess(response.data, alreadyFollowing))
}).catch((error) => {
dispatch(followAccountFail(error, locked))

View File

@ -258,7 +258,6 @@ export const deleteChatConversation = (chatConversationId) => (dispatch, getStat
dispatch(deleteChatConversationRequest(conversationId))
api(getState).delete(`/api/v1/chat_conversation/${chatConversationId}`).then((response) => {
console.log("chat_conversations delete response: ", response)
dispatch(deleteChatConversationSuccess())
}).catch((error) => {
dispatch(deleteChatConversationFail(error))

View File

@ -62,11 +62,9 @@ const sendChatMessageFail = (error) => ({
export const manageIncomingChatMessage = (chatMessage) => (dispatch, getState) => {
if (!chatMessage) return
console.log("chatMessage:", chatMessage)
dispatch(sendChatMessageSuccess(chatMessage))
const isOnline = getState().getIn(['chat_conversation_messages', chatMessage.chat_conversation_id, 'online'])
console.log("isOnline: ", isOnline)
// : todo :
// Check if is online for conversation, if not increase total/convo unread count

View File

@ -0,0 +1,35 @@
import api from '../api'
import debounce from 'lodash.debounce'
import { me } from '../initial_state'
export const CHAT_SETTING_CHANGE = 'CHAT_SETTING_CHANGE'
export const CHAT_SETTING_SAVE = 'CHAT_SETTING_SAVE'
export const changeChatSetting = (path, value) => (dispatch) => {
dispatch({
type: CHAT_SETTING_CHANGE,
path,
value,
})
dispatch(saveChatSettings())
}
/**
*
*/
export const saveChatSettings = () => (dispatch, getState) => {
debouncedChatSettingsSave(dispatch, getState)
}
const debouncedChatSettingsSave = debounce((dispatch, getState) => {
if (!me) return
if (getState().getIn(['chat_settings', 'saved'])) return
const data = getState().get('chat_settings').filter((_, path) => path !== 'saved').toJS()
api().put('/api/web/chat_settings', { data })
.then(() => dispatch({ type: CHAT_SETTING_SAVE }))
.catch(() => { /* */ })
}, 350, { trailing: true })

View File

@ -151,7 +151,6 @@ export const expandTimeline = (timelineId, path, params = {}, done = noop) => (d
dispatch(expandTimelineRequest(timelineId, isLoadingMore))
api(getState).get(path, { params }).then((response) => {
console.log("response:", response)
const next = getLinks(response).refs.find(link => link.rel === 'next')
dispatch(importFetchedStatuses(response.data))
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206, isLoadingRecent, isLoadingMore))

View File

@ -106,8 +106,7 @@ class DisplayName extends ImmutablePureComponent {
const usernameClasses = CX({
text: 1,
displayFlex: 1,
flexNormal: 1,
flexShrink1: 1,
flexShrink0: 1,
overflowWrapBreakWord: 1,
textOverflowEllipsis: 1,
cSecondary: 1,
@ -131,17 +130,10 @@ class DisplayName extends ImmutablePureComponent {
const isFollowedBy = (me !== accountId && account.getIn(['relationship', 'followed_by']))
if (isFollowedBy) {
relationshipLabel = 'Follows you'//intl.formatMessage(messages.accountFollowsYou)
relationshipLabel = 'Follows you'
}
}
// {
// /* : todo : audio-mute, bot
// account.getIn(['relationship', 'muting'])
// */
// }
// bot: { id: 'account.badges.bot', defaultMessage: 'Bot' },
return (
<div
className={containerClassName}
@ -149,7 +141,7 @@ class DisplayName extends ImmutablePureComponent {
onMouseLeave={noHover ? undefined : this.handleMouseLeave}
ref={this.setRef}
>
<span className={[_s.d, _s.flexRow, _s.aiCenter, _s.maxW100PC].join(' ')}>
<span className={[_s.d, _s.flexRow, _s.aiCenter, _s.maxW100PC, _s.flexShrink1, _s.overflowHidden].join(' ')}>
<bdi className={[_s.text, _s.whiteSpaceNoWrap, _s.textOverflowEllipsis].join(' ')}>
<strong
className={displayNameClasses}

View File

@ -55,6 +55,7 @@ class ReportModal extends ImmutablePureComponent {
return (
<ModalLayout
width={760}
noPadding
title={intl.formatMessage(messages.target, {
target: account.get('acct')
@ -67,7 +68,7 @@ class ReportModal extends ImmutablePureComponent {
classNamesSmall={[_s.d, _s.flexColumnReverse].join(' ')}
>
<ResponsiveClassesComponent
classNames={[_s.d, _s.w50PC, _s.py10, _s.px15, _s.borderRight1PX, _s.borderColorSecondary].join(' ')}
classNames={[_s.d, _s.maxW320PX, _s.py10, _s.px15, _s.borderRight1PX, _s.borderColorSecondary].join(' ')}
classNamesSmall={[_s.d, _s.w100PC, _s.py10, _s.px15, _s.borderTop1PX, _s.borderColorSecondary].join(' ')}
>
<Text color='secondary' size='small'>
@ -95,13 +96,13 @@ class ReportModal extends ImmutablePureComponent {
</ResponsiveClassesComponent>
<ResponsiveClassesComponent
classNames={[_s.d, _s.w50PC, _s.maxH80VH].join(' ')}
classNames={[_s.d, _s.flexNormal, _s.maxH80VH].join(' ')}
classNamesSmall={[_s.d, _s.w100PC, _s.h260PX].join(' ')}
>
<div className={[_s.d, _s.h100PC, _s.overflowYScroll, _s.pr15, _s.py10].join(' ')}>
<div className={[_s.d, _s.h100PC, _s.overflowYScroll].join(' ')}>
{
statusIds.map(statusId => (
<StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />
statusIds.map((statusId) => (
<StatusCheckBox id={statusId} key={`reporting-${statusId}`} disabled={isSubmitting} />
))
}
</div>

View File

@ -33,6 +33,9 @@ class ChatNavigationBar extends React.PureComponent {
const otherAccounts = chatConversation ? chatConversation.get('other_accounts') : null
const nameHTML = !!otherAccounts ? otherAccounts.get(0).get('display_name_html') : ''
// : todo :
// fix padding on mobile device
return (
<div className={[_s.d, _s.z4, _s.h53PX, _s.w100PC].join(' ')}>
<div className={[_s.d, _s.h53PX, _s.bgNavigation, _s.aiCenter, _s.z3, _s.top0, _s.right0, _s.left0, _s.posFixed].join(' ')} >
@ -46,23 +49,29 @@ class ChatNavigationBar extends React.PureComponent {
iconClassName={[_s.mr5, _s.fillNavigation].join(' ')}
/>
<div className={[_s.d, _s.h53PX, _s.flexRow, _s.jcCenter, _s.aiCenter, _s.mrAuto].join(' ')}>
<AvatarGroup accounts={otherAccounts} size={35} noHover />
<Heading size='h1'>
<div className={[_s.dangerousContent, _s.colorNavigation, _s.pl10, _s.fs19PX].join(' ')} dangerouslySetInnerHTML={{ __html: nameHTML }} />
</Heading>
<div className={[_s.d, _s.minH53PX, _s.flexRow, _s.aiCenter, _s.mrAuto, _s.flex1, _s.overflowHidden].join(' ')}>
<div className={[_s.d, _s.minH53PX, _s.jcCenter, _s.w100PC, _s.flexShrink1].join(' ')}>
<Heading size='h1'>
<div className={[_s.d, _s.w100PC].join(' ')}>
<span
className={[_s.w100PC, _s.textOverflowEllipsis, _s.colorNavigation].join(' ')}
dangerouslySetInnerHTML={{ __html: nameHTML }}
/>
</div>
</Heading>
</div>
</div>
<div className={[_s.d, _s.h53PX, _s.mlAuto, _s.aiCenter, _s.jcCenter, _s.mr15].join(' ')}>
<Button
isNarrow
backgroundColor='tertiary'
backgroundColor='none'
color='primary'
onClick={this.handleOnOpenChatConversationOptionsPopover}
className={[_s.px5].join(' ')}
icon='ellipsis'
iconClassName={[_s.cSecondary, _s.px5, _s.py5].join(' ')}
iconSize='15px'
iconClassName={[_s.colorNavigation, _s.px5, _s.py5].join(' ')}
iconSize='26px'
/>
</div>

View File

@ -24,6 +24,9 @@ class ProfileNavigationBar extends React.PureComponent {
render() {
const { titleHTML } = this.props
// : todo :
// fix padding on mobile device
return (
<div className={[_s.d, _s.z4, _s.minH53PX, _s.w100PC].join(' ')}>
<div className={[_s.d, _s.minH53PX, _s.bgNavigation, _s.aiCenter, _s.z3, _s.top0, _s.right0, _s.left0, _s.posFixed].join(' ')} >
@ -36,12 +39,17 @@ class ProfileNavigationBar extends React.PureComponent {
iconClassName={[_s.mr5, _s.fillNavigation].join(' ')}
/>
<div className={[_s.d, _s.minH53PX, _s.jcCenter, _s.mrAuto].join(' ')}>
<Heading size='h1'>
<span className={[_s.textOverflowEllipsis, _s.colorNavigation].join(' ')}>
<div dangerouslySetInnerHTML={{ __html: titleHTML }} />
</span>
</Heading>
<div className={[_s.d, _s.minH53PX, _s.mrAuto, _s.flex1, _s.overflowHidden].join(' ')}>
<div className={[_s.d, _s.minH53PX, _s.jcCenter, _s.w100PC].join(' ')}>
<Heading size='h1'>
<div className={[_s.d, _s.w100PC].join(' ')}>
<span
className={[_s.w100PC, _s.textOverflowEllipsis, _s.colorNavigation].join(' ')}
dangerouslySetInnerHTML={{ __html: titleHTML }}
/>
</div>
</Heading>
</div>
</div>
<div className={[_s.d, _s.minH53PX, _s.jcCenter, _s.mr15].join(' ')}>

View File

@ -35,7 +35,6 @@ class ChatConversationExpirationOptionsPopover extends React.PureComponent {
isXS,
} = this.props
console.log("expiresAtValue:", expiresAtValue)
if (!chatConversationId) return <div/>
const listItems = [

View File

@ -6,6 +6,7 @@ import { connect } from 'react-redux'
import { closePopover } from '../../actions/popover'
import { openModal } from '../../actions/modal'
import { hideChatConversation } from '../../actions/chat_conversations'
import { setChatConversationSelected } from '../../actions/chats'
import {
muteChatConversation,
unmuteChatConversation,
@ -107,6 +108,7 @@ const mapDispatchToProps = (dispatch, { chatConversationId }) => ({
},
onHide() {
dispatch(hideChatConversation(chatConversationId))
dispatch(setChatConversationSelected(null))
},
onMute() {
dispatch(muteChatConversation(chatConversationId))

View File

@ -0,0 +1,54 @@
import React from 'react'
import PropTypes from 'prop-types'
import PopoverLayout from './popover_layout'
import List from '../list'
class ChatSettingsPopover extends React.PureComponent {
handleOnClosePopover = () => {
this.props.onClose()
}
render() {
const { intl, isXS } = this.props
return (
<PopoverLayout width={240} isXS={isXS}>
<List
size={isXS ? 'large' : 'small'}
scrollKey='profile_options'
items={[
{
title: 'Preferences',
to: '/messages/settings',
onClick: () => this.handleOnClosePopover(),
},
{
title: 'Message Requests',
to: '/messages/requests',
onClick: () => this.handleOnClosePopover(),
},
{
title: 'Blocked Chat Messengers',
to: '/messages/blocks',
onClick: () => this.handleOnClosePopover(),
},
{
title: 'Muted Conversations',
to: '/messages/muted_conversations',
onClick: () => this.handleOnClosePopover(),
},
]}
/>
</PopoverLayout>
)
}
}
ChatSettingsPopover.propTypes = {
onClose: PropTypes.func.isRequired,
isXS: PropTypes.bool,
}
export default ChatSettingsPopover

View File

@ -70,7 +70,7 @@ class DatePickerPopover extends React.PureComponent {
<div className={[_s.d, _s.aiCenter, _s.flexRow, _s.px10, _s.py10, _s.borderTop1PX, _s.borderColorSecondary].join(' ')}>
<Text size='extraSmall' color='secondary'>
<FormattedMessage id='scheduled_for_datetime' defaultMessage='Scheduled for {datetime}' values={{
datetime: moment.utc(date).format('lll'),
datetime: moment(date).format('lll'),
}}/>
</Text>
<div className={_s.mlAuto}>

View File

@ -3,6 +3,7 @@ import {
POPOVER_CHAT_CONVERSATION_EXPIRATION_OPTIONS,
POPOVER_CHAT_CONVERSATION_OPTIONS,
POPOVER_CHAT_MESSAGE_OPTIONS,
POPOVER_CHAT_SETTINGS,
POPOVER_COMMENT_SORTING_OPTIONS,
POPOVER_COMPOSE_POST_DESTINATION,
POPOVER_DATE_PICKER,
@ -27,6 +28,7 @@ import {
ChatConversationExpirationOptionsPopover,
ChatConversationOptionsPopover,
ChatMessageOptionsPopover,
ChatSettingsPopover,
CommentSortingOptionsPopover,
ComposePostDesinationPopover,
DatePickerPopover,
@ -65,6 +67,7 @@ const POPOVER_COMPONENTS = {
[POPOVER_CHAT_CONVERSATION_EXPIRATION_OPTIONS]: ChatConversationExpirationOptionsPopover,
[POPOVER_CHAT_CONVERSATION_OPTIONS]: ChatConversationOptionsPopover,
[POPOVER_CHAT_MESSAGE_OPTIONS]: ChatMessageOptionsPopover,
[POPOVER_CHAT_SETTINGS]: ChatSettingsPopover,
[POPOVER_COMMENT_SORTING_OPTIONS]: CommentSortingOptionsPopover,
[POPOVER_COMPOSE_POST_DESTINATION]: ComposePostDesinationPopover,
[POPOVER_DATE_PICKER]: DatePickerPopover,
@ -161,7 +164,7 @@ class PopoverRoot extends React.PureComponent {
renderDelay={150}
>
{
(Component) => <Component innerRef={this.setRef} isXS={isXS} {...props} />
(Component) => <Component innerRef={this.setRef} isXS={isXS} onClose={onClose} {...props} />
}
</Bundle>
}

View File

@ -310,12 +310,15 @@ class Status extends ImmutablePureComponent {
commentSortingType,
onOpenProModal,
isDeckConnected,
statusId,
} = this.props
// const { height } = this.state
let { status } = this.props
if (!status) return null
if (!status) {
return null
}
if (isComment && !ancestorStatus && !isChild) {
// Wait to load...
@ -331,7 +334,7 @@ class Status extends ImmutablePureComponent {
if (ancestorStatus) {
status = ancestorStatus
} else {
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
if (status.get('reblog', null) !== null) {
rebloggedByText = intl.formatMessage(
{ id: 'status.reposted_by', defaultMessage: '{name} reposted' },
{ name: status.getIn(['account', 'acct']) }

View File

@ -46,17 +46,27 @@ class StatusCheckBox extends ImmutablePureComponent {
} else {
media = (
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
{Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} onOpenMedia={noop} />}
{Component => (
<Component
media={status.get('media_attachments')}
sensitive={status.get('sensitive')}
onOpenMedia={noop}
width={239}
height={110}
/>
)}
</Bundle>
);
}
}
return (
<div className={[_s.d, _s.flexRow].join(' ')}>
<div className={[_s.d].join(' ')}>
<div className={[_s.d, _s.flexRow, _s.flexWrap, _s.borderBottom1PX, _s.borderColorSecondary, _s.aiStart, _s.mb5, _s.pr15, _s.pt5, _s.w100PC].join(' ')}>
<div className={[_s.d, _s.pt5, _s.maxW100PC].join(' ')}>
<StatusContent status={status} />
{media}
<div className={_s.pl15}>
{media}
</div>
</div>
<div className={[_s.d, _s.mlAuto].join(' ')}>

View File

@ -79,7 +79,8 @@ class StatusHeader extends ImmutablePureComponent {
const textContainerClasses = CX({
d: 1,
aiStart: 1,
flexGrow1 :1,
flex1: 1,
overflowHidden: 1,
mt5: !isCompact,
})
@ -100,13 +101,15 @@ class StatusHeader extends ImmutablePureComponent {
<div className={textContainerClasses}>
<div className={[_s.d, _s.flexRow, _s.w100PC, _s.aiStart].join(' ')}>
<div className={[_s.d, _s.flexRow, _s.w100PC, _s.aiStart, _s.overflowHidden].join(' ')}>
<NavLink
className={[_s.d, _s.flexRow, _s.aiStart, _s.noUnderline].join(' ')}
className={[_s.d, _s.flexRow, _s.aiStart, _s.noUnderline, _s.flex1, _s.maxW100PC30PX].join(' ')}
to={`/${status.getIn(['account', 'acct'])}`}
title={status.getIn(['account', 'acct'])}
>
<DisplayName account={status.get('account')} noRelationship />
<div className={[_s.d, _s.w100PC, _s.overflowHidden].join(' ')}>
<DisplayName account={status.get('account')} noRelationship />
</div>
</NavLink>
{
@ -118,7 +121,7 @@ class StatusHeader extends ImmutablePureComponent {
icon='ellipsis'
iconSize='20px'
iconClassName={_s.cSecondary}
className={_s.mlAuto}
className={[_s.mlAuto].join(' ')}
onClick={this.handleOpenStatusOptionsPopover}
buttonRef={this.setStatusOptionsButton}
/>

View File

@ -44,7 +44,7 @@ class UserSuggestionsInjection extends ImmutablePureComponent {
id={injectionId}
title={title}
buttonLink='/suggestions'
buttonTitle='See more reccomendations'
buttonTitle='See more recommendations'
isXS={isXS}
>
{

View File

@ -27,6 +27,7 @@ export const PLACEHOLDER_MISSING_HEADER_SRC = '/original/missing.png'
export const POPOVER_CHAT_CONVERSATION_OPTIONS = 'CHAT_CONVERSATION_OPTIONS'
export const POPOVER_CHAT_MESSAGE_OPTIONS = 'CHAT_MESSAGE_OPTIONS'
export const POPOVER_CHAT_CONVERSATION_EXPIRATION_OPTIONS = 'CHAT_CONVERSATION_EXPIRATION_OPTIONS'
export const POPOVER_CHAT_SETTINGS = 'CHAT_SETTINGS'
export const POPOVER_COMMENT_SORTING_OPTIONS = 'COMMENT_SORTING_OPTIONS'
export const POPOVER_COMPOSE_POST_DESTINATION = 'COMPOSE_POST_DESTINATION'
export const POPOVER_DATE_PICKER = 'DATE_PICKER'

View File

@ -37,31 +37,33 @@ class ChatConversationCreate extends React.PureComponent {
return (
<Form>
<div className={[_s.d, _s.px15, _s.pt10].join(' ')}>
<Input
title='Search for a user'
value={query}
onChange={this.onChange}
/>
</div>
<div className={[_s.d, _s.bgPrimary, _s.w100PC, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
<div className={[_s.d, _s.px15, _s.pt10].join(' ')}>
<Input
title='Search for a user'
value={query}
onChange={this.onChange}
/>
</div>
<div className={[_s.d, _s.pt10].join(' ')}>
<div className={[_s.d].join(' ')}>
<Text weight='bold' size='small' color='secondary' className={[_s.d, _s.px15, _s.ml15, _s.mt5, _s.mb15].join(' ')}>
Search results ({suggestionsIds.size})
</Text>
{
suggestionsIds &&
suggestionsIds.map((accountId) => (
<Account
compact
key={`chat-conversation-account-create-${accountId}`}
id={accountId}
onActionClick={() => this.handleOnCreateChatConversation(accountId)}
actionIcon='add'
/>
))
}
<div className={[_s.d, _s.pt10].join(' ')}>
<div className={[_s.d].join(' ')}>
<Text weight='bold' size='small' color='secondary' className={[_s.d, _s.px15, _s.ml15, _s.mt5, _s.mb15].join(' ')}>
Search results ({suggestionsIds.size})
</Text>
{
suggestionsIds &&
suggestionsIds.map((accountId) => (
<Account
compact
key={`chat-conversation-account-create-${accountId}`}
id={accountId}
onActionClick={() => this.handleOnCreateChatConversation(accountId)}
actionIcon='add'
/>
))
}
</div>
</div>
</div>
</Form>

View File

@ -0,0 +1,21 @@
import React from 'react'
import PropTypes from 'prop-types'
import BlockHeading from '../components/block_heading'
import ChatConversationsList from './messages/components/chat_conversations_list'
class ChatConversationMutes extends React.PureComponent {
render() {
return (
<div className={[_s.d, _s.w100PC, _s.boxShadowNone].join(' ')}>
<div className={[_s.d, _s.h60PX, _s.w100PC, _s.px10, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
<BlockHeading title={'Muted Chat Conversations'} />
</div>
<ChatConversationsList source='mutes' />
</div>
)
}
}
export default ChatConversationMutes

View File

@ -62,23 +62,20 @@ class GroupMembers extends ImmutablePureComponent {
<Block>
<BlockHeading title='Group Members' />
{
/* : todo :
<div className={[_s.d, _s.jcCenter, _s.px15, _s.my5, _s.borderBottom1PX, _s.borderColorSecondary, _s.pt5, _s.pb15].join(' ')}>
<Input
id='group-member-search'
placeholder='Search group members'
prependIcon='search'
// value={value}
onKeyUp={this.handleKeyUp}
onChange={this.handleOnChange}
onFocus={this.handleOnFocus}
onBlur={this.handleOnBlur}
autoComplete='off'
/>
</div>
*/
}
<div className={[_s.d, _s.jcCenter, _s.px15, _s.my5, _s.borderBottom1PX, _s.borderColorSecondary, _s.pt5, _s.pb15].join(' ')}>
<Input
id='group-member-search'
placeholder='Search group members'
prependIcon='search'
// value={value}
onKeyUp={this.handleKeyUp}
onChange={this.handleOnChange}
onFocus={this.handleOnFocus}
onBlur={this.handleOnBlur}
autoComplete='off'
/>
</div>
<div className={[_s.d].join(' ')}>
<ScrollableList
scrollKey='group-members'

View File

@ -14,6 +14,7 @@ import {
import { FormattedMessage } from 'react-intl'
import Account from '../components/account'
import Block from '../components/block'
import Input from '../components/input'
import BlockHeading from '../components/block_heading'
import ColumnIndicator from '../components/column_indicator'
import ScrollableList from '../components/scrollable_list'
@ -49,6 +50,19 @@ class GroupRemovedAccounts extends ImmutablePureComponent {
return (
<Block>
<BlockHeading title='Removed Accounts' />
<div className={[_s.d, _s.jcCenter, _s.px15, _s.my5, _s.borderBottom1PX, _s.borderColorSecondary, _s.pt5, _s.pb15].join(' ')}>
<Input
id='group-member-search'
placeholder='Search removed group members'
prependIcon='search'
// value={value}
onKeyUp={this.handleKeyUp}
onChange={this.handleOnChange}
onFocus={this.handleOnFocus}
onBlur={this.handleOnBlur}
autoComplete='off'
/>
</div>
<ScrollableList
scrollKey='removed_accounts'
hasMore={hasMore}

View File

@ -27,6 +27,8 @@ class ChatMessagesComposeForm extends React.PureComponent {
handleOnSendChatMessage = () => {
this.props.onSendChatMessage(this.state.value, this.props.chatConversationId)
document.querySelector('#gabsocial').focus()
this.onBlur()
this.setState({ value: '' })
}
@ -93,7 +95,7 @@ class ChatMessagesComposeForm extends React.PureComponent {
expiresAtValue,
chatConversationId,
} = this.props
const { value } = this.state
const { focused, value } = this.state
const disabled = false
const textareaClasses = CX({
@ -141,6 +143,7 @@ class ChatMessagesComposeForm extends React.PureComponent {
onFocus={this.onFocus}
onBlur={this.onBlur}
onKeyDown={this.onKeyDown}
focused={focused}
aria-autocomplete='list'
maxLength={1600}
/>
@ -170,11 +173,11 @@ class ChatMessagesComposeForm extends React.PureComponent {
if (isXS) {
return (
<div className={[_s.d, _s.z4, _s.minH58PX, _s.w100PC].join(' ')}>
<div className={[_s.d, _s.z4, _s.minH58PX, _s.w100PC, _s.mtAuto].join(' ')}>
<div className={[_s.d, _s.minH58PX, _s.bgPrimary, _s.aiCenter, _s.z3, _s.bottom0, _s.right0, _s.left0, _s.posFixed].join(' ')} >
<div className={[_s.d, _s.w100PC, _s.pb5, _s.px15, _s.aiCenter, _s.jcCenter, _s.saveAreaInsetPB, _s.saveAreaInsetPL, _s.saveAreaInsetPR, _s.w100PC].join(' ')}>
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.minH58PX, _s.w100PC, _s.borderTop1PX, _s.borderColorSecondary, _s.px10].join(' ')}>
<div className={[_s.d, _s.flexRow, _s.radiusRounded, _s.border1PX, _s.borderColorSecondary, _s.overflowHidden].join(' ')}>
<div className={[_s.d, _s.flexRow, _s.flexGrow1, _s.radiusRounded, _s.border1PX, _s.borderColorSecondary, _s.overflowHidden].join(' ')}>
<div className={_s.d}>
{expiresBtn}
</div>
@ -182,7 +185,7 @@ class ChatMessagesComposeForm extends React.PureComponent {
{textarea}
</div>
</div>
<div className={[_s.d, _s.h100PC, _s.aiCenter, _s.jcCenter].join(' ')}>
<div className={[_s.d, _s.pl10, _s.h100PC, _s.aiCenter, _s.jcCenter].join(' ')}>
{button}
</div>
</div>

View File

@ -39,6 +39,7 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
componentDidMount () {
const { chatConversationId } = this.props
this.props.onExpandChatMessages(chatConversationId)
this.scrollToBottom()
}
componentWillUnmount() {
@ -63,10 +64,10 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
}
if (prevProps.chatMessageIds.size === 0 && this.props.chatMessageIds.size > 0 && this.scrollContainerRef) {
this.scrollContainerRef.scrollTop = this.scrollContainerRef.scrollHeight
this.scrollToBottom()
this.props.onReadChatConversation(this.props.chatConversationId)
} else if (prevProps.chatMessageIds.size < this.props.chatMessageIds.size && this.scrollContainerRef) {
this.scrollContainerRef.scrollTop = this.scrollContainerRef.scrollHeight
// this.setScrollTop(this.scrollContainerRef.scrollHeight)
}
}
@ -114,10 +115,15 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
if (!this.scrollContainerRef) return
if (this.scrollContainerRef.scrollTop !== newScrollTop) {
this.lastScrollWasSynthetic = true
console.log("newScrollTop:", newScrollTop)
this.scrollContainerRef.scrollTop = newScrollTop
}
}
scrollToBottom = () => {
this.messagesEnd.scrollIntoView({ behavior: 'smooth' });
}
_selectChild(index, align_top) {
const container = this.node.node
const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`)
@ -163,6 +169,7 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
handleWheel = throttle(() => {
this.scrollToTopOnMouseIdle = false
this.handleScroll()
}, 150, {
trailing: true,
})
@ -190,7 +197,7 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
handleMouseIdle = () => {
if (this.scrollToTopOnMouseIdle) {
this.setScrollTop(0)
this.setScrollTop(this.scrollContainerRef.scrollHeight)
}
this.mouseMovedRecently = false
@ -320,6 +327,10 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
</IntersectionObserverArticle>
))
}
<div
style={{ float: 'left', clear: 'both' }}
ref={(el) => { this.messagesEnd = el }}
/>
</div>
</div>
</div>

View File

@ -38,7 +38,7 @@ class MessagesSettings extends ImmutablePureComponent {
<BlockHeading title={'Chat Preferences'} />
</div>
<div className={[_s.d, _s.px15, _s.py15].join(' ')}>
<div className={[_s.d, _s.px15, _s.py15, _s.overflowHidden].join(' ')}>
<Form>
<Switch
label='Restrict messages from people you dont follow'

View File

@ -61,6 +61,7 @@ const mapStateToProps = (state, props) => {
const statusId = props.id || props.params.statusId
return {
statusId,
status: state.getIn(['statuses', statusId]),
}
}

View File

@ -68,6 +68,7 @@ import {
ChatConversationCreate,
ChatConversationRequests,
ChatConversationBlockedAccounts,
ChatConversationMutes,
CommunityTimeline,
Compose,
Deck,
@ -221,7 +222,7 @@ class SwitchingArea extends React.PureComponent {
<WrappedRoute path='/messages/settings' exact page={MessagesPage} component={MessagesSettings} content={children} componentParams={{ isSettings: true }} />
<WrappedRoute path='/messages/requests' exact page={MessagesPage} component={ChatConversationRequests} content={children} componentParams={{ isSettings: true, source: 'requested' }} />
<WrappedRoute path='/messages/blocks' exact page={MessagesPage} component={ChatConversationBlockedAccounts} content={children} componentParams={{ isSettings: true }} />
<WrappedRoute path='/messages/muted_conversations' exact page={MessagesPage} component={ChatConversationBlockedAccounts} content={children} componentParams={{ isSettings: true }} />
<WrappedRoute path='/messages/muted_conversations' exact page={MessagesPage} component={ChatConversationMutes} content={children} componentParams={{ isSettings: true }} />
<WrappedRoute path='/messages/:chatConversationId' exact page={MessagesPage} component={Messages} content={children} componentParams={{ source: 'approved' }} />
<WrappedRoute path='/timeline/all' exact page={CommunityPage} component={CommunityTimeline} content={children} componentParams={{ title: 'Community Feed' }} />

View File

@ -25,7 +25,9 @@ export function ChatConversationDeleteModal() { return import(/* webpackChunkNam
export function ChatConversationOptionsPopover() { return import(/* webpackChunkName: "components/chat_conversation_options_popover" */'../../../components/popover/chat_conversation_options_popover') }
export function ChatConversationRequests() { return import(/* webpackChunkName: "features/chat_conversation_requests" */'../../chat_conversation_requests') }
export function ChatConversationExpirationOptionsPopover() { return import(/* webpackChunkName: "components/chat_conversation_expiration_options_popover" */'../../../components/popover/chat_conversation_expiration_options_popover') }
export function ChatConversationMutes() { return import(/* webpackChunkName: "features/chat_conversation_mutes" */'../../chat_conversation_mutes') }
export function ChatMessageOptionsPopover() { return import(/* webpackChunkName: "components/chat_message_options_popover" */'../../../components/popover/chat_message_options_popover') }
export function ChatSettingsPopover() { return import(/* webpackChunkName: "components/chat_settings_popover" */'../../../components/popover/chat_settings_popover') }
export function CommentSortingOptionsPopover() { return import(/* webpackChunkName: "components/comment_sorting_options_popover" */'../../../components/popover/comment_sorting_options_popover') }
export function CommunityTimeline() { return import(/* webpackChunkName: "features/community_timeline" */'../../community_timeline') }
export function Compose() { return import(/* webpackChunkName: "features/compose" */'../../compose') }

View File

@ -43,15 +43,6 @@ class WrappedRoute extends React.PureComponent {
...rest
} = this.props
// : todo :
// api().get('/api/v1/accounts/verify_credentials')
// .then((res) => {
// console.log("res:", res)
// })
// .catch((err) => {
// console.log("err:", err)
// })
if (!publicRoute && !me) {
const actualUrl = encodeURIComponent(this.props.computedMatch.url)
return <Route path={this.props.path} component={() => {

View File

@ -3,9 +3,11 @@ import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { me } from '../initial_state'
import { openModal } from '../actions/modal'
import { openPopover } from '../actions/popover'
import {
CX,
BREAKPOINT_EXTRA_SMALL,
POPOVER_CHAT_SETTINGS,
MODAL_CHAT_CONVERSATION_CREATE,
} from '../constants'
import { getWindowDimension } from '../utils/is_mobile'
@ -43,6 +45,10 @@ class MessagesLayout extends React.PureComponent {
this.setState({ width })
}
handleOpenSettingsOptionsPopover = () => {
this.props.onOpenSettingsOptionsPopover()
}
onClickAdd = () => {
this.props.onOpenChatConversationCreateModal()
}
@ -74,12 +80,12 @@ class MessagesLayout extends React.PureComponent {
},
{
icon: 'cog',
to: '/messages/settings',
onClick: this.handleOpenSettingsOptionsPopover,
}
]}
title={title}
/>
<main role='main' className={[_s.d, _s.w100PC].join(' ')}>
<main role='main' className={[_s.d, _s.w100PC, _s.flexGrow1, _s.bgPrimary, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
<div className={[_s.d, _s.w100PC, _s.flexRow, _s.pb15].join(' ')}>
{
(isSettings || currentConversationIsRequest) &&
@ -98,7 +104,7 @@ class MessagesLayout extends React.PureComponent {
return (
<div className={[_s.d, _s.w100PC, _s.minH100VH, _s.bgTertiary].join(' ')}>
<ChatNavigationBar chatConversationId={selectedChatConversationId} />
<main role='main' className={[_s.d, _s.w100PC].join(' ')}>
<main role='main' className={[_s.d, _s.w100PC, _s.flexGrow1, _s.bgPrimary, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
<ChatMessageScrollingList chatConversationId={selectedChatConversationId} isXS={isXS} />
</main>
{ currentConversationIsRequest && <ChatConversationRequestApproveBar /> }
@ -106,7 +112,17 @@ class MessagesLayout extends React.PureComponent {
</div>
)
} else {
//settings
return (
<div className={[_s.d, _s.w100PC, _s.minH100VH, _s.bgTertiary].join(' ')}>
<DefaultNavigationBar showBackBtn title={title} />
<main role='main' className={[_s.d, _s.w100PC, _s.bgPrimary, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
<div className={[_s.d, _s.w100PC, _s.flexRow, _s.pb15].join(' ')}>
{children}
</div>
<FooterBar />
</main>
</div>
)
}
}
@ -174,7 +190,10 @@ const mapStateToProps = (state) => {
const mapDispatchToProps = (dispatch) => ({
onOpenChatConversationCreateModal() {
dispatch(openModal(MODAL_CHAT_CONVERSATION_CREATE))
}
},
onOpenSettingsOptionsPopover() {
dispatch(openPopover(POPOVER_CHAT_SETTINGS))
},
})
MessagesLayout.propTypes = {

View File

@ -19,6 +19,7 @@ class ModalPage extends React.PureComponent {
return (
<DefaultLayout
noComposeButton
title={title}
page={page}
showBackBtn

View File

@ -0,0 +1,26 @@
import { SETTING_CHANGE, SETTING_SAVE } from '../actions/settings'
import {
CHAT_SETTING_CHANGE,
CHAT_SETTING_SAVE,
} from '../actions/chat_settings'
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'
import uuid from '../utils/uuid'
const initialState = ImmutableMap({
saved: false,
restrict_non_followers: true,
show_active: false,
read_receipts: false,
sounds: true,
})
export default function chat_settings(state = initialState, action) {
switch(action.type) {
case CHAT_SETTING_CHANGE:
return state
.setIn(action.path, action.value)
.set('saved', false)
default:
return state
}
}

View File

@ -9,6 +9,7 @@ import chat_conversation_lists from './chat_conversation_lists'
import chat_conversation_messages from './chat_conversation_messages'
import chat_conversations from './chat_conversations'
import chat_messages from './chat_messages'
import chat_settings from './chat_settings'
import compose from './compose'
import contexts from './contexts'
import custom_emojis from './custom_emojis'
@ -60,6 +61,7 @@ const reducers = {
chat_conversation_messages,
chat_conversations,
chat_messages,
chat_settings,
compose,
contexts,
custom_emojis,

View File

@ -14,7 +14,6 @@ const initialState = ImmutableList([])
export default function toasts(state = initialState, action) {
switch(action.type) {
case TOAST_SHOW:
console.log("action:", action)
return state.set(state.size, ImmutableMap({
key: state.size > 0 ? state.last().get('key') + 1 : 0,
message: makeMessageFromData(action.toastData),

View File

@ -166,9 +166,6 @@ export const makeGetStatus = () => {
quotedStatus = quotedStatus.set('account', accountQuoted);
}
// console.log("group:", group)
//Find ancestor status
const regex = (accountRepost || accountBase).get('id') !== me && regexFromFilters(filters);
@ -247,14 +244,11 @@ export const getListOfGroups = createSelector([
(state) => state.get('groups'),
(state, { type }) => state.getIn(['group_lists', type, 'items']),
], (groups, groupIds) => {
console.log("groupIds:", groupIds)
let list = ImmutableList()
groupIds.forEach((id, i) => {
const group = groups.get(`${id}`)
console.log("groupIds:", id, i, group)
list = list.set(i, group)
})
console.log("list:", list)
return list
})

View File

@ -1,5 +1,5 @@
// https://gist.github.com/andjosh/6764939
export const scrollTo = (element, to, duration) => {
export const scrollTo = (element, to, duration = 0) => {
var start = element.scrollTop,
change = to - start,
currentTime = 0,

View File

@ -314,6 +314,7 @@ pre {
.flex1 { flex: 1; }
.flexGrow1 { flex-grow: 1; }
.flexShrink0 { flex-shrink: 0; }
.flexShrink1 { flex-shrink: 1; }
.flexRow { flex-direction: row; }
@ -597,6 +598,8 @@ pre {
.maxW100PC130PX { max-width: calc(100% - 130px); }
.maxW100PC86PX { max-width: calc(100% - 86px); }
.maxW100PC42PX { max-width: calc(100% - 42px); }
.maxW100PC30PX { max-width: calc(100% - 30px); }
.maxW320PX { max-width: 320px; }
.maxW212PX { max-width: 212px; }
.minW330PX { min-width: 330px; }
@ -1314,6 +1317,13 @@ pre {
width: 100px !important;
}
:global(.react-datepicker__day--disabled),
:global(.react-datepicker__month-text--disabled),
:global(.react-datepicker__quarter-text--disabled) {
cursor: not-allowed;
opacity: 0.2 !important;
}
@media (max-width: 500px) {
:global(.react-datepicker) {
flex-direction: column !important;

View File

@ -38,6 +38,8 @@ class ChatConversationAccount < ApplicationRecord
belongs_to :chat_conversation
belongs_to :last_chat_message, class_name: 'ChatMessage', optional: true
validate :validate_total_limit
def participant_accounts
if participant_account_ids.empty?
[account]
@ -47,4 +49,10 @@ class ChatConversationAccount < ApplicationRecord
end
end
private
def validate_total_limit
# errors.add(:base, I18n.t('scheduled_statuses.over_total_limit', limit: PER_ACCOUNT_APPROVED_LIMIT)) if account.scheduled_statuses.count >= PER_ACCOUNT_APPROVED_LIMIT
end
end

View File

@ -18,7 +18,6 @@ class HomeFeed < Feed
private
def from_database(limit, max_id, since_id, min_id)
puts "tilly from_database"
Status.as_home_timeline(@account)
.paginate_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
.reject { |status| FeedManager.instance.filter?(:home, status, @account.id) }

View File

@ -9,10 +9,6 @@ class REST::StatusSerializer < ActiveModel::Serializer
attribute :favourited, if: :current_user?
attribute :reblogged, if: :current_user?
attribute :account_id, if: :account_id?
attribute :group_id, if: :group_id?
attribute :preview_card_id, if: :preview_card_id?
attribute :content, unless: :source_requested?
attribute :rich_content, unless: :source_requested?
attribute :plain_markdown, unless: :source_requested?
@ -20,15 +16,15 @@ class REST::StatusSerializer < ActiveModel::Serializer
belongs_to :reblog, serializer: REST::StatusSerializer
belongs_to :quote, serializer: REST::StatusSerializer
belongs_to :account, serializer: REST::AccountSerializer, unless: :account_id?
belongs_to :group, serializer: REST::GroupSerializer, unless: :group_id?
belongs_to :account, serializer: REST::AccountSerializer
belongs_to :group, serializer: REST::GroupSerializer
has_many :media_attachments, serializer: REST::MediaAttachmentSerializer
has_many :ordered_mentions, key: :mentions
has_many :tags
has_many :emojis, serializer: REST::CustomEmojiSerializer
has_one :preview_card, key: :card, serializer: REST::PreviewCardSerializer, unless: :preview_card_id?
has_one :preview_card, key: :card, serializer: REST::PreviewCardSerializer
has_one :preloadable_poll, key: :poll, serializer: REST::PollSerializer
def id
@ -51,30 +47,6 @@ class REST::StatusSerializer < ActiveModel::Serializer
!current_user.nil?
end
def account_id
instance_options[:account_id]
end
def account_id?
!instance_options[:account_id].nil?
end
def group_id
instance_options[:group_id]
end
def group_id?
!instance_options[:group_id].nil?
end
def preview_card_id
instance_options[:preview_card_id]
end
def preview_card_id?
!instance_options[:preview_card_id].nil?
end
def visibility
# This visibility is masked behind "private"
# to avoid API changes because there are no

View File

@ -1,6 +1,8 @@
%tr
%td
= admin_account_link_to(account)
- if account.is_flagged_as_spam
%span SPAM
%td
- if account.user_current_sign_in_ip
%samp.ellipsized-ip{ title: account.user_current_sign_in_ip }= account.user_current_sign_in_ip

View File

@ -28,6 +28,7 @@ SimpleNavigation::Configuration.run do |navigation|
n.item :development, safe_join([fa_icon('code fw'), t('settings.development')]), settings_applications_url, if: -> { current_user.staff? }
n.item :moderation, safe_join([fa_icon('gavel fw'), t('moderation.title')]), admin_reports_url, if: proc { current_user.staff? } do |s|
s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts|/admin/pending_accounts}
s.item :action_logs, safe_join([fa_icon('bars fw'), t('admin.action_logs.title')]), admin_action_logs_url
s.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports}
s.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.admin? }

View File

@ -382,6 +382,7 @@ Rails.application.routes.draw do
namespace :web do
resource :settings, only: [:update]
resource :chat_settings, only: [:update]
resource :embed, only: [:create]
resources :push_subscriptions, only: [:create] do
member do