This commit is contained in:
mgabdev 2020-12-16 02:39:07 -05:00
parent d1ff39bb81
commit 8f94ffad9c
64 changed files with 958 additions and 870 deletions

View File

@ -1,60 +0,0 @@
# frozen_string_literal: true
class Api::V1::ChatConversationAccounts::MutedChatAccountsController < Api::BaseController
before_action -> { doorkeeper_authorize! :follow, :'read:mutes' }
before_action :require_user!
after_action :insert_pagination_headers
def show
@accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer
end
private
def load_accounts
paginated_mutes.map(&:target_account)
end
def paginated_mutes
@paginated_mutes ||= ChatMute.eager_load(target_account: :account_stat)
.where(account: current_account)
.paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),
params[:max_id],
params[:since_id]
)
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def next_path
if records_continue?
api_v1_chat_conversation_accounts_muted_chat_accounts_url pagination_params(max_id: pagination_max_id)
end
end
def prev_path
unless paginated_mutes.empty?
api_v1_chat_conversation_accounts_muted_chat_accounts_url pagination_params(since_id: pagination_since_id)
end
end
def pagination_max_id
paginated_mutes.last.id
end
def pagination_since_id
paginated_mutes.first.id
end
def records_continue?
paginated_mutes.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end

View File

@ -7,37 +7,31 @@ class Api::V1::ChatConversationAccountsController < Api::BaseController
before_action :require_user! before_action :require_user!
before_action :set_account before_action :set_account
def is_messenger_blocked
#
end
def is_messenger_muted
#
end
def block_messenger def block_messenger
BlockMessengerService.new.call(current_user.account, @account) BlockMessengerService.new.call(current_user.account, @account)
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
end end
def mute_messenger
MuteMessengerService.new.call(current_user.account, @account)
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
end
def unblock_messenger def unblock_messenger
UnblockMessengerService.new.call(current_user.account, @account) UnblockMessengerService.new.call(current_user.account, @account)
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
end end
def unmute_messenger def mute_chat_conversation
UnmuteMessegerService.new.call(current_user.account, @account) @chat_conversation_account.is_muted = true
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships @chat_conversation_account.save!
render json: @chat_conversation_account, serializer: REST::ChatConversationAccountSerializer
end
def unmute_chat_conversation
@chat_conversation_account.is_muted = false
@chat_conversation_account.save!
render json: @chat_conversation_account, serializer: REST::ChatConversationAccountSerializer
end end
def set_expiration_policy def set_expiration_policy
if current_user.account.is_pro if current_user.account.is_pro
# # : todo :
render json: @chat_conversation_account, serializer: REST::ChatConversationAccountSerializer render json: @chat_conversation_account, serializer: REST::ChatConversationAccountSerializer
else else
render json: { error: 'You need to be a GabPRO member to access this' }, status: 422 render json: { error: 'You need to be a GabPRO member to access this' }, status: 422
@ -50,6 +44,14 @@ class Api::V1::ChatConversationAccountsController < Api::BaseController
@account = Account.find(params[:id]) @account = Account.find(params[:id])
end end
def set_chat_conversation
@chat_conversation = ChatConversation.find(params[:id])
@chat_conversation_account = ChatConversationAccount.where(
account: current_account,
chat_conversation: @chat_conversation
).first
end
def check_account_suspension def check_account_suspension
gone if @account.suspended? gone if @account.suspended?
end end

View File

@ -7,6 +7,8 @@ class Api::Web::SettingsController < Api::Web::BaseController
setting.data = params[:data] setting.data = params[:data]
setting.save! setting.save!
# todo validate all data objects
render_empty_success render_empty_success
end end

View File

@ -11,7 +11,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
before_action :set_cache_headers, only: [:edit, :update] before_action :set_cache_headers, only: [:edit, :update]
def new def new
super(&:build_invite_request) super
end end
def create def create

View File

@ -24,79 +24,92 @@ export const BOOKMARK_COLLECTIONS_REMOVE_REQUEST = 'BOOKMARK_COLLECTIONS_REMOVE_
export const BOOKMARK_COLLECTIONS_REMOVE_SUCCESS = 'BOOKMARK_COLLECTIONS_REMOVE_SUCCESS' export const BOOKMARK_COLLECTIONS_REMOVE_SUCCESS = 'BOOKMARK_COLLECTIONS_REMOVE_SUCCESS'
export const BOOKMARK_COLLECTIONS_REMOVE_FAIL = 'BOOKMARK_COLLECTIONS_REMOVE_FAIL' export const BOOKMARK_COLLECTIONS_REMOVE_FAIL = 'BOOKMARK_COLLECTIONS_REMOVE_FAIL'
//
export const UPDATE_BOOKMARK_COLLECTION_FAIL = 'UPDATE_BOOKMARK_COLLECTION_FAIL'
export const UPDATE_BOOKMARK_COLLECTION_REQUEST = 'UPDATE_BOOKMARK_COLLECTION_REQUEST'
export const UPDATE_BOOKMARK_COLLECTION_SUCCESS = 'UPDATE_BOOKMARK_COLLECTION_SUCCESS'
export const UPDATE_BOOKMARK_COLLECTION_STATUS_FAIL = 'UPDATE_BOOKMARK_COLLECTION_STATUS_FAIL'
export const UPDATE_BOOKMARK_COLLECTION_STATUS_REQUEST = 'UPDATE_BOOKMARK_COLLECTION_STATUS_REQUEST'
export const UPDATE_BOOKMARK_COLLECTION_STATUS_SUCCESS = 'UPDATE_BOOKMARK_COLLECTION_STATUS_SUCCESS'
/** /**
* *
*/ */
export const fetchBookmarkedStatuses = () => (dispatch, getState) => { export const fetchBookmarkedStatuses = (bookmarkCollectionId) => (dispatch, getState) => {
if (!me) return if (!me) return
if (getState().getIn(['status_lists', 'bookmarks', 'isLoading'])) { if (getState().getIn(['status_lists', 'bookmarks', 'isLoading'])) {
return return
} }
dispatch(fetchBookmarkedStatusesRequest()) dispatch(fetchBookmarkedStatusesRequest(bookmarkCollectionId))
api(getState).get('/api/v1/bookmarks').then((response) => { api(getState).get('/api/v1/bookmarks').then((response) => {
const next = getLinks(response).refs.find(link => link.rel === 'next') const next = getLinks(response).refs.find(link => link.rel === 'next')
dispatch(importFetchedStatuses(response.data)) dispatch(importFetchedStatuses(response.data))
dispatch(fetchBookmarkedStatusesSuccess(response.data, next ? next.uri : null)) dispatch(fetchBookmarkedStatusesSuccess(response.data, bookmarkCollectionId, next ? next.uri : null))
}).catch((error) => { }).catch((error) => {
dispatch(fetchBookmarkedStatusesFail(error)) dispatch(fetchBookmarkedStatusesFail(bookmarkCollectionId, error))
}) })
} }
const fetchBookmarkedStatusesRequest = () => ({ const fetchBookmarkedStatusesRequest = (bookmarkCollectionId) => ({
type: BOOKMARKED_STATUSES_FETCH_REQUEST, type: BOOKMARKED_STATUSES_FETCH_REQUEST,
}) })
const fetchBookmarkedStatusesSuccess = (statuses, next) => ({ const fetchBookmarkedStatusesSuccess = (statuses, bookmarkCollectionId, next) => ({
type: BOOKMARKED_STATUSES_FETCH_SUCCESS, type: BOOKMARKED_STATUSES_FETCH_SUCCESS,
bookmarkCollectionId,
statuses, statuses,
next, next,
}) })
const fetchBookmarkedStatusesFail = (error) => ({ const fetchBookmarkedStatusesFail = (bookmarkCollectionId, error) => ({
type: BOOKMARKED_STATUSES_FETCH_FAIL, type: BOOKMARKED_STATUSES_FETCH_FAIL,
showToast: true, showToast: true,
bookmarkCollectionId,
error, error,
}) })
/** /**
* *
*/ */
export const expandBookmarkedStatuses = () => (dispatch, getState) => { export const expandBookmarkedStatuses = (bookmarkCollectionId) => (dispatch, getState) => {
if (!me) return if (!me) return
const url = getState().getIn(['status_lists', 'bookmarks', 'next'], null) const url = getState().getIn(['status_lists', 'bookmarks', bookmarkCollectionId, 'next'], null)
if (url === null || getState().getIn(['status_lists', 'bookmarks', 'isLoading'])) { if (url === null || getState().getIn(['status_lists', 'bookmarks', bookmarkCollectionId, 'isLoading'])) {
return return
} }
dispatch(expandBookmarkedStatusesRequest()) dispatch(expandBookmarkedStatusesRequest(bookmarkCollectionId))
api(getState).get(url).then((response) => { api(getState).get(url).then((response) => {
const next = getLinks(response).refs.find(link => link.rel === 'next') const next = getLinks(response).refs.find(link => link.rel === 'next')
dispatch(importFetchedStatuses(response.data)) dispatch(importFetchedStatuses(response.data))
dispatch(expandBookmarkedStatusesSuccess(response.data, next ? next.uri : null)) dispatch(expandBookmarkedStatusesSuccess(response.data, bookmarkCollectionId, next ? next.uri : null))
}).catch((error) => { }).catch((error) => {
dispatch(expandBookmarkedStatusesFail(error)) dispatch(expandBookmarkedStatusesFail(bookmarkCollectionId, error))
}) })
} }
const expandBookmarkedStatusesRequest = () => ({ const expandBookmarkedStatusesRequest = (bookmarkCollectionId) => ({
type: BOOKMARKED_STATUSES_EXPAND_REQUEST, type: BOOKMARKED_STATUSES_EXPAND_REQUEST,
}) })
const expandBookmarkedStatusesSuccess = (statuses, next) => ({ const expandBookmarkedStatusesSuccess = (statuses, bookmarkCollectionId, next) => ({
type: BOOKMARKED_STATUSES_EXPAND_SUCCESS, type: BOOKMARKED_STATUSES_EXPAND_SUCCESS,
statuses, statuses,
next, next,
}) })
const expandBookmarkedStatusesFail = (error) => ({ const expandBookmarkedStatusesFail = (bookmarkCollectionId, error) => ({
type: BOOKMARKED_STATUSES_EXPAND_FAIL, type: BOOKMARKED_STATUSES_EXPAND_FAIL,
showToast: true, showToast: true,
bookmarkCollectionId,
error, error,
}) })
@ -190,4 +203,64 @@ const removeBookmarkCollectionFail = (error) => ({
type: BOOKMARK_COLLECTIONS_CREATE_FAIL, type: BOOKMARK_COLLECTIONS_CREATE_FAIL,
showToast: true, showToast: true,
error, error,
}) })
/**
*
*/
export const updateBookmarkCollection = (bookmarkCollectionId, title) => (dispatch, getState) => {
if (!me || !statusId) return
dispatch(updateBookmarkCollectionRequest())
api(getState).post('/api/v1/bookmark_collections', { title }).then((response) => {
dispatch(updateBookmarkCollectionSuccess(response.data))
}).catch((error) => {
dispatch(updateBookmarkCollectionFail(error))
})
}
const updateBookmarkCollectionRequest = () => ({
type: UPDATE_BOOKMARK_COLLECTION_REQUEST,
})
const updateBookmarkCollectionSuccess = (bookmarkCollection) => ({
type: UPDATE_BOOKMARK_COLLECTION_SUCCESS,
bookmarkCollection,
})
const updateBookmarkCollectionFail = (error) => ({
type: UPDATE_BOOKMARK_COLLECTION_FAIL,
showToast: true,
error,
})
/**
*
*/
export const updateBookmarkCollectionStatus = (statusId, bookmarkCollectionId) => (dispatch, getState) => {
if (!me || !statusId) return
dispatch(updateBookmarkCollectionStatusRequest())
api(getState).post('/api/v1/bookmark_collections', { title }).then((response) => {
dispatch(updateBookmarkCollectionStatusSuccess(response.data))
}).catch((error) => {
dispatch(updateBookmarkCollectionStatusFail(error))
})
}
const updateBookmarkCollectionStatusRequest = () => ({
type: UPDATE_BOOKMARK_COLLECTION_STATUS_REQUEST,
})
const updateBookmarkCollectionStatusSuccess = (bookmarkCollection) => ({
type: UPDATE_BOOKMARK_COLLECTION_STATUS_SUCCESS,
bookmarkCollection,
})
const updateBookmarkCollectionStatusFail = (error) => ({
type: UPDATE_BOOKMARK_COLLECTION_STATUS_FAIL,
showToast: true,
error,
})

View File

