diff --git a/.ruby-version b/.ruby-version index 57cf282e..6a6a3d8e 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.6.5 +2.6.1 diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index d7629be4..00c6b356 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -4,7 +4,7 @@ class Api::BaseController < ApplicationController DEFAULT_STATUSES_LIMIT = 20 DEFAULT_ACCOUNTS_LIMIT = 20 DEFAULT_CHAT_CONVERSATION_LIMIT = 12 - DEFAULT_CHAT_CONVERSATION_MESSAGE_LIMIT = 10 + DEFAULT_CHAT_CONVERSATION_MESSAGE_LIMIT = 20 include RateLimitHeaders diff --git a/app/controllers/api/v1/chat_conversations/messages_controller.rb b/app/controllers/api/v1/chat_conversations/messages_controller.rb index 7f3fa684..e9e6a948 100644 --- a/app/controllers/api/v1/chat_conversations/messages_controller.rb +++ b/app/controllers/api/v1/chat_conversations/messages_controller.rb @@ -55,7 +55,7 @@ class Api::V1::ChatConversations::MessagesController < Api::BaseController end def insert_pagination_headers - set_pagination_headers(next_path, nil) + set_pagination_headers(next_path, prev_path) end def pagination_params(core_params) @@ -63,10 +63,27 @@ class Api::V1::ChatConversations::MessagesController < Api::BaseController end def next_path - api_v1_chat_conversations_message_url params[:id], pagination_params(since_id: pagination_since_id) + if records_continue? + api_v1_chat_conversations_message_url params[:id], pagination_params(max_id: pagination_max_id) + end + end + + def prev_path + unless @chats.empty? + api_v1_chat_conversations_message_url params[:id], pagination_params(since_id: pagination_since_id) + end + end + + def pagination_max_id + @chats.last.id end def pagination_since_id @chats.first.id end + + def records_continue? + @chats.size == limit_param(DEFAULT_CHAT_CONVERSATION_MESSAGE_LIMIT) + end + end diff --git a/app/javascript/gabsocial/actions/chat_conversation_messages.js b/app/javascript/gabsocial/actions/chat_conversation_messages.js index 2e8cb4ec..47895dc1 100644 --- a/app/javascript/gabsocial/actions/chat_conversation_messages.js +++ b/app/javascript/gabsocial/actions/chat_conversation_messages.js @@ -72,15 +72,17 @@ export const expandChatMessages = (chatConversationId, params = {}, done = noop) dispatch(expandChatMessagesRequest(chatConversationId, isLoadingMore)) - api(getState).get(`/api/v1/chat_conversations/messages/${chatConversationId}`, { params }).then((response) => { - console.log("response:", response) + api(getState).get(`/api/v1/chat_conversations/messages/${chatConversationId}`, { + params: { + max_id: params.maxId, + since_id: params.sinceId, + } + }).then((response) => { const next = getLinks(response).refs.find(link => link.rel === 'next') - console.log("next:", next, getLinks(response).refs) dispatch(importFetchedChatMessages(response.data)) dispatch(expandChatMessagesSuccess(chatConversationId, response.data, next ? next.uri : null, response.code === 206, isLoadingRecent, isLoadingMore)) done() }).catch((error) => { - console.log("error:", error) dispatch(expandChatMessagesFail(chatConversationId, error, isLoadingMore)) done() }) diff --git a/app/javascript/gabsocial/components/avatar_group.js b/app/javascript/gabsocial/components/avatar_group.js index 16b2e626..065f99a1 100644 --- a/app/javascript/gabsocial/components/avatar_group.js +++ b/app/javascript/gabsocial/components/avatar_group.js @@ -18,6 +18,7 @@ class AvatarGroup extends ImmutablePureComponent { return (
{ + !!accounts && accounts.map((account) => { const isPro = account.get('is_pro') const alt = `${account.get('display_name')} ${isPro ? '(PRO)' : ''}`.trim() diff --git a/app/javascript/gabsocial/components/back_button.js b/app/javascript/gabsocial/components/back_button.js index 9f5bf2f6..63546fcd 100644 --- a/app/javascript/gabsocial/components/back_button.js +++ b/app/javascript/gabsocial/components/back_button.js @@ -19,6 +19,7 @@ class BackButton extends React.PureComponent { handleBackClick = () => { this.historyBack() + if (!!this.props.onClick) this.props.onClick() } render() { diff --git a/app/javascript/gabsocial/components/navigation_bar/chat_navigation_bar_xs.js b/app/javascript/gabsocial/components/navigation_bar/chat_navigation_bar_xs.js new file mode 100644 index 00000000..4e2863cd --- /dev/null +++ b/app/javascript/gabsocial/components/navigation_bar/chat_navigation_bar_xs.js @@ -0,0 +1,100 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { connect } from 'react-redux' +import ImmutablePropTypes from 'react-immutable-proptypes' +import ImmutablePureComponent from 'react-immutable-pure-component' +import { makeGetChatConversation } from '../../selectors' +import { openPopover } from '../../actions/popover' +import { setChatConversationSelected } from '../../actions/chats' +import { POPOVER_CHAT_CONVERSATION_OPTIONS } from '../../constants' +import Heading from '../heading' +import Button from '../button' +import BackButton from '../back_button' +import Text from '../text' +import AvatarGroup from '../avatar_group' + +class ChatNavigationBar extends React.PureComponent { + + handleOnOpenChatConversationOptionsPopover = () => { + this.props.onOpenChatConversationOptionsPopover(this.props.chatConversationId, this.optionsBtnRef) + } + + handleOnBack = () => { + this.props.onSetChatConversationSelectedEmpty() + } + + setOptionsBtnRef = (c) => { + this.optionsBtnRef = c + } + + render() { + const { chatConversation } = this.props + + const otherAccounts = chatConversation ? chatConversation.get('other_accounts') : null + const nameHTML = !!otherAccounts ? otherAccounts.get(0).get('display_name_html') : '' + + return ( +
+
+ +
+ + + +
+ + +
+ +
+ +
+
+ +
+ +
+
+ ) + } + +} + +const mapStateToProps = (state, { chatConversationId }) => ({ + chatConversation: makeGetChatConversation()(state, { id: chatConversationId }), +}) + +const mapDispatchToProps = (dispatch) => ({ + onSetChatConversationSelectedEmpty() { + dispatch(setChatConversationSelected(null)) + }, + onOpenChatConversationOptionsPopover(chatConversationId, targetRef) { + dispatch(openPopover(POPOVER_CHAT_CONVERSATION_OPTIONS, { + chatConversationId, + targetRef, + position: 'bottom', + })) + }, +}) + +ChatNavigationBar.propTypes = { + chatConversation: ImmutablePropTypes.map, + chatConversationId: PropTypes.string.isRequired, +} + +export default connect(mapStateToProps, mapDispatchToProps)(ChatNavigationBar) \ No newline at end of file diff --git a/app/javascript/gabsocial/components/popover/chat_message_delete_popover.js b/app/javascript/gabsocial/components/popover/chat_message_delete_popover.js index b0922630..a822df01 100644 --- a/app/javascript/gabsocial/components/popover/chat_message_delete_popover.js +++ b/app/javascript/gabsocial/components/popover/chat_message_delete_popover.js @@ -42,6 +42,10 @@ class ChatMessageDeletePopover extends React.PureComponent { const mapDispatchToProps = (dispatch) => ({ onDeleteChatMessage(chatMessageId) { dispatch(deleteChatMessage(chatMessageId)) + dispatch(closePopover()) + }, + onClosePopover() { + dispatch(closePopover()) }, }) diff --git a/app/javascript/gabsocial/components/scrollable_list.js b/app/javascript/gabsocial/components/scrollable_list.js index 9c4da240..56c22de0 100644 --- a/app/javascript/gabsocial/components/scrollable_list.js +++ b/app/javascript/gabsocial/components/scrollable_list.js @@ -4,12 +4,11 @@ import throttle from 'lodash.throttle' import { List as ImmutableList } from 'immutable' import IntersectionObserverArticle from './intersection_observer_article' import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper' +import { MOUSE_IDLE_DELAY } from '../constants' import Block from './block' import ColumnIndicator from './column_indicator' import LoadMore from './load_more' -const MOUSE_IDLE_DELAY = 300 - class ScrollableList extends React.PureComponent { static contextTypes = { @@ -27,7 +26,7 @@ class ScrollableList extends React.PureComponent { lastScrollWasSynthetic = false; scrollToTopOnMouseIdle = false; - setScrollTop = newScrollTop => { + setScrollTop = (newScrollTop) => { if (this.documentElement.scrollTop !== newScrollTop) { this.lastScrollWasSynthetic = true; this.documentElement.scrollTop = newScrollTop; @@ -104,8 +103,6 @@ class ScrollableList extends React.PureComponent { if (scrollTop < 100 && this.props.onScrollToTop) { this.props.onScrollToTop() - } else if (scrollTop < 100 && this.props.onScrollToBottom) { - this.props.onScrollToBottom() } else if (this.props.onScroll) { this.props.onScroll() } @@ -194,7 +191,6 @@ class ScrollableList extends React.PureComponent { placeholderComponent: Placeholder, placeholderCount, onScrollToTop, - onScrollToBottom, } = this.props const childrenCount = React.Children.count(children); @@ -221,16 +217,6 @@ class ScrollableList extends React.PureComponent { return (
- { - (hasMore && onLoadMore && !isLoading) && !!onScrollToBottom && - - } - - { - isLoading && !!onScrollToBottom && - - } - { !!this.props.children && React.Children.map(this.props.children, (child, index) => ( @@ -287,7 +273,6 @@ ScrollableList.propTypes = { ]), children: PropTypes.node, onScrollToTop: PropTypes.func, - onScrollToBottom: PropTypes.func, onScroll: PropTypes.func, placeholderComponent: PropTypes.node, placeholderCount: PropTypes.number, diff --git a/app/javascript/gabsocial/components/timeline_injections/timeline_injection_root.js b/app/javascript/gabsocial/components/timeline_injections/timeline_injection_root.js index d8ace773..98680940 100644 --- a/app/javascript/gabsocial/components/timeline_injections/timeline_injection_root.js +++ b/app/javascript/gabsocial/components/timeline_injections/timeline_injection_root.js @@ -51,7 +51,6 @@ class TimelineInjectionRoot extends React.PureComponent { handleResize = () => { const { width } = getWindowDimension() - this.setState({ width }) } diff --git a/app/javascript/gabsocial/constants.js b/app/javascript/gabsocial/constants.js index a048b4c7..d7770923 100644 --- a/app/javascript/gabsocial/constants.js +++ b/app/javascript/gabsocial/constants.js @@ -11,6 +11,8 @@ export const BREAKPOINT_MEDIUM = 1160 export const BREAKPOINT_SMALL = 1080 export const BREAKPOINT_EXTRA_SMALL = 992 +export const MOUSE_IDLE_DELAY = 300 + export const LAZY_LOAD_SCROLL_OFFSET = 50 export const ALLOWED_AROUND_SHORT_CODE = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d' diff --git a/app/javascript/gabsocial/features/messages/components/chat_approved_conversations_sidebar.js b/app/javascript/gabsocial/features/messages/components/chat_approved_conversations_sidebar.js new file mode 100644 index 00000000..cae6e28a --- /dev/null +++ b/app/javascript/gabsocial/features/messages/components/chat_approved_conversations_sidebar.js @@ -0,0 +1,39 @@ +import React from 'react' +import PropTypes from 'prop-types' +import ResponsiveClassesComponent from '../../ui/util/responsive_classes_component' +import ChatConversationsSearch from './chat_conversations_search' +import ChatConversationsList from './chat_conversations_list' +import ChatConversationRequestsListItem from './chat_conversations_requests_list_item' + +class ChatApprovedConversationsSidebar extends React.PureComponent { + + render() { + const { source } = this.props + + return ( + +
+ + + + + +
+
+ ) + } + +} + +ChatApprovedConversationsSidebar.propTypes = { + source: PropTypes.string, +} + +export default ChatApprovedConversationsSidebar \ No newline at end of file diff --git a/app/javascript/gabsocial/features/messages/components/chat_conversation_request_approve_bar.js b/app/javascript/gabsocial/features/messages/components/chat_conversation_request_approve_bar.js new file mode 100644 index 00000000..6fd7a7a2 --- /dev/null +++ b/app/javascript/gabsocial/features/messages/components/chat_conversation_request_approve_bar.js @@ -0,0 +1,61 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { connect } from 'react-redux' +import { openPopover } from '../../../actions/popover' +import { approveChatConversationRequest } from '../../../actions/chat_conversations' +import { + POPOVER_CHAT_CONVERSATION_OPTIONS +} from '../../../constants' +import Button from '../../../components/button' +import Text from '../../../components/text' + +class ChatConversationRequestApproveBar extends React.PureComponent { + + handleOnApproveMessageRequest = () => { + this.props.onApproveChatConversationRequest(this.props.chatConversationId) + } + + setOptionsBtnRef = (c) => { + this.optionsBtnRef = c + } + + render () { + return ( +
+
+
+ +
+
+
+ ) + } + +} + +const mapDispatchToProps = (dispatch) => ({ + onApproveChatConversationRequest(chatConversationId) { + dispatch(approveChatConversationRequest(chatConversationId)) + }, + onOpenChatConversationOptionsPopover(chatConversationId, targetRef) { + dispatch(openPopover(POPOVER_CHAT_CONVERSATION_OPTIONS, { + chatConversationId, + targetRef, + position: 'bottom', + })) + }, +}) + +ChatConversationRequestApproveBar.propTypes = { + chatConversationId: PropTypes.string, + onApproveChatConversationRequest: PropTypes.func.isRequired, +} + +export default connect(null, mapDispatchToProps)(ChatConversationRequestApproveBar) \ No newline at end of file diff --git a/app/javascript/gabsocial/features/messages/components/chat_message_compose_form.js b/app/javascript/gabsocial/features/messages/components/chat_message_compose_form.js index 8c340a4b..ea78b7a3 100644 --- a/app/javascript/gabsocial/features/messages/components/chat_message_compose_form.js +++ b/app/javascript/gabsocial/features/messages/components/chat_message_compose_form.js @@ -28,55 +28,51 @@ class ChatMessagesComposeForm extends React.PureComponent { } onBlur = () => { - this.setState({ focused: false }); + this.setState({ focused: false }) } onFocus = () => { - this.setState({ focused: true }); + this.setState({ focused: true }) } onKeyDown = (e) => { - const { disabled } = this.props; + const { disabled } = this.props - if (disabled) { - e.preventDefault(); - return; - } + if (disabled) return e.preventDefault() // Ignore key events during text composition // e.key may be a name of the physical key even in this case (e.x. Safari / Chrome on Mac) - if (e.which === 229 || e.isComposing) return; + if (e.which === 229) return switch (e.key) { case 'Escape': document.querySelector('#gabsocial').focus() - break; + break case 'Enter': + this.handleOnSendChatMessage() + return e.preventDefault() case 'Tab': - // - break; + this.sendBtn.focus() + return e.preventDefault() + break } - // if (e.defaultPrevented || !this.props.onKeyDown) return; + if (e.defaultPrevented) return } setTextbox = (c) => { this.textbox = c } + setSendBtn = (c) => { + this.sendBtn = c + } + render () { - const { chatConversationId } = this.props + const { isXS, chatConversationId } = this.props const { value } = this.state const disabled = false - const textareaContainerClasses = CX({ - d: 1, - maxW100PC: 1, - flexGrow1: 1, - jcCenter: 1, - py5: 1, - }) - const textareaClasses = CX({ d: 1, font: 1, @@ -95,31 +91,59 @@ class ChatMessagesComposeForm extends React.PureComponent { py10: 1, }) + const textarea = ( +