This commit is contained in:
mgabdev
2020-12-15 19:31:30 -05:00
parent de0c977950
commit 75d52c841e
129 changed files with 2559 additions and 910 deletions

View File

@@ -0,0 +1 @@
//

View File

@@ -10,6 +10,20 @@ export const BOOKMARKED_STATUSES_EXPAND_REQUEST = 'BOOKMARKED_STATUSES_EXPAND_RE
export const BOOKMARKED_STATUSES_EXPAND_SUCCESS = 'BOOKMARKED_STATUSES_EXPAND_SUCCESS'
export const BOOKMARKED_STATUSES_EXPAND_FAIL = 'BOOKMARKED_STATUSES_EXPAND_FAIL'
//
export const BOOKMARK_COLLECTIONS_FETCH_REQUEST = 'BOOKMARK_COLLECTIONS_FETCH_REQUEST'
export const BOOKMARK_COLLECTIONS_FETCH_SUCCESS = 'BOOKMARK_COLLECTIONS_FETCH_SUCCESS'
export const BOOKMARK_COLLECTIONS_FETCH_FAIL = 'BOOKMARK_COLLECTIONS_FETCH_FAIL'
export const BOOKMARK_COLLECTIONS_CREATE_REQUEST = 'BOOKMARK_COLLECTIONS_CREATE_REQUEST'
export const BOOKMARK_COLLECTIONS_CREATE_SUCCESS = 'BOOKMARK_COLLECTIONS_CREATE_SUCCESS'
export const BOOKMARK_COLLECTIONS_CREATE_FAIL = 'BOOKMARK_COLLECTIONS_CREATE_FAIL'
export const BOOKMARK_COLLECTIONS_REMOVE_REQUEST = 'BOOKMARK_COLLECTIONS_REMOVE_REQUEST'
export const BOOKMARK_COLLECTIONS_REMOVE_SUCCESS = 'BOOKMARK_COLLECTIONS_REMOVE_SUCCESS'
export const BOOKMARK_COLLECTIONS_REMOVE_FAIL = 'BOOKMARK_COLLECTIONS_REMOVE_FAIL'
/**
*
*/
@@ -22,11 +36,11 @@ export const fetchBookmarkedStatuses = () => (dispatch, getState) => {
dispatch(fetchBookmarkedStatusesRequest())
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')
dispatch(importFetchedStatuses(response.data))
dispatch(fetchBookmarkedStatusesSuccess(response.data, next ? next.uri : null))
}).catch(error => {
}).catch((error) => {
dispatch(fetchBookmarkedStatusesFail(error))
})
}
@@ -61,11 +75,11 @@ export const expandBookmarkedStatuses = () => (dispatch, getState) => {
dispatch(expandBookmarkedStatusesRequest())
api(getState).get(url).then(response => {
api(getState).get(url).then((response) => {
const next = getLinks(response).refs.find(link => link.rel === 'next')
dispatch(importFetchedStatuses(response.data))
dispatch(expandBookmarkedStatusesSuccess(response.data, next ? next.uri : null))
}).catch(error => {
}).catch((error) => {
dispatch(expandBookmarkedStatusesFail(error))
})
}
@@ -85,3 +99,95 @@ const expandBookmarkedStatusesFail = (error) => ({
showToast: true,
error,
})
/**
*
*/
export const fetchBookmarkCollections = () => (dispatch, getState) => {
if (!me) return
if (getState().getIn(['bookmark_collections', 'isLoading'])) return
dispatch(fetchBookmarkCollectionsRequest())
api(getState).get('/api/v1/bookmark_collections').then((response) => {
dispatch(fetchBookmarkCollectionsSuccess(response.data))
}).catch((error) => {
dispatch(fetchBookmarkCollectionsFail(error))
})
}
const fetchBookmarkCollectionsRequest = () => ({
type: BOOKMARK_COLLECTIONS_FETCH_REQUEST,
})
const fetchBookmarkCollectionsSuccess = (bookmarkCollections) => ({
type: BOOKMARK_COLLECTIONS_FETCH_SUCCESS,
bookmarkCollections,
})
const fetchBookmarkCollectionsFail = (error) => ({
type: BOOKMARK_COLLECTIONS_FETCH_FAIL,
showToast: true,
error,
})
/**
*
*/
export const createBookmarkCollection = (title) => (dispatch, getState) => {
if (!me || !title) return
dispatch(createBookmarkCollectionRequest())
api(getState).post('/api/v1/bookmark_collections', { title }).then((response) => {
dispatch(createBookmarkCollectionSuccess(response.data))
}).catch((error) => {
dispatch(createBookmarkCollectionFail(error))
})
}
const createBookmarkCollectionRequest = () => ({
type: BOOKMARK_COLLECTIONS_CREATE_REQUEST,
})
const createBookmarkCollectionSuccess = (bookmarkCollection) => ({
type: BOOKMARK_COLLECTIONS_CREATE_SUCCESS,
bookmarkCollection,
})
const createBookmarkCollectionFail = (error) => ({
type: BOOKMARK_COLLECTIONS_CREATE_FAIL,
showToast: true,
error,
})
/**
*
*/
export const removeBookmarkCollection = (bookmarkCollectionId) => (dispatch, getState) => {
if (!me || !bookmarkCollectionId) return
dispatch(removeBookmarkCollectionRequest(bookmarkCollectionId))
api(getState).delete(`/api/v1/bookmark_collection/${bookmarkCollectionId}`).then((response) => {
dispatch(removeBookmarkCollectionSuccess(response.data))
}).catch((error) => {
dispatch(removeBookmarkCollectionFail(error))
})
}
const removeBookmarkCollectionRequest = (bookmarkCollectionId) => ({
type: BOOKMARK_COLLECTIONS_CREATE_REQUEST,
bookmarkCollectionId,
})
const removeBookmarkCollectionSuccess = () => ({
type: BOOKMARK_COLLECTIONS_CREATE_SUCCESS,
})
const removeBookmarkCollectionFail = (error) => ({
type: BOOKMARK_COLLECTIONS_CREATE_FAIL,
showToast: true,
error,
})

View File

@@ -46,6 +46,7 @@ export const IS_CHAT_MESSENGER_MUTED_SUCCESS = 'IS_CHAT_MESSENGER_MUTED_SUCCESS'
*
*/
export const blockChatMessenger = (accountId) => (dispatch, getState) => {
console.log("blockChatMessenger:", accountId)
if (!me || !accountId) return
dispatch(blockChatMessengerRequest(accountId))

View File

@@ -44,10 +44,10 @@ export const clearChatMessageConversation = (chatConversationId) => (dispatch) =
/**
*
*/
export const scrollBottomChatMessageConversation = (chatConversationId, top) => ({
export const scrollBottomChatMessageConversation = (chatConversationId, bottom) => ({
type: CHAT_CONVERSATION_MESSAGES_SCROLL_BOTTOM,
chatConversationId,
top,
bottom,
})
/**
@@ -56,7 +56,7 @@ export const scrollBottomChatMessageConversation = (chatConversationId, top) =>
export const expandChatMessages = (chatConversationId, params = {}, done = noop) => (dispatch, getState) => {
if (!me || !chatConversationId) return
const chatConversation = getState().getIn(['chat_messages', chatConversationId], ImmutableMap())
const chatConversation = getState().getIn(['chat_conversations', chatConversationId], ImmutableMap())
const isLoadingMore = !!params.maxId
if (!!chatConversation && (chatConversation.get('isLoading') || chatConversation.get('isError'))) {

View File

@@ -45,6 +45,22 @@ export const CHAT_CONVERSATION_DELETE_REQUEST = 'CHAT_CONVERSATION_DELETE_REQUES
export const CHAT_CONVERSATION_DELETE_SUCCESS = 'CHAT_CONVERSATION_DELETE_SUCCESS'
export const CHAT_CONVERSATION_DELETE_FAIL = 'CHAT_CONVERSATION_DELETE_FAIL'
//
export const CHAT_CONVERSATION_MARK_READ_FETCH = 'CHAT_CONVERSATION_MARK_READ_FETCH'
export const CHAT_CONVERSATION_MARK_READ_SUCCESS = 'CHAT_CONVERSATION_MARK_READ_SUCCESS'
export const CHAT_CONVERSATION_MARK_READ_FAIL = 'CHAT_CONVERSATION_MARK_READ_FAIL'
export const CHAT_CONVERSATION_HIDE_FETCH = 'CHAT_CONVERSATION_HIDE_FETCH'
export const CHAT_CONVERSATION_HIDE_SUCCESS = 'CHAT_CONVERSATION_HIDE_SUCCESS'
export const CHAT_CONVERSATION_HIDE_FAIL = 'CHAT_CONVERSATION_HIDE_FAIL'
//
export const SET_CHAT_CONVERSATION_EXPIRATION_REQUEST = 'SET_CHAT_CONVERSATION_EXPIRATION_REQUEST'
export const SET_CHAT_CONVERSATION_EXPIRATION_SUCCESS = 'SET_CHAT_CONVERSATION_EXPIRATION_SUCCESS'
export const SET_CHAT_CONVERSATION_EXPIRATION_FAIL = 'SET_CHAT_CONVERSATION_EXPIRATION_FAIL'
/**
* @description Fetch paginated active chat conversations, import accounts and set chat converations
*/
@@ -309,4 +325,93 @@ export const approveChatConversationRequestSuccess = (chatConversation) => ({
export const approveChatConversationRequestFail = () => ({
type: CHAT_CONVERSATION_REQUEST_APPROVE_FAIL,
})
})
/**
*
*/
export const hideChatConversation = (chatConversationId) => (dispatch, getState) => {
if (!me|| !chatConversationId) return
dispatch(hideChatConversationFetch(chatConversationId))
api(getState).post(`/api/v1/chat_conversation/${chatConversationId}/mark_chat_conversation_hidden`).then((response) => {
dispatch(approveChatConversationRequestSuccess(chatConversationId))
}).catch((error) => dispatch(approveChatConversationRequestFail(error)))
}
export const hideChatConversationFetch = (chatConversationId) => ({
type: CHAT_CONVERSATION_HIDE_SUCCESS,
chatConversationId,
})
export const hideChatConversationSuccess = (chatConversationId) => ({
type: CHAT_CONVERSATION_HIDE_SUCCESS,
chatConversationId,
})
export const hideChatConversationFail = () => ({
type: CHAT_CONVERSATION_HIDE_FAIL,
})
/**
*
*/
export const readChatConversation = (chatConversationId) => (dispatch, getState) => {
if (!me|| !chatConversationId) return
const chatConversation = getState().getIn(['chat_conversations', chatConversationId])
if (!chatConversation) return
if (chatConversation.get('unread_count') < 1) return
dispatch(readChatConversationFetch(chatConversation))
api(getState).post(`/api/v1/chat_conversation/${chatConversationId}/mark_chat_conversation_read`).then((response) => {
dispatch(readChatConversationSuccess(response.data))
}).catch((error) => dispatch(readChatConversationFail(error)))
}
export const readChatConversationFetch = (chatConversation) => ({
type: CHAT_CONVERSATION_MARK_READ_FETCH,
chatConversation,
})
export const readChatConversationSuccess = (chatConversation) => ({
type: CHAT_CONVERSATION_MARK_READ_SUCCESS,
chatConversation,
})
export const readChatConversationFail = () => ({
type: CHAT_CONVERSATION_MARK_READ_FAIL,
})
/**
*
*/
export const setChatConversationExpiration = (chatConversationId, expiration) => (dispatch, getState) => {
if (!me|| !chatConversationId || !expiration) return
dispatch(setChatConversationExpirationFetch(chatConversation))
api(getState).post(`/api/v1/chat_conversation/${chatConversationId}/set_expiration_policy`, {
expiration,
}).then((response) => {
dispatch(setChatConversationExpirationSuccess(response.data))
}).catch((error) => dispatch(setChatConversationExpirationFail(error)))
}
export const setChatConversationExpirationFetch = (chatConversation) => ({
type: SET_CHAT_CONVERSATION_EXPIRATION_REQUEST,
chatConversation,
})
export const setChatConversationExpirationSuccess = (chatConversation) => ({
type: SET_CHAT_CONVERSATION_EXPIRATION_REQUEST,
chatConversation,
})
export const setChatConversationExpirationFail = (error) => ({
type: SET_CHAT_CONVERSATION_EXPIRATION_REQUEST,
error,
})

View File

@@ -56,6 +56,22 @@ const sendChatMessageFail = (error) => ({
error,
})
/**
*
*/
export const manageIncomingChatMessage = (chatMessage) => (dispatch, getState) => {
if (!chatMessage) return
console.log("chatMessage:", chatMessage)
dispatch(sendChatMessageSuccess(chatMessage))
const isOnline = getState().getIn(['chat_conversation_messages', chatMessage.chat_conversation_id, 'online'])
console.log("isOnline: ", isOnline)
// : todo :
// Check if is online for conversation, if not increase total/convo unread count
}
/**
*
*/
@@ -99,24 +115,25 @@ export const purgeChatMessages = (chatConversationId) => (dispatch, getState) =>
dispatch(deleteChatMessagesRequest(chatConversationId))
api(getState).delete(`/api/v1/chat_conversations/${chatConversationId}/messages/destroy_all`).then((response) => {
api(getState).delete(`/api/v1/chat_conversations/messages/${chatConversationId}/destroy_all`).then((response) => {
dispatch(deleteChatMessagesSuccess(response.data))
}).catch((error) => {
dispatch(deleteChatMessagesFail(error))
})
}
const deleteChatMessagesRequest = () => ({
const purgeChatMessagesRequest = (chatConversationId) => ({
type: CHAT_MESSAGES_PURGE_REQUEST,
chatConversationId,
})
const deleteChatMessagesSuccess = (chatConversationId) => ({
const purgeChatMessagesSuccess = (chatConversationId) => ({
type: CHAT_MESSAGES_PURGE_SUCCESS,
chatConversationId,
showToast: true,
})
const deleteChatMessagesFail = (error) => ({
const purgeChatMessagesFail = (error) => ({
type: CHAT_MESSAGES_PURGE_FAIL,
showToast: true,
error,

View File

@@ -20,12 +20,12 @@ import { defineMessages } from 'react-intl'
import { openModal, closeModal } from './modal'
import {
MODAL_COMPOSE,
STATUS_EXPIRATION_OPTION_5_MINUTES,
STATUS_EXPIRATION_OPTION_60_MINUTES,
STATUS_EXPIRATION_OPTION_6_HOURS,
STATUS_EXPIRATION_OPTION_24_HOURS,
STATUS_EXPIRATION_OPTION_3_DAYS,
STATUS_EXPIRATION_OPTION_7_DAYS,
EXPIRATION_OPTION_5_MINUTES,
EXPIRATION_OPTION_60_MINUTES,
EXPIRATION_OPTION_6_HOURS,
EXPIRATION_OPTION_24_HOURS,
EXPIRATION_OPTION_3_DAYS,
EXPIRATION_OPTION_7_DAYS,
} from '../constants'
import { me } from '../initial_state'
import { makeGetStatus } from '../selectors'
@@ -347,17 +347,17 @@ export const submitCompose = (groupId, replyToId = null, router, isStandalone, a
let expires_at = getState().getIn(['compose', 'expires_at'], null)
if (expires_at) {
if (expires_at === STATUS_EXPIRATION_OPTION_5_MINUTES) {
if (expires_at === EXPIRATION_OPTION_5_MINUTES) {
expires_at = moment.utc().add('5', 'minute').toDate()
} else if (expires_at === STATUS_EXPIRATION_OPTION_60_MINUTES) {
} else if (expires_at === EXPIRATION_OPTION_60_MINUTES) {
expires_at = moment.utc().add('60', 'minute').toDate()
} else if (expires_at === STATUS_EXPIRATION_OPTION_6_HOURS) {
} else if (expires_at === EXPIRATION_OPTION_6_HOURS) {
expires_at = moment.utc().add('6', 'hour').toDate()
} else if (expires_at === STATUS_EXPIRATION_OPTION_24_HOURS) {
} else if (expires_at === EXPIRATION_OPTION_24_HOURS) {
expires_at = moment.utc().add('24', 'hour').toDate()
} else if (expires_at === STATUS_EXPIRATION_OPTION_3_DAYS) {
} else if (expires_at === EXPIRATION_OPTION_3_DAYS) {
expires_at = moment.utc().add('3', 'day').toDate()
} else if (expires_at === STATUS_EXPIRATION_OPTION_7_DAYS) {
} else if (expires_at === EXPIRATION_OPTION_7_DAYS) {
expires_at = moment.utc().add('7', 'day').toDate()
}
}

View File

@@ -6,7 +6,7 @@ import {
updateTimelineQueue,
} from './timelines'
import { updateNotificationsQueue } from './notifications'
import { sendChatMessageSuccess } from './chat_messages'
import { manageIncomingChatMessage } from './chat_messages'
import { fetchFilters } from './filters'
import { getLocale } from '../locales'
import { handleComposeSubmit } from './compose'
@@ -84,7 +84,7 @@ export const connectChatMessagesStream = (accountId) => {
onReceive (data) {
if (!data['event'] || !data['payload']) return
if (data.event === 'notification') {
dispatch(sendChatMessageSuccess(JSON.parse(data.payload)))
dispatch(manageIncomingChatMessage(JSON.parse(data.payload)))
}
},
}

View File

@@ -189,6 +189,7 @@ class AutosuggestTextbox extends ImmutablePureComponent {
id,
isPro,
isEdit,
isModalOpen,
} = this.props
const { suggestionsHidden } = this.state

View File

@@ -1,29 +1,24 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import {
FormattedMessage,
defineMessages,
injectIntl,
} from 'react-intl'
import { openModal } from '../actions/modal'
import {
me,
repository,
source_url,
me,
} from '../initial_state'
import { CX, DEFAULT_REL } from '../constants'
import { DEFAULT_REL } from '../constants'
import Text from './text'
import Button from './button'
import DotTextSeperator from './dot_text_seperator'
class LinkFooter extends React.PureComponent {
render() {
const {
intl,
noPadding,
onOpenHotkeys,
} = this.props
const { intl } = this.props
const currentYear = new Date().getFullYear()
@@ -32,12 +27,6 @@ class LinkFooter extends React.PureComponent {
href: 'https://help.gab.com',
text: intl.formatMessage(messages.help),
},
// : todo :
// {
// onClick: onOpenHotkeys,
// text: intl.formatMessage(messages.hotkeys),
// requiresUser: true,
// },
{
href: '/auth/edit',
text: intl.formatMessage(messages.security),
@@ -52,16 +41,16 @@ class LinkFooter extends React.PureComponent {
text: intl.formatMessage(messages.investors),
},
{
to: '/about/tos',
text: intl.formatMessage(messages.terms),
to: '/about/sales',
text: intl.formatMessage(messages.salesTerms),
},
{
to: '/about/dmca',
text: intl.formatMessage(messages.dmca),
},
{
to: '/about/sales',
text: intl.formatMessage(messages.salesTerms),
to: '/about/tos',
text: intl.formatMessage(messages.terms),
},
{
to: '/about/privacy',
@@ -75,36 +64,33 @@ class LinkFooter extends React.PureComponent {
},
]
const containerClasses = CX({
d: 1,
px10: !noPadding,
mb15: 1,
})
return (
<div className={containerClasses}>
<div className={[_s.d, _s.mb15].join(' ')}>
<nav aria-label='Footer' role='navigation' className={[_s.d, _s.flexWrap, _s.flexRow].join(' ')}>
{
linkFooterItems.map((linkFooterItem, i) => {
if (linkFooterItem.requiresUser && !me) return null
return (
<Button
isText
underlineOnHover
color='none'
backgroundColor='none'
key={`link-footer-item-${i}`}
to={linkFooterItem.to}
href={linkFooterItem.href}
data-method={linkFooterItem.logout ? 'delete' : null}
onClick={linkFooterItem.onClick || null}
className={[_s.mt5, _s.mb5, _s.pr15].join(' ')}
>
<Text size='small' color='tertiary'>
{linkFooterItem.text}
</Text>
</Button>
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.jcCenter].join(' ')}>
<Button
isText
underlineOnHover
color='none'
backgroundColor='none'
key={`link-footer-item-${i}`}
to={linkFooterItem.to}
href={linkFooterItem.href}
data-method={linkFooterItem.logout ? 'delete' : null}
onClick={linkFooterItem.onClick || null}
className={[_s.mt5].join(' ')}
>
<Text size='small' color='tertiary'>
{linkFooterItem.text}
</Text>
</Button>
{ !linkFooterItem.logout && <Text size='small' color='secondary' className={[_s.pt2, _s.mr5, _s.ml5].join(' ')}>·</Text> }
</div>
)
})
}
@@ -120,7 +106,7 @@ class LinkFooter extends React.PureComponent {
defaultMessage='Gab Social is open source software. You can contribute or report issues on our self-hosted GitLab at {gitlab}.'
values={{
gitlab: (
<a href={source_url} className={[_s.displayBlock, _s.inherit].join(' ')} rel={DEFAULT_REL} target='_blank'>
<a href={source_url} className={[_s.displayInlineBlock, _s.inherit].join(' ')} rel={DEFAULT_REL} target='_blank'>
{repository}
</a>
)
@@ -136,8 +122,6 @@ class LinkFooter extends React.PureComponent {
const messages = defineMessages({
investors: { id: 'getting_started.investors', defaultMessage: 'Investors' },
help: { id: 'getting_started.help', defaultMessage: 'Help' },
invite: { id: 'getting_started.invite', defaultMessage: 'Invite people' },
hotkeys: { id: 'navigation_bar.keyboard_shortcuts', defaultMessage: 'Hotkeys' },
security: { id: 'getting_started.security', defaultMessage: 'Security' },
about: { id: 'navigation_bar.info', defaultMessage: 'About' },
developers: { id: 'getting_started.developers', defaultMessage: 'Developers' },
@@ -148,16 +132,8 @@ const messages = defineMessages({
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
})
const mapDispatchToProps = (dispatch) => ({
onOpenHotkeys() {
dispatch(openModal('HOTKEYS'))
},
})
LinkFooter.propTypes = {
intl: PropTypes.object.isRequired,
noPadding: PropTypes.bool,
onOpenHotkeys: PropTypes.func.isRequired,
}
export default injectIntl(connect(null, mapDispatchToProps)(LinkFooter))
export default injectIntl(LinkFooter)

View File

@@ -79,7 +79,7 @@ class MediaItem extends ImmutablePureComponent {
posAbs: 1,
top0: 1,
h100PC: 1,
w100PC: 1,
// w100PC: 1,
py2: !isSmall,
px2: !isSmall,
})
@@ -87,7 +87,7 @@ class MediaItem extends ImmutablePureComponent {
const linkClasses = CX({
d: 1,
w100PC: 1,
h100PC: 1,
// h100PC: 1,
overflowHidden: 1,
border1PX: 1,
borderColorPrimary: 1,
@@ -96,7 +96,7 @@ class MediaItem extends ImmutablePureComponent {
const statusUrl = `/${account.getIn(['acct'])}/posts/${status.get('id')}`;
return (
<div className={[_s.d, _s.w25PC, _s.pt25PC].join(' ')}>
<div className={[_s.d, _s.pt25PC].join(' ')}>
<div className={containerClasses}>
<NavLink
to={statusUrl}
@@ -117,6 +117,7 @@ class MediaItem extends ImmutablePureComponent {
visible &&
<Image
height='100%'
width=''
src={attachment.get('preview_url')}
alt={attachment.get('description')}
title={attachment.get('description')}

View File

@@ -49,9 +49,9 @@ class ComposeModal extends ImmutablePureComponent {
const title = isEditing ? messages.edit : isComment ? messages.comment : messages.title
return (
<div style={{width: '512px'}} className={[_s.d, _s.modal].join(' ')}>
<div style={{width: '580px'}} className={[_s.d, _s.modal].join(' ')}>
<Block>
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.jcCenter, _s.borderBottom1PX, _s.borderColorSecondary, _s.h53PX, _s.pl10, _s.pr15].join(' ')}>
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.jcCenter, _s.borderBottom1PX, _s.borderColorSecondary, _s.h53PX, _s.pl5, _s.pr10].join(' ')}>
<div className={[_s.d, _s.w115PX, _s.aiStart, _s.jcCenter, _s.mrAuto].join(' ')}>
<Button
backgroundColor='none'
@@ -69,8 +69,8 @@ class ComposeModal extends ImmutablePureComponent {
<ComposeFormSubmitButton type='header' />
</div>
</div>
<div className={[_s.d].join(' ')}>
<TimelineComposeBlock isModal />
<div className={[_s.d, _s.pt5].join(' ')}>
<TimelineComposeBlock isModal formLocation='modal' />
</div>
</Block>
</div>

View File

@@ -1,6 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { MODAL_DECK_COLUMN_ADD_OPTIONS } from '../../constants'
import { setDeckColumnAtIndex } from '../../actions/deck'
import { openModal } from '../../actions/modal'
import ModalLayout from './modal_layout'
@@ -10,27 +11,19 @@ import Text from '../text'
class DeckColumnAddModal extends React.PureComponent {
onAdd = (column) => {
console.log("onAdd column: ", column)
switch (column) {
case 'user':
//
break
case 'list':
//
break
case 'group':
//
break
case 'hashtag':
//
break
default:
this.props.dispatch(setDeckColumnAtIndex(column))
this.props.onClose()
break
const moreOptions = ['user', 'list', 'group', 'hashtag']
if (moreOptions.indexOf(column) > -1) {
this.openOptionsModal(column)
} else {
this.props.dispatch(setDeckColumnAtIndex(column))
this.props.onClose()
}
}
openOptionsModal = (column) => {
this.props.dispatch(openModal(MODAL_DECK_COLUMN_ADD_OPTIONS, { column }))
}
render() {
const {
intl,

View File

@@ -0,0 +1,75 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { openModal } from '../../actions/modal'
import { MODAL_DECK_COLUMN_ADD } from '../../constants'
import Heading from '../heading'
import Button from '../button'
import Block from '../block'
class DeckColumnAddOptionsModal extends React.PureComponent {
state = {
selectedItem: null,
}
onClickClose = () => {
this.props.onClose()
this.props.dispatch(openModal(MODAL_DECK_COLUMN_ADD))
}
handleAdd = () => {
//
}
render() {
const { column } = this.props
const { selectedItem } = this.state
// user, hashtag, list, groups
if (!column) return <div />
const title = `Select a ${column}`
return (
<div style={{width: '520px'}} className={[_s.d, _s.modal].join(' ')}>
<Block>
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.jcCenter, _s.borderBottom1PX, _s.borderColorSecondary, _s.h53PX, _s.pl10, _s.pr15].join(' ')}>
<div className={[_s.d, _s.w115PX, _s.aiStart, _s.jcCenter, _s.mrAuto].join(' ')}>
<Button
backgroundColor='none'
title='Back'
onClick={this.onClickClose}
color='secondary'
icon='back'
iconSize='16px'
/>
</div>
<Heading size='h2'>
{title}
</Heading>
<div className={[_s.d, _s.w115PX, _s.aiEnd, _s.jcCenter, _s.mlAuto].join(' ')}>
<Button
isDisabled={!selectedItem}
onClick={this.handleAdd}
>
Add
</Button>
</div>
</div>
<div className={[_s.d].join(' ')}>
test
</div>
</Block>
</div>
)
}
}
DeckColumnAddOptionsModal.propTypes = {
onClose: PropTypes.func.isRequired,
column: PropTypes.string.isRequired,
}
export default connect()(DeckColumnAddOptionsModal)

View File

@@ -16,6 +16,7 @@ import {
MODAL_COMPOSE,
MODAL_CONFIRM,
MODAL_DECK_COLUMN_ADD,
MODAL_DECK_COLUMN_ADD_OPTIONS,
MODAL_DISPLAY_OPTIONS,
MODAL_EDIT_PROFILE,
MODAL_EDIT_SHORTCUTS,
@@ -51,6 +52,7 @@ import {
ComposeModal,
ConfirmationModal,
DeckColumnAddModal,
DeckColumnAddOptionsModal,
DisplayOptionsModal,
EditProfileModal,
EditShortcutsModal,
@@ -89,6 +91,7 @@ const MODAL_COMPONENTS = {
[MODAL_COMPOSE]: ComposeModal,
[MODAL_CONFIRM]: ConfirmationModal,
[MODAL_DECK_COLUMN_ADD]: DeckColumnAddModal,
[MODAL_DECK_COLUMN_ADD_OPTIONS]: DeckColumnAddOptionsModal,
[MODAL_DISPLAY_OPTIONS]: DisplayOptionsModal,
[MODAL_EDIT_SHORTCUTS]: EditShortcutsModal,
[MODAL_EDIT_PROFILE]: EditProfileModal,

View File

@@ -49,7 +49,7 @@ class ChatNavigationBar extends React.PureComponent {
<div className={[_s.d, _s.h53PX, _s.flexRow, _s.jcCenter, _s.aiCenter, _s.mrAuto].join(' ')}>
<AvatarGroup accounts={otherAccounts} size={35} noHover />
<Heading size='h1'>
<div className={[_s.dangerousContent, _s.pl10, _s.fs19PX].join(' ')} dangerouslySetInnerHTML={{ __html: nameHTML }} />
<div className={[_s.dangerousContent, _s.colorNavigation, _s.pl10, _s.fs19PX].join(' ')} dangerouslySetInnerHTML={{ __html: nameHTML }} />
</Heading>
</div>

View File

@@ -8,7 +8,7 @@ import Heading from '../heading'
import Button from '../button'
import BackButton from '../back_button'
import Text from '../text'
import CharacterCounter from '../character_counter'
import ComposeFormSubmitButton from '../../features/compose/components/compose_form_submit_button'
class ComposeNavigationBar extends React.PureComponent {
@@ -26,13 +26,7 @@ class ComposeNavigationBar extends React.PureComponent {
} = this.props
const disabledButton = isSubmitting || isUploading || isChangingUpload || length(text) > MAX_POST_CHARACTER_COUNT || (length(text.trim()) === 0 && !anyMedia)
const buttonOptions = {
backgroundColor: disabledButton ? 'tertiary' : 'brand',
color: disabledButton ? 'tertiary' : 'white',
isDisabled: disabledButton,
onClick: this.handleOnPost,
}
return (
<div className={[_s.d, _s.z4, _s.h53PX, _s.w100PC].join(' ')}>
<div className={[_s.d, _s.h53PX, _s.bgNavigation, _s.aiCenter, _s.z3, _s.top0, _s.right0, _s.left0, _s.posFixed].join(' ')} >
@@ -48,18 +42,14 @@ class ComposeNavigationBar extends React.PureComponent {
<div className={[_s.d, _s.h53PX, _s.flexRow, _s.jcCenter, _s.aiCenter, _s.mrAuto].join(' ')}>
<Heading size='h1'>
Compose
<span className={[_s.dangerousContent, _s.fs24PX, _s.colorNavigation].join(' ')}>
Compose
</span>
</Heading>
</div>
<div className={[_s.d, _s.h53PX, _s.flexRow, _s.mlAuto, _s.aiCenter, _s.jcCenter, _s.mr15].join(' ')}>
<CharacterCounter max={MAX_POST_CHARACTER_COUNT} text={text} />
<Button {...buttonOptions}>
<Text color='inherit' weight='bold' size='medium' className={_s.px5}>
POST
</Text>
</Button>
<ComposeFormSubmitButton type='navigation' />
</div>
</div>

View File

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

View File

@@ -0,0 +1,135 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { defineMessages, injectIntl } from 'react-intl'
import { closePopover } from '../../actions/popover'
import { changeExpiresAt } from '../../actions/compose'
import {
EXPIRATION_OPTION_5_MINUTES,
EXPIRATION_OPTION_60_MINUTES,
EXPIRATION_OPTION_6_HOURS,
EXPIRATION_OPTION_24_HOURS,
EXPIRATION_OPTION_3_DAYS,
EXPIRATION_OPTION_7_DAYS,
} from '../../constants'
import PopoverLayout from './popover_layout'
import List from '../list'
class ChatConversationExpirationOptionsPopover extends React.PureComponent {
handleOnSetExpiration = (expiresAt) => {
this.props.onChangeExpiresAt(expiresAt)
this.handleOnClosePopover()
}
handleOnClosePopover = () => {
this.props.onClosePopover()
}
render() {
const {
expiresAtValue,
intl,
isXS,
} = this.props
const listItems = [
{
hideArrow: true,
title: 'None',
onClick: () => this.handleOnSetStatusExpiration(null),
isActive: !expiresAtValue,
},
{
hideArrow: true,
title: intl.formatMessage(messages.minutes, { number: 5 }),
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_5_MINUTES),
isActive: expiresAtValue === EXPIRATION_OPTION_5_MINUTES,
},
{
hideArrow: true,
title: intl.formatMessage(messages.minutes, { number: 60 }),
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_60_MINUTES),
isActive: expiresAtValue === EXPIRATION_OPTION_60_MINUTES,
},
{
hideArrow: true,
title: '6 hours',
title: intl.formatMessage(messages.hours, { number: 6 }),
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_6_HOURS),
isActive: expiresAtValue === EXPIRATION_OPTION_6_HOURS,
},
{
hideArrow: true,
title: intl.formatMessage(messages.hours, { number: 24 }),
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_24_HOURS),
isActive: expiresAtValue === EXPIRATION_OPTION_24_HOURS,
},
{
hideArrow: true,
title: '3 days',
title: intl.formatMessage(messages.days, { number: 3 }),
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_3_DAYS),
isActive: expiresAtValue === EXPIRATION_OPTION_3_DAYS,
},
{
hideArrow: true,
title: intl.formatMessage(messages.days, { number: 7 }),
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_7_DAYS),
isActive: expiresAtValue === EXPIRATION_OPTION_7_DAYS,
},
]
if (expiresAtValue) {
listItems.unshift({
hideArrow: true,
title: 'Remove expiration',
onClick: () => this.handleOnSetStatusExpiration(null),
},)
}
return (
<PopoverLayout
width={210}
isXS={isXS}
onClose={this.handleOnClosePopover}
>
<Text className={[_s.d, _s.px15, _s.py10, _s.bgSecondary].join(' ')}>This chats delete after:</Text>
<List
scrollKey='chat_conversation_expiration'
items={listItems}
size={isXS ? 'large' : 'small'}
/>
</PopoverLayout>
)
}
}
const messages = defineMessages({
minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' },
hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' },
days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
})
const mapStateToProps = (state) => ({
expiresAtValue: state.getIn(['compose', 'expires_at']),
})
const mapDispatchToProps = (dispatch) => ({
onChangeExpiresAt(expiresAt) {
dispatch(changeExpiresAt(expiresAt))
},
onClosePopover() {
dispatch(closePopover())
},
})
ChatConversationExpirationOptionsPopover.defaultProps = {
expiresAtValue: PropTypes.string.isRequired,
intl: PropTypes.object.isRequired,
isXS: PropTypes.bool,
onChangeExpiresAt: PropTypes.func.isRequired,
}
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ChatConversationExpirationOptionsPopover))

View File

@@ -5,14 +5,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component'
import { connect } from 'react-redux'
import { closePopover } from '../../actions/popover'
import { openModal } from '../../actions/modal'
import {
isChatMessengerBlocked,
isChatMessengerMuted,
blockChatMessenger,
unblockChatMessenger,
muteChatMessenger,
unmuteChatMessenger,
} from '../../actions/chat_conversation_accounts'
import { hideChatConversation } from '../../actions/chat_conversations'
import { purgeChatMessages } from '../../actions/chat_messages'
import { MODAL_PRO_UPGRADE } from '../../constants'
import { me } from '../../initial_state'
import { makeGetChatConversation } from '../../selectors'
@@ -27,21 +21,6 @@ class ChatConversationOptionsPopover extends ImmutablePureComponent {
this.handleOnClosePopover()
}
handleOnBlock = () => {
this.props.onBlock()
this.handleOnClosePopover()
}
handleOnUnblock = () => {
this.props.onUnblock()
this.handleOnClosePopover()
}
handleOnMute = () => {
this.props.onMute()
this.handleOnClosePopover()
}
handleOnUnmute = () => {
this.props.onUnute()
this.handleOnClosePopover()
@@ -51,7 +30,7 @@ class ChatConversationOptionsPopover extends ImmutablePureComponent {
if (!this.props.isPro) {
this.props.openProUpgradeModal()
} else {
this.props.onPurge()
this.props.onPurge(this.props.chatConversationId)
}
this.handleOnClosePopover()
@@ -68,18 +47,6 @@ class ChatConversationOptionsPopover extends ImmutablePureComponent {
} = this.props
const items = [
{
hideArrow: true,
title: 'Block Messenger',
subtitle: 'The messenger will not be able to message you.',
onClick: () => this.handleOnBlock(),
},
{
hideArrow: true,
title: 'Mute Messenger',
subtitle: 'You will not be notified of new messsages',
onClick: () => this.handleOnMute(),
},
{
hideArrow: true,
title: 'Hide Conversation',
@@ -123,6 +90,12 @@ const mapDispatchToProps = (dispatch) => ({
onSetCommentSortingSetting(type) {
dispatch(closePopover())
},
onPurge(chatConversationId) {
dispatch(purgeChatMessages(chatConversationId))
},
onHide(chatConversationId) {
dispatch(hideChatConversation(chatConversationId))
},
onClosePopover: () => dispatch(closePopover()),
})

View File

@@ -1,58 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { closePopover } from '../../actions/popover'
import { deleteChatMessage } from '../../actions/chat_messages'
import PopoverLayout from './popover_layout'
import Button from '../button'
import Text from '../text'
class ChatMessageDeletePopover extends React.PureComponent {
handleOnClick = () => {
this.props.onDeleteChatMessage(this.props.chatMessageId)
}
handleOnClosePopover = () => {
this.props.onClosePopover()
}
render() {
const { isXS } = this.props
return (
<PopoverLayout
width={96}
isXS={isXS}
onClose={this.handleOnClosePopover}
>
<Button
onClick={this.handleOnClick}
color='primary'
backgroundColor='tertiary'
className={[_s.radiusSmall].join(' ')}
>
<Text align='center' color='inherit'>Remove</Text>
</Button>
</PopoverLayout>
)
}
}
const mapDispatchToProps = (dispatch) => ({
onDeleteChatMessage(chatMessageId) {
dispatch(deleteChatMessage(chatMessageId))
dispatch(closePopover())
},
onClosePopover() {
dispatch(closePopover())
},
})
ChatMessageDeletePopover.propTypes = {
isXS: PropTypes.bool,
chatMessageId: PropTypes.string.isRequired,
onDeleteChatMessage: PropTypes.func.isRequired,
}
export default connect(null, mapDispatchToProps)(ChatMessageDeletePopover)

View File

@@ -0,0 +1,139 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { closePopover } from '../../actions/popover'
import { deleteChatMessage } from '../../actions/chat_messages'
import {
isChatMessengerBlocked,
isChatMessengerMuted,
blockChatMessenger,
unblockChatMessenger,
muteChatMessenger,
unmuteChatMessenger,
reportChatMessage,
} from '../../actions/chat_conversation_accounts'
import { makeGetChatMessage } from '../../selectors'
import { me } from '../../initial_state'
import PopoverLayout from './popover_layout'
import Button from '../button'
import List from '../list'
import Text from '../text'
class ChatMessageOptionsPopover extends React.PureComponent {
handleOnDelete = () => {
this.props.onDeleteChatMessage(this.props.chatMessageId)
}
handleOnReport = () => {
this.props.onReportChatMessage(this.props.chatMessageId)
}
handleOnBlock = () => {
if (this.props.isBlocked) {
this.props.unblockChatMessenger(this.props.fromAccountId)
} else {
this.props.blockChatMessenger(this.props.fromAccountId)
}
}
handleOnMute = () => {
if (this.props.isMuted) {
this.props.unmuteChatMessenger(this.props.fromAccountId)
} else {
this.props.muteChatMessenger(this.props.fromAccountId)
}
}
handleOnClosePopover = () => {
this.props.onClosePopover()
}
render() {
const {
isXS,
isMine,
isMuted,
isBlocked,
} = this.props
const items = isMine ? [
{
hideArrow: true,
title: 'Delete Message',
onClick: () => this.handleOnDelete(),
}
] : [
{
hideArrow: true,
title: 'Report Messenger',
onClick: () => this.handleOnReport(),
},
{},
{
hideArrow: true,
title: isBlocked ? 'Unblock Messenger' : 'Block Messenger',
subtitle: isBlocked ? '' : 'The messenger will not be able to message you.',
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 (
<PopoverLayout
width={isMine ? 160 : 200}
isXS={isXS}
onClose={this.handleOnClosePopover}
>
<List items={items} />
</PopoverLayout>
)
}
}
const mapStateToProps = (state, { chatMessageId }) => ({
isMine: state.getIn(['chat_messages', chatMessageId, 'from_account_id']) === me,
fromAccountId: state.getIn(['chat_messages', chatMessageId, 'from_account_id']),
isBlocked: state.getIn(['chat_messages', chatMessageId, 'from_account_id']),
isMuted: state.getIn(['chat_messages', chatMessageId, 'from_account_id']),
})
const mapDispatchToProps = (dispatch) => ({
onDeleteChatMessage(chatMessageId) {
dispatch(deleteChatMessage(chatMessageId))
dispatch(closePopover())
},
onBlock(accountId) {
dispatch(blockChatMessenger(accountId))
},
onUnblock(accountId) {
dispatch(unblockChatMessenger(accountId))
},
onMute(accountId) {
dispatch(muteChatMessenger(accountId))
},
onUnmute(accountId) {
dispatch(unmuteChatMessenger(accountId))
},
onReportChatMessage(chatMessageId) {
dispatch(reportChatMessage(chatMessageId))
},
onClosePopover() {
dispatch(closePopover())
},
})
ChatMessageOptionsPopover.propTypes = {
isXS: PropTypes.bool,
chatMessageId: PropTypes.string.isRequired,
isBlocked: PropTypes.bool.isRequired,
isMuted: PropTypes.bool.isRequired,
onDeleteChatMessage: PropTypes.func.isRequired,
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatMessageOptionsPopover)

View File

@@ -0,0 +1,61 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { closePopover } from '../../actions/popover'
import PopoverLayout from './popover_layout'
import List from '../list'
import Text from '../text'
class ComposePostDesinationPopover extends React.PureComponent {
handleOnClosePopover = () => {
this.props.onClosePopover()
}
render() {
const {
isXS,
} = this.props
// TIMELINE
// GROUP - MY GROUPS
const items = [
{
hideArrow: true,
title: 'Timeline',
onClick: () => this.handleOnDelete(),
},
{
title: 'Group',
onClick: () => this.handleOnReport(),
},
]
return (
<PopoverLayout
width={180}
isXS={isXS}
onClose={this.handleOnClosePopover}
>
<Text className={[_s.d, _s.px15, _s.py10, _s.bgSecondary].join(' ')}>Post to:</Text>
<List items={items} />
</PopoverLayout>
)
}
}
const mapStateToProps = (state) => ({
//
})
const mapDispatchToProps = (dispatch) => ({
onClosePopover: () => dispatch(closePopover()),
})
ComposePostDesinationPopover.propTypes = {
isXS: PropTypes.bool,
onClosePopover: PropTypes.func.isRequired,
}
export default connect(mapStateToProps, mapDispatchToProps)(ComposePostDesinationPopover)

View File

@@ -1,8 +1,9 @@
import {
BREAKPOINT_EXTRA_SMALL,
POPOVER_CHAT_CONVERSATION_OPTIONS,
POPOVER_CHAT_MESSAGE_DELETE,
POPOVER_CHAT_MESSAGE_OPTIONS,
POPOVER_COMMENT_SORTING_OPTIONS,
POPOVER_COMPOSE_POST_DESTINATION,
POPOVER_DATE_PICKER,
POPOVER_EMOJI_PICKER,
POPOVER_GROUP_LIST_SORT_OPTIONS,
@@ -23,8 +24,9 @@ import {
} from '../../constants'
import {
ChatConversationOptionsPopover,
ChatMessageDeletePopover,
ChatMessageOptionsPopover,
CommentSortingOptionsPopover,
ComposePostDesinationPopover,
DatePickerPopover,
EmojiPickerPopover,
GroupListSortOptionsPopover,
@@ -59,8 +61,9 @@ const initialState = getWindowDimension()
const POPOVER_COMPONENTS = {
[POPOVER_CHAT_CONVERSATION_OPTIONS]: ChatConversationOptionsPopover,
[POPOVER_CHAT_MESSAGE_DELETE]: ChatMessageDeletePopover,
[POPOVER_CHAT_MESSAGE_OPTIONS]: ChatMessageOptionsPopover,
[POPOVER_COMMENT_SORTING_OPTIONS]: CommentSortingOptionsPopover,
[POPOVER_COMPOSE_POST_DESTINATION]: ComposePostDesinationPopover,
[POPOVER_DATE_PICKER]: DatePickerPopover,
[POPOVER_EMOJI_PICKER]: EmojiPickerPopover,
[POPOVER_GROUP_LIST_SORT_OPTIONS]: GroupListSortOptionsPopover,

View File

@@ -5,15 +5,16 @@ import { defineMessages, injectIntl } from 'react-intl'
import { closePopover } from '../../actions/popover'
import { changeExpiresAt } from '../../actions/compose'
import {
STATUS_EXPIRATION_OPTION_5_MINUTES,
STATUS_EXPIRATION_OPTION_60_MINUTES,
STATUS_EXPIRATION_OPTION_6_HOURS,
STATUS_EXPIRATION_OPTION_24_HOURS,
STATUS_EXPIRATION_OPTION_3_DAYS,
STATUS_EXPIRATION_OPTION_7_DAYS,
EXPIRATION_OPTION_5_MINUTES,
EXPIRATION_OPTION_60_MINUTES,
EXPIRATION_OPTION_6_HOURS,
EXPIRATION_OPTION_24_HOURS,
EXPIRATION_OPTION_3_DAYS,
EXPIRATION_OPTION_7_DAYS,
} from '../../constants'
import PopoverLayout from './popover_layout'
import List from '../list'
import Text from '../text'
class StatusExpirationOptionsPopover extends React.PureComponent {
@@ -34,43 +35,49 @@ class StatusExpirationOptionsPopover extends React.PureComponent {
} = this.props
const listItems = [
{
hideArrow: true,
title: 'None',
onClick: () => this.handleOnSetStatusExpiration(null),
isActive: !expiresAtValue,
},
{
hideArrow: true,
title: intl.formatMessage(messages.minutes, { number: 5 }),
onClick: () => this.handleOnSetStatusExpiration(STATUS_EXPIRATION_OPTION_5_MINUTES),
isActive: expiresAtValue === STATUS_EXPIRATION_OPTION_5_MINUTES,
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_5_MINUTES),
isActive: expiresAtValue === EXPIRATION_OPTION_5_MINUTES,
},
{
hideArrow: true,
title: intl.formatMessage(messages.minutes, { number: 60 }),
onClick: () => this.handleOnSetStatusExpiration(STATUS_EXPIRATION_OPTION_60_MINUTES),
isActive: expiresAtValue === STATUS_EXPIRATION_OPTION_60_MINUTES,
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_60_MINUTES),
isActive: expiresAtValue === EXPIRATION_OPTION_60_MINUTES,
},
{
hideArrow: true,
title: '6 hours',
title: intl.formatMessage(messages.hours, { number: 6 }),
onClick: () => this.handleOnSetStatusExpiration(STATUS_EXPIRATION_OPTION_6_HOURS),
isActive: expiresAtValue === STATUS_EXPIRATION_OPTION_6_HOURS,
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_6_HOURS),
isActive: expiresAtValue === EXPIRATION_OPTION_6_HOURS,
},
{
hideArrow: true,
title: intl.formatMessage(messages.hours, { number: 24 }),
onClick: () => this.handleOnSetStatusExpiration(STATUS_EXPIRATION_OPTION_24_HOURS),
isActive: expiresAtValue === STATUS_EXPIRATION_OPTION_24_HOURS,
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_24_HOURS),
isActive: expiresAtValue === EXPIRATION_OPTION_24_HOURS,
},
{
hideArrow: true,
title: '3 days',
title: intl.formatMessage(messages.days, { number: 3 }),
onClick: () => this.handleOnSetStatusExpiration(STATUS_EXPIRATION_OPTION_3_DAYS),
isActive: expiresAtValue === STATUS_EXPIRATION_OPTION_3_DAYS,
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_3_DAYS),
isActive: expiresAtValue === EXPIRATION_OPTION_3_DAYS,
},
{
hideArrow: true,
title: intl.formatMessage(messages.days, { number: 7 }),
onClick: () => this.handleOnSetStatusExpiration(STATUS_EXPIRATION_OPTION_7_DAYS),
isActive: expiresAtValue === STATUS_EXPIRATION_OPTION_7_DAYS,
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_7_DAYS),
isActive: expiresAtValue === EXPIRATION_OPTION_7_DAYS,
},
]
@@ -88,8 +95,9 @@ class StatusExpirationOptionsPopover extends React.PureComponent {
isXS={isXS}
onClose={this.handleOnClosePopover}
>
<Text className={[_s.d, _s.px15, _s.py10, _s.bgSecondary].join(' ')}>This gab deletes after:</Text>
<List
scrollKey='group_list_sort_options'
scrollKey='status_expiration'
items={listItems}
size={isXS ? 'large' : 'small'}
/>

View File

@@ -5,6 +5,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'
import { connect } from 'react-redux'
import { defineMessages, injectIntl } from 'react-intl'
import { openModal } from '../../actions/modal'
import { showToast } from '../../actions/toasts'
import { closePopover } from '../../actions/popover'
import PopoverLayout from './popover_layout'
import Button from '../button'
@@ -31,6 +32,7 @@ class StatusSharePopover extends ImmutablePureComponent {
}
document.body.removeChild(textarea)
this.props.onShowCopyToast()
this.handleClosePopover()
}
@@ -157,6 +159,9 @@ const messages = defineMessages({
const mapDispatchToProps = (dispatch) => ({
onClosePopover: () => dispatch(closePopover()),
onShowCopyToast() {
dispatch(showToast())
},
})
StatusSharePopover.propTypes = {

View File

@@ -49,6 +49,7 @@ class StatusVisibilityDropdown extends React.PureComponent {
isXS={isXS}
onClose={this.handleOnClosePopover}
>
<Text className={[_s.d, _s.px15, _s.py10, _s.bgSecondary].join(' ')}>Status Visibility:</Text>
<div className={[_s.d].join(' ')}>
{
options.map((option, i) => {

View File

@@ -148,7 +148,7 @@ class StyleButton extends React.PureComponent {
px10: 1,
mr5: 1,
noSelect: 1,
bgSecondaryDark_onHover: 1,
bgSubtle_onHover: 1,
bgBrandLight: active,
bgTransparent: 1,
radiusSmall: 1,
@@ -162,7 +162,7 @@ class StyleButton extends React.PureComponent {
onMouseDown={this.handleOnClick}
title={label}
>
<Icon id={icon} size='12px' className={_s[iconColor]} />
<Icon id={icon} size='16px' className={_s[iconColor]} />
</button>
)
}

View File

@@ -43,12 +43,16 @@ class DeckSidebar extends ImmutablePureComponent {
this.props.onOpenComposeModal()
}
scrollToItem = () => {
}
setAvatarNode = (c) => {
this.avatarNode = c
}
render() {
const { account, logoDisabled } = this.props
const { account, gabDeckOrder, logoDisabled } = this.props
const isPro = !!account ? account.get('is_pro') : false
@@ -83,6 +87,22 @@ class DeckSidebar extends ImmutablePureComponent {
<Divider isSmall />
<div className={[_s.d, _s.aiCenter, _s.jcCenter].join(' ')}>
{
!!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>
<Divider isSmall />
{ isPro && <NavigationBarButton title='&nbsp;' icon='add' onClick={this.handleOnOpenNewColumnModel} /> }
@@ -119,6 +139,7 @@ const mapStateToProps = (state) => ({
account: makeGetAccount()(state, me),
theme: state.getIn(['settings', 'displayOptions', 'theme'], DEFAULT_THEME),
logoDisabled: state.getIn(['settings', 'displayOptions', 'logoDisabled'], false),
gabDeckOrder: state.getIn(['settings', 'gabDeckOrder']),
})
const mapDispatchToProps = (dispatch) => ({

View File

@@ -10,12 +10,14 @@ import ComposeFormContainer from '../features/compose/containers/compose_form_co
import ResponsiveClassesComponent from '../features/ui/util/responsive_classes_component'
import Responsive from '../features/ui/util/responsive_component'
import Avatar from './avatar'
import Heading from './heading'
import Button from './button'
import Text from './text'
class TimelineComposeBlock extends ImmutablePureComponent {
render() {
const {
formLocation,
account,
size,
intl,
@@ -27,7 +29,7 @@ class TimelineComposeBlock extends ImmutablePureComponent {
return (
<section className={_s.d}>
<div className={[_s.d, _s.flexRow].join(' ')}>
<ComposeFormContainer {...rest} isModal={isModal} />
<ComposeFormContainer {...rest} isModal={isModal} formLocation={formLocation} />
</div>
</section>
)
@@ -39,17 +41,7 @@ class TimelineComposeBlock extends ImmutablePureComponent {
classNames={[_s.d, _s.boxShadowBlock, _s.bgPrimary, _s.overflowHidden, _s.radiusSmall].join(' ')}
classNamesXS={[_s.d, _s.boxShadowBlock, _s.bgPrimary, _s.overflowHidden].join(' ')}
>
<Responsive min={BREAKPOINT_EXTRA_SMALL}>
<div className={[_s.d, _s.bgSubtle, _s.borderTop1PX, _s.borderBottom1PX, _s.borderColorSecondary, _s.px15, _s.py2, _s.aiCenter, _s.flexRow].join(' ')}>
<div className={_s.mr10}>
<Avatar account={account} size={20} noHover />
</div>
<Heading size='h5'>
{intl.formatMessage(messages.createPost)}
</Heading>
</div>
</Responsive>
<ComposeFormContainer {...rest} />
<ComposeFormContainer {...rest} formLocation={formLocation} />
</ResponsiveClassesComponent>
</section>
)
@@ -70,10 +62,12 @@ TimelineComposeBlock.propTypes = {
account: ImmutablePropTypes.map.isRequired,
size: PropTypes.number,
isModal: PropTypes.bool,
formLocation: PropTypes.string,
}
TimelineComposeBlock.defaultProps = {
size: 32,
formLocation: 'timeline',
}
export default injectIntl(connect(mapStateToProps)(TimelineComposeBlock))

View File

@@ -344,7 +344,7 @@ class Video extends ImmutablePureComponent {
this.video.play()
}
setTimeout(() => { // : hack :
this.video.requestPictureInPicture()
this.video.requestPictureInPicture()
}, 500)
} else {
document.exitPictureInPicture()

View File

@@ -10,7 +10,6 @@ export const BREAKPOINT_LARGE = 1280
export const BREAKPOINT_MEDIUM = 1160
export const BREAKPOINT_SMALL = 1080
export const BREAKPOINT_EXTRA_SMALL = 992
export const BREAKPOINT_EXTRA_EXTRA_SMALL = 767
export const MOUSE_IDLE_DELAY = 300
@@ -26,8 +25,9 @@ export const URL_GAB_PRO = 'https://pro.gab.com'
export const PLACEHOLDER_MISSING_HEADER_SRC = '/original/missing.png'
export const POPOVER_CHAT_CONVERSATION_OPTIONS = 'CHAT_CONVERSATION_OPTIONS'
export const POPOVER_CHAT_MESSAGE_DELETE = 'CHAT_MESSAGE_DELETE'
export const POPOVER_CHAT_MESSAGE_OPTIONS = 'CHAT_MESSAGE_OPTIONS'
export const POPOVER_COMMENT_SORTING_OPTIONS = 'COMMENT_SORTING_OPTIONS'
export const POPOVER_COMPOSE_POST_DESTINATION = 'COMPOSE_POST_DESTINATION'
export const POPOVER_DATE_PICKER = 'DATE_PICKER'
export const POPOVER_EMOJI_PICKER = 'EMOJI_PICKER'
export const POPOVER_GROUP_LIST_SORT_OPTIONS = 'GROUP_LIST_SORT_OPTIONS'
@@ -54,6 +54,7 @@ export const MODAL_COMMUNITY_TIMELINE_SETTINGS = 'COMMUNITY_TIMELINE_SETTINGS'
export const MODAL_COMPOSE = 'COMPOSE'
export const MODAL_CONFIRM = 'CONFIRM'
export const MODAL_DECK_COLUMN_ADD = 'DECK_COLUMN_ADD'
export const MODAL_DECK_COLUMN_ADD_OPTIONS = 'DECK_COLUMN_ADD_OPTIONS'
export const MODAL_DISPLAY_OPTIONS = 'DISPLAY_OPTIONS'
export const MODAL_EDIT_PROFILE = 'EDIT_PROFILE'
export const MODAL_EDIT_SHORTCUTS = 'EDIT_SHORTCUTS'
@@ -130,12 +131,12 @@ export const GAB_COM_INTRODUCE_YOURSELF_GROUP_ID = '12'
export const MIN_ACCOUNT_CREATED_AT_ONBOARDING = 1594789200000 // 2020-07-15
export const STATUS_EXPIRATION_OPTION_5_MINUTES = '5-minutes'
export const STATUS_EXPIRATION_OPTION_60_MINUTES = '60-minutes'
export const STATUS_EXPIRATION_OPTION_6_HOURS = '6-hours'
export const STATUS_EXPIRATION_OPTION_24_HOURS = '24-hours'
export const STATUS_EXPIRATION_OPTION_3_DAYS = '3-days'
export const STATUS_EXPIRATION_OPTION_7_DAYS = '7-days'
export const EXPIRATION_OPTION_5_MINUTES = 'five_minutes'
export const EXPIRATION_OPTION_60_MINUTES = 'one_hour'
export const EXPIRATION_OPTION_6_HOURS = 'six_hours'
export const EXPIRATION_OPTION_24_HOURS = 'one_day'
export const EXPIRATION_OPTION_3_DAYS = 'three_days'
export const EXPIRATION_OPTION_7_DAYS = 'one_week'
export const GROUP_TIMELINE_SORTING_TYPE_HOT = 'hot'
export const GROUP_TIMELINE_SORTING_TYPE_NEWEST = 'newest'

View File

@@ -0,0 +1,64 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { fetchBookmarkCollections } from '../actions/bookmarks'
import ColumnIndicator from '../components/column_indicator'
import List from '../components/list'
class BookmarkCollections extends ImmutablePureComponent {
componentDidMount() {
this.props.onFetchBookmarkCollections()
}
render() {
const {
isLoading,
isError,
bookmarkCollections,
} = this.props
if (isError) {
return <ColumnIndicator type='error' message='Error fetching bookmark collections' />
}
const listItems = shortcuts.map((s) => ({
to: s.get('to'),
title: s.get('title'),
image: s.get('image'),
}))
return (
<List
scrollKey='bookmark-collections'
emptyMessage='You have no bookmark collections'
items={listItems}
showLoading={isLoading}
/>
)
}
}
const mapStateToProps = (state) => ({
isError: state.getIn(['bookmark_collections', 'isError']),
isLoading: state.getIn(['bookmark_collections', 'isLoading']),
shortcuts: state.getIn(['bookmark_collections', 'items']),
})
const mapDispatchToProps = (dispatch) => ({
onFetchBookmarkCollections() {
dispatch(fetchBookmarkCollections())
},
})
BookmarkCollections.propTypes = {
isLoading: PropTypes.bool.isRequired,
isError: PropTypes.bool.isRequired,
onFetchBookmarkCollections: PropTypes.func.isRequired,
bookmarkCollections: ImmutablePropTypes.list,
}
export default connect(mapStateToProps, mapDispatchToProps)(BookmarkCollections)

View File

@@ -1,75 +1,94 @@
import React from 'react'
import PropTypes from 'prop-types'
import { defineMessages, injectIntl } from 'react-intl'
import { connect } from 'react-redux'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { length } from 'stringz'
import { isMobile } from '../../../utils/is_mobile'
import { countableText } from '../../ui/util/counter'
import {
CX,
MAX_POST_CHARACTER_COUNT,
ALLOWED_AROUND_SHORT_CODE,
BREAKPOINT_EXTRA_SMALL,
BREAKPOINT_EXTRA_EXTRA_SMALL,
BREAKPOINT_MEDIUM,
MODAL_COMPOSE,
POPOVER_COMPOSE_POST_DESTINATION,
} from '../../../constants'
import AutosuggestTextbox from '../../../components/autosuggest_textbox'
import Responsive from '../../ui/util/responsive_component'
import ResponsiveClassesComponent from '../../ui/util/responsive_classes_component'
import { openModal } from '../../../actions/modal'
import { openPopover } from '../../../actions/popover'
import Avatar from '../../../components/avatar'
import Button from '../../../components/button'
import EmojiPickerButton from './emoji_picker_button'
import PollButton from './poll_button'
import PollForm from './poll_form'
import SchedulePostButton from './schedule_post_button'
import SpoilerButton from './spoiler_button'
import ExpiresPostButton from './expires_post_button'
import RichTextEditorButton from './rich_text_editor_button'
import StatusContainer from '../../../containers/status_container'
import StatusVisibilityButton from './status_visibility_button'
import UploadButton from './media_upload_button'
import UploadForm from './upload_form'
import Input from '../../../components/input'
import Text from '../../../components/text'
import Icon from '../../../components/icon'
import ComposeExtraButtonList from './compose_extra_button_list'
import Text from '../../../components/text'
class ComposeDestinationHeader extends ImmutablePureComponent {
handleOnClick = () => {
this.props.onOpenPopover(this.desinationBtn)
}
handleOnExpand = () => {
this.props.onOpenModal()
}
setDestinationBtn = (c) => {
this.desinationBtn = c
}
render() {
const { account } = this.props
const { account, isModal } = this.props
const title = 'Post to timeline'
return (
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.bgPrimary, _s.borderBottom1PX, _s.borderTop1PX, _s.borderColorSecondary, _s.mb5, _s.mt5, _s.px15, _s.w100PC, _s.h40PX].join(' ')}>
<Avatar account={account} size={28} />
<div className={[_s.ml15].join(' ')}>
<Button
isNarrow
radiusSmall
backgroundColor='tertiary'
color='primary'
onClick={this.handleOnClick}
>
<Text color='inherit' size='small' className={_s.jcCenter}>
{title}
<Icon id='caret-down' size='8px' className={_s.ml5} />
</Text>
</Button>
<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(' ')}>
<Avatar account={account} size={28} />
<div className={[_s.ml15].join(' ')}>
<Button
isNarrow
isOutline
radiusSmall
buttonRef={this.setDestinationBtn}
backgroundColor='secondary'
color='primary'
onClick={this.handleOnClick}
className={[_s.border1PX, _s.borderColorPrimary].join(' ')}
>
<Text color='inherit' size='small' className={_s.jcCenter}>
{title}
<Icon id='caret-down' size='8px' className={_s.ml5} />
</Text>
</Button>
</div>
</div>
{
!isModal &&
<Button
isText
isNarrow
backgroundColor='none'
color='tertiary'
icon='fullscreen'
onClick={this.handleOnExpand}
/>
}
</div>
)
}
}
const mapDispatchToProps = (dispatch) => ({
onOpenModal() {
dispatch(openModal(MODAL_COMPOSE))
},
onOpenPopover(targetRef) {
dispatch(openPopover(POPOVER_COMPOSE_POST_DESTINATION, {
targetRef,
position: 'bottom',
}))
},
})
ComposeDestinationHeader.propTypes = {
account: ImmutablePropTypes.map,
isModal: PropTypes.bool,
onOpenModal: PropTypes.func.isRequired,
onOpenPopover: PropTypes.func.isRequired,
}
export default ComposeDestinationHeader
export default connect(null, mapDispatchToProps)(ComposeDestinationHeader)

View File

@@ -22,14 +22,14 @@ class ComposeExtraButton extends React.PureComponent {
const containerClasses = CX({
d: 1,
mr5: 1,
jcCenter: 1,
h40PX: 1,
mr5: 1,
})
const btnClasses = CX({
d: 1,
circle: 1,
circle: small,
noUnderline: 1,
font: 1,
cursorPointer: 1,
@@ -37,21 +37,25 @@ class ComposeExtraButton extends React.PureComponent {
outlineNone: 1,
bgTransparent: 1,
flexRow: 1,
aiCenter: 1,
// jcCenter: !small,
bgSubtle_onHover: !active,
bgBrandLight: active,
py10: 1,
px10: 1,
px10: small,
radiusSmall: !small,
})
const iconClasses = CX(iconClassName, {
const iconClasses = CX(active ? null : iconClassName, {
cSecondary: !active,
cWhite: active,
mr10: 1,
mr10: !small,
py2: small,
ml10: small,
ml10: !small,
px2: small,
})
const iconSize = !small ? '18px' : '16px'
const iconSize = '16px'
const textColor = !active ? 'primary' : 'white'
return (
@@ -65,13 +69,13 @@ class ComposeExtraButton extends React.PureComponent {
backgroundColor='none'
iconClassName={iconClasses}
icon={icon}
iconSize={iconSize}
iconSize='16px'
buttonRef={!children ? buttonRef : undefined}
>
{ children }
{
!small &&
<Text color={textColor} weight='medium' className={[_s.pr5].join(' ')}>
<Text color={textColor} weight='medium' className={[_s.pr10].join(' ')}>
{title}
</Text>
}

View File

@@ -6,6 +6,7 @@ import {
} from '../../../constants'
import Responsive from '../../ui/util/responsive_component'
import ResponsiveClassesComponent from '../../ui/util/responsive_classes_component'
import Text from '../../../components/text'
import EmojiPickerButton from './emoji_picker_button'
import PollButton from './poll_button'
import SchedulePostButton from './schedule_post_button'
@@ -22,6 +23,7 @@ class ComposeExtraButtonList extends React.PureComponent {
state = {
height: initialState.height,
width: initialState.width,
}
componentDidMount() {
@@ -31,9 +33,9 @@ class ComposeExtraButtonList extends React.PureComponent {
}
handleResize = () => {
const { height } = getWindowDimension()
const { height, width } = getWindowDimension()
this.setState({ height })
this.setState({ height, width })
}
componentWillUnmount() {
@@ -48,26 +50,33 @@ class ComposeExtraButtonList extends React.PureComponent {
edit,
hidePro,
isModal,
isStandalone,
formLocation,
} = this.props
const { height } = this.state
const { height, width } = this.state
const small = (height <= 660 || isModal) && !isStandalone
const isXS = width <= BREAKPOINT_EXTRA_SMALL
const isStandalone = formLocation === 'standalone'
const isTimeline = formLocation === 'timeline'
const small = (!isModal && isXS && !isStandalone) || isTimeline
console.log("small, formLocation:", small, formLocation)
const containerClasses = CX({
d: 1,
w100PC: 1,
bgPrimary: 1,
px15: 1,
py10: 1,
px5: 1,
py5: 1,
mb10: 1,
mtAuto: 1,
boxShadowBlockY: 1,
topLeftRadiusSmall: 1,
radiusSmall: 1,
borderTop1PX: 1,
borderBottom1PX: 1,
boxShadowBlock: 1,
borderColorSecondary: 1,
topRightRadiusSmall: 1,
flexRow: small,
overflowXScroll: small,
noScrollbar: small,
flexWrap: 1,
flexRow: (small || !isTimeline || isXS) && !isStandalone,
jcSpaceAround: isXS,
})
return (
@@ -79,8 +88,8 @@ class ComposeExtraButtonList extends React.PureComponent {
<SpoilerButton small={small} />
{ !hidePro && !edit && <SchedulePostButton small={small} /> }
{ !hidePro && !edit && <ExpiresPostButton small={small} /> }
{ !hidePro && <RichTextEditorButton small={small} /> }
</div>
{ !hidePro && !isXS && <RichTextEditorButton small={small} /> }
</div>
)
}
}
@@ -90,7 +99,7 @@ ComposeExtraButtonList.propTypes = {
edit: PropTypes.bool,
isMatch: PropTypes.bool,
isModal: PropTypes.bool,
isStandalone: PropTypes.bool,
formLocation: PropTypes.string,
}
export default ComposeExtraButtonList

View File

@@ -1,5 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import { NavLink } from 'react-router-dom'
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
@@ -11,7 +12,6 @@ import {
MAX_POST_CHARACTER_COUNT,
ALLOWED_AROUND_SHORT_CODE,
BREAKPOINT_EXTRA_SMALL,
BREAKPOINT_EXTRA_EXTRA_SMALL,
BREAKPOINT_MEDIUM,
} from '../../../constants'
import AutosuggestTextbox from '../../../components/autosuggest_textbox'
@@ -62,80 +62,70 @@ class ComposeForm extends ImmutablePureComponent {
}
handleComposeFocus = () => {
this.setState({
composeFocused: true,
});
this.setState({ composeFocused: true })
}
handleKeyDown = (e) => {
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
this.handleSubmit();
this.handleSubmit()
}
}
handleClick = (e) => {
const { isStandalone, isModalOpen, shouldCondense } = this.props
const { isModalOpen, shouldCondense } = this.props
if (!this.form) return false
if (e.target) {
if (e.target.classList.contains('react-datepicker__time-list-item')) return false
}
if (!this.form.contains(e.target)) {
this.handleClickOutside()
} else {
// : todo :
// if mobile go to /compose else openModal
if (!isStandalone && !isModalOpen && !shouldCondense) {
this.props.openComposeModal()
return false
}
}
}
handleClickOutside = () => {
const { shouldCondense, scheduledAt, text, isModalOpen } = this.props;
const condensed = shouldCondense && !text;
const { shouldCondense, scheduledAt, text, isModalOpen } = this.props
const condensed = shouldCondense && !text
if (condensed && scheduledAt && !isModalOpen) { //Reset scheduled date if condensing
this.props.setScheduledAt(null);
this.props.setScheduledAt(null)
}
this.setState({
composeFocused: false,
});
this.setState({ composeFocused: false })
}
handleSubmit = () => {
// if (this.props.text !== this.autosuggestTextarea.textbox.value) {
// Something changed the text inside the textarea (e.g. browser extensions like Grammarly)
// Update the state to match the current text
// this.props.onChange(this.autosuggestTextarea.textbox.value);
// this.props.onChange(this.autosuggestTextarea.textbox.value)
// }
// Submit disabled:
const { isSubmitting, isChangingUpload, isUploading, anyMedia, groupId, autoJoinGroup } = this.props
const fulltext = [this.props.spoilerText, countableText(this.props.text)].join('');
const fulltext = [this.props.spoilerText, countableText(this.props.text)].join('')
if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > MAX_POST_CHARACTER_COUNT || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
return;
return
}
this.props.onSubmit(groupId, this.props.replyToId, this.context.router, autoJoinGroup)
}
onSuggestionsClearRequested = () => {
this.props.onClearSuggestions();
this.props.onClearSuggestions()
}
onSuggestionsFetchRequested = (token) => {
this.props.onFetchSuggestions(token);
this.props.onFetchSuggestions(token)
}
onSuggestionSelected = (tokenStart, token, value) => {
this.props.onSuggestionSelected(tokenStart, token, value, ['text']);
this.props.onSuggestionSelected(tokenStart, token, value, ['text'])
}
onSpoilerSuggestionSelected = (tokenStart, token, value) => {
this.props.onSuggestionSelected(tokenStart, token, value, ['spoiler_text']);
this.props.onSuggestionSelected(tokenStart, token, value, ['spoiler_text'])
}
handleChangeSpoilerText = (value) => {
@@ -143,11 +133,11 @@ class ComposeForm extends ImmutablePureComponent {
}
componentDidMount() {
document.addEventListener('click', this.handleClick, false);
document.addEventListener('click', this.handleClick, false)
}
componentWillUnmount() {
document.removeEventListener('click', this.handleClick, false);
document.removeEventListener('click', this.handleClick, false)
}
componentDidUpdate(prevProps) {
@@ -156,24 +146,24 @@ class ComposeForm extends ImmutablePureComponent {
// This statement does several things:
// - If we're beginning a reply, and,
// - Replying to zero or one users, places the cursor at the end of the textbox.
// - Replying to more than one user, selects any usernames past the first;
// - Replying to more than one user, selects any usernames past the first
// this provides a convenient shortcut to drop everyone else from the conversation.
if (this.props.focusDate !== prevProps.focusDate) {
let selectionEnd, selectionStart;
let selectionEnd, selectionStart
if (this.props.preselectDate !== prevProps.preselectDate) {
selectionEnd = this.props.text.length;
selectionStart = this.props.text.search(/\s/) + 1;
selectionEnd = this.props.text.length
selectionStart = this.props.text.search(/\s/) + 1
} else if (typeof this.props.caretPosition === 'number') {
selectionStart = this.props.caretPosition;
selectionEnd = this.props.caretPosition;
selectionStart = this.props.caretPosition
selectionEnd = this.props.caretPosition
} else {
selectionEnd = this.props.text.length;
selectionStart = selectionEnd;
selectionEnd = this.props.text.length
selectionStart = selectionEnd
}
// this.autosuggestTextarea.textbox.setSelectionRange(selectionStart, selectionEnd);
// this.autosuggestTextarea.textbox.focus();
// this.autosuggestTextarea.textbox.setSelectionRange(selectionStart, selectionEnd)
// this.autosuggestTextarea.textbox.focus()
}
}
@@ -190,7 +180,6 @@ class ComposeForm extends ImmutablePureComponent {
intl,
account,
onPaste,
showSearch,
anyMedia,
shouldCondense,
autoFocus,
@@ -208,218 +197,151 @@ class ComposeForm extends ImmutablePureComponent {
isSubmitting,
isPro,
hidePro,
isStandalone,
dontOpenModal,
formLocation,
} = this.props
const disabled = isSubmitting
const text = [this.props.spoilerText, countableText(this.props.text)].join('');
const text = [this.props.spoilerText, countableText(this.props.text)].join('')
const disabledButton = isSubmitting || isUploading || isChangingUpload || length(text) > MAX_POST_CHARACTER_COUNT || (length(text.trim()) === 0 && !anyMedia)
const shouldAutoFocus = autoFocus && !showSearch && !isMobile(window.innerWidth)
const shouldAutoFocus = autoFocus && !isMobile(window.innerWidth)
const parentContainerClasses = CX({
const containerClasses = CX({
d: 1,
w100PC: 1,
flexRow: !shouldCondense,
pb10: !shouldCondense,
pb10: 1,
calcMaxH410PX: 1,
minH200PX: isModalOpen && isMatch,
overflowYScroll: 1,
boxShadowBlock: 1,
borderTop1PX: 1,
borderColorSecondary: 1,
})
const childContainerClasses = CX({
d: 1,
flexWrap: 1,
overflowHidden: 1,
flex1: 1,
minH28PX: 1,
py2: shouldCondense,
aiEnd: shouldCondense,
flexRow: shouldCondense,
radiusSmall: shouldCondense,
bgSubtle: shouldCondense,
px5: shouldCondense,
})
const actionsContainerClasses = CX({
d: 1,
flexRow: 1,
aiCenter: !shouldCondense,
aiStart: shouldCondense,
mt10: !shouldCondense,
px10: !shouldCondense,
mlAuto: shouldCondense,
flexWrap: !shouldCondense,
})
const commentPublishBtnClasses = CX({
d: 1,
jcCenter: 1,
displayNone: length(this.props.text) === 0,
})
const textbox = (
<AutosuggestTextbox
ref={(isModalOpen && shouldCondense) ? null : this.setAutosuggestTextarea}
placeholder={intl.formatMessage((shouldCondense || !!reduxReplyToId) && isMatch ? messages.commentPlaceholder : messages.placeholder)}
disabled={disabled}
value={this.props.text}
valueMarkdown={this.props.markdown}
onChange={this.handleChange}
suggestions={this.props.suggestions}
onKeyDown={this.handleKeyDown}
onFocus={this.handleComposeFocus}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
onPaste={onPaste}
autoFocus={shouldAutoFocus}
small={shouldCondense}
isModalOpen={isModalOpen}
isPro={isPro}
isEdit={!!edit}
id='main-composer'
/>
)
if (shouldCondense) {
return (
<div className={[_s.d, _s.w100PC].join(' ')}>
<div className={[_s.d, _s.flexRow, _s.w100PC].join(' ')}>
<div className={[_s.d, _s.flexRow, _s.w100PC, _s.aiCenter].join(' ')}>
<div className={[_s.d, _s.mr10].join(' ')}>
<Avatar account={account} size={28} noHover />
<Avatar account={account} size={30} noHover />
</div>
<div
className={[_s.d, _s.flexWrap, _s.overflowHidden, _s.flex1, _s.minH28PX, _s.py2, _s.aiEnd, _s.flexRow, _s.radiusSmall, _s.bgSubtle, _s.px5].join(' ')}
className={[_s.d, _s.flexWrap, _s.overflowHidden, _s.flex1, _s.minH28PX, _s.py5, _s.aiEnd, _s.flexRow, _s.radiusSmall, _s.bgSubtle, _s.px5].join(' ')}
ref={this.setForm}
onClick={this.handleClick}
>
<AutosuggestTextbox
ref={(isModalOpen && shouldCondense) ? null : this.setAutosuggestTextarea}
placeholder={intl.formatMessage(messages.commentPlaceholder)}
disabled={disabled}
value={this.props.text}
onChange={this.handleChange}
suggestions={this.props.suggestions}
onKeyDown={this.handleKeyDown}
onFocus={this.handleComposeFocus}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
onPaste={onPaste}
autoFocus={shouldAutoFocus}
small={shouldCondense}
isPro={isPro}
isEdit={!!edit}
id='comment-composer'
/>
<div className={[_s.d, _s.flexRow, _s.aiStart, _s.mlAuto].join(' ')}>
<div className={[_s.d, _s.flexRow, _s.mrAuto].join(' ')}>
<div className={commentPublishBtnClasses}>
<Button
isNarrow
onClick={this.handleSubmit}
isDisabled={disabledButton}
className={_s.px10}
>
{intl.formatMessage(scheduledAt ? messages.schedulePost : messages.post)}
</Button>
</div>
</div>
</div>
{ textbox }
{ isMatch && <ComposeFormSubmitButton type='comment' /> }
</div>
</div>
{
(isUploading || anyMedia) &&
(isUploading || anyMedia) && isMatch &&
<div className={[_s.d, _s.w100PC, _s.pl35, _s.mt5].join(' ')}>
<UploadForm replyToId={replyToId} isModalOpen={isModalOpen} />
<UploadForm isModalOpen={isModalOpen} />
</div>
}
</div>
)
}
if (isStandalone || isModalOpen) {
return (
<div className={[_s.d, _s.w100PC, _s.flexGrow1, _s.bgTertiary].join(' ')}>
<div className={[_s.d, _s.pb10, _s.calcMaxH370PX, _s.overflowYScroll, _s.boxShadowBlock, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
<ComposeDestinationHeader account={account} />
<div
className={[_s.d, _s.bgPrimary, _s.boxShadowBlock, _s.w100PC, _s.minH200PX, _s.pb10, _s.borderBottom1PX, _s.borderTop1PX, _s.borderColorSecondary].join(' ')}
ref={this.setForm}
onClick={this.handleClick}
>
{
!!reduxReplyToId && isModalOpen && isMatch &&
<div className={[_s.d, _s.px15, _s.py10, _s.mt5].join(' ')}>
<StatusContainer contextType='compose' id={reduxReplyToId} isChild />
</div>
}
{
!!spoiler &&
<div className={[_s.d, _s.px15, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
<Input
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
value={this.props.spoilerText}
onChange={this.handleChangeSpoilerText}
disabled={!this.props.spoiler}
prependIcon='warning'
maxLength={256}
id='cw-spoiler-input'
/>
</div>
}
<AutosuggestTextbox
ref={(isModalOpen && shouldCondense) ? null : this.setAutosuggestTextarea}
placeholder={intl.formatMessage((shouldCondense || !!reduxReplyToId) && isMatch ? messages.commentPlaceholder : messages.placeholder)}
disabled={disabled}
value={this.props.text}
valueMarkdown={this.props.markdown}
onChange={this.handleChange}
suggestions={this.props.suggestions}
onKeyDown={this.handleKeyDown}
onFocus={this.handleComposeFocus}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
onPaste={onPaste}
autoFocus={shouldAutoFocus}
small={shouldCondense}
isPro={isPro}
isEdit={!!edit}
id='main-composer'
/>
{
(isUploading || anyMedia) &&
<div className={[_s.d, _s.px15, _s.mt5].join(' ')}>
<UploadForm replyToId={replyToId} isModalOpen={isModalOpen} />
</div>
}
{
!edit && hasPoll &&
<div className={[_s.d, _s.px15, _s.mt5].join(' ')}>
<PollForm replyToId={replyToId} />
</div>
}
{
!!quoteOfId && isModalOpen && isMatch &&
<div className={[_s.d, _s.px15, _s.py10, _s.mt5].join(' ')}>
<StatusContainer contextType='compose' id={quoteOfId} isChild />
</div>
}
</div>
</div>
{ !isModalOpen && <ComposeFormSubmitButton /> }
<ComposeExtraButtonList isStandalone={isStandalone} isMatch={isMatch} hidePro={hidePro} edit={edit} isModal={isModalOpen} />
</div>
)
}
return (
<div
className={[_s.d, _s.w100PC, _s.pb10, _s.flexWrap, _s.overflowHidden, _s.flex1].join(' ')}
ref={this.setForm}
onClick={this.handleClick}
>
<Text className={[_s.d, _s.px15, _s.pt15, _s.pb10].join(' ')} size='medium' color='tertiary'>
{intl.formatMessage((shouldCondense || !!reduxReplyToId) && isMatch ? messages.commentPlaceholder : messages.placeholder)}
</Text>
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.mt5, _s.px10, _s.flexWrap].join(' ')}>
<UploadButton />
<EmojiPickerButton isMatch={isMatch} />
<PollButton />
<MoreButton />
<ComposeFormSubmitButton />
<div className={[_s.d, _s.w100PC, _s.flexGrow1, _s.bgPrimary].join(' ')}>
<div className={[_s.d, _s.calcMaxH410PX, _s.overflowYScroll].join(' ')}>
<Responsive min={BREAKPOINT_EXTRA_SMALL}>
<ComposeDestinationHeader account={account} isModal={isModalOpen} />
</Responsive>
<div className={containerClasses} ref={this.setForm} onClick={this.handleClick}>
{
!!reduxReplyToId && isModalOpen && isMatch &&
<div className={[_s.d, _s.px15, _s.py10, _s.mt5].join(' ')}>
<StatusContainer contextType='compose' id={reduxReplyToId} isChild />
</div>
}
{
!!spoiler &&
<div className={[_s.d, _s.px15, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
<Input
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
value={this.props.spoilerText}
onChange={this.handleChangeSpoilerText}
disabled={!this.props.spoiler}
prependIcon='warning'
maxLength={256}
id='cw-spoiler-input'
/>
</div>
}
{ textbox }
{
(isUploading || anyMedia) &&
<div className={[_s.d, _s.px15, _s.mt5].join(' ')}>
<div className={[_s.d, _s.borderTop1PX, _s.borderColorSecondary].join(' ')} />
<UploadForm />
</div>
}
{
!edit && hasPoll &&
<div className={[_s.d, _s.px15, _s.mt5].join(' ')}>
<PollForm />
</div>
}
{
!!quoteOfId && isModalOpen && isMatch &&
<div className={[_s.d, _s.px15, _s.py10, _s.mt5].join(' ')}>
<StatusContainer contextType='compose' id={quoteOfId} isChild />
</div>
}
</div>
</div>
<div className={[_s.d, _s.posAbs, _s.z2, _s.left0, _s.right0, _s.bottom0, _s.top0].join(' ')} />
<div className={[_s.d, _s.px10].join(' ')}>
<ComposeExtraButtonList formLocation={formLocation} isMatch={isMatch} hidePro={hidePro} edit={edit} isModal={isModalOpen} />
</div>
{
(!disabledButton || isModalOpen) && isMatch &&
<div className={[_s.d, _s.mb10, _s.px10].join(' ')}>
<ComposeFormSubmitButton type='block' />
</div>
}
<Responsive max={BREAKPOINT_EXTRA_SMALL}>
{
formLocation === 'timeline' &&
<NavLink to='/compose' className={[_s.d, _s.z4, _s.posAbs, _s.top0, _s.left0, _s.right0, _s.bottom0].join(' ')} />
}
</Responsive>
</div>
)
}
@@ -450,9 +372,7 @@ ComposeForm.propTypes = {
onFetchSuggestions: PropTypes.func.isRequired,
onSuggestionSelected: PropTypes.func.isRequired,
onChangeSpoilerText: PropTypes.func.isRequired,
openComposeModal: PropTypes.func.isRequired,
onPaste: PropTypes.func.isRequired,
showSearch: PropTypes.bool,
anyMedia: PropTypes.bool,
shouldCondense: PropTypes.bool,
autoFocus: PropTypes.bool,
@@ -466,11 +386,7 @@ ComposeForm.propTypes = {
isPro: PropTypes.bool,
hidePro: PropTypes.bool,
autoJoinGroup: PropTypes.bool,
isStandalone: PropTypes.bool,
}
ComposeForm.defaultProps = {
showSearch: false,
formLocation: PropTypes.string,
}
export default injectIntl(ComposeForm)

View File

@@ -1,30 +1,80 @@
import React from 'react'
import PropTypes from 'prop-types'
import { CX } from '../../../constants'
import { connect } from 'react-redux'
import { defineMessages, injectIntl } from 'react-intl'
import { length } from 'stringz'
import { countableText } from '../../ui/util/counter'
import { submitCompose } from '../../../actions/compose'
import {
CX,
MAX_POST_CHARACTER_COUNT,
} from '../../../constants'
import Button from '../../../components/button'
import Text from '../../../components/text'
class ComposeFormSubmitButton extends React.PureComponent {
handleSubmit = () => {
}
render() {
const {
intl,
title,
active,
small,
disabledButton,
type,
edit,
text,
isSubmitting,
isChangingUpload,
isUploading,
anyMedia,
quoteOfId,
scheduledAt,
hasPoll,
} = this.props
const disabledButton = isSubmitting || isUploading || isChangingUpload || length(text) > MAX_POST_CHARACTER_COUNT || (length(text.trim()) === 0 && !anyMedia)
if (type === 'comment') {
const commentPublishBtnClasses = CX({
d: 1,
jcCenter: 1,
displayNone: disabledButton,
})
return (
<div className={[_s.d, _s.flexRow, _s.aiStart, _s.mlAuto].join(' ')}>
<div className={[_s.d, _s.flexRow, _s.mrAuto].join(' ')}>
<div className={commentPublishBtnClasses}>
<Button
isNarrow
radiusSmall
onClick={this.handleSubmit}
isDisabled={disabledButton}
className={_s.px15}
>
{intl.formatMessage(scheduledAt ? messages.schedulePost : messages.post)}
</Button>
</div>
</div>
</div>
)
}
const containerClasses = CX({
d: 1,
mr5: 1,
jcCenter: 1,
h40PX: 1,
})
const btnClasses = CX({
d: 1,
circle: 1,
radiusSmall: 1,
noUnderline: 1,
font: 1,
cursorPointer: 1,
@@ -37,31 +87,33 @@ class ComposeFormSubmitButton extends React.PureComponent {
py10: 1,
px10: 1,
})
const iconClasses = CX({
cSecondary: !active,
cWhite: active,
mr10: 1,
py2: small,
ml10: small,
})
const iconSize = !small ? '18px' : '16px'
const textColor = !active ? 'primary' : 'white'
let backgroundColor, color
if (disabledButton) {
backgroundColor = 'tertiary'
color = 'tertiary'
} else if (type === 'navigation') {
backgroundColor = 'white'
color = 'brand'
} else {
backgroundColor = 'brand'
color = 'white'
}
return (
<div className={containerClasses}>
<div className={[_s.d, _s.w100PC, _s.py10, _s.px10].join(' ')}>
<div className={[_s.d, _s.w100PC].join(' ')}>
<Button
isBlock
radiusSmall
isDisabled={disabledButton}
backgroundColor={disabledButton ? 'secondary' : 'brand'}
color={disabledButton ? 'tertiary' : 'white'}
backgroundColor={backgroundColor}
color={color}
className={[_s.fs15PX, _s.px15, _s.flexGrow1, _s.mlAuto].join(' ')}
onClick={this.handleSubmit}
>
<Text color='inherit' weight='medium' align='center'>
post
<Text color='inherit' size='medium' weight='bold' align='center'>
{intl.formatMessage(scheduledAt ? messages.schedulePost : edit ? messages.postEdit : messages.post)}
</Text>
</Button>
</div>
@@ -71,9 +123,32 @@ class ComposeFormSubmitButton extends React.PureComponent {
}
// {intl.formatMessage(scheduledAt ? messages.schedulePost : edit ? messages.postEdit : messages.post)}
const messages = defineMessages({
post: { id: 'compose_form.post', defaultMessage: 'Post' },
postEdit: { id: 'compose_form.post_edit', defaultMessage: 'Post Edit' },
schedulePost: { id: 'compose_form.schedule_post', defaultMessage: 'Schedule Post' },
})
const mapStateToProps = (state) => ({
edit: state.getIn(['compose', 'id']) !== null,
text: state.getIn(['compose', 'text']),
isSubmitting: state.getIn(['compose', 'is_submitting']),
isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
isUploading: state.getIn(['compose', 'is_uploading']),
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
quoteOfId: state.getIn(['compose', 'quote_of_id']),
scheduledAt: state.getIn(['compose', 'scheduled_at']),
hasPoll: state.getIn(['compose', 'poll']),
})
const mapDispatchToProps = (dispatch) => ({
onSubmit(groupId, replyToId = null, router, isStandalone, autoJoinGroup) {
dispatch(submitCompose(groupId, replyToId, router, isStandalone, autoJoinGroup))
}
})
ComposeFormSubmitButton.propTypes = {
type: PropTypes.oneOf(['header', 'block', 'comment'])
type: PropTypes.oneOf(['header', 'navigation', 'block', 'comment'])
}
export default ComposeFormSubmitButton
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ComposeFormSubmitButton))

View File

@@ -48,7 +48,7 @@ class ExpiresPostButton extends React.PureComponent {
}
const messages = defineMessages({
expires: { id: 'expiration.title', defaultMessage: 'Add status expiration' },
expires: { id: 'expiration.title', defaultMessage: 'Status expiration' },
})
const mapStateToProps = (state) => ({

View File

@@ -19,7 +19,7 @@ class Upload extends ImmutablePureComponent {
}
state = {
hovered: false,
hovering: false,
focused: false,
dirtyDescription: null,
}
@@ -45,11 +45,11 @@ class Upload extends ImmutablePureComponent {
}
handleMouseEnter = () => {
this.setState({ hovered: true })
this.setState({ hovering: true })
}
handleMouseLeave = () => {
this.setState({ hovered: false })
this.setState({ hovering: false })
}
handleInputFocus = () => {
@@ -75,66 +75,60 @@ class Upload extends ImmutablePureComponent {
render() {
const { intl, media } = this.props
const active = this.state.hovered || this.state.focused
const description = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || ''
const { hovering } = this.state
const descriptionContainerClasses = CX({
d: 1,
posAbs: 1,
right0: 1,
bottom0: 1,
left0: 1,
mt5: 1,
mb5: 1,
ml5: 1,
mr5: 1,
displayNone: !active,
})
const active = hovering || this.state.focused
const description = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || ''
return (
<div
tabIndex='0'
className={[_s.d, _s.w50PC, _s.px5, _s.py5].join(' ')}
className={[_s.d, _s.w100PC, _s.mt10].join(' ')}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onClick={this.handleClick}
role='button'
>
<div className={[_s.d, _s.radiusSmall, _s.overflowHidden, _s.h158PX].join(' ')}>
<div className={[_s.d, _s.radiusSmall, _s.borderColorSecondary, _s.border1PX, _s.overflowHidden, _s.maxH100VH, _s.minH106PX].join(' ')}>
<Image
className={[_s.d, _s.h158PX].join(' ')}
className={[_s.d, _s.minH106PX, _s.maxH100VH].join(' ')}
src={media.get('preview_url')}
/>
{ hovering && <div className={[_s.d, _s.posAbs, _s.z2, _s.top0, _s.bottom0, _s.right0, _s.left0, _s.bgBlackOpaquest].join(' ')} /> }
{
media.get('type') === 'gifv' &&
<div className={[_s.d, _s.posAbs, _s.z2, _s.radiusSmall, _s.bgBlackOpaque, _s.px5, _s.py5, _s.ml10, _s.mt10, _s.top0, _s.left0].join(' ')}>
<div className={[_s.d, _s.posAbs, _s.z3, _s.radiusSmall, _s.bgBlackOpaque, _s.px5, _s.py5, _s.ml10, _s.mt10, _s.bottom0, _s.right0].join(' ')}>
<Text size='extraSmall' color='white' weight='medium'>GIF</Text>
</div>
}
<Button
backgroundColor='black'
color='white'
title={intl.formatMessage(messages.delete)}
onClick={this.handleUndoClick}
icon='close'
iconSize='10px'
iconClassName={_s.inherit}
className={[_s.top0, _s.right0, _s.posAbs, _s.mr5, _s.mt5, _s.px10].join(' ')}
/>
<div className={descriptionContainerClasses}>
<Input
small
hideLabel
id={`input-${media.get('id')}`}
title={intl.formatMessage(messages.description)}
placeholder={intl.formatMessage(messages.description)}
value={description}
maxLength={420}
onFocus={this.handleInputFocus}
onChange={this.handleInputChange}
onBlur={this.handleInputBlur}
onKeyDown={this.handleKeyDown}
<div className={[_s.d, _s.posAbs, _s.px15, _s.pt15, _s.z3, _s.flexRow, _s.top0, _s.left0, _s.right0].join(' ')}>
{
active &&
<div className={[_s.d, _s.flexGrow1, _s.mr15].join(' ')}>
<Input
small
hideLabel
id={`input-${media.get('id')}`}
title={intl.formatMessage(messages.description)}
placeholder={intl.formatMessage(messages.description)}
value={description}
maxLength={420}
onFocus={this.handleInputFocus}
onChange={this.handleInputChange}
onBlur={this.handleInputBlur}
onKeyDown={this.handleKeyDown}
/>
</div>
}
<Button
backgroundColor='black'
color='white'
title={intl.formatMessage(messages.delete)}
onClick={this.handleUndoClick}
icon='close'
iconSize='10px'
iconClassName={_s.inherit}
className={[_s.mlAuto, _s.px10].join(' ')}
/>
</div>
</div>

View File

@@ -11,7 +11,7 @@ class SensitiveMediaButton extends React.PureComponent {
const { active, disabled, onClick, intl } = this.props
return (
<div className={[_s.d, _s.aiStart, _s.px5].join(' ')}>
<div className={[_s.d, _s.aiStart, _s.px5, _s.py10].join(' ')}>
<Switch
id='mark-sensitive'
type='checkbox'

View File

@@ -18,23 +18,16 @@ class UploadForm extends ImmutablePureComponent {
return (
<div className={_s.d}>
{ isUploading && <ProgressBar small progress={uploadProgress} /> }
<div className={[_s.d, _s.flexRow, _s.flexWrap].join(' ')}>
{
mediaIds.map(id => (
<Upload id={id} key={id} />
))
}
{mediaIds.map(id => (
<Upload id={id} key={id} />
))}
</div>
{
!mediaIds.isEmpty() &&
<SensitiveMediaButton />
}
{
isUploading &&
<ProgressBar small progress={uploadProgress} />
}
{ !mediaIds.isEmpty() && <SensitiveMediaButton /> }
{ isUploading && <ProgressBar small progress={uploadProgress} /> }
</div>
)
}
@@ -48,7 +41,6 @@ const mapStateToProps = (state) => ({
})
UploadForm.propTypes = {
isModalOpen: PropTypes.bool,
isUploading: PropTypes.bool,
mediaIds: ImmutablePropTypes.list.isRequired,
uploadProgress: PropTypes.number,

View File

@@ -11,7 +11,7 @@ class Compose extends React.PureComponent {
}
render () {
return <ComposeFormContainer isStandalone />
return <ComposeFormContainer formLocation='standalone' />
}
}

View File

@@ -186,9 +186,9 @@ class SlideFirstPost extends React.PureComponent {
<div className={[_s.d, _s.mt15, _s.boxShadowBlock, _s.radiusSmall].join(' ')}>
<ComposeFormContainer
formLocation='introduction'
groupId={GAB_COM_INTRODUCE_YOURSELF_GROUP_ID}
hidePro
autoFocus
autoJoinGroup
/>
</div>
@@ -325,7 +325,7 @@ class Introduction extends ImmutablePureComponent {
<Button
href={currentIndex === 3 ? '/home' : undefined}
onClick={this.handleNext}
className={_s.px10}
className={[_s.px10, _s.aiCenter, _s.flexRow].join(' ')}
icon={currentIndex !== 3 ? 'arrow-right' : undefined}
iconSize={currentIndex !== 3 ? '18px' : undefined}
>
@@ -336,7 +336,7 @@ class Introduction extends ImmutablePureComponent {
<Text color='white' className={_s.px5}>{nextTitle}</Text>
</Responsive>
<Responsive max={BREAKPOINT_EXTRA_SMALL}>
<Text color='white' className={[_s.px5, _s.mr10].join(' ')}>Done</Text>
<Text color='white' className={[_s.px5, _s.mr5].join(' ')}>Done</Text>
<Icon id='check' size='14px' className={_s.cWhite} />
</Responsive>
</React.Fragment>

View File

@@ -35,16 +35,17 @@ class ChatConversationsListItem extends ImmutablePureComponent {
if (!chatConversation) return <div/>
console.log("chatConversation:", chatConversation)
const containerClasses = CX({
d: 1,
w100PC: 1,
bgTransparent: 1,
bgSubtle_onHover: 1,
borderBottom1PX: 1,
borderColorSecondary: 1,
noUnderline: 1,
outlineNone: 1,
cursorPointer: 1,
pl15: 1,
})
const innerContainerClasses = CX({
@@ -71,6 +72,9 @@ class ChatConversationsListItem extends ImmutablePureComponent {
className={containerClasses}
onClick={this.handleOnClick}
>
{ chatConversation.get('is_unread') && <div className={[_s.d, _s.posAbs, _s.left0, _s.top50PC, _s.ml10, _s.mtNeg5PX, _s.circle, _s.w10PX, _s.h10PX, _s.bgBrand].join(' ')} /> }
<div className={innerContainerClasses}>
<AvatarGroup accounts={otherAccounts} size={avatarSize} noHover />
@@ -88,6 +92,7 @@ class ChatConversationsListItem extends ImmutablePureComponent {
<div className={[_s.py5, _s.dangerousContent, _s.textAlignLeft].join(' ')} dangerouslySetInnerHTML={content} />
</div>
<div className={[_s.d, _s.posAbs, _s.h1PX, _s.w100PC, _s.bottom0, _s.right0, _s.bgSecondary].join(' ')} />
</div>
</button>
)

View File

@@ -8,6 +8,7 @@ import { openModal } from '../../../actions/modal'
import { sendChatMessage } from '../../../actions/chat_messages'
import { CX } from '../../../constants'
import Button from '../../../components/button'
import Icon from '../../../components/icon'
import Input from '../../../components/input'
import Text from '../../../components/text'
@@ -23,6 +24,10 @@ class ChatMessagesComposeForm extends React.PureComponent {
this.setState({ value: '' })
}
handleOnExpire = () => {
//
}
onChange = (e) => {
this.setState({ value: e.target.value })
}
@@ -68,6 +73,10 @@ class ChatMessagesComposeForm extends React.PureComponent {
this.sendBtn = c
}
setExpiresBtn = (c) => {
this.expiresBtn = c
}
render () {
const { isXS, chatConversationId } = this.props
const { value } = this.state
@@ -85,9 +94,7 @@ class ChatMessagesComposeForm extends React.PureComponent {
px10: 1,
fs14PX: 1,
maxH200PX: 1,
borderColorSecondary: 1,
border1PX: 1,
radiusRounded: 1,
w100PC: 1,
py10: 1,
})
@@ -105,6 +112,7 @@ class ChatMessagesComposeForm extends React.PureComponent {
onBlur={this.onBlur}
onKeyDown={this.onKeyDown}
aria-autocomplete='list'
maxLength={1600}
/>
)
@@ -114,18 +122,33 @@ class ChatMessagesComposeForm extends React.PureComponent {
disabled={disabled}
onClick={this.handleOnSendChatMessage}
>
<Text color='inherit' weight='medium' className={_s.px10}>Send</Text>
<Text color='inherit' weight='medium' className={isXS ? undefined : _s.px10}>Send</Text>
</Button>
)
const expiresBtn = (
<button
ref={this.setExpiresBtn}
className={[_s.d, _s.bgSubtle, _s.borderRight1PX, _s.borderColorSecondary, _s.w40PX, _s.h100PC, _s.aiCenter, _s.jcCenter, _s.cursorPointer, _s.outlineNone].join(' ')}
onClick={this.handleOnExpire}
>
<Icon id='stopwatch' className={[_s.cPrimary, _s.ml2].join(' ')} size='15px' />
</button>
)
if (isXS) {
return (
<div className={[_s.d, _s.z4, _s.minH58PX, _s.w100PC].join(' ')}>
<div className={[_s.d, _s.minH58PX, _s.bgPrimary, _s.aiCenter, _s.z3, _s.bottom0, _s.right0, _s.left0, _s.posFixed].join(' ')} >
<div className={[_s.d, _s.w100PC, _s.pb5, _s.px15, _s.aiCenter, _s.jcCenter, _s.saveAreaInsetPB, _s.saveAreaInsetPL, _s.saveAreaInsetPR, _s.w100PC].join(' ')}>
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.minH58PX, _s.w100PC, _s.borderTop1PX, _s.borderColorSecondary, _s.px10].join(' ')}>
<div className={[_s.d, _s.pr15, _s.flexGrow1, _s.py10].join(' ')}>
{textarea}
<div className={[_s.d, _s.flexRow, _s.radiusRounded, _s.border1PX, _s.borderColorSecondary, _s.overflowHidden].join(' ')}>
<div className={_s.d}>
{expiresBtn}
</div>
<div className={[_s.d, _s.flexGrow1].join(' ')}>
{textarea}
</div>
</div>
<div className={[_s.d, _s.h100PC, _s.aiCenter, _s.jcCenter].join(' ')}>
{button}
@@ -140,9 +163,16 @@ class ChatMessagesComposeForm extends React.PureComponent {
return (
<div className={[_s.d, _s.posAbs, _s.bottom0, _s.left0, _s.right0, _s.flexRow, _s.aiCenter, _s.minH58PX, _s.bgPrimary, _s.w100PC, _s.borderTop1PX, _s.borderColorSecondary, _s.px15].join(' ')}>
<div className={[_s.d, _s.pr15, _s.flexGrow1, _s.py10].join(' ')}>
{textarea}
<div className={[_s.d, _s.flexRow, _s.radiusRounded, _s.border1PX, _s.borderColorSecondary, _s.overflowHidden].join(' ')}>
<div className={_s.d}>
{expiresBtn}
</div>
<div className={[_s.d, _s.flexGrow1].join(' ')}>
{textarea}
</div>
</div>
</div>
<div className={[_s.d, _s.h100PC, _s.aiCenter, _s.jcCenter].join(' ')}>
<div className={[_s.d, _s.h100PC, _s.mtAuto, _s.mb10, _s.aiCenter, _s.jcCenter].join(' ')}>
{button}
</div>
</div>
@@ -163,4 +193,4 @@ ChatMessagesComposeForm.propTypes = {
onSendMessage: PropTypes.func.isRequired,
}
export default connect(null, mapDispatchToProps)(ChatMessagesComposeForm)
export default connect(mapDispatchToProps)(ChatMessagesComposeForm)

View File

@@ -8,7 +8,7 @@ import { NavLink } from 'react-router-dom'
import { openPopover } from '../../../actions/popover'
import {
CX,
POPOVER_CHAT_MESSAGE_DELETE,
POPOVER_CHAT_MESSAGE_OPTIONS,
} from '../../../constants'
import { me } from '../../../initial_state'
import Input from '../../../components/input'
@@ -51,7 +51,7 @@ class ChatMessageItem extends ImmutablePureComponent {
}
handleMoreClick = () => {
this.props.onOpenChatMessageDeletePopover(this.props.chatMessageId, this.deleteBtnRef)
this.props.onOpenChatMessageOptionsPopover(this.props.chatMessageId, this.deleteBtnRef)
}
setDeleteBtnRef = (c) => {
@@ -122,7 +122,7 @@ class ChatMessageItem extends ImmutablePureComponent {
const buttonContainerClasses = CX({
d: 1,
flexRow: 1,
displayNone: !isHovering && alt,
displayNone: !isHovering,
})
return (
@@ -145,19 +145,16 @@ class ChatMessageItem extends ImmutablePureComponent {
<div className={messageInnerContainerClasses}>
<div className={[_s.py5, _s.dangerousContent, _s.cPrimary].join(' ')} dangerouslySetInnerHTML={content} />
</div>
{
alt &&
<div className={buttonContainerClasses}>
<Button
buttonRef={this.setDeleteBtnRef}
onClick={this.handleMoreClick}
color='tertiary'
backgroundColor='none'
icon='ellipsis'
iconSize='18px'
/>
</div>
}
<div className={buttonContainerClasses}>
<Button
buttonRef={this.setDeleteBtnRef}
onClick={this.handleMoreClick}
color='tertiary'
backgroundColor='none'
icon='ellipsis'
iconSize='18px'
/>
</div>
</div>
<div className={lowerContainerClasses}>
<Text size='extraSmall' color='tertiary' align={alt ? 'right' : 'left'}>
@@ -178,8 +175,8 @@ const mapStateToProps = (state, { lastChatMessageId, chatMessageId }) => ({
})
const mapDispatchToProps = (dispatch) => ({
onOpenChatMessageDeletePopover(chatMessageId, targetRef) {
dispatch(openPopover(POPOVER_CHAT_MESSAGE_DELETE, {
onOpenChatMessageOptionsPopover(chatMessageId, targetRef) {
dispatch(openPopover(POPOVER_CHAT_MESSAGE_OPTIONS, {
targetRef,
chatMessageId,
position: 'top',

View File

@@ -15,6 +15,7 @@ import {
expandChatMessages,
scrollBottomChatMessageConversation,
} from '../../../actions/chat_conversation_messages'
import { readChatConversation } from '../../../actions/chat_conversations'
import IntersectionObserverArticle from '../../../components/intersection_observer_article'
import IntersectionObserverWrapper from '../../ui/util/intersection_observer_wrapper'
import ChatMessagePlaceholder from '../../../components/placeholder/chat_message_placeholder'
@@ -58,7 +59,6 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
// Reset the scroll position when a new child comes in in order not to
// jerk the scrollbar around if you're already scrolled down the page.
if (snapshot !== null && this.scrollContainerRef) {
console.log("snapshot:", snapshot)
this.setScrollTop(this.scrollContainerRef.scrollHeight - snapshot)
}
@@ -68,6 +68,7 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
if (prevProps.chatMessageIds.size === 0 && this.props.chatMessageIds.size > 0 && this.scrollContainerRef) {
this.scrollContainerRef.scrollTop = this.scrollContainerRef.scrollHeight
this.props.onReadChatConversation(this.props.chatConversationId)
}
}
@@ -363,6 +364,9 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
onSetChatConversationSelected: (chatConversationId) => {
dispatch(setChatConversationSelected(chatConversationId))
},
onReadChatConversation(chatConversationId) {
dispatch(readChatConversation(chatConversationId))
},
})
ChatMessageScrollingList.propTypes = {

View File

@@ -52,11 +52,13 @@ import DeckPage from '../../pages/deck_page'
import {
About,
AccountAlbums,
AccountGallery,
AccountTimeline,
AccountCommentsTimeline,
Assets,
BlockedAccounts,
BookmarkCollections,
BookmarkedStatuses,
CaliforniaConsumerProtection,
CaliforniaConsumerProtectionContact,
@@ -274,9 +276,11 @@ class SwitchingArea extends React.PureComponent {
<WrappedRoute path='/:username/photos' page={ProfilePage} component={AccountGallery} content={children} componentParams={{ noSidebar: true, mediaType: 'photo' }} />
<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/likes' page={ProfilePage} component={LikedStatuses} content={children} />
<WrappedRoute path='/:username/bookmarks' page={ProfilePage} component={BookmarkedStatuses} content={children} />
<WrappedRoute path='/:username/bookmarks' page={ProfilePage} component={BookmarkCollections} content={children} />
<WrappedRoute path='/:username/:bookmarkCollectionId/bookmarks' page={ProfilePage} component={BookmarkedStatuses} content={children} />
<WrappedRoute path='/:username/posts/:statusId' publicRoute exact page={BasicPage} component={StatusFeature} content={children} componentParams={{ title: 'Status', page: 'status' }} />

View File

@@ -6,6 +6,7 @@ export function AccountGallery() { return import(/* webpackChunkName: "features/
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 BlockedAccounts() { return import(/* webpackChunkName: "features/blocked_accounts" */'../../blocked_accounts') }
export function BookmarkCollections() { return import(/* webpackChunkName: "features/bookmark_collections" */'../../bookmark_collections') }
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 CaliforniaConsumerProtection() { return import(/* webpackChunkName: "features/california_consumer_protection" */'../../about/california_consumer_protection') }
@@ -17,17 +18,19 @@ export function ChatConversationDeleteModal() { return import(/* webpackChunkNam
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 ChatConversationRequests() { return import(/* webpackChunkName: "features/chat_conversation_requests" */'../../chat_conversation_requests') }
export function ChatMessageDeletePopover() { return import(/* webpackChunkName: "components/chat_message_delete_popover" */'../../../components/popover/chat_message_delete_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 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 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 ComposePostDesinationPopover() { return import(/* webpackChunkName: "components/compose_post_destination_popover" */'../../../components/popover/compose_post_destination_popover') }
export function ConfirmationModal() { return import(/* webpackChunkName: "components/confirmation_modal" */'../../../components/modal/confirmation_modal') }
export function DatePickerPopover() { return import(/* webpackChunkName: "components/date_picker_popover" */'../../../components/popover/date_picker_popover') }
export function Deck() { return import(/* webpackChunkName: "features/deck" */'../../deck') }
export function DeckColumnAddModal() { return import(/* webpackChunkName: "components/deck_column_add_modal" */'../../../components/modal/deck_column_add_modal') }
export function DeckColumnAddOptionsModal() { return import(/* webpackChunkName: "components/deck_column_add_options_modal" */'../../../components/modal/deck_column_add_options_modal') }
export function DisplayOptionsModal() { return import(/* webpackChunkName: "components/display_options_modal" */'../../../components/modal/display_options_modal') }
export function DMCA() { return import(/* webpackChunkName: "features/about/dmca" */'../../about/dmca') }
export function EditProfileModal() { return import(/* webpackChunkName: "components/edit_profile_modal" */'../../../components/modal/edit_profile_modal') }

View File

@@ -20,6 +20,7 @@ export const isStaff = getMeta('is_staff');
export const unreadCount = getMeta('unread_count');
export const lastReadNotificationId = getMeta('last_read_notification_id');
export const monthlyExpensesComplete = getMeta('monthly_expenses_complete');
export const trendingHashtags = getMeta('trending_hashtags');
export const isFirstSession = getMeta('is_first_session');
export const emailConfirmed = getMeta('email_confirmed');
export const meEmail = getMeta('email');

View File

@@ -5,13 +5,48 @@ import {
BREAKPOINT_EXTRA_SMALL,
} from '../constants'
import { me } from '../initial_state'
import Button from '../components/button'
import Text from '../components/text'
import DeckSidebar from '../components/sidebar/deck_sidebar'
import WrappedBundle from '../features/ui/util/wrapped_bundle'
import { getWindowDimension } from '../utils/is_mobile'
const initialState = getWindowDimension()
class DeckLayout extends React.PureComponent {
state = {
width: initialState.width,
}
componentDidMount() {
this.handleResize()
window.addEventListener('resize', this.handleResize, false)
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize, false)
}
handleResize = () => {
const { width } = getWindowDimension()
this.setState({ width })
}
render() {
const { children, title } = this.props
const { width } = this.state
const isXS = width <= BREAKPOINT_EXTRA_SMALL
if (isXS) {
return (
<div className={[_s.d, _s.aiCenter, _s.jcCenter, _s.w100PC, _s.h100VH, _s.bgTertiary].join(' ')}>
<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>
</div>
)
}
const mainBlockClasses = CX({
d: 1,

View File

@@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { defineMessages, injectIntl } from 'react-intl'
import { me } from '../initial_state'
import { me, trendingHashtags } from '../initial_state'
import {
BREAKPOINT_EXTRA_SMALL,
CX,
@@ -42,26 +42,20 @@ class SearchLayout extends React.PureComponent {
title: 'Explore',
onClick: () => this.setState({ currentExploreTabIndex: 0 }),
component: ExploreTimeline,
},
{
title: '#Election2020',
onClick: () => this.setState({ currentExploreTabIndex: 1 }),
component: HashtagTimeline,
componentParams: { params: { id: 'election2020' } },
},
{
title: '#RiggedElection',
onClick: () => this.setState({ currentExploreTabIndex: 2 }),
component: HashtagTimeline,
componentParams: { params: { id: 'riggedelection' } },
},
{
title: '#StopTheSteal',
onClick: () => this.setState({ currentExploreTabIndex: 3 }),
component: HashtagTimeline,
componentParams: { params: { id: 'stopthesteal' } },
},
}
]
if (Array.isArray(trendingHashtags)) {
trendingHashtags.forEach((tag, i) => {
let j = i + 1
this.exploreTabs.push({
title: `#${tag}`,
onClick: () => this.setState({ currentExploreTabIndex: j }),
component: HashtagTimeline,
componentParams: { params: { id: `${tag}`.toLowerCase() } },
})
})
}
this.searchTabs = [
{

View File

@@ -0,0 +1,62 @@
import {
BOOKMARK_COLLECTIONS_FETCH_REQUEST,
BOOKMARK_COLLECTIONS_FETCH_SUCCESS,
BOOKMARK_COLLECTIONS_FETCH_FAIL,
} from '../actions/bookmarks'
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'
const initialState = ImmutableMap({
items: ImmutableList(),
isLoading: false,
isFetched: false,
isError: false,
})
const normalizeBookmarkCollection = (bookmarkCollection) => {
return {
id: shortcut.id,
shortcut_type: 'account',
shortcut_id: shortcut.shortcut_id,
title: shortcut.shortcut.acct,
image: shortcut.shortcut.avatar_static,
to: `/${shortcut.shortcut.acct}`,
}
}
const normalizeBookmarkCollections = (shortcuts) => {
return fromJS(shortcuts.map((shortcut) => {
return normalizeShortcut(shortcut)
}))
}
export default function albums(state = initialState, action) {
switch(action.type) {
case SHORTCUTS_FETCH_REQUEST:
return state.withMutations((map) => {
map.set('isLoading', true)
map.set('isFetched', false)
map.set('isError', false)
})
case SHORTCUTS_FETCH_SUCCESS:
return state.withMutations((map) => {
map.set('items', normalizeShortcuts(action.shortcuts))
map.set('isLoading', false)
map.set('isFetched', true)
map.set('isError', false)
})
case SHORTCUTS_FETCH_FAIL:
return state.withMutations((map) => {
map.set('isLoading', false)
map.set('isFetched', true)
map.set('isError', true)
})
case BOOKMARK_COLLECTIONS_CREATE_REQUEST:
return state.update('items', list => list.push(fromJS(normalizeShortcut(action.shortcut))))
case BOOKMARK_COLLECTIONS_REMOVE_REQUEST:
return state.update('items', list => list.filterNot((item) => {
return `${item.get('id')}` === `${action.shortcutId}`
}))
default:
return state
}
}

View File

@@ -0,0 +1,62 @@
import {
BOOKMARK_COLLECTIONS_FETCH_REQUEST,
BOOKMARK_COLLECTIONS_FETCH_SUCCESS,
BOOKMARK_COLLECTIONS_FETCH_FAIL,
} from '../actions/bookmarks'
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'
const initialState = ImmutableMap({
items: ImmutableList(),
isLoading: false,
isFetched: false,
isError: false,
})
const normalizeBookmarkCollection = (bookmarkCollection) => {
return {
id: shortcut.id,
shortcut_type: 'account',
shortcut_id: shortcut.shortcut_id,
title: shortcut.shortcut.acct,
image: shortcut.shortcut.avatar_static,
to: `/${shortcut.shortcut.acct}`,
}
}
const normalizeBookmarkCollections = (shortcuts) => {
return fromJS(shortcuts.map((shortcut) => {
return normalizeShortcut(shortcut)
}))
}
export default function bookmark_collections(state = initialState, action) {
switch(action.type) {
case SHORTCUTS_FETCH_REQUEST:
return state.withMutations((map) => {
map.set('isLoading', true)
map.set('isFetched', false)
map.set('isError', false)
})
case SHORTCUTS_FETCH_SUCCESS:
return state.withMutations((map) => {
map.set('items', normalizeShortcuts(action.shortcuts))
map.set('isLoading', false)
map.set('isFetched', true)
map.set('isError', false)
})
case SHORTCUTS_FETCH_FAIL:
return state.withMutations((map) => {
map.set('isLoading', false)
map.set('isFetched', true)
map.set('isError', true)
})
case BOOKMARK_COLLECTIONS_CREATE_REQUEST:
return state.update('items', list => list.push(fromJS(normalizeShortcut(action.shortcut))))
case BOOKMARK_COLLECTIONS_REMOVE_REQUEST:
return state.update('items', list => list.filterNot((item) => {
return `${item.get('id')}` === `${action.shortcutId}`
}))
default:
return state
}
}

View File

@@ -7,6 +7,7 @@ import { me } from '../initial_state'
import {
CHAT_MESSAGES_SEND_SUCCESS,
CHAT_MESSAGES_DELETE_REQUEST,
CHAT_MESSAGES_PURGE_REQUEST,
} from '../actions/chat_messages'
import {
CHAT_CONVERSATIONS_APPROVED_FETCH_SUCCESS,
@@ -14,6 +15,7 @@ import {
CHAT_CONVERSATIONS_REQUESTED_FETCH_SUCCESS,
CHAT_CONVERSATIONS_REQUESTED_EXPAND_SUCCESS,
CHAT_CONVERSATION_REQUEST_APPROVE_SUCCESS,
CHAT_CONVERSATION_MARK_READ_SUCCESS,
} from '../actions/chat_conversations'
const initialState = ImmutableMap()
@@ -50,6 +52,11 @@ export default function chat_conversations(state = initialState, action) {
case CHAT_MESSAGES_DELETE_REQUEST:
// : todo : set last conversation message to one prior to this one
return state
case CHAT_MESSAGES_PURGE_REQUEST:
// : todo :
return state
case CHAT_CONVERSATION_MARK_READ_SUCCESS:
return importChatConversation(state, action.chatConversation)
default:
return state
}

View File

@@ -2,6 +2,7 @@ import { Map as ImmutableMap, fromJS } from 'immutable'
import {
CHAT_MESSAGES_SEND_SUCCESS,
CHAT_MESSAGES_DELETE_REQUEST,
CHAT_MESSAGES_PURGE_REQUEST,
} from '../actions/chat_messages'
import {
CHAT_MESSAGES_IMPORT,
@@ -26,6 +27,8 @@ export default function chat_messages(state = initialState, action) {
return importChatMessage(state, action.chatMessage)
case CHAT_MESSAGES_DELETE_REQUEST:
return deleteChatMessage(state, action.chatMessageId)
case CHAT_MESSAGES_PURGE_REQUEST:
return state
default:
return state
}

View File

@@ -11,6 +11,7 @@ import {
import {
CHAT_CONVERSATION_APPROVED_UNREAD_COUNT_FETCH_SUCCESS,
CHAT_CONVERSATION_REQUESTED_COUNT_FETCH_SUCCESS,
CHAT_CONVERSATION_MARK_READ_FETCH,
} from '../actions/chat_conversations'
import {
CHAT_MESSAGES_FETCH_SUCCESS,
@@ -34,6 +35,10 @@ export default function chats(state = initialState, action) {
return state.set('chatConversationRequestCount', action.count)
case CHAT_CONVERSATION_APPROVED_UNREAD_COUNT_FETCH_SUCCESS:
return state.set('chatsUnreadCount', action.count)
case CHAT_CONVERSATION_MARK_READ_FETCH:
const chatConversationUnreadCount = action.chatConversation.get('unread_count')
const totalUnreadCount = state.get('chatsUnreadCount')
return state.set('chatsUnreadCount', Math.max(totalUnreadCount - chatConversationUnreadCount, 0))
default:
return state
}