@ -24,23 +24,13 @@ export const IS_CHAT_MESSENGER_BLOCKED_SUCCESS = 'IS_CHAT_MESSENGER_BLOCKED_SUCC
// //
export const CHAT_MESSENGER_MUTES_FETCH_REQUEST = 'CHAT_MESSENGER_MUTES_FETCH_REQUEST' export const MUTE_CHAT_CONVERSATION_REQUEST = 'MUTE_CHAT_CONVERSATION_REQUEST'
export const CHAT_MESSENGER_MUTES_FETCH_SUCCESS = 'CHAT_MESSENGER_MUTES_FETCH_SUCCESS' export const MUTE_CHAT_CONVERSATION_SUCCESS = 'MUTE_CHAT_CONVERSATION_SUCCESS'
export const CHAT_MESSENGER_MUTES_FETCH_FAIL = 'CHAT_MESSENGER_MUTES_FETCH_FAIL' export const MUTE_CHAT_CONVERSATION_FAIL = 'MUTE_CHAT_CONVERSATION_FAIL'
export const CHAT_MESSENGER_MUTES_EXPAND_REQUEST = 'CHAT_MESSENGER_MUTES_EXPAND_REQUEST' export const UNMUTE_CHAT_CONVERSATION_REQUEST = 'UNMUTE_CHAT_CONVERSATION_REQUEST'
export const CHAT_MESSENGER_MUTES_EXPAND_SUCCESS = 'CHAT_MESSENGER_MUTES_EXPAND_SUCCESS' export const UNMUTE_CHAT_CONVERSATION_SUCCESS = 'UNMUTE_CHAT_CONVERSATION_SUCCESS'
export const CHAT_MESSENGER_MUTES_EXPAND_FAIL = 'CHAT_MESSENGER_MUTES_EXPAND_FAIL' export const UNMUTE_CHAT_CONVERSATION_FAIL = 'UNMUTE_CHAT_CONVERSATION_FAIL'
export const MUTE_CHAT_MESSAGER_REQUEST = 'MUTE_CHAT_MESSAGER_REQUEST'
export const MUTE_CHAT_MESSAGER_SUCCESS = 'MUTE_CHAT_MESSAGER_SUCCESS'
export const MUTE_CHAT_MESSAGER_FAIL = 'MUTE_CHAT_MESSAGER_FAIL'
export const UNMUTE_CHAT_MESSAGER_REQUEST = 'UNMUTE_CHAT_MESSAGER_REQUEST'
export const UNMUTE_CHAT_MESSAGER_SUCCESS = 'UNMUTE_CHAT_MESSAGER_SUCCESS'
export const UNMUTE_CHAT_MESSAGER_FAIL = 'UNMUTE_CHAT_MESSAGER_FAIL'
export const IS_CHAT_MESSENGER_MUTED_SUCCESS = 'IS_CHAT_MESSENGER_MUTED_SUCCESS'
/** /**
* *
@ -96,12 +86,12 @@ const unblockChatMessengerRequest = (accountId) => ({
}) })
const unblockChatMessengerSuccess = () => ({ const unblockChatMessengerSuccess = () => ({
type: UNBLOCK_CHAT_MESSAGER_REQUEST, type: UNBLOCK_CHAT_MESSAGER_SUCCESS,
showToast: true, showToast: true,
}) })
const unblockChatMessengerFail = (accountId, error) => ({ const unblockChatMessengerFail = (accountId, error) => ({
type: UNBLOCK_CHAT_MESSAGER_REQUEST, type: UNBLOCK_CHAT_MESSAGER_FAIL,
showToast: true, showToast: true,
accountId, accountId,
error, error,
@ -195,147 +185,64 @@ export const expandChatMessengerBlocksFail = (error) => ({
/** /**
* *
*/ */
export const muteChatMessenger = (accountId) => (dispatch, getState) => { export const muteChatConversation = (chatConversationId) => (dispatch, getState) => {
if (!me || !accountId) return if (!me || !chatConversationId) return
dispatch(muteChatMessengerRequest(accountId)) dispatch(muteChatConversationRequest(chatConversationId))
api(getState).post(`/api/v1/chat_conversation_accounts/${accountId}/mute_messenger`).then((response) => { api(getState).post(`/api/v1/chat_conversation_accounts/${chatConversationId}/mute_chat_conversation`).then((response) => {
dispatch(muteChatMessengerSuccess()) dispatch(muteChatConversationSuccess(response.data))
}).catch((error) => { }).catch((error) => {
dispatch(muteChatMessengerFail(accountId, error)) dispatch(muteChatMessengerFail(error))
}) })
} }
const muteChatMessengerRequest = (accountId) => ({ const muteChatConversationRequest = (accountId) => ({
type: MUTE_CHAT_MESSAGER_REQUEST, type: MUTE_CHAT_CONVERSATION_REQUEST,
accountId, accountId,
}) })
const muteChatMessengerSuccess = () => ({ const muteChatConversationSuccess = (chatConversation) => ({
type: MUTE_CHAT_MESSAGER_SUCCESS, type: MUTE_CHAT_CONVERSATION_SUCCESS,
chatConversation,
showToast: true, showToast: true,
}) })
const muteChatMessengerFail = (accountId, error) => ({ const muteChatConversationFail = (error) => ({
type: MUTE_CHAT_MESSAGER_FAIL, type: MUTE_CHAT_CONVERSATION_FAIL,
showToast: true, showToast: true,
accountId,
error, error,
}) })
/** /**
* *
*/ */
export const unmuteChatMessenger = (accountId) => (dispatch, getState) => { export const unmuteChatConversation = (chatConversationId) => (dispatch, getState) => {
if (!me || !accountId) return if (!me || !chatConversationId) return
dispatch(unmuteChatMessengerRequest(accountId)) dispatch(unmuteChatConversationRequest(chatConversationId))
api(getState).post(`/api/v1/chat_conversation_accounts/${accountId}/unmute_messenger`).then((response) => { api(getState).post(`/api/v1/chat_conversation_accounts/${chatConversationId}/unmute_chat_conversation`).then((response) => {
dispatch(unmuteChatMessengerSuccess()) dispatch(unmuteChatConversationSuccess(response.data))
}).catch((error) => { }).catch((error) => {
dispatch(unmuteChatMessengerFail(accountId, error)) dispatch(unmuteChatConversationFail(error))
}) })
} }
const unmuteChatMessengerRequest = (accountId) => ({ const unmuteChatConversationRequest = (accountId) => ({
type: UNMUTE_CHAT_MESSAGER_REQUEST, type: UNMUTE_CHAT_CONVERSATION_REQUEST,
accountId, accountId,
}) })
const unmuteChatMessengerSuccess = () => ({ const unmuteChatConversationSuccess = (chatConversation) => ({
type: UNMUTE_CHAT_MESSAGER_REQUEST, type: UNMUTE_CHAT_CONVERSATION_SUCCESS,
chatConversation,
showToast: true, showToast: true,
}) })
const unmuteChatMessengerFail = (accountId, error) => ({ const unmuteChatConversationFail = (accountId, error) => ({
type: UNMUTE_CHAT_MESSAGER_REQUEST, type: UNMUTE_CHAT_CONVERSATION_FAIL,
showToast: true, showToast: true,
accountId, accountId,
error, error,
}) })
/**
* @description Check if a chat messenger is muted by the current user account.
* @param {String} accountId
*/
export const isChatMessengerMuted = (accountId) => (dispatch, getState) => {
if (!me || !accountId) return
api(getState).post(`/api/v1/chat_conversation_accounts/${accountId}/is_messenger_muted`).then((response) => {
dispatch(isChatMessengerMutedSuccess(response.data))
})
}
const isChatMessengerMutedSuccess = (data) => ({
type: IS_CHAT_MESSENGER_MUTED_SUCCESS,
data,
})
/**
*
*/
export const fetchChatMessengerMutes = () => (dispatch, getState) => {
if (!me) return
dispatch(fetchChatMessengerMutesRequest())
api(getState).get('/api/v1/chat_conversation_accounts/muted_chat_accounts').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next')
dispatch(importFetchedAccounts(response.data))
dispatch(fetchChatMessengerMutesSuccess(response.data, next ? next.uri : null))
}).catch(error => dispatch(fetchChatMessengerMutesFail(error)))
}
export const fetchChatMessengerMutesRequest = () => ({
type: CHAT_MESSENGER_MUTES_FETCH_REQUEST,
})
export const fetchChatMessengerMutesSuccess = (accounts, next) => ({
type: CHAT_MESSENGER_MUTES_FETCH_SUCCESS,
accounts,
next,
})
export const fetchChatMessengerMutesFail = (error) => ({
type: CHAT_MESSENGER_MUTES_FETCH_FAIL,
showToast: true,
error,
})
/**
*
*/
export const expandChatMessengerMutes = () => (dispatch, getState) => {
if (!me) return
const url = getState().getIn(['user_lists', 'chat_mutes', me, 'next'])
const isLoading = getState().getIn(['user_lists', 'chat_mutes', me, 'isLoading'])
if (url === null || isLoading) return
dispatch(expandChatMessengerMutesRequest())
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next')
dispatch(importFetchedAccounts(response.data))
dispatch(expandChatMessengerMutesSuccess(response.data, next ? next.uri : null))
}).catch(error => dispatch(expandChatMessengerMutesFail(error)))
}
export const expandChatMessengerMutesRequest = () => ({
type: CHAT_MESSENGER_MUTES_EXPAND_REQUEST,
})
export const expandChatMessengerMutesSuccess = (accounts, next) => ({
type: CHAT_MESSENGER_MUTES_EXPAND_SUCCESS,
accounts,
next,
})
export const expandChatMessengerMutesFail = (error) => ({
type: CHAT_MESSENGER_MUTES_EXPAND_FAIL,
error,
})

View File

