From 826773ee79e6bb406ebd2c033564c81b57d91954 Mon Sep 17 00:00:00 2001 From: 2458773093 <2458773093@protonmail.com> Date: Tue, 16 Jul 2019 00:51:05 +0300 Subject: [PATCH 01/19] admin tool for editing pro status of accounts --- app/controllers/admin/accounts_controller.rb | 17 ++++++++++++++++- app/policies/account_policy.rb | 4 ++++ .../admin/accounts/_edit_pro_fields.html.haml | 7 +++++++ app/views/admin/accounts/edit_pro.html.haml | 8 ++++++++ app/views/admin/accounts/show.html.haml | 3 +++ config/routes.rb | 2 ++ 6 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 app/views/admin/accounts/_edit_pro_fields.html.haml create mode 100644 app/views/admin/accounts/edit_pro.html.haml diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index 5acbf749..0fa6d072 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -2,7 +2,7 @@ module Admin class AccountsController < BaseController - before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize, :approve, :reject, :verify, :unverify, :add_donor_badge, :remove_donor_badge, :add_investor_badge, :remove_investor_badge] + before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize, :approve, :reject, :verify, :unverify, :add_donor_badge, :remove_donor_badge, :add_investor_badge, :remove_investor_badge, :edit_pro, :save_pro] before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload] before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject] @@ -162,6 +162,17 @@ module Admin redirect_to admin_account_path(@account.id) end + def edit_pro + authorize @account, :edit_pro? + end + + def save_pro + authorize @account, :edit_pro? + + @account.update!(pro_params) + redirect_to edit_pro_admin_account_path(@account.id) + end + private def set_account @@ -196,5 +207,9 @@ module Admin :staff ) end + + def pro_params + params.require(:account).permit(:is_pro, :pro_expires_at) + end end end diff --git a/app/policies/account_policy.rb b/app/policies/account_policy.rb index d838f16e..7e95367f 100644 --- a/app/policies/account_policy.rb +++ b/app/policies/account_policy.rb @@ -61,6 +61,10 @@ class AccountPolicy < ApplicationPolicy staff? end + def edit_pro? + staff? + end + def update_badges? staff? end diff --git a/app/views/admin/accounts/_edit_pro_fields.html.haml b/app/views/admin/accounts/_edit_pro_fields.html.haml new file mode 100644 index 00000000..a48a5980 --- /dev/null +++ b/app/views/admin/accounts/_edit_pro_fields.html.haml @@ -0,0 +1,7 @@ +.fields-row + .fields-row__column.fields-row__column-6.fields-group + %label{for: "is_pro"} + PRO + = f.check_box :is_pro, wrapper: :with_label, hint: false, id: "is_pro" + .fields-row__column.fields-row__column-6.fields-group + = f.input :pro_expires_at, as: :string, wrapper: :with_label, hint: false \ No newline at end of file diff --git a/app/views/admin/accounts/edit_pro.html.haml b/app/views/admin/accounts/edit_pro.html.haml new file mode 100644 index 00000000..66a0f17d --- /dev/null +++ b/app/views/admin/accounts/edit_pro.html.haml @@ -0,0 +1,8 @@ +- content_for :page_title do + = 'Edit PRO status of @' + @account.acct + += simple_form_for @account, url: save_pro_admin_account_path(@account.id), method: :put do |f| + = render 'edit_pro_fields', f: f + + .actions + = f.button :button, t('generic.save_changes'), type: :submit \ No newline at end of file diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index a45f7a6a..4e709352 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -134,6 +134,9 @@ - if @account.is_pro? =fa_icon 'check' %time.formatted{ datetime: @account.pro_expires_at.iso8601, title: l(@account.pro_expires_at) }= l @account.pro_expires_at + %td + - if @account.local? + = table_link_to '', t('admin.accounts.edit_pro'), edit_pro_admin_account_path(@account.id), class: 'button' if can?(:verify, @account) %tr %th= t('admin.accounts.is_verified') diff --git a/config/routes.rb b/config/routes.rb index 5966cc72..b1a24b99 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -201,6 +201,8 @@ Rails.application.routes.draw do post :remove_donor_badge post :add_investor_badge post :remove_investor_badge + get :edit_pro + put :save_pro end resource :change_email, only: [:show, :update] From 17af572ec9eb2f5b781d35e033009803083153bb Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Tue, 16 Jul 2019 14:41:36 -0400 Subject: [PATCH 02/19] Fixed status/repost functionality to show status if owned by given username fixes: #47 --- app/javascript/gabsocial/components/status.js | 6 +++- .../gabsocial/features/reblogs/index.js | 31 ++++++++++++++++--- .../gabsocial/features/status/index.js | 6 +++- app/javascript/gabsocial/selectors/index.js | 9 +++++- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/app/javascript/gabsocial/components/status.js b/app/javascript/gabsocial/components/status.js index f8d588a4..74c75fe7 100644 --- a/app/javascript/gabsocial/components/status.js +++ b/app/javascript/gabsocial/components/status.js @@ -179,7 +179,11 @@ class Status extends ImmutablePureComponent { } const { status } = this.props; - this.context.router.history.push(`/${status.getIn(['account', 'acct'])}/posts/${status.getIn(['reblog', 'id'], status.get('id'))}`); + + const isReblog = !!status.getIn(['reblog', 'id']); + const originalPosterUsername = isReblog ? status.getIn(['reblog', 'account', 'acct']) : status.getIn(['account', 'acct']); + + this.context.router.history.push(`/${originalPosterUsername}/posts/${status.getIn(['reblog', 'id'], status.get('id'))}`); } } diff --git a/app/javascript/gabsocial/features/reblogs/index.js b/app/javascript/gabsocial/features/reblogs/index.js index 34976469..bebd2812 100644 --- a/app/javascript/gabsocial/features/reblogs/index.js +++ b/app/javascript/gabsocial/features/reblogs/index.js @@ -4,16 +4,28 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; +import MissingIndicator from '../../components/missing_indicator'; import { fetchReblogs } from '../../actions/interactions'; +import { fetchStatus } from '../../actions/statuses'; import { FormattedMessage } from 'react-intl'; import AccountContainer from '../../containers/account_container'; import Column from '../ui/components/column'; import ColumnBackButton from '../../components/column_back_button'; import ScrollableList from '../../components/scrollable_list'; +import { makeGetStatus } from '../../selectors'; -const mapStateToProps = (state, props) => ({ - accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId]), -}); +const mapStateToProps = (state, props) => { + const getStatus = makeGetStatus(); + const status = getStatus(state, { + id: props.params.statusId, + username: props.params.username + }); + + return { + status, + accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId]), + } +}; export default @connect(mapStateToProps) class Reblogs extends ImmutablePureComponent { @@ -22,20 +34,23 @@ class Reblogs extends ImmutablePureComponent { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, accountIds: ImmutablePropTypes.list, + status: ImmutablePropTypes.map, }; componentWillMount () { this.props.dispatch(fetchReblogs(this.props.params.statusId)); + this.props.dispatch(fetchStatus(this.props.params.statusId)); } componentWillReceiveProps(nextProps) { if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { this.props.dispatch(fetchReblogs(nextProps.params.statusId)); + this.props.dispatch(fetchStatus(nextProps.params.statusId)); } } render () { - const { accountIds } = this.props; + const { accountIds, status } = this.props; if (!accountIds) { return ( @@ -45,6 +60,14 @@ class Reblogs extends ImmutablePureComponent { ); } + if (!status) { + return ( + + + + ); + } + const emptyMessage = ; return ( diff --git a/app/javascript/gabsocial/features/status/index.js b/app/javascript/gabsocial/features/status/index.js index 59244854..0e8c3e00 100644 --- a/app/javascript/gabsocial/features/status/index.js +++ b/app/javascript/gabsocial/features/status/index.js @@ -63,7 +63,11 @@ const makeMapStateToProps = () => { const getStatus = makeGetStatus(); const mapStateToProps = (state, props) => { - const status = getStatus(state, { id: props.params.statusId }); + const status = getStatus(state, { + id: props.params.statusId, + username: props.params.username + }); + let ancestorsIds = Immutable.List(); let descendantsIds = Immutable.List(); diff --git a/app/javascript/gabsocial/selectors/index.js b/app/javascript/gabsocial/selectors/index.js index 70f08a8e..5ebd1e9c 100644 --- a/app/javascript/gabsocial/selectors/index.js +++ b/app/javascript/gabsocial/selectors/index.js @@ -70,14 +70,21 @@ export const makeGetStatus = () => { (state, { id }) => state.getIn(['statuses', state.getIn(['statuses', id, 'reblog'])]), (state, { id }) => state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]), (state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]), + (state, { username }) => username, getFilters, ], - (statusBase, statusReblog, accountBase, accountReblog, filters) => { + (statusBase, statusReblog, accountBase, accountReblog, username, filters) => { if (!statusBase) { return null; } + const accountUsername = accountBase.get('acct'); + //Must be owner of status if username exists + if (accountUsername !== username && username !== undefined) { + return null; + } + if (statusReblog) { statusReblog = statusReblog.set('account', accountReblog); } else { From 45754503ca11082950d398624d3a583a3b629d15 Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Tue, 16 Jul 2019 14:50:37 -0400 Subject: [PATCH 03/19] Updated notification badge number formatter fixes: #44 --- .../gabsocial/components/icon_with_badge.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/javascript/gabsocial/components/icon_with_badge.js b/app/javascript/gabsocial/components/icon_with_badge.js index aa87f425..c42b9337 100644 --- a/app/javascript/gabsocial/components/icon_with_badge.js +++ b/app/javascript/gabsocial/components/icon_with_badge.js @@ -1,14 +1,17 @@ import React from 'react'; import PropTypes from 'prop-types'; import Icon from 'gabsocial/components/icon'; +import { shortNumberFormat } from 'gabsocial/utils/numbers'; -const formatNumber = num => num > 40 ? '40+' : num; +const IconWithBadge = ({ id, count, className }) => { + if (count < 1) return null; -const IconWithBadge = ({ id, count, className }) => ( - - {count > 0 && {formatNumber(count)}} - -); + return ( + + {count > 0 && {shortNumberFormat(count)}} + + ) +}; IconWithBadge.propTypes = { id: PropTypes.string.isRequired, @@ -16,4 +19,4 @@ IconWithBadge.propTypes = { className: PropTypes.string, }; -export default IconWithBadge; \ No newline at end of file +export default IconWithBadge; From 601d91f73e0cff76e8eb62f56e5397223f94a951 Mon Sep 17 00:00:00 2001 From: Dank Gabs Date: Tue, 16 Jul 2019 17:09:47 -0400 Subject: [PATCH 04/19] Patch Fix for hidden poll choices and results on light theme. --- app/javascript/styles/gabsocial/polls.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/styles/gabsocial/polls.scss b/app/javascript/styles/gabsocial/polls.scss index b1cc6017..660f4a5e 100644 --- a/app/javascript/styles/gabsocial/polls.scss +++ b/app/javascript/styles/gabsocial/polls.scss @@ -29,6 +29,7 @@ overflow: hidden; text-overflow: ellipsis; color: #fff; + body.theme-gabsocial-light & {color: $gab-default-text-light;} input[type=radio], input[type=checkbox] { display: none; From 5ca9ea500db3ea5b78f1011881890cb004c33843 Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Wed, 17 Jul 2019 18:46:42 -0400 Subject: [PATCH 05/19] Removed unused redirect after compose submit route: /posts/new no longer exists --- app/javascript/gabsocial/actions/compose.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/javascript/gabsocial/actions/compose.js b/app/javascript/gabsocial/actions/compose.js index ec098608..f1cef65f 100644 --- a/app/javascript/gabsocial/actions/compose.js +++ b/app/javascript/gabsocial/actions/compose.js @@ -154,8 +154,6 @@ export function submitCompose(routerHistory) { }).then(function (response) { if (response.data.visibility === 'direct' && getState().getIn(['conversations', 'mounted']) <= 0 && routerHistory) { routerHistory.push('/messages'); - } else if (routerHistory && routerHistory.location.pathname === '/posts/new' && window.history.state) { - routerHistory.goBack(); } dispatch(insertIntoTagHistory(response.data.tags, status)); From 6abffdb5059aca693e1a3924f2aa5be5ada6e657 Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Wed, 17 Jul 2019 18:47:58 -0400 Subject: [PATCH 06/19] Removed set height of 100% on body --- app/javascript/styles/gabsocial/basics.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/javascript/styles/gabsocial/basics.scss b/app/javascript/styles/gabsocial/basics.scss index d3edae2a..1996b64f 100644 --- a/app/javascript/styles/gabsocial/basics.scss +++ b/app/javascript/styles/gabsocial/basics.scss @@ -48,7 +48,6 @@ body { &.app-body { position: absolute; width: 100%; - height: 100%; padding: 0; overflow: hidden; overflow-y: scroll; From 2eea42714810519477f2114093ae200a0f5870fe Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Wed, 17 Jul 2019 18:49:52 -0400 Subject: [PATCH 07/19] Removed focus of compose/cw after submit or spoiler change fixes: #21 --- .../gabsocial/features/compose/components/compose_form.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/javascript/gabsocial/features/compose/components/compose_form.js b/app/javascript/gabsocial/features/compose/components/compose_form.js index 8f595904..03818e2a 100644 --- a/app/javascript/gabsocial/features/compose/components/compose_form.js +++ b/app/javascript/gabsocial/features/compose/components/compose_form.js @@ -174,14 +174,6 @@ class ComposeForm extends ImmutablePureComponent { this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd); this.autosuggestTextarea.textarea.focus(); - } else if(prevProps.isSubmitting && !this.props.isSubmitting) { - this.autosuggestTextarea.textarea.focus(); - } else if (this.props.spoiler !== prevProps.spoiler) { - if (this.props.spoiler) { - this.spoilerText.input.focus(); - } else { - this.autosuggestTextarea.textarea.focus(); - } } } From 2aa38d53ea450e4cddaf3166987adf82c68f6706 Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Wed, 17 Jul 2019 18:53:09 -0400 Subject: [PATCH 08/19] Updated timeline_queue_button_header fixes: #27 put queue header in dom but hide if no count if count, animate in so it doesn't jump the feed --- .../components/timeline_queue_button_header.js | 11 +++++++---- app/javascript/styles/gabsocial/components.scss | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/app/javascript/gabsocial/components/timeline_queue_button_header.js b/app/javascript/gabsocial/components/timeline_queue_button_header.js index e1bfe6d0..f50e736f 100644 --- a/app/javascript/gabsocial/components/timeline_queue_button_header.js +++ b/app/javascript/gabsocial/components/timeline_queue_button_header.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import { shortNumberFormat } from '../utils/numbers'; +import classNames from 'classnames'; export default class TimelineQueueButtonHeader extends React.PureComponent { static propTypes = { @@ -18,19 +19,21 @@ export default class TimelineQueueButtonHeader extends React.PureComponent { render () { const { count, itemType, onClick } = this.props; - if (count <= 0) return null; + const classes = classNames('timeline-queue-header', { + 'hidden': (count <= 0) + }); return ( -
+
- 0) && + />}
); diff --git a/app/javascript/styles/gabsocial/components.scss b/app/javascript/styles/gabsocial/components.scss index 11373a5a..8ee1c6f2 100644 --- a/app/javascript/styles/gabsocial/components.scss +++ b/app/javascript/styles/gabsocial/components.scss @@ -5108,21 +5108,31 @@ noscript { .timeline-queue-header { display: block; width: 100%; - height: 52px; + max-height: 46px; position: relative; background-color: darken($ui-base-color, 8%); border-bottom: 1px solid; border-top: 1px solid; border-color: darken($ui-base-color, 4%); + transition: max-height 2.5s ease; + overflow: hidden; + + &.hidden { + max-height: 0px; + } &__btn { display: block; width: 100%; height: 100%; text-align: center; - line-height: 52px; + line-height: 46px; font-size: 14px; cursor: pointer; color: $secondary-text-color; + + span { + height: 46px; + } } } From 399891f25e826f3c282f8b8b6e1b3b6daf034b4a Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Wed, 17 Jul 2019 18:54:02 -0400 Subject: [PATCH 09/19] Updated floatingActionButton to only show if someone is logged in --- .../gabsocial/features/ui/components/columns_area.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/gabsocial/features/ui/components/columns_area.js b/app/javascript/gabsocial/features/ui/components/columns_area.js index 361291ef..6715e86c 100644 --- a/app/javascript/gabsocial/features/ui/components/columns_area.js +++ b/app/javascript/gabsocial/features/ui/components/columns_area.js @@ -7,6 +7,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import ReactSwipeableViews from 'react-swipeable-views'; import { links, getIndex, getLink } from './tabs_bar'; import { Link } from 'react-router-dom'; +import { me } from 'gabsocial/initial_state'; import BundleContainer from '../containers/bundle_container'; import ColumnLoading from './column_loading'; @@ -64,8 +65,7 @@ class ColumnsArea extends ImmutablePureComponent { {layout.RIGHT}
- - {floatingActionButton} + {me && floatingActionButton} From 08f3f5f6eceaf93f5a4a1953be6024f3cfb2c6af Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Wed, 17 Jul 2019 18:54:48 -0400 Subject: [PATCH 10/19] Updated scrollable_list intersectionObserverWrapper removed root, uses the document/window to scroll so no options are needed --- app/javascript/gabsocial/components/scrollable_list.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/javascript/gabsocial/components/scrollable_list.js b/app/javascript/gabsocial/components/scrollable_list.js index 33662df8..ad7a078b 100644 --- a/app/javascript/gabsocial/components/scrollable_list.js +++ b/app/javascript/gabsocial/components/scrollable_list.js @@ -116,10 +116,7 @@ export default class ScrollableList extends PureComponent { } attachIntersectionObserver () { - this.intersectionObserverWrapper.connect({ - root: this.node, - rootMargin: '300% 0px', - }); + this.intersectionObserverWrapper.connect(); } detachIntersectionObserver () { From c466fc6b86c6449bfe76bb3fd7714cf7445ae26e Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Wed, 17 Jul 2019 18:55:28 -0400 Subject: [PATCH 11/19] Added missing isLoading prop to ScrollableList --- app/javascript/gabsocial/components/status_list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/gabsocial/components/status_list.js b/app/javascript/gabsocial/components/status_list.js index 91bbc511..7651d230 100644 --- a/app/javascript/gabsocial/components/status_list.js +++ b/app/javascript/gabsocial/components/status_list.js @@ -133,7 +133,7 @@ export default class StatusList extends ImmutablePureComponent { return [ , - + {scrollableContent} ]; From d978734bceff746882c0c772ae39baa81cbc330b Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Wed, 17 Jul 2019 18:57:44 -0400 Subject: [PATCH 12/19] Removed unnecessary scrollContainer in status, account_gallery document scrolls, not individual containers/components simply removed the wrapping and updated indentation --- .../features/account_gallery/index.js | 75 +++++++++---------- .../gabsocial/features/status/index.js | 67 ++++++++--------- 2 files changed, 68 insertions(+), 74 deletions(-) diff --git a/app/javascript/gabsocial/features/account_gallery/index.js b/app/javascript/gabsocial/features/account_gallery/index.js index 8d77b27c..9c2fc52e 100644 --- a/app/javascript/gabsocial/features/account_gallery/index.js +++ b/app/javascript/gabsocial/features/account_gallery/index.js @@ -12,7 +12,6 @@ import Column from '../ui/components/column'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { getAccountGallery } from 'gabsocial/selectors'; import MediaItem from './components/media_item'; -import { ScrollContainer } from 'react-router-scroll-4'; import LoadMore from 'gabsocial/components/load_more'; import MissingIndicator from 'gabsocial/components/missing_indicator'; import { openModal } from 'gabsocial/actions/modal'; @@ -189,46 +188,44 @@ class AccountGallery extends ImmutablePureComponent { return ( - -
-
-
- - - - - - - - - -
+
+
+
+ + + + + + + + +
- -
- {attachments.map((attachment, index) => attachment === null ? ( - 0 ? attachments.getIn(index - 1, 'id') : null} onLoadMore={this.handleLoadMore} /> - ) : ( - - ))} - - { - attachments.size == 0 && -
- -
- } - - {loadOlder} -
- - {isLoading && attachments.size === 0 && ( -
- -
- )}
- + +
+ {attachments.map((attachment, index) => attachment === null ? ( + 0 ? attachments.getIn(index - 1, 'id') : null} onLoadMore={this.handleLoadMore} /> + ) : ( + + ))} + + { + attachments.size == 0 && +
+ +
+ } + + {loadOlder} +
+ + {isLoading && attachments.size === 0 && ( +
+ +
+ )} +
); } diff --git a/app/javascript/gabsocial/features/status/index.js b/app/javascript/gabsocial/features/status/index.js index 0e8c3e00..e88ab4c1 100644 --- a/app/javascript/gabsocial/features/status/index.js +++ b/app/javascript/gabsocial/features/status/index.js @@ -33,7 +33,6 @@ import { import { initMuteModal } from '../../actions/mutes'; import { initReport } from '../../actions/reports'; import { makeGetStatus } from '../../selectors'; -import { ScrollContainer } from 'react-router-scroll-4'; import ColumnHeader from '../../components/column_header'; import StatusContainer from '../../containers/status_container'; import { openModal } from '../../actions/modal'; @@ -471,43 +470,41 @@ class Status extends ImmutablePureComponent { /> } - -
- {ancestors} +
+ {ancestors} - -
- + +
+ - -
-
+ +
+
- {descendants} -
- + {descendants} +
); } From dea606b62d360ea57004c577072b00c068796944 Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Wed, 17 Jul 2019 18:59:50 -0400 Subject: [PATCH 13/19] Added timeline scrollTop action, added to status_list_container, scrollable_list (previously removed, adding back now) --- app/javascript/gabsocial/actions/timelines.js | 9 +++++++++ app/javascript/gabsocial/components/scrollable_list.js | 2 ++ .../features/ui/containers/status_list_container.js | 7 +++++++ app/javascript/gabsocial/reducers/timelines.js | 10 ++++++++++ 4 files changed, 28 insertions(+) diff --git a/app/javascript/gabsocial/actions/timelines.js b/app/javascript/gabsocial/actions/timelines.js index 4214cbde..78f372b2 100644 --- a/app/javascript/gabsocial/actions/timelines.js +++ b/app/javascript/gabsocial/actions/timelines.js @@ -7,6 +7,7 @@ export const TIMELINE_DELETE = 'TIMELINE_DELETE'; export const TIMELINE_CLEAR = 'TIMELINE_CLEAR'; export const TIMELINE_UPDATE_QUEUE = 'TIMELINE_UPDATE_QUEUE'; export const TIMELINE_DEQUEUE = 'TIMELINE_DEQUEUE'; +export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP'; export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST'; export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS'; @@ -210,3 +211,11 @@ export function disconnectTimeline(timeline) { timeline, }; }; + +export function scrollTopTimeline(timeline, top) { + return { + type: TIMELINE_SCROLL_TOP, + timeline, + top, + }; +}; diff --git a/app/javascript/gabsocial/components/scrollable_list.js b/app/javascript/gabsocial/components/scrollable_list.js index ad7a078b..6de613d5 100644 --- a/app/javascript/gabsocial/components/scrollable_list.js +++ b/app/javascript/gabsocial/components/scrollable_list.js @@ -26,6 +26,8 @@ export default class ScrollableList extends PureComponent { alwaysPrepend: PropTypes.bool, emptyMessage: PropTypes.node, children: PropTypes.node, + onScrollToTop: PropTypes.func, + onScroll: PropTypes.func, }; state = { diff --git a/app/javascript/gabsocial/features/ui/containers/status_list_container.js b/app/javascript/gabsocial/features/ui/containers/status_list_container.js index ee58b9ed..f130f9f4 100644 --- a/app/javascript/gabsocial/features/ui/containers/status_list_container.js +++ b/app/javascript/gabsocial/features/ui/containers/status_list_container.js @@ -5,6 +5,7 @@ import { createSelector } from 'reselect'; import { debounce } from 'lodash'; import { me } from '../../../initial_state'; import { dequeueTimeline } from 'gabsocial/actions/timelines'; +import { scrollTopTimeline } from '../../../actions/timelines'; const makeGetStatusIds = () => createSelector([ (state, { type }) => state.getIn(['settings', type], ImmutableMap()), @@ -45,6 +46,12 @@ const mapDispatchToProps = (dispatch, ownProps) => ({ onDequeueTimeline(timelineId) { dispatch(dequeueTimeline(timelineId, ownProps.onLoadMore)); }, + onScrollToTop: debounce(() => { + dispatch(scrollTopTimeline(ownProps.timelineId, true)); + }, 100), + onScroll: debounce(() => { + dispatch(scrollTopTimeline(ownProps.timelineId, false)); + }, 100), }); export default connect(mapStateToProps, mapDispatchToProps)(StatusList); diff --git a/app/javascript/gabsocial/reducers/timelines.js b/app/javascript/gabsocial/reducers/timelines.js index b0c1babf..11acf1e6 100644 --- a/app/javascript/gabsocial/reducers/timelines.js +++ b/app/javascript/gabsocial/reducers/timelines.js @@ -10,6 +10,7 @@ import { TIMELINE_UPDATE_QUEUE, TIMELINE_DEQUEUE, MAX_QUEUED_ITEMS, + TIMELINE_SCROLL_TOP, } from '../actions/timelines'; import { ACCOUNT_BLOCK_SUCCESS, @@ -137,6 +138,13 @@ const filterTimelines = (state, relationship, statuses) => { return state; }; +const updateTop = (state, timeline, top) => { + return state.update(timeline, initialTimeline, map => map.withMutations(mMap => { + if (top) mMap.set('unread', 0); + mMap.set('top', top); + })); +}; + const filterTimeline = (timeline, state, relationship, statuses) => state.updateIn([timeline, 'items'], ImmutableList(), list => list.filterNot(statusId => @@ -171,6 +179,8 @@ export default function timelines(state = initialState, action) { return filterTimeline('home', state, action.relationship, action.statuses); case TIMELINE_CONNECT: return state.update(action.timeline, initialTimeline, map => map.set('online', true)); + case TIMELINE_SCROLL_TOP: + return updateTop(state, action.timeline, action.top); case TIMELINE_DISCONNECT: return state.update( action.timeline, From a6e80559adf0eb6c856225278c1c9ab7acc30589 Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Wed, 17 Jul 2019 19:00:31 -0400 Subject: [PATCH 14/19] Added onScroll props to status_list (previously removed, adding back now) --- app/javascript/gabsocial/components/status_list.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/javascript/gabsocial/components/status_list.js b/app/javascript/gabsocial/components/status_list.js index 7651d230..03df0c25 100644 --- a/app/javascript/gabsocial/components/status_list.js +++ b/app/javascript/gabsocial/components/status_list.js @@ -25,6 +25,8 @@ export default class StatusList extends ImmutablePureComponent { timelineId: PropTypes.string, queuedItemSize: PropTypes.number, onDequeueTimeline: PropTypes.func, + onScrollToTop: PropTypes.func, + onScroll: PropTypes.func, }; componentDidMount() { From a8bc9be5e71de6977d0783558146cfd58c91b10d Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Wed, 17 Jul 2019 19:03:25 -0400 Subject: [PATCH 15/19] Updated scrollable_list to use documentElement for (primary/only) scrolling functionality added infinite scrolling, updated intersection observer, updates all components that send onScroll, onScrollTop props, removed div ref/setRef to this.node, removed all references to this.node --- .../gabsocial/components/scrollable_list.js | 80 ++++++++++++++++--- 1 file changed, 68 insertions(+), 12 deletions(-) diff --git a/app/javascript/gabsocial/components/scrollable_list.js b/app/javascript/gabsocial/components/scrollable_list.js index 6de613d5..1dbbdb23 100644 --- a/app/javascript/gabsocial/components/scrollable_list.js +++ b/app/javascript/gabsocial/components/scrollable_list.js @@ -42,9 +42,9 @@ export default class ScrollableList extends PureComponent { scrollToTopOnMouseIdle = false; setScrollTop = newScrollTop => { - if (this.node.scrollTop !== newScrollTop) { + if (this.documentElement.scrollTop !== newScrollTop) { this.lastScrollWasSynthetic = true; - this.node.scrollTop = newScrollTop; + this.documentElement.scrollTop = newScrollTop; } }; @@ -62,7 +62,7 @@ export default class ScrollableList extends PureComponent { this.clearMouseIdleTimer(); this.mouseIdleTimer = setTimeout(this.handleMouseIdle, MOUSE_IDLE_DELAY); - if (!this.mouseMovedRecently && this.node.scrollTop === 0) { + if (!this.mouseMovedRecently && this.documentElement.scrollTop === 0) { // Only set if we just started moving and are scrolled to the top. this.scrollToTopOnMouseIdle = true; } @@ -81,19 +81,25 @@ export default class ScrollableList extends PureComponent { } componentDidMount () { + this.window = window; + this.documentElement = document.documentElement; + + this.attachScrollListener(); this.attachIntersectionObserver(); + // Handle initial scroll posiiton + this.handleScroll(); } getScrollPosition = () => { - if (this.node && (this.node.scrollTop > 0 || this.mouseMovedRecently)) { - return { height: this.node.scrollHeight, top: this.node.scrollTop }; + if (this.documentElement && (this.documentElement.scrollTop > 0 || this.mouseMovedRecently)) { + return { height: this.documentElement.scrollHeight, top: this.documentElement.scrollTop }; } else { return null; } } updateScrollBottom = (snapshot) => { - const newScrollTop = this.node.scrollHeight - snapshot; + const newScrollTop = this.documentElement.scrollHeight - snapshot; this.setScrollTop(newScrollTop); } @@ -102,7 +108,61 @@ export default class ScrollableList extends PureComponent { // 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.setScrollTop(this.node.scrollHeight - snapshot); + this.setScrollTop(this.documentElement.scrollHeight - snapshot); + } + } + + attachScrollListener () { + this.window.addEventListener('scroll', this.handleScroll); + this.window.addEventListener('wheel', this.handleWheel); + } + + detachScrollListener () { + this.window.removeEventListener('scroll', this.handleScroll); + this.window.removeEventListener('wheel', this.handleWheel); + } + + handleScroll = throttle(() => { + if (this.window) { + const { scrollTop, scrollHeight, clientHeight } = this.documentElement; + const offset = scrollHeight - scrollTop - clientHeight; + + if (600 > offset && this.props.onLoadMore && this.props.hasMore && !this.props.isLoading) { + this.props.onLoadMore(); + } + + if (scrollTop < 100 && this.props.onScrollToTop) { + this.props.onScrollToTop(); + } else if (this.props.onScroll) { + this.props.onScroll(); + } + + if (!this.lastScrollWasSynthetic) { + // If the last scroll wasn't caused by setScrollTop(), assume it was + // intentional and cancel any pending scroll reset on mouse idle + this.scrollToTopOnMouseIdle = false; + } + this.lastScrollWasSynthetic = false; + } + }, 150, { + trailing: true, + }); + + handleWheel = throttle(() => { + this.scrollToTopOnMouseIdle = false; + }, 150, { + trailing: true, + }); + + getSnapshotBeforeUpdate (prevProps) { + const someItemInserted = React.Children.count(prevProps.children) > 0 && + React.Children.count(prevProps.children) < React.Children.count(this.props.children) && + this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props); + + if (someItemInserted && (this.documentElement.scrollTop > 0 || this.mouseMovedRecently)) { + return this.documentElement.scrollHeight - this.documentElement.scrollTop; + } else { + return null; } } @@ -138,10 +198,6 @@ export default class ScrollableList extends PureComponent { return firstChild && firstChild.key; } - setRef = (c) => { - this.node = c; - } - handleLoadMore = e => { e.preventDefault(); this.props.onLoadMore(); @@ -158,7 +214,7 @@ export default class ScrollableList extends PureComponent { if (showLoading) { scrollableArea = ( -
+
{prepend}
From 42e9d5a3602259884eb0236a62375566d8c2cce2 Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Wed, 17 Jul 2019 23:59:28 -0400 Subject: [PATCH 16/19] Fix issue with notification badge number not showing on mobile only hide first descendants of tab bar btn (the title of the tab) instead of all span children --- app/javascript/styles/gabsocial/components/tabs-bar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/styles/gabsocial/components/tabs-bar.scss b/app/javascript/styles/gabsocial/components/tabs-bar.scss index 1fcae0d4..825ebadf 100644 --- a/app/javascript/styles/gabsocial/components/tabs-bar.scss +++ b/app/javascript/styles/gabsocial/components/tabs-bar.scss @@ -138,7 +138,7 @@ height: 38px; border-bottom: 4px solid $gab-default-text-light; } - & span {display: none;} + & > span {display: none;} } &.home { padding: 16px 0 0 25px; From b1637266519fda39943d1852f39f63915adbb546 Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Thu, 18 Jul 2019 00:17:36 -0400 Subject: [PATCH 17/19] Removed floating action button from columns area --- .../features/ui/components/columns_area.js | 19 ++----------------- .../ui/containers/columns_area_container.js | 11 +---------- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/app/javascript/gabsocial/features/ui/components/columns_area.js b/app/javascript/gabsocial/features/ui/components/columns_area.js index 6715e86c..34981a6a 100644 --- a/app/javascript/gabsocial/features/ui/components/columns_area.js +++ b/app/javascript/gabsocial/features/ui/components/columns_area.js @@ -1,13 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; +import { injectIntl } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ReactSwipeableViews from 'react-swipeable-views'; import { links, getIndex, getLink } from './tabs_bar'; import { Link } from 'react-router-dom'; -import { me } from 'gabsocial/initial_state'; import BundleContainer from '../containers/bundle_container'; import ColumnLoading from './column_loading'; @@ -16,33 +15,20 @@ import BundleColumnError from './bundle_column_error'; import { Compose, Notifications, HomeTimeline, CommunityTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, ListTimeline } from '../../ui/util/async-components'; import Icon from 'gabsocial/components/icon'; -const messages = defineMessages({ - publish: { id: 'compose_form.publish', defaultMessage: 'Gab' }, -}); - -const shouldHideFAB = path => path.match(/^\/statuses\/|^\/search|^\/getting-started/); - export default @(component => injectIntl(component, { withRef: true })) class ColumnsArea extends ImmutablePureComponent { - static contextTypes = { - router: PropTypes.object.isRequired, - }; - static propTypes = { intl: PropTypes.object.isRequired, columns: ImmutablePropTypes.list.isRequired, - isModalOpen: PropTypes.bool.isRequired, children: PropTypes.node, layout: PropTypes.object, }; render () { - const { columns, children, isModalOpen, intl, onOpenCompose } = this.props; + const { columns, children, intl } = this.props; const layout = this.props.layout || {LEFT:null,RIGHT:null}; - const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : ; - return (
@@ -65,7 +51,6 @@ class ColumnsArea extends ImmutablePureComponent { {layout.RIGHT}
- {me && floatingActionButton}
diff --git a/app/javascript/gabsocial/features/ui/containers/columns_area_container.js b/app/javascript/gabsocial/features/ui/containers/columns_area_container.js index 1985ac11..4e26f301 100644 --- a/app/javascript/gabsocial/features/ui/containers/columns_area_container.js +++ b/app/javascript/gabsocial/features/ui/containers/columns_area_container.js @@ -1,17 +1,8 @@ import { connect } from 'react-redux'; import ColumnsArea from '../components/columns_area'; -import { openModal } from '../../../actions/modal'; const mapStateToProps = state => ({ columns: state.getIn(['settings', 'columns']), - isModalOpen: !!state.get('modal').modalType, }); - -const mapDispatchToProps = (dispatch) => ({ - onOpenCompose() { - dispatch(openModal('COMPOSE')); - }, -}); - -export default connect(mapStateToProps, mapDispatchToProps, null, { forwardRef: true })(ColumnsArea); +export default connect(mapStateToProps, null, null, { forwardRef: true })(ColumnsArea); From abd4a831a2ae84dff987fb01418b2e8b35de7f05 Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Thu, 18 Jul 2019 00:22:19 -0400 Subject: [PATCH 18/19] Added floating action button to ui/index hide on search, post, getting-started, if no user --- app/javascript/gabsocial/features/ui/index.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/javascript/gabsocial/features/ui/index.js b/app/javascript/gabsocial/features/ui/index.js index 1b341552..22f2f730 100644 --- a/app/javascript/gabsocial/features/ui/index.js +++ b/app/javascript/gabsocial/features/ui/index.js @@ -64,6 +64,7 @@ import '../../components/status'; const messages = defineMessages({ beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Gab Social.' }, + publish: { id: 'compose_form.publish', defaultMessage: 'Gab' }, }); const mapStateToProps = state => ({ @@ -126,6 +127,8 @@ const LAYOUT = { }, }; +const shouldHideFAB = path => path.match(/^\/posts\/|^\/search|^\/getting-started/); + class SwitchingColumnsArea extends React.PureComponent { static propTypes = { @@ -475,9 +478,13 @@ class UI extends React.PureComponent { this.context.router.history.push('/follow_requests'); } + handleOpenComposeModal = () => { + this.props.dispatch(openModal("COMPOSE")); + } + render () { const { draggingOver } = this.state; - const { children, isComposing, location, dropdownMenuIsOpen } = this.props; + const { intl, children, isComposing, location, dropdownMenuIsOpen } = this.props; const handlers = me ? { help: this.handleHotkeyToggleHelp, @@ -497,6 +504,8 @@ class UI extends React.PureComponent { goToRequests: this.handleHotkeyGoToRequests, } : {}; + const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : ; + return (
@@ -505,6 +514,8 @@ class UI extends React.PureComponent { {children} + {me && floatingActionButton} + From 8ccb71066535ca17c2d89d71c2a747dbdffe94f6 Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Thu, 18 Jul 2019 00:59:54 -0400 Subject: [PATCH 19/19] Updated status component to use properStatus when going to status page Takes into account if is reblog or not and uses the original poster information if so, otherwise use status url as normal. Fixes issue with clicking on status and it goes to wrong url of the reposter and shows error message. --- app/javascript/gabsocial/components/status.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/app/javascript/gabsocial/components/status.js b/app/javascript/gabsocial/components/status.js index 74c75fe7..bc72ffed 100644 --- a/app/javascript/gabsocial/components/status.js +++ b/app/javascript/gabsocial/components/status.js @@ -168,8 +168,7 @@ class Status extends ImmutablePureComponent { return; } - const { status } = this.props; - this.context.router.history.push(`/${status.getIn(['account', 'acct'])}/posts/${status.getIn(['reblog', 'id'], status.get('id'))}`); + this.context.router.history.push(`/${this._properStatus().getIn(['account', 'acct'])}/posts/${this._properStatus().get('id')}`); } handleExpandClick = (e) => { @@ -178,12 +177,7 @@ class Status extends ImmutablePureComponent { return; } - const { status } = this.props; - - const isReblog = !!status.getIn(['reblog', 'id']); - const originalPosterUsername = isReblog ? status.getIn(['reblog', 'account', 'acct']) : status.getIn(['account', 'acct']); - - this.context.router.history.push(`/${originalPosterUsername}/posts/${status.getIn(['reblog', 'id'], status.get('id'))}`); + this.context.router.history.push(`/${this._properStatus().getIn(['account', 'acct'])}/posts/${this._properStatus().get('id')}`); } } @@ -222,7 +216,7 @@ class Status extends ImmutablePureComponent { } handleHotkeyOpen = () => { - this.context.router.history.push(`/${status.getIn(['account', 'acct'])}/posts/${this._properStatus().get('id')}`); + this.context.router.history.push(`/${this._properStatus().getIn(['account', 'acct'])}/posts/${this._properStatus().get('id')}`); } handleHotkeyOpenProfile = () => {