@ -11,6 +11,8 @@ export const SET_CHAT_CONVERSATION_SELECTED = 'SET_CHAT_CONVERSATION_SELECTED'
* *
*/ */
export const fetchChatConversationAccountSuggestions = (query) => throttle((dispatch, getState) => { export const fetchChatConversationAccountSuggestions = (query) => throttle((dispatch, getState) => {
if (!query) return
api(getState).get('/api/v1/accounts/search', { api(getState).get('/api/v1/accounts/search', {
params: { params: {
q: query, q: query,

View File

@ -0,0 +1,39 @@
import React from 'react'
import PropTypes from 'prop-types'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { CX } from '../constants'
import Button from './button'
import Icon from './icon'
import Image from './image'
import Text from './text'
class Album extends React.PureComponent {
handleOnClick = (e) => {
//
}
render() {
const { album } = this.props
return (
<Button
to={to}
href={href}
onClick={this.handleOnClick}
noClasses
>
</Button>
)
}
}
Album.propTypes = {
album: ImmutablePropTypes.map,
isAddable: PropTypes.bool,
}
export default Album

View File

@ -0,0 +1,29 @@
import React from 'react'
import PropTypes from 'prop-types'
import { defineMessages, injectIntl } from 'react-intl'
import ModalLayout from './modal_layout'
import BookmarkCollectionCreate from '../../features/bookmark_collection_create'
class BookmarkCollectionCreateModal extends React.PureComponent {
render() {
const { onClose } = this.props
return (
<ModalLayout
title='Create Bookmark Collection'
width={500}
onClose={onClose}
>
<BookmarkCollectionCreate isModal />
</ModalLayout>
)
}
}
BookmarkCollectionCreateModal.propTypes = {
onClose: PropTypes.func.isRequired,
}
export default BookmarkCollectionCreateModal

View File

@ -1,88 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { changeSetting, saveSettings } from '../../actions/settings'
import ModalLayout from './modal_layout'
import Button from '../button'
import SettingSwitch from '../setting_switch'
import Text from '../text'
class CommunityTimelineSettingsModal extends ImmutablePureComponent {
handleSaveAndClose = () => {
this.props.onSave()
}
render() {
const {
intl,
settings,
onChange,
onClose,
} = this.props
return (
<ModalLayout
onClose={onClose}
width={320}
title={intl.formatMessage(messages.title)}
>
<div className={[_s.d, _s.pb10].join(' ')}>
<SettingSwitch
prefix='community_timeline'
settings={settings}
settingPath={['shows', 'onlyMedia']}
onChange={onChange}
label={intl.formatMessage(messages.onlyMedia)}
/>
</div>
<Button
backgroundColor='brand'
color='white'
className={_s.jcCenter}
onClick={this.handleSaveAndClose}
>
<Text color='inherit' weight='bold' align='center'>
{intl.formatMessage(messages.saveAndClose)}
</Text>
</Button>
</ModalLayout>
)
}
}
const messages = defineMessages({
title: { id: 'community_timeline_settings', defaultMessage: 'Community Feed Settings' },
saveAndClose: { id: 'saveClose', defaultMessage: 'Save & Close' },
onlyMedia: { id: 'community.column_settings.media_only', defaultMessage: 'Media Only' },
})
const mapStateToProps = (state) => ({
settings: state.getIn(['settings', 'community']),
})
const mapDispatchToProps = (dispatch, { onClose }) => ({
onChange(key, checked) {
dispatch(changeSetting(['community', ...key], checked))
},
onSave() {
dispatch(saveSettings())
onClose()
},
})
CommunityTimelineSettingsModal.propTypes = {
intl: PropTypes.object.isRequired,
settings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,
}
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(CommunityTimelineSettingsModal))

View File

@ -1,36 +1,138 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { openModal } from '../../actions/modal' import { openModal } from '../../actions/modal'
import { setDeckColumnAtIndex } from '../../actions/deck'
import { getOrderedLists, getListOfGroups } from '../../selectors'
import { fetchLists } from '../../actions/lists'
import { fetchGroupsByTab } from '../../actions/groups'
import { MODAL_DECK_COLUMN_ADD } from '../../constants' import { MODAL_DECK_COLUMN_ADD } from '../../constants'
import Heading from '../heading' import Heading from '../heading'
import Button from '../button' import Button from '../button'
import Block from '../block' import Block from '../block'
import Input from '../input'
import List from '../list'
class DeckColumnAddOptionsModal extends React.PureComponent { class DeckColumnAddOptionsModal extends ImmutablePureComponent {
state = { state = {
selectedItem: null, hashtagValue: '',
usernameValue: '',
}
componentDidMount() {
switch (this.props.column) {
case 'list':
this.props.onFetchLists()
break
case 'group':
this.props.onFetchMemberGroups()
break
default:
break
}
} }
onClickClose = () => { onClickClose = () => {
this.props.onClose() this.props.onClose()
this.props.dispatch(openModal(MODAL_DECK_COLUMN_ADD)) this.props.onOpenDeckColumnAddModal()
} }
handleAdd = () => { handleAdd = (id) => {
// this.props.onSetDeckColumn(id)
this.props.onClose()
}
handleAddHashtag = () => {
this.handleAdd(`hashtag.${this.state.hashtagValue}`)
this.setState({ hashtagValue: '' })
}
onChangeHashtagValue = (hashtagValue) => {
this.setState({ hashtagValue })
}
onChangeUsernameValue = (usernameValue) => {
this.setState({ usernameValue })
}
getContentForColumn = () => {
const { column, lists, groups, accounts } = this.props
const { hashtagValue } = this.state
if (column === 'hashtag') {
return (
<div className={[_s.d, _s.px15, _s.py10].join(' ')}>
<Input
type='text'
value={hashtagValue}
placeholder='gabfam'
id='hashtag-deck'
title='Enter hashtag'
onChange={this.onChangeHashtagValue}
/>
</div>
)
} else if (column === 'list') {
const listItems = lists.map((list) => ({
onClick: () => this.handleAdd(`list.${list.get('id')}`),
title: list.get('title'),
}))
return (
<div className={[_s.d, _s.maxH340PX, _s.overflowYScroll].join(' ')}>
<List
scrollKey='lists-deck-add'
showLoading={lists.size === 0}
emptyMessage="You don't have any lists yet."
items={listItems}
/>
</div>
)
} else if (column === 'group') {
const listItems = groups.map((group) => ({
onClick: () => this.handleAdd(`group.${group.get('id')}`),
title: group.get('title'),
}))
return (
<div className={[_s.d, _s.maxH340PX, _s.overflowYScroll].join(' ')}>
<List
scrollKey='groups-deck-add'
showLoading={groups.size === 0}
emptyMessage="You are not a member of any groups yet."
items={listItems}
/>
</div>
)
} else if (column === 'group') {
return (
<div className={[_s.d, _s.px15, _s.py10].join(' ')}>
<Input
type='text'
value={usernameValue}
placeholder=''
id='user-deck'
title='Enter username'
onChange={this.onChangeUsernameValue}
/>
</div>
)
}
} }
render() { render() {
const { column } = this.props const { column } = this.props
const { selectedItem } = this.state const { hashtagValue } = this.state
// user, hashtag, list, groups
if (!column) return <div /> if (!column) return <div />
const title = `Select a ${column}` const title = `Select a ${column}`
const content = this.getContentForColumn()
return ( return (
<div style={{width: '520px'}} className={[_s.d, _s.modal].join(' ')}> <div style={{width: '520px'}} className={[_s.d, _s.modal].join(' ')}>
<Block> <Block>
@ -49,16 +151,19 @@ class DeckColumnAddOptionsModal extends React.PureComponent {
{title} {title}
</Heading> </Heading>
<div className={[_s.d, _s.w115PX, _s.aiEnd, _s.jcCenter, _s.mlAuto].join(' ')}> <div className={[_s.d, _s.w115PX, _s.aiEnd, _s.jcCenter, _s.mlAuto].join(' ')}>
<Button {
isDisabled={!selectedItem} column === 'hashtag' &&
onClick={this.handleAdd} <Button
> isDisabled={!hashtagValue}
Add onClick={this.handleAddHashtag}
</Button> >
Add
</Button>
}
</div> </div>
</div> </div>
<div className={[_s.d].join(' ')}> <div className={[_s.d].join(' ')}>
test {content}
</div> </div>
</Block> </Block>
</div> </div>
@ -67,9 +172,33 @@ class DeckColumnAddOptionsModal extends React.PureComponent {
} }
const mapStateToProps = (state) => ({
lists: getOrderedLists(state),
groups: getListOfGroups(state, { type: 'member' }),
accounts: [],
})
const mapDispatchToProps = (dispatch) => ({
onFetchLists() {
dispatch(fetchLists())
},
onFetchMemberGroups() {
dispatch(fetchGroupsByTab('member'))
},
onSetDeckColumn(id) {
dispatch(setDeckColumnAtIndex(id))
},
onOpenDeckColumnAddModal() {
dispatch(openModal(MODAL_DECK_COLUMN_ADD))
},
})
DeckColumnAddOptionsModal.propTypes = { DeckColumnAddOptionsModal.propTypes = {
groupIds: ImmutablePropTypes.list,
onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired,
onFetchLists: PropTypes.func.isRequired,
onSetDeckColumn: PropTypes.func.isRequired,
column: PropTypes.string.isRequired, column: PropTypes.string.isRequired,
} }
export default connect()(DeckColumnAddOptionsModal) export default connect(mapStateToProps, mapDispatchToProps)(DeckColumnAddOptionsModal)

View File

@ -1,88 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { changeSetting, saveSettings } from '../../actions/settings'
import ModalLayout from './modal_layout'
import Button from '../button'
import SettingSwitch from '../setting_switch'
import Text from '../text'
class HashtagTimelineSettingsModal extends ImmutablePureComponent {
handleSaveAndClose = () => {
this.props.onSave()
}
render() {
const { intl, settings, onChange, onClose } = this.props
// : todo :
return (
<ModalLayout
width={320}
title={intl.formatMessage(messages.title)}
onClose={onClose}
>
<div className={[_s.d, _s.pb10].join(' ')}>
<SettingSwitch
prefix='community_timeline'
settings={settings}
settingPath={['shows', 'inSidebar']}
onChange={onChange}
label={intl.formatMessage(messages.showInSidebar)}
/>
</div>
<Button
backgroundColor='brand'
color='white'
className={_s.jcCenter}
onClick={this.handleSaveAndClose}
>
<Text color='inherit' weight='bold' align='center'>
{intl.formatMessage(messages.saveAndClose)}
</Text>
</Button>
</ModalLayout>
)
}
}
const messages = defineMessages({
title: { id: 'hashtag_timeline_settings', defaultMessage: 'Hashtag Timeline Settings' },
saveAndClose: { id: 'saveClose', defaultMessage: 'Save & Close' },
onlyMedia: { id: 'community.column_settings.media_only', defaultMessage: 'Media Only' },
showInSidebar: { id: 'show_in_sidebar', defaultMessage: 'Show in Sidebar' },
})
const mapStateToProps = (state) => ({
settings: state.getIn(['settings', 'community']),
})
const mapDispatchToProps = (dispatch, { onClose }) => {
return {
onChange(key, checked) {
dispatch(changeSetting(['community', ...key], checked))
},
onSave() {
dispatch(saveSettings())
onClose()
},
}
}
HasttagTimelineSettingsModal.propTypes = {
intl: PropTypes.object.isRequired,
settings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
}
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(HashtagTimelineSettingsModal))

View File

@ -8,11 +8,12 @@ import ModalBase from './modal_base'
import BundleErrorModal from './bundle_error_modal' import BundleErrorModal from './bundle_error_modal'
import LoadingModal from './loading_modal' import LoadingModal from './loading_modal'
import { import {
MODAL_ALBUM_CREATE,
MODAL_BLOCK_ACCOUNT, MODAL_BLOCK_ACCOUNT,
MODAL_BOOKMARK_COLLECTION_CREATE,
MODAL_BOOST, MODAL_BOOST,
MODAL_CHAT_CONVERSATION_CREATE, MODAL_CHAT_CONVERSATION_CREATE,
MODAL_CHAT_CONVERSATION_DELETE, MODAL_CHAT_CONVERSATION_DELETE,
MODAL_COMMUNITY_TIMELINE_SETTINGS,
MODAL_COMPOSE, MODAL_COMPOSE,
MODAL_CONFIRM, MODAL_CONFIRM,
MODAL_DECK_COLUMN_ADD, MODAL_DECK_COLUMN_ADD,
@ -24,7 +25,6 @@ import {
MODAL_GROUP_CREATE, MODAL_GROUP_CREATE,
MODAL_GROUP_DELETE, MODAL_GROUP_DELETE,
MODAL_GROUP_PASSWORD, MODAL_GROUP_PASSWORD,
MODAL_HASHTAG_TIMELINE_SETTINGS,
MODAL_HOME_TIMELINE_SETTINGS, MODAL_HOME_TIMELINE_SETTINGS,
MODAL_HOTKEYS, MODAL_HOTKEYS,
MODAL_LIST_ADD_USER, MODAL_LIST_ADD_USER,
@ -44,11 +44,12 @@ import {
MODAL_VIDEO, MODAL_VIDEO,
} from '../../constants' } from '../../constants'
import { import {
AlbumCreateModal,
BlockAccountModal, BlockAccountModal,
BookmarkCollectionCreateModal,
BoostModal, BoostModal,
ChatConversationCreateModal, ChatConversationCreateModal,
ChatConversationDeleteModal, ChatConversationDeleteModal,
CommunityTimelineSettingsModal,
ComposeModal, ComposeModal,
ConfirmationModal, ConfirmationModal,
DeckColumnAddModal, DeckColumnAddModal,
@ -62,7 +63,6 @@ import {
GroupMembersModal, GroupMembersModal,
GroupPasswordModal, GroupPasswordModal,
GroupRemovedAccountsModal, GroupRemovedAccountsModal,
HashtagTimelineSettingsModal,
HomeTimelineSettingsModal, HomeTimelineSettingsModal,
HotkeysModal, HotkeysModal,
ListAddUserModal, ListAddUserModal,
@ -83,11 +83,12 @@ import {
} from '../../features/ui/util/async_components' } from '../../features/ui/util/async_components'
const MODAL_COMPONENTS = { const MODAL_COMPONENTS = {
[MODAL_ALBUM_CREATE]: AlbumCreateModal,
[MODAL_BLOCK_ACCOUNT]: BlockAccountModal, [MODAL_BLOCK_ACCOUNT]: BlockAccountModal,
[MODAL_BOOKMARK_COLLECTION_CREATE]: BookmarkCollectionCreateModal,
[MODAL_BOOST]: BoostModal, [MODAL_BOOST]: BoostModal,
[MODAL_CHAT_CONVERSATION_CREATE]: ChatConversationCreateModal, [MODAL_CHAT_CONVERSATION_CREATE]: ChatConversationCreateModal,
[MODAL_CHAT_CONVERSATION_DELETE]: ChatConversationDeleteModal, [MODAL_CHAT_CONVERSATION_DELETE]: ChatConversationDeleteModal,
[MODAL_COMMUNITY_TIMELINE_SETTINGS]: CommunityTimelineSettingsModal,
[MODAL_COMPOSE]: ComposeModal, [MODAL_COMPOSE]: ComposeModal,
[MODAL_CONFIRM]: ConfirmationModal, [MODAL_CONFIRM]: ConfirmationModal,
[MODAL_DECK_COLUMN_ADD]: DeckColumnAddModal, [MODAL_DECK_COLUMN_ADD]: DeckColumnAddModal,
@ -99,7 +100,6 @@ const MODAL_COMPONENTS = {
[MODAL_GROUP_CREATE]: GroupCreateModal, [MODAL_GROUP_CREATE]: GroupCreateModal,
[MODAL_GROUP_DELETE]: GroupDeleteModal, [MODAL_GROUP_DELETE]: GroupDeleteModal,
[MODAL_GROUP_PASSWORD]: GroupPasswordModal, [MODAL_GROUP_PASSWORD]: GroupPasswordModal,
[MODAL_HASHTAG_TIMELINE_SETTINGS]: HashtagTimelineSettingsModal,
[MODAL_HOME_TIMELINE_SETTINGS]: HomeTimelineSettingsModal, [MODAL_HOME_TIMELINE_SETTINGS]: HomeTimelineSettingsModal,
[MODAL_HOTKEYS]: HotkeysModal, [MODAL_HOTKEYS]: HotkeysModal,
[MODAL_LIST_ADD_USER]: ListAddUserModal, [MODAL_LIST_ADD_USER]: ListAddUserModal,

View File

@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import BackButton from '../back_button' import BackButton from '../back_button'
import Button from '../button'
import Heading from '../heading' import Heading from '../heading'
class ProfileNavigationBar extends React.PureComponent { class ProfileNavigationBar extends React.PureComponent {
@ -28,6 +29,19 @@ class ProfileNavigationBar extends React.PureComponent {
</Heading> </Heading>
</div> </div>
<div className={[_s.d, _s.minH53PX, _s.jcCenter, _s.mr15].join(' ')}>
<Button
icon='ellipsis'
iconSize='26px'
iconClassName={_s.inheritFill}
color='brand'
backgroundColor='none'
className={[_s.jcCenter, _s.aiCenter, _s.ml10, _s.px10].join(' ')}
onClick={this.handleOpenMore}
buttonRef={this.setOpenMoreNodeRef}
/>
</div>
</div> </div>
</div> </div>

View File

@ -61,7 +61,7 @@ class MediaGalleryPanel extends ImmutablePureComponent {
noPadding noPadding
title={intl.formatMessage(messages.title)} title={intl.formatMessage(messages.title)}
headerButtonTitle={!!account ? intl.formatMessage(messages.show_all) : undefined} headerButtonTitle={!!account ? intl.formatMessage(messages.show_all) : undefined}
headerButtonTo={!!account ? `/${account.get('acct')}/photos` : undefined} headerButtonTo={!!account ? `/${account.get('acct')}/albums` : undefined}
> >
<div className={[_s.d, _s.flexRow, _s.flexWrap, _s.px10, _s.py10].join(' ')}> <div className={[_s.d, _s.flexRow, _s.flexWrap, _s.px10, _s.py10].join(' ')}>
{ {

View File

@ -32,35 +32,27 @@ class ProfileStatsPanel extends ImmutablePureComponent {
!!account && !!account &&
<ResponsiveClassesComponent <ResponsiveClassesComponent
classNames={[_s.d, _s.flexRow].join(' ')} classNames={[_s.d, _s.flexRow].join(' ')}
classNamesXS={[_s.d, _s.flexRow, _s.mt15, _s.pt10].join(' ')} classNamesXS={[_s.d, _s.flexRow, _s.mt15, _s.flexWrap].join(' ')}
> >
<UserStat <UserStat
title={intl.formatMessage(messages.gabs)} title={intl.formatMessage(messages.gabs)}
value={shortNumberFormat(account.get('statuses_count'))} value={shortNumberFormat(account.get('statuses_count'))}
to={`/${account.get('acct')}`} to={`/${account.get('acct')}`}
isCentered={noPanel} isInline={noPanel}
/> />
<UserStat <UserStat
title={intl.formatMessage(messages.followers)} title={intl.formatMessage(messages.followers)}
value={shortNumberFormat(account.get('followers_count'))} value={shortNumberFormat(account.get('followers_count'))}
to={`/${account.get('acct')}/followers`} to={`/${account.get('acct')}/followers`}
isCentered={noPanel} isInline={noPanel}
/> />
<UserStat <UserStat
isLast
title={intl.formatMessage(messages.follows)} title={intl.formatMessage(messages.follows)}
value={shortNumberFormat(account.get('following_count'))} value={shortNumberFormat(account.get('following_count'))}
to={`/${account.get('acct')}/following`} to={`/${account.get('acct')}/following`}
isCentered={noPanel} isInline={noPanel}
/> />
{
account.get('id') === me &&
<UserStat
title={intl.formatMessage(messages.likes)}
value={shortNumberFormat(0)}
to={`/${account.get('acct')}/likes`}
isCentered={noPanel}
/>
}
</ResponsiveClassesComponent> </ResponsiveClassesComponent>
} }
</Wrapper> </Wrapper>

View File

@ -53,6 +53,12 @@ class ChatConversationOptionsPopover extends ImmutablePureComponent {
subtitle: 'Hide until next message', subtitle: 'Hide until next message',
onClick: () => this.handleOnHide(), onClick: () => this.handleOnHide(),
}, },
{
hideArrow: true,
title: 'Mute Conversation',
subtitle: "Don't get notified of new messages",
onClick: () => this.handleOnHide(),
},
{}, {},
{ {
hideArrow: true, hideArrow: true,

View File

@ -4,14 +4,11 @@ import { connect } from 'react-redux'
import { closePopover } from '../../actions/popover' import { closePopover } from '../../actions/popover'
import { deleteChatMessage } from '../../actions/chat_messages' import { deleteChatMessage } from '../../actions/chat_messages'
import { import {
isChatMessengerBlocked,
isChatMessengerMuted,
blockChatMessenger, blockChatMessenger,
unblockChatMessenger, unblockChatMessenger,
muteChatMessenger,
unmuteChatMessenger,
reportChatMessage, reportChatMessage,
} from '../../actions/chat_conversation_accounts' } from '../../actions/chat_conversation_accounts'
import { fetchRelationships } from '../../actions/accounts'
import { makeGetChatMessage } from '../../selectors' import { makeGetChatMessage } from '../../selectors'
import { me } from '../../initial_state' import { me } from '../../initial_state'
import PopoverLayout from './popover_layout' import PopoverLayout from './popover_layout'
@ -21,6 +18,12 @@ import Text from '../text'
class ChatMessageOptionsPopover extends React.PureComponent { class ChatMessageOptionsPopover extends React.PureComponent {
componentDidMount() {
if (!this.props.isMine) {
this.props.onFetchRelationships(this.props.fromAccountId)
}
}
handleOnDelete = () => { handleOnDelete = () => {
this.props.onDeleteChatMessage(this.props.chatMessageId) this.props.onDeleteChatMessage(this.props.chatMessageId)
} }
@ -31,17 +34,9 @@ class ChatMessageOptionsPopover extends React.PureComponent {
handleOnBlock = () => { handleOnBlock = () => {
if (this.props.isBlocked) { if (this.props.isBlocked) {
this.props.unblockChatMessenger(this.props.fromAccountId) this.props.onUnblock(this.props.fromAccountId)
} else { } else {
this.props.blockChatMessenger(this.props.fromAccountId) this.props.onBlock(this.props.fromAccountId)
}
}
handleOnMute = () => {
if (this.props.isMuted) {
this.props.unmuteChatMessenger(this.props.fromAccountId)
} else {
this.props.muteChatMessenger(this.props.fromAccountId)
} }
} }
@ -53,7 +48,6 @@ class ChatMessageOptionsPopover extends React.PureComponent {
const { const {
isXS, isXS,
isMine, isMine,
isMuted,
isBlocked, isBlocked,
} = this.props } = this.props
@ -76,12 +70,6 @@ class ChatMessageOptionsPopover extends React.PureComponent {
subtitle: isBlocked ? '' : 'The messenger will not be able to message you.', subtitle: isBlocked ? '' : 'The messenger will not be able to message you.',
onClick: () => this.handleOnBlock(), onClick: () => this.handleOnBlock(),
}, },
{
hideArrow: true,
title: isMuted ? 'Unmute Messenger' : 'Mute Messenger',
subtitle: isMuted ? '' : 'You will not be notified of new messsages',
onClick: () => this.handleOnMute(),
},
] ]
return ( return (
@ -96,12 +84,15 @@ class ChatMessageOptionsPopover extends React.PureComponent {
} }
} }
const mapStateToProps = (state, { chatMessageId }) => ({ const mapStateToProps = (state, { chatMessageId }) => {
isMine: state.getIn(['chat_messages', chatMessageId, 'from_account_id']) === me, const fromAccountId = state.getIn(['chat_messages', chatMessageId, 'from_account_id'])
fromAccountId: state.getIn(['chat_messages', chatMessageId, 'from_account_id']),
isBlocked: state.getIn(['chat_messages', chatMessageId, 'from_account_id']), return {
isMuted: state.getIn(['chat_messages', chatMessageId, 'from_account_id']), fromAccountId,
}) isMine: fromAccountId === me,
isBlocked: state.getIn(['relationships', fromAccountId, 'chat_blocked_by'], false),
}
}
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
onDeleteChatMessage(chatMessageId) { onDeleteChatMessage(chatMessageId) {
@ -114,15 +105,12 @@ const mapDispatchToProps = (dispatch) => ({
onUnblock(accountId) { onUnblock(accountId) {
dispatch(unblockChatMessenger(accountId)) dispatch(unblockChatMessenger(accountId))
}, },
onMute(accountId) {
dispatch(muteChatMessenger(accountId))
},
onUnmute(accountId) {
dispatch(unmuteChatMessenger(accountId))
},
onReportChatMessage(chatMessageId) { onReportChatMessage(chatMessageId) {
dispatch(reportChatMessage(chatMessageId)) dispatch(reportChatMessage(chatMessageId))
}, },
onFetchRelationships(accountId) {
// dispatch(fetchRelationships(accountId))
},
onClosePopover() { onClosePopover() {
dispatch(closePopover()) dispatch(closePopover())
}, },
@ -130,9 +118,9 @@ const mapDispatchToProps = (dispatch) => ({
ChatMessageOptionsPopover.propTypes = { ChatMessageOptionsPopover.propTypes = {
isXS: PropTypes.bool, isXS: PropTypes.bool,
isMine: PropTypes.bool,
chatMessageId: PropTypes.string.isRequired, chatMessageId: PropTypes.string.isRequired,
isBlocked: PropTypes.bool.isRequired, isBlocked: PropTypes.bool.isRequired,
isMuted: PropTypes.bool.isRequired,
onDeleteChatMessage: PropTypes.func.isRequired, onDeleteChatMessage: PropTypes.func.isRequired,
} }

View File

@ -4,6 +4,7 @@ import { connect } from 'react-redux'
import { closePopover } from '../../actions/popover' import { closePopover } from '../../actions/popover'
import PopoverLayout from './popover_layout' import PopoverLayout from './popover_layout'
import List from '../list' import List from '../list'
import Button from '../button'
import Text from '../text' import Text from '../text'
class ComposePostDesinationPopover extends React.PureComponent { class ComposePostDesinationPopover extends React.PureComponent {
@ -38,8 +39,20 @@ class ComposePostDesinationPopover extends React.PureComponent {
isXS={isXS} isXS={isXS}
onClose={this.handleOnClosePopover} onClose={this.handleOnClosePopover}
> >
<Text className={[_s.d, _s.px15, _s.py10, _s.bgSecondary].join(' ')}>Post to:</Text> <div className={[_s.d]}>
<List items={items} /> <Text className={[_s.d, _s.px15, _s.py10, _s.bgSecondary].join(' ')}>Post to:</Text>
<List items={items} />
</div>
<div>
<Text className={[_s.d, _s.px15, _s.py10, _s.bgSecondary].join(' ')}>
<Button
isText
icon='back'
/>
Select group:
</Text>
<List items={items} />
</div>
</PopoverLayout> </PopoverLayout>
) )
} }

View File

@ -78,6 +78,7 @@ class StatusOptionsPopover extends ImmutablePureComponent {
handleGroupRemoveAccount = () => { handleGroupRemoveAccount = () => {
const { status } = this.props const { status } = this.props
// : todo : check
this.props.onGroupRemoveAccount(status.getIn(['group', 'id']), status.getIn(['account', 'id'])) this.props.onGroupRemoveAccount(status.getIn(['group', 'id']), status.getIn(['account', 'id']))
} }

View File

@ -107,7 +107,7 @@ class ProfileHeader extends ImmutablePureComponent {
title: intl.formatMessage(messages.comments), title: intl.formatMessage(messages.comments),
}, },
{ {
to: `/${account.get('acct')}/photos`, to: `/${account.get('acct')}/albums`,
title: intl.formatMessage(messages.photos), title: intl.formatMessage(messages.photos),
}, },
{ {
@ -119,7 +119,11 @@ class ProfileHeader extends ImmutablePureComponent {
const isMyProfile = !account ? false : account.get('id') === me const isMyProfile = !account ? false : account.get('id') === me
if (isMyProfile) { if (isMyProfile) {
tabs.push({ tabs.push({
to: `/${account.get('acct')}/bookmarks`, to: `/${account.get('acct')}/likes`,
title: 'Likes',
})
tabs.push({
to: `/${account.get('acct')}/bookmark_collections`,
title: intl.formatMessage(messages.bookmarks), title: intl.formatMessage(messages.bookmarks),
}) })
} }
@ -149,21 +153,12 @@ class ProfileHeader extends ImmutablePureComponent {
displayNone: stickied, displayNone: stickied,
}) })
const mobileAvatarContainerClasses = CX({
d: 1,
circle: 1,
boxShadowProfileAvatar: 1,
mtNeg50PX: !headerMissing,
})
const mobileDescriptionContainerClasses = CX({ const mobileDescriptionContainerClasses = CX({
d: 1, d: 1,
w100PC: 1, w100PC: 1,
px15: 1, px15: 1,
mt5: !!me,
mb10: 1, mb10: 1,
pt15: !!me, pt15: !!me,
pb10: 1,
}) })
return ( return (
@ -174,10 +169,10 @@ class ProfileHeader extends ImmutablePureComponent {
<div className={[_s.d, _s.w100PC].join(' ')}> <div className={[_s.d, _s.w100PC].join(' ')}>
{ {
!headerMissing && !headerMissing &&
<div className={[_s.d, _s.h200PX, _s.px10, _s.w100PC, _s.mt10, _s.overflowHidden].join(' ')}> <div className={[_s.d, _s.h172PX, _s.w100PC, _s.overflowHidden].join(' ')}>
<Image <Image
alt={intl.formatMessage(messages.headerPhoto)} alt={intl.formatMessage(messages.headerPhoto)}
className={[_s.topRightRadiusSmall, _s.topLeftRadiusSmall, _s.h100PC].join(' ')} className={_s.h100PC}
src={headerSrc} src={headerSrc}
expandOnClick expandOnClick
/> />
@ -185,85 +180,80 @@ class ProfileHeader extends ImmutablePureComponent {
} }
{ {
headerMissing && headerMissing &&
<div className={[_s.d, _s.h20PX, _s.w100PC].join(' ')} /> <div className={[_s.d, _s.h122PX, _s.w100PC, _s.bgSecondary].join(' ')} />
} }
<div className={[_s.d, _s.w100PC].join(' ')}> <div className={[_s.d, _s.w100PC].join(' ')}>
<div className={[_s.d, _s.aiCenter, _s.px15, _s.mb5].join(' ')}> <div className={[_s.d, _s.px15].join(' ')}>
<div className={mobileAvatarContainerClasses}> <div class={[_s.d, _s.flexRow].join(' ')}>
<Avatar size={100} account={account} noHover expandOnClick /> <div className={[_s.d, _s.circle, _s.boxShadowProfileAvatar, _s.mtNeg32PX].join(' ')}>
<Avatar size={88} account={account} noHover expandOnClick />
</div>
{
account && account.get('id') === me &&
<div className={[_s.d, _s.flexRow, _s.pt10, _s.flexGrow1, _s.h40PX, _s.jcEnd].join(' ')}>
<Button
isOutline
backgroundColor='none'
color='brand'
className={[_s.jcCenter, _s.aiCenter, _s.h40PX].join(' ')}
onClick={this.handleOnEditProfile}
>
<Text color='inherit' weight='bold' size='medium' className={_s.px15}>
{intl.formatMessage(messages.editProfile)}
</Text>
</Button>
</div>
}
{
account && account.get('id') !== me && !!me &&
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.pt15, _s.flexGrow1, _s.h40PX, _s.jcEnd].join(' ')}>
<Button
icon={isShortcut ? 'star' : 'star-outline'}
iconSize='18px'
color='brand'
backgroundColor='none'
className={[_s.jcCenter, _s.aiCenter, _s.px10, _s.mr10].join(' ')}
onClick={this.handleToggleShortcut}
/>
<div className={[_s.d, _s.flexRow, _s.h40PX].join(' ')}>
<Button
isOutline
icon='chat'
iconSize='18px'
iconClassName={_s.inheritFill}
color='brand'
backgroundColor='none'
className={[_s.jcCenter, _s.aiCenter, _s.mr10, _s.px10].join(' ')}
onClick={this.handleOpenMore}
buttonRef={this.setOpenMoreNodeRef}
/>
</div>
<div className={[_s.d, _s.flexRow, _s.h40PX].join(' ')}>
<AccountActionButton account={account} />
</div>
</div>
}
</div> </div>
<div className={[_s.d, _s.flexRow, _s.flexNormal, _s.py10].join(' ')}> <div className={[_s.d, _s.flexRow, _s.flexNormal, _s.pt10].join(' ')}>
<DisplayName <DisplayName
account={account} account={account}
isMultiline isMultiline
isLarge isLarge
isCentered
noHover noHover
/> />
</div> </div>
</div> </div>
<div className={[_s.d, _s.bgPrimary, _s.aiCenter].join(' ')}> <div className={[_s.d, _s.bgPrimary, _s.aiCenter].join(' ')}>
{
account && account.get('id') === me &&
<div className={[_s.d,_s.py5].join(' ')}>
<Button
isOutline
backgroundColor='none'
color='brand'
className={[_s.jcCenter, _s.aiCenter].join(' ')}
onClick={this.handleOnEditProfile}
>
<Text color='inherit' weight='bold' size='medium' className={_s.px15}>
{intl.formatMessage(messages.editProfile)}
</Text>
</Button>
</div>
}
{
account && account.get('id') !== me && !!me &&
<div className={[_s.d, _s.flexRow, _s.py5].join(' ')}>
<Button
icon={isShortcut ? 'star' : 'star-outline'}
iconSize='18px'
color='brand'
backgroundColor='none'
className={[_s.jcCenter, _s.aiCenter, _s.px10, _s.mr15].join(' ')}
onClick={this.handleToggleShortcut}
/>
<div className={[_s.d, _s.flexRow, _s.mr15].join(' ')}>
<AccountActionButton account={account} />
</div>
<div>
<Button
isOutline
icon='ellipsis'
iconSize='18px'
iconClassName={_s.inheritFill}
color='brand'
backgroundColor='none'
className={[_s.jcCenter, _s.aiCenter, _s.ml10, _s.px10].join(' ')}
onClick={this.handleOpenMore}
buttonRef={this.setOpenMoreNodeRef}
/>
</div>
</div>
}
<div className={mobileDescriptionContainerClasses}> <div className={mobileDescriptionContainerClasses}>
{children} {children}
</div> </div>
<div className={[_s.d, _s.mt10, _s.mb10, _s.pt5, _s.w100PC, _s.pr10].join(' ')}> <div className={[_s.d, _s.mt10, _s.mb10, _s.pt5, _s.w100PC].join(' ')}>
<Pills pills={tabs} /> <Pills pills={tabs} />
</div> </div>
</div> </div>

View File

@ -89,18 +89,12 @@ class DeckSidebar extends ImmutablePureComponent {
<div className={[_s.d, _s.aiCenter, _s.jcCenter].join(' ')}> <div className={[_s.d, _s.aiCenter, _s.jcCenter].join(' ')}>
{ {
/*
!!gabDeckOrder && gabDeckOrder.map((item, i) => ( !!gabDeckOrder && gabDeckOrder.map((item, i) => (
<Button
isText
key={`gab-deck-sidebar-dot-${i}`}
onClick={this.scrollToItem}
backgroundColor='secondary'
className={[_s.mt5, _s.mb5, _s.px10, _s.py10, _s.circle].join(' ')}
icon='notifications'
iconClassName={_s.cPrimary}
/>
)) ))
} */
}
</div> </div>
<Divider isSmall /> <Divider isSmall />

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
import { NavLink } from 'react-router-dom' import { NavLink } from 'react-router-dom'
import { CX } from '../constants' import { CX } from '../constants'
import Text from './text' import Text from './text'
import DotTextSeperator from './dot_text_seperator'
/** /**
* Renders a user stat component * Renders a user stat component
@ -30,17 +31,29 @@ class UserStat extends React.PureComponent {
title, title,
value, value,
isCentered, isCentered,
isInline,
isLast,
} = this.props } = this.props
const { hovering } = this.state const { hovering } = this.state
const align = isCentered ? 'center' : 'left' const align = isCentered ? 'center' : 'left'
const titleSize = isInline ? 'normal' : 'extraLarge'
const subtitleSize = isInline ? 'normal' : 'small'
const containerClasses = CX({ const containerClasses = CX({
d: 1, d: 1,
cursorPointer: 1, cursorPointer: 1,
noUnderline: 1, noUnderline: 1,
flexNormal: isCentered, flexNormal: isCentered,
flexGrow1: !isCentered, flexGrow1: !isCentered && !isInline,
pr15: !isCentered, flexRow: isInline,
aiCenter: isInline,
pr15: !isCentered && !isInline,
pr10: !isCentered && isInline,
})
const subtitleClasses = CX({
pr5: isInline,
pl5: isInline,
}) })
return ( return (
@ -51,12 +64,13 @@ class UserStat extends React.PureComponent {
onMouseEnter={this.handleOnMouseEnter} onMouseEnter={this.handleOnMouseEnter}
onMouseLeave={this.handleOnMouseLeave} onMouseLeave={this.handleOnMouseLeave}
> >
<Text size='extraLarge' weight='bold' color='brand' align={align}> <Text size={titleSize} weight='bold' color='brand' align={align}>
{value} {value}
</Text> </Text>
<Text size='small' weight='medium' color='secondary' hasUnderline={hovering} align={align}> <Text size={subtitleSize} weight='medium' color='secondary' hasUnderline={hovering} align={align} className={subtitleClasses}>
{title} {title}
</Text> </Text>
{ !isLast && isInline && <DotTextSeperator /> }
</NavLink> </NavLink>
) )
} }
@ -72,6 +86,8 @@ UserStat.propTypes = {
PropTypes.object, PropTypes.object,
]).isRequired, ]).isRequired,
isCentered: PropTypes.bool, isCentered: PropTypes.bool,
isInline: PropTypes.bool,
isLast: PropTypes.bool,
} }
export default UserStat export default UserStat

View File

@ -46,11 +46,12 @@ export const POPOVER_TIMELINE_INJECTION_OPTIONS = 'TIMELINE_INJECTION_OPTIONS'
export const POPOVER_USER_INFO = 'USER_INFO' export const POPOVER_USER_INFO = 'USER_INFO'
export const POPOVER_VIDEO_STATS = 'VIDEO_STATS' export const POPOVER_VIDEO_STATS = 'VIDEO_STATS'
export const MODAL_ALBUM_CREATE = 'ALBUM_CREATE'
export const MODAL_BLOCK_ACCOUNT = 'BLOCK_ACCOUNT' export const MODAL_BLOCK_ACCOUNT = 'BLOCK_ACCOUNT'
export const MODAL_BOOKMARK_COLLECTION_CREATE = 'BOOKMARK_COLLECTION_CREATE'
export const MODAL_BOOST = 'BOOST' export const MODAL_BOOST = 'BOOST'
export const MODAL_CHAT_CONVERSATION_CREATE = 'CHAT_CONVERSATION_CREATE' export const MODAL_CHAT_CONVERSATION_CREATE = 'CHAT_CONVERSATION_CREATE'
export const MODAL_CHAT_CONVERSATION_DELETE = 'CHAT_CONVERSATION_DELETE' export const MODAL_CHAT_CONVERSATION_DELETE = 'CHAT_CONVERSATION_DELETE'
export const MODAL_COMMUNITY_TIMELINE_SETTINGS = 'COMMUNITY_TIMELINE_SETTINGS'
export const MODAL_COMPOSE = 'COMPOSE' export const MODAL_COMPOSE = 'COMPOSE'
export const MODAL_CONFIRM = 'CONFIRM' export const MODAL_CONFIRM = 'CONFIRM'
export const MODAL_DECK_COLUMN_ADD = 'DECK_COLUMN_ADD' export const MODAL_DECK_COLUMN_ADD = 'DECK_COLUMN_ADD'
@ -62,7 +63,6 @@ export const MODAL_EMAIL_CONFIRMATION_REMINDER = 'EMAIL_CONFIRMATION_REMINDER'
export const MODAL_GROUP_CREATE = 'GROUP_CREATE' export const MODAL_GROUP_CREATE = 'GROUP_CREATE'
export const MODAL_GROUP_DELETE = 'GROUP_DELETE' export const MODAL_GROUP_DELETE = 'GROUP_DELETE'
export const MODAL_GROUP_PASSWORD = 'GROUP_PASSWORD' export const MODAL_GROUP_PASSWORD = 'GROUP_PASSWORD'
export const MODAL_HASHTAG_TIMELINE_SETTINGS = 'HASHTAG_TIMELINE_SETTINGS'
export const MODAL_HOME_TIMELINE_SETTINGS = 'HOME_TIMELINE_SETTINGS' export const MODAL_HOME_TIMELINE_SETTINGS = 'HOME_TIMELINE_SETTINGS'
export const MODAL_HOTKEYS = 'HOTKEYS' export const MODAL_HOTKEYS = 'HOTKEYS'
export const MODAL_LIST_ADD_USER = 'LIST_ADD_USER' export const MODAL_LIST_ADD_USER = 'LIST_ADD_USER'

View File

@ -0,0 +1,150 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { injectIntl, defineMessages } from 'react-intl'
import { expandAccountMediaTimeline } from '../actions/timelines'
import { getAccountGallery } from '../selectors'
import ColumnIndicator from '../components/column_indicator'
import MediaItem from '../components/media_item'
import LoadMore from '../components/load_more'
import Block from '../components/block'
import MediaGalleryPlaceholder from '../components/placeholder/media_gallery_placeholder'
class AccountAlbums extends ImmutablePureComponent {
componentDidMount() {
const { accountId, mediaType } = this.props
if (accountId && accountId !== -1) {
this.props.dispatch(expandAccountMediaTimeline(accountId, { mediaType }))
}
}
componentWillReceiveProps(nextProps) {
if (
(nextProps.accountId && nextProps.accountId !== this.props.accountId) ||
(nextProps.accountId && nextProps.mediaType !== this.props.mediaType)
) {
this.props.dispatch(expandAccountMediaTimeline(nextProps.accountId, {
mediaType: nextProps.mediaType,
}))
}
}
handleScrollToBottom = () => {
if (this.props.hasMore) {
this.handleLoadMore(this.props.attachments.size > 0 ? this.props.attachments.last().getIn(['status', 'id']) : undefined)
}
}
handleScroll = (e) => {
const { scrollTop, scrollHeight, clientHeight } = e.target
const offset = scrollHeight - scrollTop - clientHeight
if (150 > offset && !this.props.isLoading) {
this.handleScrollToBottom()
}
}
handleLoadMore = (maxId) => {
if (this.props.accountId && this.props.accountId !== -1) {
this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, {
maxId,
mediaType: this.props.mediaType,
}))
}
}
handleLoadOlder = (e) => {
e.preventDefault()
this.handleScrollToBottom()
}
render() {
const {
attachments,
isLoading,
hasMore,
intl,
account,
} = this.props
if (!account) return null
return (
<Block>
<div
role='feed'
onScroll={this.handleScroll}
className={[_s.d, _s.flexRow, _s.flexWrap, _s.py5, _s.px5].join(' ')}
>
{
attachments.map((attachment, i) => (
<MediaItem
key={attachment.get('id')}
attachment={attachment}
account={account}
/>
))
}
{
isLoading && attachments.size === 0 &&
<div className={[_s.d, _s.w100PC].join(' ')}>
<MediaGalleryPlaceholder />
</div>
}
{
!isLoading && attachments.size === 0 &&
<ColumnIndicator type='error' message={intl.formatMessage(messages.none)} />
}
</div>
{
hasMore && !(isLoading && attachments.size === 0) &&
<LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />
}
</Block>
)
}
}
const messages = defineMessages({
none: { id: 'account_gallery.none', defaultMessage: 'No media to show.' },
})
const mapStateToProps = (state, { account, mediaType }) => {
const accountId = !!account ? account.get('id') : -1
return {
accountId,
attachments: getAccountGallery(state, accountId, mediaType),
isLoading: state.getIn(['timelines', `account:${accountId}:media`, 'isLoading']),
hasMore: state.getIn(['timelines', `account:${accountId}:media`, 'hasMore']),
}
}
const mapDispatchToProps = (dispatch) => ({
})
AccountAlbums.propTypes = {
dispatch: PropTypes.func.isRequired,
account: ImmutablePropTypes.map,
accountId: PropTypes.string,
attachments: ImmutablePropTypes.list.isRequired,
isLoading: PropTypes.bool,
hasMore: PropTypes.bool,
intl: PropTypes.object.isRequired,
mediaType: PropTypes.oneOf([
'photo',
'video',
]),
}
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(AccountAlbums))

View File

@ -0,0 +1,73 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { defineMessages, injectIntl } from 'react-intl'
import { changeListEditorTitle, submitListEditor } from '../actions/lists'
import { closeModal } from '../actions/modal'
import { MODAL_LIST_CREATE } from '../constants'
import Button from '../components/button'
import Input from '../components/input'
import Form from '../components/form'
import Text from '../components/text'
class BookmarkCollectionCreate extends React.PureComponent {
state = {
value: '',
}
onChange = (value) => {
this.setState({ value })
}
handleOnSubmit = () => {
this.props.onSubmit()
}
render() {
const { disabled, isModal } = this.props
const { value } = this.state
const isDisabled = !value || disabled
return (
<Form>
<Input
title='Title'
placeholder='Bookmark collection title'
value={value}
onChange={this.onChange}
/>
<Button
isDisabled={isDisabled}
onClick={this.handleOnSubmit}
className={[_s.mt10].join(' ')}
>
<Text color='inherit' align='center'>
Create
</Text>
</Button>
</Form>
)
}
}
const mapStateToProps = (state) => ({
disabled: state.getIn(['listEditor', 'isSubmitting']),
})
const mapDispatchToProps = (dispatch, { isModal }) => ({
onSubmit() {
if (isModal) dispatch(closeModal(MODAL_LIST_CREATE))
dispatch(submitListEditor(true))
},
})
BookmarkCollectionCreate.propTypes = {
onSubmit: PropTypes.func.isRequired,
isModal: PropTypes.bool,
}
export default connect(mapStateToProps, mapDispatchToProps)(BookmarkCollectionCreate)

View File

@ -3,8 +3,18 @@ import PropTypes from 'prop-types'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import ImmutablePureComponent from 'react-immutable-pure-component' import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes' import ImmutablePropTypes from 'react-immutable-proptypes'
import {
MODAL_BOOKMARK_COLLECTION_CREATE,
} from '../constants'
import {
meUsername,
} from '../initial_state'
import { fetchBookmarkCollections } from '../actions/bookmarks' import { fetchBookmarkCollections } from '../actions/bookmarks'
import { openModal } from '../actions/modal'
import ColumnIndicator from '../components/column_indicator' import ColumnIndicator from '../components/column_indicator'
import Block from '../components/block'
import Button from '../components/button'
import Text from '../components/text'
import List from '../components/list' import List from '../components/list'
class BookmarkCollections extends ImmutablePureComponent { class BookmarkCollections extends ImmutablePureComponent {
@ -13,6 +23,10 @@ class BookmarkCollections extends ImmutablePureComponent {
this.props.onFetchBookmarkCollections() this.props.onFetchBookmarkCollections()
} }
handleOpenModal = () => {
this.props.onOpenModal()
}
render() { render() {
const { const {
isLoading, isLoading,
@ -24,19 +38,32 @@ class BookmarkCollections extends ImmutablePureComponent {
return <ColumnIndicator type='error' message='Error fetching bookmark collections' /> return <ColumnIndicator type='error' message='Error fetching bookmark collections' />
} }
const listItems = shortcuts.map((s) => ({ const listItems = [{ to: `/${meUsername}/bookmark_collections/bookmarks`, title: 'Bookmarks' }].concat(!!bookmarkCollections ? bookmarkCollections.map((s) => ({
to: s.get('to'), to: s.get('to'),
title: s.get('title'), title: s.get('title'),
image: s.get('image'), })) : [])
}))
return ( return (
<List <Block>
scrollKey='bookmark-collections' <div className={[_s.d, _s.px15, _s.py10].join(' ')}>
emptyMessage='You have no bookmark collections' <div className={[_s.d, _s.flexRow, _s.aiCenter].join(' ')}>
items={listItems} <Text size='extraLarge' weight='bold'>Bookmark Collections</Text>
showLoading={isLoading} <Button
/> className={[_s.px10, _s.mlAuto].join(' ')}
onClick={this.handleOpenModal}
backgroundColor='tertiary'
color='tertiary'
icon='add'
/>
</div>
</div>
<List
scrollKey='bookmark-collections'
emptyMessage='You have no bookmark collections'
items={listItems}
showLoading={isLoading}
/>
</Block>
) )
} }
@ -49,6 +76,9 @@ const mapStateToProps = (state) => ({
}) })
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
onOpenModal() {
dispatch(openModal(MODAL_BOOKMARK_COLLECTION_CREATE))
},
onFetchBookmarkCollections() { onFetchBookmarkCollections() {
dispatch(fetchBookmarkCollections()) dispatch(fetchBookmarkCollections())
}, },
@ -58,6 +88,7 @@ BookmarkCollections.propTypes = {
isLoading: PropTypes.bool.isRequired, isLoading: PropTypes.bool.isRequired,
isError: PropTypes.bool.isRequired, isError: PropTypes.bool.isRequired,
onFetchBookmarkCollections: PropTypes.func.isRequired, onFetchBookmarkCollections: PropTypes.func.isRequired,
onOpenModal: PropTypes.func.isRequired,
bookmarkCollections: ImmutablePropTypes.list, bookmarkCollections: ImmutablePropTypes.list,
} }

View File

@ -13,11 +13,11 @@ import ColumnIndicator from '../components/column_indicator'
class BookmarkedStatuses extends ImmutablePureComponent { class BookmarkedStatuses extends ImmutablePureComponent {
componentWillMount() { componentWillMount() {
this.props.dispatch(fetchBookmarkedStatuses()) this.props.dispatch(fetchBookmarkedStatuses(this.props.bookmarkCollectionId))
} }
handleLoadMore = debounce(() => { handleLoadMore = debounce(() => {
this.props.dispatch(expandBookmarkedStatuses()) this.props.dispatch(expandBookmarkedStatuses(this.props.bookmarkCollectionId))
}, 300, { leading: true }) }, 300, { leading: true })
render() { render() {
@ -46,14 +46,13 @@ class BookmarkedStatuses extends ImmutablePureComponent {
} }
const mapStateToProps = (state, { params: { username } }) => { const mapStateToProps = (state, { params: { username, bookmarkCollectionId } }) => ({
return { bookmarkCollectionId,
isMyAccount: (username.toLowerCase() === meUsername.toLowerCase()), isMyAccount: (username.toLowerCase() === meUsername.toLowerCase()),
statusIds: state.getIn(['status_lists', 'bookmarks', 'items']), statusIds: state.getIn(['status_lists', 'bookmarks', 'items']),
isLoading: state.getIn(['status_lists', 'bookmarks', 'isLoading'], true), isLoading: state.getIn(['status_lists', 'bookmarks', 'isLoading'], true),
hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']), hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']),
} })
}
BookmarkedStatuses.propTypes = { BookmarkedStatuses.propTypes = {
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,

View File

@ -21,7 +21,6 @@ class ChatConversationCreate extends React.PureComponent {
} }
handleOnCreateChatConversation = (accountId) => { handleOnCreateChatConversation = (accountId) => {
console.log("handleOnCreateChatConversation:", accountId)
this.props.onCreateChatConversation(accountId) this.props.onCreateChatConversation(accountId)
} }
@ -69,7 +68,6 @@ const mapStateToProps = (state) => ({
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
onChange: (value) => { onChange: (value) => {
console.log("value", value)
dispatch(fetchChatConversationAccountSuggestions(value)) dispatch(fetchChatConversationAccountSuggestions(value))
}, },
onCreateChatConversation: (accountId) => { onCreateChatConversation: (accountId) => {

View File

@ -1,87 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { injectIntl, FormattedMessage } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import debounce from 'lodash.debounce'
import { me } from '../initial_state'
import {
fetchChatMessengerMutes,
expandChatMessengerMutes,
unmuteChatMessenger,
} from '../actions/chat_conversation_accounts'
import Account from '../components/account'
import Block from '../components/block'
import BlockHeading from '../components/block_heading'
import ScrollableList from '../components/scrollable_list'
class ChatConversationMutedAccounts extends ImmutablePureComponent {
componentWillMount() {
this.props.onFetchMutes()
}
handleLoadMore = debounce(() => {
this.props.onExpandMutes()
}, 300, { leading: true })
render() {
const {
accountIds,
hasMore,
isLoading,
} = this.props
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={<FormattedMessage id='navigation_bar.chat_mutes' defaultMessage='Muted chat users' />} />
</div>
<ScrollableList
scrollKey='chat_muted_accounts'
onLoadMore={this.handleLoadMore}
hasMore={hasMore}
isLoading={isLoading}
emptyMessage={<FormattedMessage id='empty_column.chat_mutes' defaultMessage="You haven't muted any chat users yet." />}
>
{
accountIds && accountIds.map((id) =>
<Account
key={`mutes-${id}`}
id={id}
compact
actionIcon='subtract'
onActionClick={() => this.props.onRemove(id)}
actionTitle='Remove'
/>
)
}
</ScrollableList>
</div>
)
}
}
const mapStateToProps = (state) => ({
accountIds: state.getIn(['user_lists', 'chat_mutes', me, 'items']),
hasMore: !!state.getIn(['user_lists', 'chat_mutes', me, 'next']),
isLoading: state.getIn(['user_lists', 'chat_mutes', me, 'isLoading']),
})
const mapDispatchToProps = (dispatch) => ({
onFetchMutes: () => dispatch(fetchChatMessengerMutes()),
onExpandMutes: () => dispatch(expandChatMessengerMutes()),
onRemove: (accountId) => dispatch(unmuteChatMessenger(accountId)),
})
ChatConversationMutedAccounts.propTypes = {
accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool,
isLoading: PropTypes.bool,
onExpandMutes: PropTypes.func.isRequired,
onFetchMutes: PropTypes.func.isRequired,
}
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ChatConversationMutedAccounts))

View File

@ -30,34 +30,38 @@ class ComposeDestinationHeader extends ImmutablePureComponent {
} }
render() { render() {
const { account, isModal } = this.props const { account, isModal, formLocation } = this.props
const isIntroduction = formLocation === 'introduction'
const title = 'Post to timeline' const title = 'Post to timeline'
return ( return (
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.bgPrimary, _s.w100PC, _s.h40PX, _s.pr15].join(' ')}> <div className={[_s.d, _s.flexRow, _s.aiCenter, _s.bgPrimary, _s.w100PC, _s.h40PX, _s.pr15].join(' ')}>
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.pl15, _s.flexGrow1, _s.mrAuto, _s.h40PX].join(' ')}> <div className={[_s.d, _s.flexRow, _s.aiCenter, _s.pl15, _s.flexGrow1, _s.mrAuto, _s.h40PX].join(' ')}>
<Avatar account={account} size={28} /> <Avatar account={account} size={28} />
<div className={[_s.ml15].join(' ')}> {
<Button !isIntroduction &&
isNarrow <div className={[_s.ml15].join(' ')}>
isOutline <Button
radiusSmall isNarrow
buttonRef={this.setDestinationBtn} isOutline
backgroundColor='secondary' radiusSmall
color='primary' buttonRef={this.setDestinationBtn}
onClick={this.handleOnClick} backgroundColor='secondary'
className={[_s.border1PX, _s.borderColorPrimary].join(' ')} color='primary'
> onClick={this.handleOnClick}
<Text color='inherit' size='small' className={_s.jcCenter}> className={[_s.border1PX, _s.borderColorPrimary].join(' ')}
{title} >
<Icon id='caret-down' size='8px' className={_s.ml5} /> <Text color='inherit' size='small' className={_s.jcCenter}>
</Text> {title}
</Button> <Icon id='caret-down' size='8px' className={_s.ml5} />
</div> </Text>
</Button>
</div>
}
</div> </div>
{ {
!isModal && !isModal && !isIntroduction &&
<Button <Button
isText isText
isNarrow isNarrow
@ -89,6 +93,7 @@ ComposeDestinationHeader.propTypes = {
isModal: PropTypes.bool, isModal: PropTypes.bool,
onOpenModal: PropTypes.func.isRequired, onOpenModal: PropTypes.func.isRequired,
onOpenPopover: PropTypes.func.isRequired, onOpenPopover: PropTypes.func.isRequired,
formLocation: PropTypes.string,
} }
export default connect(null, mapDispatchToProps)(ComposeDestinationHeader) export default connect(null, mapDispatchToProps)(ComposeDestinationHeader)

View File

@ -57,6 +57,7 @@ class ComposeExtraButtonList extends React.PureComponent {
const isXS = width <= BREAKPOINT_EXTRA_SMALL const isXS = width <= BREAKPOINT_EXTRA_SMALL
const isStandalone = formLocation === 'standalone' const isStandalone = formLocation === 'standalone'
const isTimeline = formLocation === 'timeline' const isTimeline = formLocation === 'timeline'
const isIntroduction = formLocation === 'introduction'
const small = (!isModal && isXS && !isStandalone) || isTimeline const small = (!isModal && isXS && !isStandalone) || isTimeline
console.log("small, formLocation:", small, formLocation) console.log("small, formLocation:", small, formLocation)
@ -84,8 +85,8 @@ class ComposeExtraButtonList extends React.PureComponent {
<UploadButton small={small} /> <UploadButton small={small} />
<EmojiPickerButton isMatch={isMatch} small={small} /> <EmojiPickerButton isMatch={isMatch} small={small} />
{ !edit && <PollButton small={small} /> } { !edit && <PollButton small={small} /> }
<StatusVisibilityButton small={small} /> { !isIntroduction && <StatusVisibilityButton small={small} /> }
<SpoilerButton small={small} /> { !isIntroduction && <SpoilerButton small={small} /> }
{ !hidePro && !edit && <SchedulePostButton small={small} /> } { !hidePro && !edit && <SchedulePostButton small={small} /> }
{ !hidePro && !edit && <ExpiresPostButton small={small} /> } { !hidePro && !edit && <ExpiresPostButton small={small} /> }
{ !hidePro && !isXS && <RichTextEditorButton small={small} /> } { !hidePro && !isXS && <RichTextEditorButton small={small} /> }

View File

@ -273,7 +273,7 @@ class ComposeForm extends ImmutablePureComponent {
<div className={[_s.d, _s.calcMaxH410PX, _s.overflowYScroll].join(' ')}> <div className={[_s.d, _s.calcMaxH410PX, _s.overflowYScroll].join(' ')}>
<Responsive min={BREAKPOINT_EXTRA_SMALL}> <Responsive min={BREAKPOINT_EXTRA_SMALL}>
<ComposeDestinationHeader account={account} isModal={isModalOpen} /> <ComposeDestinationHeader formLocation={formLocation} account={account} isModal={isModalOpen} />
</Responsive> </Responsive>
<div className={containerClasses} ref={this.setForm} onClick={this.handleClick}> <div className={containerClasses} ref={this.setForm} onClick={this.handleClick}>

View File

@ -25,7 +25,9 @@ import Text from '../components/text'
import { import {
AccountTimeline, AccountTimeline,
Compose, Compose,
GroupTimeline,
LikedStatuses, LikedStatuses,
ListTimeline,
HomeTimeline, HomeTimeline,
Notifications, Notifications,
HashtagTimeline, HashtagTimeline,
@ -73,7 +75,7 @@ class Deck extends React.PureComponent {
let Component = null let Component = null
let componentParams = {} let componentParams = {}
let title, icon = '' let title, subtitle, icon = ''
switch (deckColumn) { switch (deckColumn) {
case 'notifications': case 'notifications':
@ -123,18 +125,32 @@ class Deck extends React.PureComponent {
break break
} }
// : todo :
if (!Component) { if (!Component) {
if (deckColumn.indexOf('user.') > -1) { if (deckColumn.indexOf('user.') > -1) {
} else if (deckColumn.indexOf('list.') > -1) { } else if (deckColumn.indexOf('list.') > -1) {
const listId = deckColumn.replace('list.', '')
title = 'List'
subtitle = listId
icon = 'list'
Component = ListTimeline
componentParams = { params: { id: listId }}
} else if (deckColumn.indexOf('group.') > -1) { } else if (deckColumn.indexOf('group.') > -1) {
const groupId = deckColumn.replace('group.', '')
title = 'Group'
subtitle = groupId
icon = 'group'
Component = GroupTimeline
componentParams = { params: { id: groupId }}
} else if (deckColumn.indexOf('news.') > -1) { } else if (deckColumn.indexOf('news.') > -1) {
// : todo :
} else if (deckColumn.indexOf('hashtag.') > -1) { } else if (deckColumn.indexOf('hashtag.') > -1) {
const hashtag = deckColumn.replace('hashtag.', '')
title = 'Hashtag'
subtitle = hashtag
icon = 'apps'
Component = HashtagTimeline
componentParams = { params: { id: hashtag }}
} }
} }
@ -146,7 +162,7 @@ class Deck extends React.PureComponent {
index={index} index={index}
sortIndex={index} sortIndex={index}
> >
<DeckColumn title={title} icon={icon} index={index}> <DeckColumn title={title} subtitle={subtitle} icon={icon} index={index}>
<WrappedBundle component={Component} componentParams={componentParams} /> <WrappedBundle component={Component} componentParams={componentParams} />
</DeckColumn> </DeckColumn>
</SortableItem> </SortableItem>
@ -158,6 +174,8 @@ class Deck extends React.PureComponent {
const isEmpty = gabDeckOrder.size === 0 const isEmpty = gabDeckOrder.size === 0
console.log("gabDeckOrder:", gabDeckOrder)
return ( return (
<SortableContainer <SortableContainer
axis='x' axis='x'

View File

@ -22,10 +22,6 @@ class ListTimeline extends ImmutablePureComponent {
this.handleConnect(this.props.params.id) this.handleConnect(this.props.params.id)
} }
componentWillUnmount() {
this.handleDisconnect()
}
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (nextProps.params.id !== this.props.params.id) { if (nextProps.params.id !== this.props.params.id) {
this.handleConnect(nextProps.params.id) this.handleConnect(nextProps.params.id)

View File

@ -193,4 +193,4 @@ ChatMessagesComposeForm.propTypes = {
onSendMessage: PropTypes.func.isRequired, onSendMessage: PropTypes.func.isRequired,
} }
export default connect(mapDispatchToProps)(ChatMessagesComposeForm) export default connect(null, mapDispatchToProps)(ChatMessagesComposeForm)

View File

@ -30,10 +30,6 @@ class ChatSettingsSidebar extends React.PureComponent {
title: 'Blocked Chats', title: 'Blocked Chats',
to: '/messages/blocks', to: '/messages/blocks',
}, },
{
title: 'Muted Chats',
to: '/messages/mutes',
},
]} ]}
/> />
</ResponsiveClassesComponent> </ResponsiveClassesComponent>

View File

@ -56,16 +56,17 @@ import {
AccountGallery, AccountGallery,
AccountTimeline, AccountTimeline,
AccountCommentsTimeline, AccountCommentsTimeline,
AlbumCreate,
Assets, Assets,
BlockedAccounts, BlockedAccounts,
BookmarkCollections, BookmarkCollections,
BookmarkCollectionCreate,
BookmarkedStatuses, BookmarkedStatuses,
CaliforniaConsumerProtection, CaliforniaConsumerProtection,
CaliforniaConsumerProtectionContact, CaliforniaConsumerProtectionContact,
ChatConversationCreate, ChatConversationCreate,
ChatConversationRequests, ChatConversationRequests,
ChatConversationBlockedAccounts, ChatConversationBlockedAccounts,
ChatConversationMutedAccounts,
CommunityTimeline, CommunityTimeline,
Compose, Compose,
Deck, Deck,
@ -219,7 +220,6 @@ class SwitchingArea extends React.PureComponent {
<WrappedRoute path='/messages/settings' exact page={MessagesPage} component={MessagesSettings} content={children} componentParams={{ isSettings: true }} /> <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/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/blocks' exact page={MessagesPage} component={ChatConversationBlockedAccounts} content={children} componentParams={{ isSettings: true }} />
<WrappedRoute path='/messages/mutes' exact page={MessagesPage} component={ChatConversationMutedAccounts} content={children} componentParams={{ isSettings: true }} />
<WrappedRoute path='/messages/:chatConversationId' exact page={MessagesPage} component={Messages} content={children} componentParams={{ source: 'approved' }} /> <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' }} /> <WrappedRoute path='/timeline/all' exact page={CommunityPage} component={CommunityTimeline} content={children} componentParams={{ title: 'Community Feed' }} />
@ -278,9 +278,13 @@ class SwitchingArea extends React.PureComponent {
<WrappedRoute path='/:username/videos' page={ProfilePage} component={AccountGallery} content={children} componentParams={{ noSidebar: true, mediaType: 'video' }} /> <WrappedRoute path='/:username/videos' page={ProfilePage} component={AccountGallery} content={children} componentParams={{ noSidebar: true, mediaType: 'video' }} />
<WrappedRoute path='/:username/albums' page={ProfilePage} component={AccountAlbums} content={children} componentParams={{ noSidebar: true, mediaType: 'photo' }} /> <WrappedRoute path='/:username/albums' page={ProfilePage} component={AccountAlbums} content={children} componentParams={{ noSidebar: true, mediaType: 'photo' }} />
<WrappedRoute path='/:username/album_create' page={ModalPage} component={AlbumCreate} content={children} componentParams={{ title: 'Create Album', page: 'create-album' }} />
<WrappedRoute path='/:username/album_edit/:albumId' page={ModalPage} component={AlbumCreate} content={children} componentParams={{ title: 'Create Album', page: 'edit-album' }} />
<WrappedRoute path='/:username/likes' page={ProfilePage} component={LikedStatuses} content={children} /> <WrappedRoute path='/:username/likes' page={ProfilePage} component={LikedStatuses} content={children} />
<WrappedRoute path='/:username/bookmarks' page={ProfilePage} component={BookmarkCollections} content={children} /> <WrappedRoute path='/:username/bookmark_collections' page={ProfilePage} component={BookmarkCollections} content={children} />
<WrappedRoute path='/:username/:bookmarkCollectionId/bookmarks' page={ProfilePage} component={BookmarkedStatuses} content={children} /> <WrappedRoute path='/:username/bookmark_collections/create' page={ModalPage} component={BookmarkCollectionCreate} content={children} componentParams={{ title: 'Create Bookmark Collection', page: 'create-bookmark-collection' }} />
<WrappedRoute path='/:username/bookmark_collections/:bookmarkCollectionId' page={ProfilePage} component={BookmarkedStatuses} content={children} />
<WrappedRoute path='/:username/posts/:statusId' publicRoute exact page={BasicPage} component={StatusFeature} content={children} componentParams={{ title: 'Status', page: 'status' }} /> <WrappedRoute path='/:username/posts/:statusId' publicRoute exact page={BasicPage} component={StatusFeature} content={children} componentParams={{ title: 'Status', page: 'status' }} />

View File

@ -3,10 +3,14 @@ export function AboutSidebar() { return import(/* webpackChunkName: "components/
export function AccountTimeline() { return import(/* webpackChunkName: "features/account_timeline" */'../../account_timeline') } export function AccountTimeline() { return import(/* webpackChunkName: "features/account_timeline" */'../../account_timeline') }
export function AccountCommentsTimeline() { return import(/* webpackChunkName: "features/account_comments_timeline" */'../../account_comments_timeline') } export function AccountCommentsTimeline() { return import(/* webpackChunkName: "features/account_comments_timeline" */'../../account_comments_timeline') }
export function AccountGallery() { return import(/* webpackChunkName: "features/account_gallery" */'../../account_gallery') } export function AccountGallery() { return import(/* webpackChunkName: "features/account_gallery" */'../../account_gallery') }
export function AlbumCreate() { return import(/* webpackChunkName: "features/album_create" */'../../album_create') }
export function AlbumCreateModal() { return import(/* webpackChunkName: "components/album_create_modal" */'../../../components/modal/album_create_modal') }
export function Assets() { return import(/* webpackChunkName: "features/about/assets" */'../../about/assets') } export function Assets() { return import(/* webpackChunkName: "features/about/assets" */'../../about/assets') }
export function BlockAccountModal() { return import(/* webpackChunkName: "components/block_account_modal" */'../../../components/modal/block_account_modal') } export function BlockAccountModal() { return import(/* webpackChunkName: "components/block_account_modal" */'../../../components/modal/block_account_modal') }
export function BlockedAccounts() { return import(/* webpackChunkName: "features/blocked_accounts" */'../../blocked_accounts') } export function BlockedAccounts() { return import(/* webpackChunkName: "features/blocked_accounts" */'../../blocked_accounts') }
export function BookmarkCollections() { return import(/* webpackChunkName: "features/bookmark_collections" */'../../bookmark_collections') } export function BookmarkCollections() { return import(/* webpackChunkName: "features/bookmark_collections" */'../../bookmark_collections') }
export function BookmarkCollectionCreate() { return import(/* webpackChunkName: "features/bookmark_collection_create" */'../../bookmark_collection_create') }
export function BookmarkCollectionCreateModal() { return import(/* webpackChunkName: "components/bookmark_collection_create_modal" */'../../../components/modal/bookmark_collection_create_modal') }
export function BookmarkedStatuses() { return import(/* webpackChunkName: "features/bookmarked_statuses" */'../../bookmarked_statuses') } export function BookmarkedStatuses() { return import(/* webpackChunkName: "features/bookmarked_statuses" */'../../bookmarked_statuses') }
export function BoostModal() { return import(/* webpackChunkName: "components/boost_modal" */'../../../components/modal/boost_modal') } export function BoostModal() { return import(/* webpackChunkName: "components/boost_modal" */'../../../components/modal/boost_modal') }
export function CaliforniaConsumerProtection() { return import(/* webpackChunkName: "features/california_consumer_protection" */'../../about/california_consumer_protection') } export function CaliforniaConsumerProtection() { return import(/* webpackChunkName: "features/california_consumer_protection" */'../../about/california_consumer_protection') }
@ -15,13 +19,11 @@ export function ChatConversationBlockedAccounts() { return import(/* webpackChun
export function ChatConversationCreate() { return import(/* webpackChunkName: "features/chat_conversation_create" */'../../chat_conversation_create') } export function ChatConversationCreate() { return import(/* webpackChunkName: "features/chat_conversation_create" */'../../chat_conversation_create') }
export function ChatConversationCreateModal() { return import(/* webpackChunkName: "components/chat_conversation_create_modal" */'../../../components/modal/chat_conversation_create_modal') } export function ChatConversationCreateModal() { return import(/* webpackChunkName: "components/chat_conversation_create_modal" */'../../../components/modal/chat_conversation_create_modal') }
export function ChatConversationDeleteModal() { return import(/* webpackChunkName: "components/chat_conversation_delete_modal" */'../../../components/modal/chat_conversation_delete_modal') } export function ChatConversationDeleteModal() { return import(/* webpackChunkName: "components/chat_conversation_delete_modal" */'../../../components/modal/chat_conversation_delete_modal') }
export function ChatConversationMutedAccounts() { return import(/* webpackChunkName: "features/chat_conversation_muted_accounts" */'../../chat_conversation_muted_accounts') }
export function ChatConversationOptionsPopover() { return import(/* webpackChunkName: "components/chat_conversation_options_popover" */'../../../components/popover/chat_conversation_options_popover') } 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 ChatConversationRequests() { return import(/* webpackChunkName: "features/chat_conversation_requests" */'../../chat_conversation_requests') }
export function ChatMessageOptionsPopover() { return import(/* webpackChunkName: "components/chat_message_options_popover" */'../../../components/popover/chat_message_options_popover') } export function ChatMessageOptionsPopover() { return import(/* webpackChunkName: "components/chat_message_options_popover" */'../../../components/popover/chat_message_options_popover') }
export function CommentSortingOptionsPopover() { return import(/* webpackChunkName: "components/comment_sorting_options_popover" */'../../../components/popover/comment_sorting_options_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 CommunityTimeline() { return import(/* webpackChunkName: "features/community_timeline" */'../../community_timeline') }
export function CommunityTimelineSettingsModal() { return import(/* webpackChunkName: "components/community_timeline_settings_modal" */'../../../components/modal/community_timeline_settings_modal') }
export function Compose() { return import(/* webpackChunkName: "features/compose" */'../../compose') } export function Compose() { return import(/* webpackChunkName: "features/compose" */'../../compose') }
export function ComposeForm() { return import(/* webpackChunkName: "components/compose_form" */'../../compose/components/compose_form') } export function ComposeForm() { return import(/* webpackChunkName: "components/compose_form" */'../../compose/components/compose_form') }
export function ComposeModal() { return import(/* webpackChunkName: "components/compose_modal" */'../../../components/modal/compose_modal') } export function ComposeModal() { return import(/* webpackChunkName: "components/compose_modal" */'../../../components/modal/compose_modal') }
@ -70,7 +72,6 @@ export function GroupsCategories() { return import(/* webpackChunkName: "feature
export function GroupCategory() { return import(/* webpackChunkName: "features/group_category" */'../../group_category') } export function GroupCategory() { return import(/* webpackChunkName: "features/group_category" */'../../group_category') }
export function GroupTag() { return import(/* webpackChunkName: "features/group_tag" */'../../group_tag') } export function GroupTag() { return import(/* webpackChunkName: "features/group_tag" */'../../group_tag') }
export function HashtagTimeline() { return import(/* webpackChunkName: "features/hashtag_timeline" */'../../hashtag_timeline') } export function HashtagTimeline() { return import(/* webpackChunkName: "features/hashtag_timeline" */'../../hashtag_timeline') }
export function HashtagTimelineSettingsModal() { return import(/* webpackChunkName: "components/hashtag_timeline_settings_modal" */'../../../components/modal/hashtag_timeline_settings_modal') }
export function HomeTimeline() { return import(/* webpackChunkName: "features/home_timeline" */'../../home_timeline') } export function HomeTimeline() { return import(/* webpackChunkName: "features/home_timeline" */'../../home_timeline') }
export function HomeTimelineSettingsModal() { return import(/* webpackChunkName: "components/home_timeline_settings_modal" */'../../../components/modal/home_timeline_settings_modal') } export function HomeTimelineSettingsModal() { return import(/* webpackChunkName: "components/home_timeline_settings_modal" */'../../../components/modal/home_timeline_settings_modal') }
export function HotkeysModal() { return import(/* webpackChunkName: "components/hotkeys_modal" */'../../../components/modal/hotkeys_modal') } export function HotkeysModal() { return import(/* webpackChunkName: "components/hotkeys_modal" */'../../../components/modal/hotkeys_modal') }

View File

@ -41,7 +41,7 @@ class DeckLayout extends React.PureComponent {
if (isXS) { if (isXS) {
return ( return (
<div className={[_s.d, _s.aiCenter, _s.jcCenter, _s.w100PC, _s.h100VH, _s.bgTertiary].join(' ')}> <div className={[_s.d, _s.aiCenter, _s.jcCenter, _s.w100PC, _s.h100VH, _s.bgTertiary, _s.px15, _s.py15].join(' ')}>
<Text className={_s.mb10}>Gab Deck is not available on mobile or tablet devices. Please only access using a desktop computer.</Text> <Text className={_s.mb10}>Gab Deck is not available on mobile or tablet devices. Please only access using a desktop computer.</Text>
<Button to='/home'>Return home</Button> <Button to='/home'>Return home</Button>
</div> </div>

View File

@ -110,7 +110,7 @@ class ProfileLayout extends ImmutablePureComponent {
<div className={[_s.d, _s.w100PC, , _s.flexRow, _s.jcEnd, _s.py15].join(' ')}> <div className={[_s.d, _s.w100PC, , _s.flexRow, _s.jcEnd, _s.py15].join(' ')}>
<div className={[_s.d, _s.w100PC, _s.z1].join(' ')}> <div className={[_s.d, _s.w100PC, _s.z1].join(' ')}>
<div className={_s.d}> <div className={[_s.d, _s.boxShadowNone].join(' ')}>
{children} {children}
</div> </div>
</div> </div>

View File

@ -1,11 +1,8 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { defineMessages, injectIntl } from 'react-intl' import { defineMessages, injectIntl } from 'react-intl'
import { openModal } from '../actions/modal'
import PageTitle from '../features/ui/util/page_title' import PageTitle from '../features/ui/util/page_title'
import DefaultLayout from '../layouts/default_layout' import DefaultLayout from '../layouts/default_layout'
import { MODAL_COMMUNITY_TIMELINE_SETTINGS } from '../constants'
import { import {
LinkFooter, LinkFooter,
GroupsPanel, GroupsPanel,
@ -16,10 +13,6 @@ import {
class CommunityPage extends React.PureComponent { class CommunityPage extends React.PureComponent {
onOpenCommunityPageSettingsModal = () => {
this.props.dispatch(openModal(MODAL_COMMUNITY_TIMELINE_SETTINGS))
}
render() { render() {
const { children, intl } = this.props const { children, intl } = this.props
@ -29,12 +22,6 @@ class CommunityPage extends React.PureComponent {
<DefaultLayout <DefaultLayout
title={title} title={title}
page='community' page='community'
actions={[
{
icon: 'ellipsis',
onClick: this.onOpenCommunityPageSettingsModal,
},
]}
layout={[ layout={[
ProgressPanel, ProgressPanel,
TrendsBreakingPanel, TrendsBreakingPanel,
@ -60,4 +47,4 @@ CommunityPage.propTypes = {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
} }
export default injectIntl(connect()(CommunityPage)) export default injectIntl(CommunityPage)

View File

@ -1,12 +1,9 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { openModal } from '../actions/modal'
import { defineMessages, injectIntl } from 'react-intl' import { defineMessages, injectIntl } from 'react-intl'
import isObject from 'lodash.isobject' import isObject from 'lodash.isobject'
import PageTitle from '../features/ui/util/page_title' import PageTitle from '../features/ui/util/page_title'
import DefaultLayout from '../layouts/default_layout' import DefaultLayout from '../layouts/default_layout'
import { MODAL_HASHTAG_TIMELINE_SETTINGS } from '../constants'
import { import {
LinkFooter, LinkFooter,
ProgressPanel, ProgressPanel,
@ -16,17 +13,6 @@ import {
class HashtagPage extends React.PureComponent { class HashtagPage extends React.PureComponent {
onOpenHashtagPageSettingsModal = () => {
const { params } = this.props
const hashtag = isObject(params) ? params.id : ''
if (!hashtag) return
this.props.dispatch(openModal(MODAL_HASHTAG_TIMELINE_SETTINGS, {
hashtag,
}))
}
render() { render() {
const { const {
children, children,
@ -41,12 +27,6 @@ class HashtagPage extends React.PureComponent {
showBackBtn showBackBtn
title={intl.formatMessage(messages.hashtagTimeline)} title={intl.formatMessage(messages.hashtagTimeline)}
page={`hashtag.${hashtag}`} page={`hashtag.${hashtag}`}
actions={[
{
icon: 'ellipsis',
onClick: this.onOpenHashtagPageSettingsModal,
},
]}
layout={[ layout={[
ProgressPanel, ProgressPanel,
TrendsBreakingPanel, TrendsBreakingPanel,
@ -73,4 +53,4 @@ HashtagPage.propTypes = {
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,
} }
export default injectIntl(connect()(HashtagPage)) export default injectIntl(HashtagPage)

View File

@ -10,41 +10,47 @@ import {
ACCOUNT_MUTE_SUCCESS, ACCOUNT_MUTE_SUCCESS,
ACCOUNT_UNMUTE_SUCCESS, ACCOUNT_UNMUTE_SUCCESS,
RELATIONSHIPS_FETCH_SUCCESS, RELATIONSHIPS_FETCH_SUCCESS,
} from '../actions/accounts'; } from '../actions/accounts'
import { Map as ImmutableMap, fromJS } from 'immutable'; import {
BLOCK_CHAT_MESSAGER_SUCCESS,
UNBLOCK_CHAT_MESSAGER_SUCCESS,
} from '../actions/chat_conversation_accounts'
import { Map as ImmutableMap, fromJS } from 'immutable'
const normalizeRelationship = (state, relationship) => state.set(relationship.id, fromJS(relationship)); const normalizeRelationship = (state, relationship) => state.set(relationship.id, fromJS(relationship))
const normalizeRelationships = (state, relationships) => { const normalizeRelationships = (state, relationships) => {
relationships.forEach(relationship => { relationships.forEach(relationship => {
state = normalizeRelationship(state, relationship); state = normalizeRelationship(state, relationship)
}); })
return state; return state
}; }
const initialState = ImmutableMap(); const initialState = ImmutableMap()
export default function relationships(state = initialState, action) { export default function relationships(state = initialState, action) {
switch(action.type) { switch(action.type) {
case ACCOUNT_FOLLOW_REQUEST: case ACCOUNT_FOLLOW_REQUEST:
return state.setIn([action.id, action.locked ? 'requested' : 'following'], true); return state.setIn([action.id, action.locked ? 'requested' : 'following'], true)
case ACCOUNT_FOLLOW_FAIL: case ACCOUNT_FOLLOW_FAIL:
return state.setIn([action.id, action.locked ? 'requested' : 'following'], false); return state.setIn([action.id, action.locked ? 'requested' : 'following'], false)
case ACCOUNT_UNFOLLOW_REQUEST: case ACCOUNT_UNFOLLOW_REQUEST:
return state.setIn([action.id, 'following'], false); return state.setIn([action.id, 'following'], false)
case ACCOUNT_UNFOLLOW_FAIL: case ACCOUNT_UNFOLLOW_FAIL:
return state.setIn([action.id, 'following'], true); return state.setIn([action.id, 'following'], true)
case ACCOUNT_FOLLOW_SUCCESS: case ACCOUNT_FOLLOW_SUCCESS:
case ACCOUNT_UNFOLLOW_SUCCESS: case ACCOUNT_UNFOLLOW_SUCCESS:
case ACCOUNT_BLOCK_SUCCESS: case ACCOUNT_BLOCK_SUCCESS:
case ACCOUNT_UNBLOCK_SUCCESS: case ACCOUNT_UNBLOCK_SUCCESS:
case ACCOUNT_MUTE_SUCCESS: case ACCOUNT_MUTE_SUCCESS:
case ACCOUNT_UNMUTE_SUCCESS: case ACCOUNT_UNMUTE_SUCCESS:
return normalizeRelationship(state, action.relationship); case BLOCK_CHAT_MESSAGER_SUCCESS:
case UNBLOCK_CHAT_MESSAGER_SUCCESS:
return normalizeRelationship(state, action.relationship)
case RELATIONSHIPS_FETCH_SUCCESS: case RELATIONSHIPS_FETCH_SUCCESS:
return normalizeRelationships(state, action.relationships); return normalizeRelationships(state, action.relationships)
default: default:
return state; return state
} }
}; }

View File

@ -50,18 +50,12 @@ const initialState = ImmutableMap({
theme: 'white', theme: 'white',
}), }),
home: ImmutableMap({ // home: ImmutableMap({
shows: ImmutableMap({ // shows: ImmutableMap({
reply: true, // reply: true,
repost: true, // repost: true,
}), // }),
}), // }),
community: ImmutableMap({
shows: ImmutableMap({
onlyMedia: false,
}),
}),
}) })
const defaultColumns = fromJS([ const defaultColumns = fromJS([

View File

@ -5,14 +5,19 @@ import {
} from '../actions/toasts' } from '../actions/toasts'
import { Map as ImmutableMap, List as ImmutableList } from 'immutable' import { Map as ImmutableMap, List as ImmutableList } from 'immutable'
const makeMessageFromData = (data) => {
return `${data.type}`.split('_').join(' ').toLowerCase()
}
const initialState = ImmutableList([]) const initialState = ImmutableList([])
export default function toasts(state = initialState, action) { export default function toasts(state = initialState, action) {
switch(action.type) { switch(action.type) {
case TOAST_SHOW: case TOAST_SHOW:
console.log("action:", action)
return state.set(state.size, ImmutableMap({ return state.set(state.size, ImmutableMap({
key: state.size > 0 ? state.last().get('key') + 1 : 0, key: state.size > 0 ? state.last().get('key') + 1 : 0,
message: 'action.message', message: makeMessageFromData(action.toastData),
type: action.toastType, type: action.toastType,
})) }))
case TOAST_DISMISS: case TOAST_DISMISS:

View File

@ -62,13 +62,6 @@ import {
CHAT_MESSENGER_BLOCKS_EXPAND_REQUEST, CHAT_MESSENGER_BLOCKS_EXPAND_REQUEST,
CHAT_MESSENGER_BLOCKS_EXPAND_SUCCESS, CHAT_MESSENGER_BLOCKS_EXPAND_SUCCESS,
CHAT_MESSENGER_BLOCKS_EXPAND_FAIL, CHAT_MESSENGER_BLOCKS_EXPAND_FAIL,
CHAT_MESSENGER_MUTES_FETCH_REQUEST,
CHAT_MESSENGER_MUTES_FETCH_SUCCESS,
CHAT_MESSENGER_MUTES_FETCH_FAIL,
CHAT_MESSENGER_MUTES_EXPAND_REQUEST,
CHAT_MESSENGER_MUTES_EXPAND_SUCCESS,
CHAT_MESSENGER_MUTES_EXPAND_FAIL,
} from '../actions/chat_conversation_accounts' } from '../actions/chat_conversation_accounts'
import { import {
GROUP_MEMBERS_FETCH_SUCCESS, GROUP_MEMBERS_FETCH_SUCCESS,
@ -243,17 +236,6 @@ export default function userLists(state = initialState, action) {
case CHAT_MESSENGER_BLOCKS_EXPAND_FAIL: case CHAT_MESSENGER_BLOCKS_EXPAND_FAIL:
return setListFailed(state, 'chat_blocks', me) return setListFailed(state, 'chat_blocks', me)
case CHAT_MESSENGER_MUTES_FETCH_REQUEST:
case CHAT_MESSENGER_MUTES_EXPAND_REQUEST:
return state.setIn(['chat_mutes', me, 'isLoading'], true)
case CHAT_MESSENGER_MUTES_FETCH_SUCCESS:
return normalizeList(state, 'chat_mutes', me, action.accounts, action.next)
case CHAT_MESSENGER_MUTES_EXPAND_SUCCESS:
return appendToList(state, 'chat_mutes', me, action.accounts, action.next)
case CHAT_MESSENGER_MUTES_FETCH_FAIL:
case CHAT_MESSENGER_MUTES_EXPAND_FAIL:
return setListFailed(state, 'chat_mutes', me)
default: default:
return state; return state;
} }

View File

@ -232,7 +232,7 @@ export const getToasts = createSelector([
let arr = [] let arr = []
base.forEach(item => { base.forEach((item) => {
arr.push({ arr.push({
message: item.get('message'), message: item.get('message'),
type: item.get('type'), type: item.get('type'),
@ -241,4 +241,20 @@ export const getToasts = createSelector([
}) })
return arr return arr
})
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

@ -576,6 +576,7 @@ pre {
.h220PX { height: 220px; } .h220PX { height: 220px; }
.h215PX { height: 215px; } .h215PX { height: 215px; }
.h200PX { height: 200px; } .h200PX { height: 200px; }
.h172PX { height: 172px; }
.h158PX { height: 158px; } .h158PX { height: 158px; }
.h122PX { height: 122px; } .h122PX { height: 122px; }
.h60PX { height: 60px; } .h60PX { height: 60px; }

View File

@ -14,6 +14,7 @@
# updated_at :datetime not null # updated_at :datetime not null
# unread_count :bigint(8) default(0), not null # unread_count :bigint(8) default(0), not null
# chat_message_expiration_policy :string # chat_message_expiration_policy :string
# is_muted :boolean default(FALSE), not null
# #
# : todo : expires # : todo : expires

View File

@ -1,29 +0,0 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: chat_mutes
#
# id :bigint(8) not null, primary key
# account_id :integer not null
# target_account_id :integer not null
# created_at :datetime not null
# updated_at :datetime not null
#
class ChatMute < ApplicationRecord
include Paginable
include RelationshipCacheable
belongs_to :account
belongs_to :target_account, class_name: 'Account'
validates :account_id, uniqueness: { scope: :target_account_id }
after_commit :remove_blocking_cache
private
def remove_blocking_cache
Rails.cache.delete("exclude_chat_account_ids_for:#{account_id}")
end
end

View File

@ -22,6 +22,9 @@ class LinkBlock < ApplicationRecord
Addressable::URI.parse(array[0]).normalize Addressable::URI.parse(array[0]).normalize
} }
url = urls.first url = urls.first
return false if url.nil?
link_for_fetch = TagManager.instance.normalize_link(url) link_for_fetch = TagManager.instance.normalize_link(url)
link_for_fetch = link_for_fetch.chomp("/") link_for_fetch = link_for_fetch.chomp("/")

View File

@ -27,6 +27,9 @@ class AccountRelationshipsPresenter
private private
# : todo :
# chat muting, chat blocking
def cached def cached
return @cached if defined?(@cached) return @cached if defined?(@cached)

View File

@ -3,7 +3,7 @@
class REST::ChatConversationAccountSerializer < ActiveModel::Serializer class REST::ChatConversationAccountSerializer < ActiveModel::Serializer
attributes :id, :is_hidden, :is_approved, :unread_count, attributes :id, :is_hidden, :is_approved, :unread_count,
:is_unread, :chat_conversation_id, :created_at, :is_unread, :chat_conversation_id, :created_at,
:is_blocked, :is_muted, :chat_message_expiration_policy :is_muted, :chat_message_expiration_policy
has_many :participant_accounts, key: :other_accounts, serializer: REST::AccountSerializer has_many :participant_accounts, key: :other_accounts, serializer: REST::AccountSerializer
has_one :last_chat_message, serializer: REST::ChatMessageSerializer, unless: :last_chat_message_id? has_one :last_chat_message, serializer: REST::ChatMessageSerializer, unless: :last_chat_message_id?
@ -24,12 +24,4 @@ class REST::ChatConversationAccountSerializer < ActiveModel::Serializer
object.unread_count > 0 object.unread_count > 0
end end
def is_blocked
false
end
def is_muted
false
end
end end

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class MuteChatMessengerService < BaseService
def call(account, target_account)
return if account.id == target_account.id
mute = account.chat_mute!(target_account)
mute
end
end

View File

@ -52,16 +52,17 @@ class PostChatMessageService < BaseService
@chat_conversation_recipients_accounts = ChatConversationAccount.where(chat_conversation: @chat_conversation) @chat_conversation_recipients_accounts = ChatConversationAccount.where(chat_conversation: @chat_conversation)
@chat_conversation_recipients_accounts.each do |recipient| @chat_conversation_recipients_accounts.each do |recipient|
recipient.last_chat_message_id = @chat.id recipient.last_chat_message_id = @chat.id
recipient.is_hidden = false # reset to show unless blocked recipient.is_hidden = false # : todo : reset to show unless blocked
# Get not mine # Get not mine
if @account_conversation.id != recipient.id if @account_conversation.id != recipient.id
recipient.unread_count = recipient.unread_count + 1 recipient.unread_count = recipient.unread_count + 1
# : todo : # check if muting
# check if muting, redis unless recipient.is_muted
payload = InlineRenderer.render(@chat, recipient.account, :chat_message) payload = InlineRenderer.render(@chat, recipient.account, :chat_message)
Redis.current.publish("chat_messages:#{recipient.account.id}", Oj.dump(event: :notification, payload: payload)) Redis.current.publish("chat_messages:#{recipient.account.id}", Oj.dump(event: :notification, payload: payload))
end
else else
recipient.unread_count = 0 recipient.unread_count = 0
end end
@ -80,8 +81,10 @@ class PostChatMessageService < BaseService
raise ActiveRecord::RecordInvalid raise ActiveRecord::RecordInvalid
end end
def set_message_expiration_date def set_message_expiration_date!
case @account_conversation.expiration_policy @expires_at = nil
case @account_conversation.chat_message_expiration_policy
when :five_minutes when :five_minutes
@expires_at = 5.minutes @expires_at = 5.minutes
when :sixty_minutes when :sixty_minutes

View File

@ -111,6 +111,8 @@ class PostStatusService < BaseService
return if group_id.blank? return if group_id.blank?
return if @autoJoinGroup return if @autoJoinGroup
# : todo : check removedaccounts if exist dont allow
raise GabSocial::ValidationError, I18n.t('statuses.not_a_member_of_group') if not GroupAccount.where(account: @account, group_id: group_id).exists? raise GabSocial::ValidationError, I18n.t('statuses.not_a_member_of_group') if not GroupAccount.where(account: @account, group_id: group_id).exists?
end end

View File

@ -1,8 +0,0 @@
# frozen_string_literal: true
class UnmuteChatMessengerService < BaseService
def call(account, target_account)
return unless account.chat_muting?(target_account)
account.chat_unmute!(target_account)
end
end

View File

@ -228,15 +228,14 @@ Rails.application.routes.draw do
resource :chat_conversation_accounts, only: :show do resource :chat_conversation_accounts, only: :show do
resource :blocked_chat_accounts, only: :show, controller: 'chat_conversation_accounts/blocked_chat_accounts' resource :blocked_chat_accounts, only: :show, controller: 'chat_conversation_accounts/blocked_chat_accounts'
resource :muted_chat_accounts, only: :show, controller: 'chat_conversation_accounts/muted_chat_accounts'
member do member do
get :is_messenger_blocked get :is_messenger_blocked
get :is_messenger_muted get :is_messenger_muted
post :block_messenger post :block_messenger
post :unblock_messenger post :unblock_messenger
post :mute_messenger post :mute_chat_conversation
post :unmute_messenger post :unmute_chat_conversation
post :set_expiration_policy post :set_expiration_policy
end end
end end
@ -392,6 +391,13 @@ Rails.application.routes.draw do
get '/', to: 'react#react', as: :homepage get '/', to: 'react#react', as: :homepage
get '/about', to: 'react#react'
get '/about/tos', to: 'react#react'
get '/about/privacy', to: 'react#react'
get '/about/investors', to: 'react#react'
get '/about/dmca', to: 'react#react'
get '/about/sales', to: 'react#react'
match '*unmatched_route', match '*unmatched_route',
via: :all, via: :all,
to: 'application#raise_not_found', to: 'application#raise_not_found',

View File

@ -0,0 +1,9 @@
class AddIsMutedToChatConversationAccounts < ActiveRecord::Migration[5.2]
def up
safety_assured { add_column :chat_conversation_accounts, :is_muted, :bool, default: false, null: false }
end
def down
remove_column :chat_conversation_accounts, :is_muted
end
end

View File

@ -0,0 +1,5 @@
class RemoveChatMutes < ActiveRecord::Migration[5.2]
def change
drop_table :chat_mutes
end
end

View File

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_12_15_203113) do ActiveRecord::Schema.define(version: 2020_12_16_051551) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_stat_statements" enable_extension "pg_stat_statements"
@ -214,6 +214,7 @@ ActiveRecord::Schema.define(version: 2020_12_15_203113) do
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.bigint "unread_count", default: 0, null: false t.bigint "unread_count", default: 0, null: false
t.string "chat_message_expiration_policy" t.string "chat_message_expiration_policy"
t.boolean "is_muted", default: false, null: false
t.index ["account_id"], name: "index_chat_conversation_accounts_on_account_id" t.index ["account_id"], name: "index_chat_conversation_accounts_on_account_id"
t.index ["chat_conversation_id"], name: "index_chat_conversation_accounts_on_chat_conversation_id" t.index ["chat_conversation_id"], name: "index_chat_conversation_accounts_on_chat_conversation_id"
end end
@ -234,14 +235,6 @@ ActiveRecord::Schema.define(version: 2020_12_15_203113) do
t.index ["from_account_id", "chat_conversation_id"], name: "index_chat_messages_on_from_account_id_and_chat_conversation_id" t.index ["from_account_id", "chat_conversation_id"], name: "index_chat_messages_on_from_account_id_and_chat_conversation_id"
end end
create_table "chat_mutes", force: :cascade do |t|
t.integer "account_id", null: false
t.integer "target_account_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["account_id", "target_account_id"], name: "index_chat_mutes_on_account_id_and_target_account_id", unique: true
end
create_table "conversations", force: :cascade do |t| create_table "conversations", force: :cascade do |t|
t.string "uri" t.string "uri"
t.datetime "created_at", null: false t.datetime "created_at", null: false