From a06e7bc3fb04614e4114a3e655cd4fb4cf39e05d Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 18 Jun 2019 16:26:10 +0200 Subject: Change plaintext icon in composer options --- app/javascript/flavours/glitch/features/compose/components/options.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/compose/components/options.js b/app/javascript/flavours/glitch/features/compose/components/options.js index 0c94f5514..ed52b1997 100644 --- a/app/javascript/flavours/glitch/features/compose/components/options.js +++ b/app/javascript/flavours/glitch/features/compose/components/options.js @@ -232,7 +232,7 @@ class ComposerOptions extends ImmutablePureComponent { const contentTypeItems = { plain: { - icon: 'align-left', + icon: 'file-text', name: 'text/plain', text: , }, -- cgit From f4b008906dcfc3333ec533884aa10b65f8ae41a2 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Sun, 16 Jun 2019 21:04:09 +0200 Subject: Change preferences link in local settings modal from sliders to cog for consistency --- .../flavours/glitch/features/local_settings/navigation/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/local_settings/navigation/index.js b/app/javascript/flavours/glitch/features/local_settings/navigation/index.js index c583c4863..01368abad 100644 --- a/app/javascript/flavours/glitch/features/local_settings/navigation/index.js +++ b/app/javascript/flavours/glitch/features/local_settings/navigation/index.js @@ -74,7 +74,7 @@ export default class LocalSettingsNavigation extends React.PureComponent { active={index === 5} href={ preferencesLink } index={5} - icon='sliders' + icon='cog' title={intl.formatMessage(messages.preferences)} /> Date: Wed, 19 Jun 2019 15:16:18 +0200 Subject: Add NavigationBar to getting started column in single-column mode Fixes #1131 --- app/javascript/flavours/glitch/features/getting_started/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/getting_started/index.js b/app/javascript/flavours/glitch/features/getting_started/index.js index f669220e3..36a445dca 100644 --- a/app/javascript/flavours/glitch/features/getting_started/index.js +++ b/app/javascript/flavours/glitch/features/getting_started/index.js @@ -14,6 +14,7 @@ import { List as ImmutableList } from 'immutable'; import { createSelector } from 'reselect'; import { fetchLists } from 'flavours/glitch/actions/lists'; import { preferencesLink, signOutLink } from 'flavours/glitch/util/backend_links'; +import NavigationBar from '../compose/components/navigation_bar'; import LinkFooter from 'flavours/glitch/features/ui/components/link_footer'; const messages = defineMessages({ @@ -165,7 +166,8 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
- + {!multiColumn && } + {multiColumn && } {navItems} {listItems} -- cgit From 38d28824475056766c97385b66f4e04a5123e3a2 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Wed, 19 Jun 2019 15:41:17 +0200 Subject: Fix NavigationBar styling --- .../features/compose/components/navigation_bar.js | 20 +++++++++++--------- .../flavours/glitch/styles/components/drawer.scss | 17 ++++++++++------- 2 files changed, 21 insertions(+), 16 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js b/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js index 3148434f1..f6bfbdd1e 100644 --- a/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js +++ b/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js @@ -20,16 +20,18 @@ export default class NavigationBar extends ImmutablePureComponent { - - @{this.props.account.get('acct')} - +
+ + @{this.props.account.get('acct')} + - { profileLink !== undefined && ( - - )} + { profileLink !== undefined && ( + + )} +
); }; diff --git a/app/javascript/flavours/glitch/styles/components/drawer.scss b/app/javascript/flavours/glitch/styles/components/drawer.scss index f054ddbc0..0e50482f6 100644 --- a/app/javascript/flavours/glitch/styles/components/drawer.scss +++ b/app/javascript/flavours/glitch/styles/components/drawer.scss @@ -163,18 +163,15 @@ .drawer--account { padding: 10px; color: $darker-text-color; + display: flex; + align-items: center; - & > a { + a { color: inherit; text-decoration: none; } - & > .avatar { - float: left; - margin-right: 10px; - } - - & > .acct { + .acct { display: block; color: $secondary-text-color; font-weight: 500; @@ -184,6 +181,12 @@ } } +.navigation-bar__profile { + flex: 1 1 auto; + margin-left: 8px; + overflow: hidden; +} + .drawer--results { background: $ui-base-color; overflow-x: hidden; -- cgit From 967456b6a9ef34d8ffc386b89993d790c26089e1 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 19 Jun 2019 23:42:38 +0200 Subject: [Glitch] Add audio uploads Port front-end changes from f7f23b4a19a84371f44ec5297125e96ba81681a1 to glitch-soc Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/components/status.js | 12 ++++++------ .../glitch/features/compose/containers/options_container.js | 2 +- .../glitch/features/status/components/detailed_status.js | 12 ++++++------ 3 files changed, 13 insertions(+), 13 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index f6d73475a..7bd2bc4a8 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -521,16 +521,16 @@ export default class Status extends ImmutablePureComponent { media={status.get('media_attachments')} /> ); - } else if (attachments.getIn([0, 'type']) === 'video') { // Media type is 'video' - const video = status.getIn(['media_attachments', 0]); + } else if (['video', 'audio'].includes(attachments.getIn([0, 'type']))) { + const attachment = status.getIn(['media_attachments', 0]); media = ( {Component => ( item.get('type') === 'video') : true), + allowMedia: !poll && (media ? media.size < 4 && !media.some(item => ['video', 'audio'].includes(item.get('type'))) : true), hasMedia: media && !!media.size, allowPoll: !(media && !!media.size), showContentTypeChoice: state.getIn(['local_settings', 'show_content_type_choice']), diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js index ddedac4d4..91d41d77a 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js @@ -131,14 +131,14 @@ export default class DetailedStatus extends ImmutablePureComponent { } else if (status.get('media_attachments').size > 0) { if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) { media = ; - } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') { - const video = status.getIn(['media_attachments', 0]); + } else if (['video', 'audio'].includes(status.getIn(['media_attachments', 0, 'type']))) { + const attachment = status.getIn(['media_attachments', 0]); media = ( ); - mediaIcon = 'video-camera'; + mediaIcon = attachment.get('type') === 'video' ? 'video-camera' : 'music'; } else { // Media type is 'image' or 'gifv' media = ( diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js index 91d41d77a..1c2258256 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js @@ -150,7 +150,7 @@ export default class DetailedStatus extends ImmutablePureComponent { onToggleVisibility={this.props.onToggleMediaVisibility} /> ); - mediaIcon = 'video-camera'; + mediaIcon = attachment.get('type') === 'video' ? 'video-camera' : 'music'; } else { media = ( Date: Tue, 25 Jun 2019 22:57:56 +0200 Subject: Scroll to compose form rather than reply indicator on focus --- .../flavours/glitch/features/compose/components/compose_form.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js index cbce675d5..822cfa95d 100644 --- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js +++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js @@ -296,12 +296,12 @@ class ComposeForm extends ImmutablePureComponent { let disabledButton = isSubmitting || isUploading || isChangingUpload || (!text.trim().length && !anyMedia); return ( -
+
-
+
Date: Wed, 26 Jun 2019 19:33:04 +0200 Subject: [Glitch] Add option to disable blurhash previews Port 3086c645fde2345d34e401bdf3e2f19f19da3294 to glitch-soc --- app/javascript/flavours/glitch/components/media_gallery.js | 4 +++- app/javascript/flavours/glitch/features/video/index.js | 4 ++-- app/javascript/flavours/glitch/util/initial_state.js | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js index 291caff45..04d3ce751 100644 --- a/app/javascript/flavours/glitch/components/media_gallery.js +++ b/app/javascript/flavours/glitch/components/media_gallery.js @@ -6,7 +6,7 @@ import IconButton from './icon_button'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { isIOS } from 'flavours/glitch/util/is_mobile'; import classNames from 'classnames'; -import { autoPlayGif, displayMedia } from 'flavours/glitch/util/initial_state'; +import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/util/initial_state'; import { decode } from 'blurhash'; const messages = defineMessages({ @@ -101,6 +101,8 @@ class Item extends React.PureComponent { } _decode () { + if (!useBlurhash) return; + const hash = this.props.attachment.get('blurhash'); const pixels = decode(hash, 32, 32); diff --git a/app/javascript/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js index b73ea0b07..112f9d101 100644 --- a/app/javascript/flavours/glitch/features/video/index.js +++ b/app/javascript/flavours/glitch/features/video/index.js @@ -5,7 +5,7 @@ import { fromJS, is } from 'immutable'; import { throttle } from 'lodash'; import classNames from 'classnames'; import { isFullscreen, requestFullscreen, exitFullscreen } from 'flavours/glitch/util/fullscreen'; -import { displayMedia } from 'flavours/glitch/util/initial_state'; +import { displayMedia, useBlurhash } from 'flavours/glitch/util/initial_state'; import { decode } from 'blurhash'; const messages = defineMessages({ @@ -312,7 +312,7 @@ export default class Video extends React.PureComponent { } _decode () { - if (!this.canvas) return; + if (!this.canvas || !useBlurhash) return; const hash = this.props.blurhash; const pixels = decode(hash, 32, 32); diff --git a/app/javascript/flavours/glitch/util/initial_state.js b/app/javascript/flavours/glitch/util/initial_state.js index f42c06a3a..e8811a6ce 100644 --- a/app/javascript/flavours/glitch/util/initial_state.js +++ b/app/javascript/flavours/glitch/util/initial_state.js @@ -29,5 +29,6 @@ export const mascot = getMeta('mascot'); export const isStaff = getMeta('is_staff'); export const defaultContentType = getMeta('default_content_type'); export const forceSingleColumn = getMeta('advanced_layout') === false; +export const useBlurhash = getMeta('use_blurhash'); export default initialState; -- cgit From 6ad870a410c7379eb8f3e719f3ad217354691546 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Thu, 27 Jun 2019 22:30:55 +0200 Subject: Change search components classes and styling to match upstream --- .../glitch/features/compose/components/search.js | 18 ++--- .../flavours/glitch/features/ui/index.js | 2 +- .../flavours/glitch/styles/components/drawer.scss | 78 +--------------------- .../flavours/glitch/styles/components/search.scss | 27 ++++++-- .../glitch/styles/components/single_column.scss | 2 +- 5 files changed, 34 insertions(+), 93 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/compose/components/search.js b/app/javascript/flavours/glitch/features/compose/components/search.js index 1d96933ea..b25555d0f 100644 --- a/app/javascript/flavours/glitch/features/compose/components/search.js +++ b/app/javascript/flavours/glitch/features/compose/components/search.js @@ -36,7 +36,7 @@ class SearchPopout extends React.PureComponent {
{({ opacity, scaleX, scaleY }) => ( -
+

    @@ -128,14 +128,14 @@ class Search extends React.PureComponent { render () { const { intl, value, submitted } = this.props; const { expanded } = this.state; - const active = value.length > 0 || submitted; - const computedClass = classNames('drawer--search', { active }); + const hasValue = value.length > 0 || submitted; return ( -
    +
    +
    - - + +
    - + +
    diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js index 787488db4..e072c22ec 100644 --- a/app/javascript/flavours/glitch/features/ui/index.js +++ b/app/javascript/flavours/glitch/features/ui/index.js @@ -317,7 +317,7 @@ export default class UI extends React.Component { handleHotkeySearch = e => { e.preventDefault(); - const element = this.node.querySelector('.drawer--search input'); + const element = this.node.querySelector('.search__input'); if (element) { element.focus(); diff --git a/app/javascript/flavours/glitch/styles/components/drawer.scss b/app/javascript/flavours/glitch/styles/components/drawer.scss index 0e50482f6..0994a9b43 100644 --- a/app/javascript/flavours/glitch/styles/components/drawer.scss +++ b/app/javascript/flavours/glitch/styles/components/drawer.scss @@ -73,90 +73,16 @@ } } -.drawer--search { +.search { position: relative; margin-bottom: 10px; flex: none; @include limited-single-column('screen and (max-width: 360px)') { margin-bottom: 0 } @include single-column('screen and (max-width: 630px)') { font-size: 16px } - - input { - display: block; - box-sizing: border-box; - margin: 0; - border: none; - padding: 15px 30px 15px 15px; - width: 100%; - outline: 0; - color: $darker-text-color; - background: $ui-base-color; - font-size: 14px; - font-family: inherit; - line-height: 16px; - - &:focus { - outline: 0; - background: lighten($ui-base-color, 4%); - } - } - - & > .icon { - display: block; - position: absolute; - top: 10px; - right: 10px; - width: 18px; - height: 18px; - color: $secondary-text-color; - font-size: 18px; - line-height: 18px; - z-index: 2; - - .fa { - display: inline-block; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - opacity: 0; - cursor: default; - pointer-events: none; - transition: all 100ms linear; - transition-property: color, transform, opacity; - } - - .fa-search { - opacity: 0.3; - transform: rotate(0deg); - } - - .fa-times-circle { - transform: rotate(-90deg); - cursor: pointer; - - &:hover { color: $primary-text-color } - } - } - - &.active { - & > .icon { - .fa-search { - opacity: 0; - transform: rotate(90deg); - } - - .fa-times-circle { - opacity: 0.3; - pointer-events: auto; - transform: rotate(0deg); - } - } - } } -.drawer--search--popout { +.search-popout { @include search-popout(); } diff --git a/app/javascript/flavours/glitch/styles/components/search.scss b/app/javascript/flavours/glitch/styles/components/search.scss index 3ef141133..7c5039efc 100644 --- a/app/javascript/flavours/glitch/styles/components/search.scss +++ b/app/javascript/flavours/glitch/styles/components/search.scss @@ -3,13 +3,25 @@ } .search__input { + @include search-input(); + display: block; - padding: 10px; + padding: 15px; padding-right: 30px; - @include search-input(); + line-height: 18px; + font-size: 16px; } .search__icon { + &::-moz-focus-inner { + border: 0; + } + + &::-moz-focus-inner, + &:focus { + outline: 0 !important; + } + .fa { position: absolute; top: 16px; @@ -18,7 +30,7 @@ display: inline-block; opacity: 0; transition: all 100ms linear; - transition-property: transform, opacity; + transition-property: color, transform, opacity; font-size: 18px; width: 18px; height: 18px; @@ -33,17 +45,18 @@ } .fa-search { - transform: rotate(90deg); + transform: rotate(0deg); &.active { - pointer-events: none; - transform: rotate(0deg); + pointer-events: auto; + opacity: 0.3; } } .fa-times-circle { top: 17px; transform: rotate(0deg); + color: $action-button-color; cursor: pointer; &.active { @@ -51,7 +64,7 @@ } &:hover { - color: $primary-text-color; + color: lighten($action-button-color, 7%); } } } diff --git a/app/javascript/flavours/glitch/styles/components/single_column.scss b/app/javascript/flavours/glitch/styles/components/single_column.scss index ca962abd2..e0f3d62a7 100644 --- a/app/javascript/flavours/glitch/styles/components/single_column.scss +++ b/app/javascript/flavours/glitch/styles/components/single_column.scss @@ -6,7 +6,7 @@ height: calc(100% - 10px); overflow-y: hidden; - .drawer--search input { + .search__input { line-height: 18px; font-size: 16px; padding: 15px; -- cgit From 43698e08cad195df6f85aea26c710c2e1614a4a3 Mon Sep 17 00:00:00 2001 From: ThibG Date: Thu, 27 Jun 2019 21:12:26 +0200 Subject: [Glitch] Add message telling FTS is disabled when no toot can be found because of this Port ca8944728f4568bbef8edae99382cd44cbc144d6 to glitch-soc --- app/javascript/flavours/glitch/actions/search.js | 7 +++---- .../glitch/features/compose/components/search_results.js | 16 ++++++++++++++-- .../compose/containers/search_results_container.js | 1 + app/javascript/flavours/glitch/reducers/search.js | 3 ++- .../flavours/glitch/styles/components/search.scss | 5 +++++ 5 files changed, 25 insertions(+), 7 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/actions/search.js b/app/javascript/flavours/glitch/actions/search.js index b2d24e10b..9ce77b24b 100644 --- a/app/javascript/flavours/glitch/actions/search.js +++ b/app/javascript/flavours/glitch/actions/search.js @@ -48,7 +48,7 @@ export function submitSearch() { dispatch(importFetchedStatuses(response.data.statuses)); } - dispatch(fetchSearchSuccess(response.data)); + dispatch(fetchSearchSuccess(response.data, value)); dispatch(fetchRelationships(response.data.accounts.map(item => item.id))); }).catch(error => { dispatch(fetchSearchFail(error)); @@ -62,12 +62,11 @@ export function fetchSearchRequest() { }; }; -export function fetchSearchSuccess(results) { +export function fetchSearchSuccess(results, searchTerm) { return { type: SEARCH_FETCH_SUCCESS, results, - accounts: results.accounts, - statuses: results.statuses, + searchTerm, }; }; diff --git a/app/javascript/flavours/glitch/features/compose/components/search_results.js b/app/javascript/flavours/glitch/features/compose/components/search_results.js index 69df8cdc9..dd99f3430 100644 --- a/app/javascript/flavours/glitch/features/compose/components/search_results.js +++ b/app/javascript/flavours/glitch/features/compose/components/search_results.js @@ -7,6 +7,7 @@ import StatusContainer from 'flavours/glitch/containers/status_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; import Hashtag from 'flavours/glitch/components/hashtag'; import Icon from 'flavours/glitch/components/icon'; +import { searchEnabled } from 'flavours/glitch/util/initial_state'; const messages = defineMessages({ dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' }, @@ -20,6 +21,7 @@ class SearchResults extends ImmutablePureComponent { suggestions: ImmutablePropTypes.list.isRequired, fetchSuggestions: PropTypes.func.isRequired, dismissSuggestion: PropTypes.func.isRequired, + searchTerm: PropTypes.string, intl: PropTypes.object.isRequired, }; @@ -27,8 +29,8 @@ class SearchResults extends ImmutablePureComponent { this.props.fetchSuggestions(); } - render() { - const { intl, results, suggestions, dismissSuggestion } = this.props; + render () { + const { intl, results, suggestions, dismissSuggestion, searchTerm } = this.props; if (results.isEmpty() && !suggestions.isEmpty()) { return ( @@ -51,6 +53,16 @@ class SearchResults extends ImmutablePureComponent {
); + } else if(results.get('statuses') && results.get('statuses').size === 0 && !searchEnabled && !(searchTerm.startsWith('@') || searchTerm.startsWith('#') || searchTerm.includes(' '))) { + statuses = ( +
+
+ +
+ +
+
+ ); } let accounts, statuses, hashtags; diff --git a/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js b/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js index f9637861a..e4d5f3420 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js @@ -5,6 +5,7 @@ import { fetchSuggestions, dismissSuggestion } from '../../../actions/suggestion const mapStateToProps = state => ({ results: state.getIn(['search', 'results']), suggestions: state.getIn(['suggestions', 'items']), + searchTerm: state.getIn(['search', 'searchTerm']), }); const mapDispatchToProps = dispatch => ({ diff --git a/app/javascript/flavours/glitch/reducers/search.js b/app/javascript/flavours/glitch/reducers/search.js index 9a525bf47..1c32a5b9f 100644 --- a/app/javascript/flavours/glitch/reducers/search.js +++ b/app/javascript/flavours/glitch/reducers/search.js @@ -16,6 +16,7 @@ const initialState = ImmutableMap({ submitted: false, hidden: false, results: ImmutableMap(), + searchTerm: '', }); export default function search(state = initialState, action) { @@ -40,7 +41,7 @@ export default function search(state = initialState, action) { accounts: ImmutableList(action.results.accounts.map(item => item.id)), statuses: ImmutableList(action.results.statuses.map(item => item.id)), hashtags: fromJS(action.results.hashtags), - })).set('submitted', true); + })).set('submitted', true).set('searchTerm', action.searchTerm); default: return state; } diff --git a/app/javascript/flavours/glitch/styles/components/search.scss b/app/javascript/flavours/glitch/styles/components/search.scss index 7c5039efc..117da362f 100644 --- a/app/javascript/flavours/glitch/styles/components/search.scss +++ b/app/javascript/flavours/glitch/styles/components/search.scss @@ -78,6 +78,11 @@ font-weight: 500; } +.search-results__info { + padding: 10px; + color: $secondary-text-color; +} + .trends { &__header { color: $dark-text-color; -- cgit From 4d964398dede71a0512430d904951cff2c7f58c5 Mon Sep 17 00:00:00 2001 From: ThibG Date: Fri, 28 Jun 2019 13:52:15 +0200 Subject: [Glitch] Fix swiping columns on mobile sometimes failing Port 072158ee973f8e09a0abd34a825d9bce038a5d68 to glitch-soc --- .../flavours/glitch/features/ui/components/columns_area.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.js b/app/javascript/flavours/glitch/features/ui/components/columns_area.js index 3a188ca87..30097f064 100644 --- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js +++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.js @@ -112,6 +112,11 @@ export default class ColumnsArea extends ImmutablePureComponent { // React-router does this for us, but too late, feeling laggy. document.querySelector(currentLinkSelector).classList.remove('active'); document.querySelector(nextLinkSelector).classList.add('active'); + + if (!this.state.shouldAnimate && typeof this.pendingIndex === 'number') { + this.context.router.history.push(getLink(this.pendingIndex)); + this.pendingIndex = null; + } } handleAnimationEnd = () => { @@ -162,7 +167,6 @@ export default class ColumnsArea extends ImmutablePureComponent { const { shouldAnimate } = this.state; const columnIndex = getIndex(this.context.router.history.location.pathname); - this.pendingIndex = null; if (singleColumn) { const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : ; -- cgit From 662252c8f77e8dd8fd015bc6ece0943f068b1710 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 28 Jun 2019 15:54:10 +0200 Subject: [Glitch] Add categories for custom emojis Port front-end changes from e64e6a03dd1e0978fee48f0596dcfbc7fd29958f to glitch-soc Signed-off-by: Thibaut Girka --- .../flavours/glitch/features/emoji_picker/index.js | 30 ++++++++++++---------- app/javascript/flavours/glitch/util/emoji/index.js | 3 +++ 2 files changed, 19 insertions(+), 14 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/emoji_picker/index.js b/app/javascript/flavours/glitch/features/emoji_picker/index.js index a78117971..9821502d3 100644 --- a/app/javascript/flavours/glitch/features/emoji_picker/index.js +++ b/app/javascript/flavours/glitch/features/emoji_picker/index.js @@ -11,7 +11,7 @@ import Overlay from 'react-overlays/lib/Overlay'; import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import detectPassiveEvents from 'detect-passive-events'; -import { buildCustomEmojis } from 'flavours/glitch/util/emoji'; +import { buildCustomEmojis, categoriesFromEmojis } from 'flavours/glitch/util/emoji'; const messages = defineMessages({ emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, @@ -110,19 +110,6 @@ let EmojiPicker, Emoji; // load asynchronously const backgroundImageFn = () => `${assetHost}/emoji/sheet_10.png`; const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false; -const categoriesSort = [ - 'recent', - 'custom', - 'people', - 'nature', - 'foods', - 'activity', - 'places', - 'objects', - 'symbols', - 'flags', -]; - class ModifierPickerMenu extends React.PureComponent { static propTypes = { @@ -320,8 +307,23 @@ class EmojiPickerMenu extends React.PureComponent { } const title = intl.formatMessage(messages.emoji); + const { modifierOpen } = this.state; + const categoriesSort = [ + 'recent', + 'people', + 'nature', + 'foods', + 'activity', + 'places', + 'objects', + 'symbols', + 'flags', + ]; + + categoriesSort.splice(1, 0, ...Array.from(categoriesFromEmojis(custom_emojis)).sort()); + return (
{ keywords: [name], imageUrl: url, custom: true, + customCategory: emoji.get('category'), }); }); return emojis; }; + +export const categoriesFromEmojis = customEmojis => customEmojis.reduce((set, emoji) => set.add(emoji.get('category') ? `custom-${emoji.get('category')}` : 'custom'), new Set()); -- cgit From 82cd138c89730279205a0dccab4ee5c39f92b342 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Sat, 29 Jun 2019 10:43:45 +0200 Subject: Fix some React warnings --- .../flavours/glitch/components/intersection_observer_article.js | 2 +- app/javascript/flavours/glitch/components/status_action_bar.js | 8 ++++---- .../flavours/glitch/features/local_settings/page/item/index.js | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/components/intersection_observer_article.js b/app/javascript/flavours/glitch/components/intersection_observer_article.js index 900c98638..295347c29 100644 --- a/app/javascript/flavours/glitch/components/intersection_observer_article.js +++ b/app/javascript/flavours/glitch/components/intersection_observer_article.js @@ -119,7 +119,7 @@ export default class IntersectionObserverArticle extends ImmutablePureComponent data-id={id} tabIndex='0' style={style}> - {children && React.cloneElement(children, { hidden: !isIntersecting && (isHidden || cachedHeight) })} + {children && React.cloneElement(children, { hidden: !isIntersecting && (isHidden || !!cachedHeight) })} ); } diff --git a/app/javascript/flavours/glitch/components/status_action_bar.js b/app/javascript/flavours/glitch/components/status_action_bar.js index 85bc4a976..c424fbde1 100644 --- a/app/javascript/flavours/glitch/components/status_action_bar.js +++ b/app/javascript/flavours/glitch/components/status_action_bar.js @@ -284,11 +284,11 @@ export default class StatusActionBar extends ImmutablePureComponent {
{replyButton} {!directMessage && [ - , - , + , + , shareButton, - , -
+ , +
, ]} diff --git a/app/javascript/flavours/glitch/features/local_settings/page/item/index.js b/app/javascript/flavours/glitch/features/local_settings/page/item/index.js index 66b937365..5a68523f6 100644 --- a/app/javascript/flavours/glitch/features/local_settings/page/item/index.js +++ b/app/javascript/flavours/glitch/features/local_settings/page/item/index.js @@ -8,7 +8,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; export default class LocalSettingsPageItem extends React.PureComponent { static propTypes = { - children: PropTypes.element.isRequired, + children: PropTypes.node.isRequired, dependsOn: PropTypes.array, dependsOnNot: PropTypes.array, id: PropTypes.string.isRequired, @@ -63,12 +63,12 @@ export default class LocalSettingsPageItem extends React.PureComponent { disabled={!enabled} /> {opt.message} - {opt.hint && {opt.hint}} + {opt.hint && {opt.hint}} ); }); return ( -
+
{children} {optionElems} -- cgit From 9d6b46fe34ede48e84c95ce9b05ba5ce3b28d488 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Sat, 29 Jun 2019 15:24:44 +0200 Subject: Minor optimization regarding regexp filtering in timelines --- .../ui/containers/status_list_container.js | 23 ++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js b/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js index e0c017f82..deb8b7763 100644 --- a/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js +++ b/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js @@ -6,19 +6,26 @@ import { createSelector } from 'reselect'; import { debounce } from 'lodash'; import { me } from 'flavours/glitch/util/initial_state'; -const makeGetStatusIds = () => createSelector([ - (state, { type }) => state.getIn(['settings', type], ImmutableMap()), - (state, { type }) => state.getIn(['timelines', type, 'items'], ImmutableList()), - (state) => state.get('statuses'), -], (columnSettings, statusIds, statuses) => { - const rawRegex = columnSettings.getIn(['regex', 'body'], '').trim(); - let regex = null; +const getRegex = createSelector([ + (state, { type }) => state.getIn(['settings', type, 'regex', 'body']), +], (rawRegex) => { + let regex = null; try { - regex = rawRegex && new RegExp(rawRegex, 'i'); + regex = rawRegex && new RegExp(rawRegex.trim(), 'i'); } catch (e) { // Bad regex, don't affect filters } + return regex; +}); + +const makeGetStatusIds = () => createSelector([ + (state, { type }) => state.getIn(['settings', type], ImmutableMap()), + (state, { type }) => state.getIn(['timelines', type, 'items'], ImmutableList()), + (state) => state.get('statuses'), + getRegex, +], (columnSettings, statusIds, statuses, regex) => { + const rawRegex = columnSettings.getIn(['regex', 'body'], '').trim(); return statusIds.filter(id => { if (id === null) return true; -- cgit From 47c30be8d810fd6d5d27285a097d01843f4dd9f6 Mon Sep 17 00:00:00 2001 From: ThibG Date: Tue, 2 Jul 2019 16:03:54 +0200 Subject: [Glitch] Memoize ancestorIds and descendantIds in detailed status view Port 99924f282f53593e670c70a38450a1c0e2d24c20 to glitch-soc --- .../flavours/glitch/features/status/index.js | 68 ++++++++++++++-------- 1 file changed, 44 insertions(+), 24 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js index 76bfaaffa..40e100fd5 100644 --- a/app/javascript/flavours/glitch/features/status/index.js +++ b/app/javascript/flavours/glitch/features/status/index.js @@ -4,6 +4,7 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { createSelector } from 'reselect'; import { fetchStatus } from 'flavours/glitch/actions/statuses'; import MissingIndicator from 'flavours/glitch/components/missing_indicator'; import DetailedStatus from './components/detailed_status'; @@ -61,39 +62,58 @@ const messages = defineMessages({ const makeMapStateToProps = () => { const getStatus = makeGetStatus(); - const mapStateToProps = (state, props) => { - const status = getStatus(state, { id: props.params.statusId }); + const getAncestorsIds = createSelector([ + (_, { id }) => id, + state => state.getIn(['contexts', 'inReplyTos']), + ], (statusId, inReplyTos) => { let ancestorsIds = Immutable.List(); + ancestorsIds = ancestorsIds.withMutations(mutable => { + let id = statusId; + + while (id) { + mutable.unshift(id); + id = inReplyTos.get(id); + } + }); + + return ancestorsIds; + }); + + const getDescendantsIds = createSelector([ + (_, { id }) => id, + state => state.getIn(['contexts', 'replies']), + ], (statusId, contextReplies) => { let descendantsIds = Immutable.List(); + descendantsIds = descendantsIds.withMutations(mutable => { + const ids = [statusId]; - if (status) { - ancestorsIds = ancestorsIds.withMutations(mutable => { - let id = status.get('in_reply_to_id'); + while (ids.length > 0) { + let id = ids.shift(); + const replies = contextReplies.get(id); - while (id) { - mutable.unshift(id); - id = state.getIn(['contexts', 'inReplyTos', id]); + if (statusId !== id) { + mutable.push(id); } - }); - descendantsIds = descendantsIds.withMutations(mutable => { - const ids = [status.get('id')]; + if (replies) { + replies.reverse().forEach(reply => { + ids.unshift(reply); + }); + } + } + }); - while (ids.length > 0) { - let id = ids.shift(); - const replies = state.getIn(['contexts', 'replies', id]); + return descendantsIds; + }); - if (status.get('id') !== id) { - mutable.push(id); - } + const mapStateToProps = (state, props) => { + const status = getStatus(state, { id: props.params.statusId }); + let ancestorsIds = Immutable.List(); + let descendantsIds = Immutable.List(); - if (replies) { - replies.reverse().forEach(reply => { - ids.unshift(reply); - }); - } - } - }); + if (status) { + ancestorsIds = getAncestorsIds(state, { id: status.get('in_reply_to_id') }); + descendantsIds = getDescendantsIds(state, { id: status.get('id') }); } return { -- cgit From 64f3bc77acf2b992a189769b09427f93e7e5eb60 Mon Sep 17 00:00:00 2001 From: ThibG Date: Sat, 6 Jul 2019 18:18:08 +0200 Subject: [Glitch] Only scroll to the compose form if it's not horizontally in the viewport Port c07cca4727041ea5a5721acbc603d4bfb45a15a6 to glitch-soc --- .../flavours/glitch/features/compose/components/compose_form.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js index 822cfa95d..3d9002fe4 100644 --- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js +++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js @@ -197,7 +197,10 @@ class ComposeForm extends ImmutablePureComponent { handleFocus = () => { if (this.composeForm && !this.props.singleColumn) { - this.composeForm.scrollIntoView(); + const { left, right } = this.composeForm.getBoundingClientRect(); + if (left < 0 || right > (window.innerWidth || document.documentElement.clientWidth)) { + this.composeForm.scrollIntoView(); + } } } -- cgit From 16b79a6237b90fd0c2550794631a7ced146b01e3 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Fri, 12 Jul 2019 18:27:43 +0200 Subject: Add options to configure filtering behavior --- .../flavours/glitch/components/status.js | 8 +++++--- .../features/local_settings/navigation/index.js | 20 +++++++++++++------ .../glitch/features/local_settings/page/index.js | 23 ++++++++++++++++++++++ .../flavours/glitch/reducers/local_settings.js | 1 + 4 files changed, 43 insertions(+), 9 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index 973275fbb..c8d0dacb0 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -506,9 +506,11 @@ class Status extends ImmutablePureComponent {
- + {settings.get('filtering_behavior') === 'hide' && ( + + )}
); diff --git a/app/javascript/flavours/glitch/features/local_settings/navigation/index.js b/app/javascript/flavours/glitch/features/local_settings/navigation/index.js index 01368abad..47f3d6d15 100644 --- a/app/javascript/flavours/glitch/features/local_settings/navigation/index.js +++ b/app/javascript/flavours/glitch/features/local_settings/navigation/index.js @@ -13,6 +13,7 @@ const messages = defineMessages({ general: { id: 'settings.general', defaultMessage: 'General' }, compose: { id: 'settings.compose_box_opts', defaultMessage: 'Compose box' }, content_warnings: { id: 'settings.content_warnings', defaultMessage: 'Content Warnings' }, + filters: { id: 'settings.filters', defaultMessage: 'Filters' }, collapsed: { id: 'settings.collapsed_statuses', defaultMessage: 'Collapsed toots' }, media: { id: 'settings.media', defaultMessage: 'Media' }, preferences: { id: 'settings.preferences', defaultMessage: 'Preferences' }, @@ -60,27 +61,34 @@ export default class LocalSettingsNavigation extends React.PureComponent { active={index === 3} index={3} onNavigate={onNavigate} - icon='angle-double-up' - title={intl.formatMessage(messages.collapsed)} + icon='filter' + title={intl.formatMessage(messages.filters)} /> +
), + ({ intl, onChange, settings }) => ( +
+

+ + + +
+ ), ({ onChange, settings }) => (

diff --git a/app/javascript/flavours/glitch/reducers/local_settings.js b/app/javascript/flavours/glitch/reducers/local_settings.js index 68e1c8424..6fd3d901b 100644 --- a/app/javascript/flavours/glitch/reducers/local_settings.js +++ b/app/javascript/flavours/glitch/reducers/local_settings.js @@ -21,6 +21,7 @@ const initialState = ImmutableMap({ inline_preview_cards: true, hicolor_privacy_icons: false, show_content_type_choice: false, + filtering_behavior: 'hide', content_warnings : ImmutableMap({ auto_unfold : false, filter : null, -- cgit From e91bf82083ac390a0cf229d8e94fa412fdec57ff Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 16 Jul 2019 06:30:47 +0200 Subject: [Glitch] Add option to disable real-time updates in web UI Port 729723f857d11434c0f78d63fe16537d77f1c77c to glitch-soc Signed-off-by: Thibaut Girka --- .../flavours/glitch/actions/notifications.js | 32 ++++++++++++----- .../flavours/glitch/actions/timelines.js | 41 +++++++++++++++------- .../flavours/glitch/components/load_pending.js | 22 ++++++++++++ .../flavours/glitch/components/scrollable_list.js | 13 ++++++- .../components/column_settings.js | 2 +- .../notifications/components/setting_toggle.js | 5 +-- .../glitch/features/notifications/index.js | 11 +++++- .../ui/containers/status_list_container.js | 5 ++- .../flavours/glitch/reducers/notifications.js | 28 ++++++++++----- .../flavours/glitch/reducers/timelines.js | 40 ++++++++++++++------- app/javascript/flavours/glitch/util/compare_id.js | 5 +-- .../flavours/glitch/util/initial_state.js | 1 + 12 files changed, 154 insertions(+), 51 deletions(-) create mode 100644 app/javascript/flavours/glitch/components/load_pending.js (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js index c057a5298..0c2331374 100644 --- a/app/javascript/flavours/glitch/actions/notifications.js +++ b/app/javascript/flavours/glitch/actions/notifications.js @@ -12,6 +12,8 @@ import { defineMessages } from 'react-intl'; import { List as ImmutableList } from 'immutable'; import { unescapeHTML } from 'flavours/glitch/util/html'; import { getFiltersRegex } from 'flavours/glitch/selectors'; +import { usePendingItems as preferPendingItems } from 'flavours/glitch/util/initial_state'; +import compareId from 'flavours/glitch/util/compare_id'; export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; @@ -32,8 +34,9 @@ export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL'; export const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET'; -export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR'; -export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP'; +export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR'; +export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP'; +export const NOTIFICATIONS_LOAD_PENDING = 'NOTIFICATIONS_LOAD_PENDING'; export const NOTIFICATIONS_MOUNT = 'NOTIFICATIONS_MOUNT'; export const NOTIFICATIONS_UNMOUNT = 'NOTIFICATIONS_UNMOUNT'; @@ -52,6 +55,10 @@ const fetchRelatedRelationships = (dispatch, notifications) => { } }; +export const loadPending = () => ({ + type: NOTIFICATIONS_LOAD_PENDING, +}); + export function updateNotifications(notification, intlMessages, intlLocale) { return (dispatch, getState) => { const showInColumn = getState().getIn(['settings', 'notifications', 'shows', notification.type], true); @@ -83,6 +90,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) { dispatch({ type: NOTIFICATIONS_UPDATE, notification, + usePendingItems: preferPendingItems, meta: (playSound && !filtered) ? { sound: 'boop' } : undefined, }); @@ -136,10 +144,19 @@ export function expandNotifications({ maxId } = {}, done = noOp) { : excludeTypesFromFilter(activeFilter), }; - if (!maxId && notifications.get('items').size > 0) { - params.since_id = notifications.getIn(['items', 0, 'id']); + if (!params.max_id && (notifications.get('items', ImmutableList()).size + notifications.get('pendingItems', ImmutableList()).size) > 0) { + const a = notifications.getIn(['pendingItems', 0, 'id']); + const b = notifications.getIn(['items', 0, 'id']); + + if (a && b && compareId(a, b) > 0) { + params.since_id = a; + } else { + params.since_id = b || a; + } } + const isLoadingRecent = !!params.since_id; + dispatch(expandNotificationsRequest(isLoadingMore)); api(getState).get('/api/v1/notifications', { params }).then(response => { @@ -148,7 +165,7 @@ export function expandNotifications({ maxId } = {}, done = noOp) { dispatch(importFetchedAccounts(response.data.map(item => item.account))); dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status))); - dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore)); + dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore, isLoadingRecent && preferPendingItems)); fetchRelatedRelationships(dispatch, response.data); done(); }).catch(error => { @@ -165,13 +182,12 @@ export function expandNotificationsRequest(isLoadingMore) { }; }; -export function expandNotificationsSuccess(notifications, next, isLoadingMore) { +export function expandNotificationsSuccess(notifications, next, isLoadingMore, usePendingItems) { return { type: NOTIFICATIONS_EXPAND_SUCCESS, notifications, - accounts: notifications.map(item => item.account), - statuses: notifications.map(item => item.status).filter(status => !!status), next, + usePendingItems, skipLoading: !isLoadingMore, }; }; diff --git a/app/javascript/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js index cca571583..f5bc0fd23 100644 --- a/app/javascript/flavours/glitch/actions/timelines.js +++ b/app/javascript/flavours/glitch/actions/timelines.js @@ -1,6 +1,8 @@ import { importFetchedStatus, importFetchedStatuses } from './importer'; import api, { getLinks } from 'flavours/glitch/util/api'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import compareId from 'flavours/glitch/util/compare_id'; +import { usePendingItems as preferPendingItems } from 'flavours/glitch/util/initial_state'; export const TIMELINE_UPDATE = 'TIMELINE_UPDATE'; export const TIMELINE_DELETE = 'TIMELINE_DELETE'; @@ -10,10 +12,15 @@ export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST'; export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS'; export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL'; -export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP'; +export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP'; +export const TIMELINE_LOAD_PENDING = 'TIMELINE_LOAD_PENDING'; +export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT'; +export const TIMELINE_CONNECT = 'TIMELINE_CONNECT'; -export const TIMELINE_CONNECT = 'TIMELINE_CONNECT'; -export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT'; +export const loadPending = timeline => ({ + type: TIMELINE_LOAD_PENDING, + timeline, +}); export function updateTimeline(timeline, status, accept) { return dispatch => { @@ -27,6 +34,7 @@ export function updateTimeline(timeline, status, accept) { type: TIMELINE_UPDATE, timeline, status, + usePendingItems: preferPendingItems, }); }; }; @@ -71,8 +79,15 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) { return; } - if (!params.max_id && !params.pinned && timeline.get('items', ImmutableList()).size > 0) { - params.since_id = timeline.getIn(['items', 0]); + if (!params.max_id && !params.pinned && (timeline.get('items', ImmutableList()).size + timeline.get('pendingItems', ImmutableList()).size) > 0) { + const a = timeline.getIn(['pendingItems', 0]); + const b = timeline.getIn(['items', 0]); + + if (a && b && compareId(a, b) > 0) { + params.since_id = a; + } else { + params.since_id = b || a; + } } const isLoadingRecent = !!params.since_id; @@ -82,7 +97,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) { api(getState).get(path, { params }).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); - dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206, isLoadingRecent, isLoadingMore)); + dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems)); done(); }).catch(error => { dispatch(expandTimelineFail(timelineId, error, isLoadingMore)); @@ -117,7 +132,7 @@ export function expandTimelineRequest(timeline, isLoadingMore) { }; }; -export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadingRecent, isLoadingMore) { +export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadingRecent, isLoadingMore, usePendingItems) { return { type: TIMELINE_EXPAND_SUCCESS, timeline, @@ -125,6 +140,7 @@ export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadi next, partial, isLoadingRecent, + usePendingItems, skipLoading: !isLoadingMore, }; }; @@ -153,9 +169,8 @@ export function connectTimeline(timeline) { }; }; -export function disconnectTimeline(timeline) { - return { - type: TIMELINE_DISCONNECT, - timeline, - }; -}; +export const disconnectTimeline = timeline => ({ + type: TIMELINE_DISCONNECT, + timeline, + usePendingItems: preferPendingItems, +}); diff --git a/app/javascript/flavours/glitch/components/load_pending.js b/app/javascript/flavours/glitch/components/load_pending.js new file mode 100644 index 000000000..7e2702403 --- /dev/null +++ b/app/javascript/flavours/glitch/components/load_pending.js @@ -0,0 +1,22 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import PropTypes from 'prop-types'; + +export default class LoadPending extends React.PureComponent { + + static propTypes = { + onClick: PropTypes.func, + count: PropTypes.number, + } + + render() { + const { count } = this.props; + + return ( + + ); + } + +} diff --git a/app/javascript/flavours/glitch/components/scrollable_list.js b/app/javascript/flavours/glitch/components/scrollable_list.js index 462185bbc..5f42bdd8b 100644 --- a/app/javascript/flavours/glitch/components/scrollable_list.js +++ b/app/javascript/flavours/glitch/components/scrollable_list.js @@ -3,6 +3,7 @@ import { ScrollContainer } from 'react-router-scroll-4'; import PropTypes from 'prop-types'; import IntersectionObserverArticleContainer from 'flavours/glitch/containers/intersection_observer_article_container'; import LoadMore from './load_more'; +import LoadPending from './load_pending'; import IntersectionObserverWrapper from 'flavours/glitch/util/intersection_observer_wrapper'; import { throttle } from 'lodash'; import { List as ImmutableList } from 'immutable'; @@ -21,6 +22,7 @@ export default class ScrollableList extends PureComponent { static propTypes = { scrollKey: PropTypes.string.isRequired, onLoadMore: PropTypes.func, + onLoadPending: PropTypes.func, onScrollToTop: PropTypes.func, onScroll: PropTypes.func, trackScroll: PropTypes.bool, @@ -28,6 +30,7 @@ export default class ScrollableList extends PureComponent { isLoading: PropTypes.bool, showLoading: PropTypes.bool, hasMore: PropTypes.bool, + numPending: PropTypes.number, prepend: PropTypes.node, alwaysPrepend: PropTypes.bool, emptyMessage: PropTypes.node, @@ -222,12 +225,18 @@ export default class ScrollableList extends PureComponent { return !(location.state && location.state.mastodonModalOpen); } + handleLoadPending = e => { + e.preventDefault(); + this.props.onLoadPending(); + } + render () { - const { children, scrollKey, trackScroll, shouldUpdateScroll, showLoading, isLoading, hasMore, prepend, alwaysPrepend, emptyMessage, onLoadMore } = this.props; + const { children, scrollKey, trackScroll, shouldUpdateScroll, showLoading, isLoading, hasMore, numPending, prepend, alwaysPrepend, emptyMessage, onLoadMore } = this.props; const { fullscreen } = this.state; const childrenCount = React.Children.count(children); const loadMore = (hasMore && onLoadMore) ? : null; + const loadPending = (numPending > 0) ? : null; let scrollableArea = null; if (showLoading) { @@ -248,6 +257,8 @@ export default class ScrollableList extends PureComponent {
{prepend} + {loadPending} + {React.Children.map(this.props.children, (child, index) => (
- } /> + } />
diff --git a/app/javascript/flavours/glitch/features/notifications/components/setting_toggle.js b/app/javascript/flavours/glitch/features/notifications/components/setting_toggle.js index ac2211e48..0264b6815 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/setting_toggle.js +++ b/app/javascript/flavours/glitch/features/notifications/components/setting_toggle.js @@ -12,6 +12,7 @@ export default class SettingToggle extends React.PureComponent { label: PropTypes.node.isRequired, meta: PropTypes.node, onChange: PropTypes.func.isRequired, + defaultValue: PropTypes.bool, } onChange = ({ target }) => { @@ -19,12 +20,12 @@ export default class SettingToggle extends React.PureComponent { } render () { - const { prefix, settings, settingPath, label, meta } = this.props; + const { prefix, settings, settingPath, label, meta, defaultValue } = this.props; const id = ['setting-toggle', prefix, ...settingPath].filter(Boolean).join('-'); return (
- + {meta && {meta}}
diff --git a/app/javascript/flavours/glitch/features/notifications/index.js b/app/javascript/flavours/glitch/features/notifications/index.js index f2a1ccc3b..bf805c69a 100644 --- a/app/javascript/flavours/glitch/features/notifications/index.js +++ b/app/javascript/flavours/glitch/features/notifications/index.js @@ -10,6 +10,7 @@ import { scrollTopNotifications, mountNotifications, unmountNotifications, + loadPending, } from 'flavours/glitch/actions/notifications'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import NotificationContainer from './containers/notification_container'; @@ -48,6 +49,7 @@ const mapStateToProps = state => ({ isLoading: state.getIn(['notifications', 'isLoading'], true), isUnread: state.getIn(['notifications', 'unread']) > 0, hasMore: state.getIn(['notifications', 'hasMore']), + numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size, notifCleaningActive: state.getIn(['notifications', 'cleaningMode']), }); @@ -80,6 +82,7 @@ export default class Notifications extends React.PureComponent { isUnread: PropTypes.bool, multiColumn: PropTypes.bool, hasMore: PropTypes.bool, + numPending: PropTypes.number, localSettings: ImmutablePropTypes.map, notifCleaningActive: PropTypes.bool, onEnterCleaningMode: PropTypes.func, @@ -100,6 +103,10 @@ export default class Notifications extends React.PureComponent { this.props.dispatch(expandNotifications({ maxId: last && last.get('id') })); }, 300, { leading: true }); + handleLoadPending = () => { + this.props.dispatch(loadPending()); + }; + handleScrollToTop = debounce(() => { this.props.dispatch(scrollTopNotifications(true)); }, 100); @@ -170,7 +177,7 @@ export default class Notifications extends React.PureComponent { } render () { - const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, showFilterBar } = this.props; + const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar } = this.props; const pinned = !!columnId; const emptyMessage = ; @@ -212,8 +219,10 @@ export default class Notifications extends React.PureComponent { isLoading={isLoading} showLoading={isLoading && notifications.size === 0} hasMore={hasMore} + numPending={numPending} emptyMessage={emptyMessage} onLoadMore={this.handleLoadOlder} + onLoadPending={this.handleLoadPending} onScrollToTop={this.handleScrollToTop} onScroll={this.handleScroll} shouldUpdateScroll={shouldUpdateScroll} diff --git a/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js b/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js index deb8b7763..4ca853563 100644 --- a/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js +++ b/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js @@ -1,6 +1,6 @@ import { connect } from 'react-redux'; import StatusList from 'flavours/glitch/components/status_list'; -import { scrollTopTimeline } from 'flavours/glitch/actions/timelines'; +import { scrollTopTimeline, loadPending } from 'flavours/glitch/actions/timelines'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; import { createSelector } from 'reselect'; import { debounce } from 'lodash'; @@ -62,6 +62,7 @@ const makeMapStateToProps = () => { isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true), isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false), hasMore: state.getIn(['timelines', timelineId, 'hasMore']), + numPending: state.getIn(['timelines', timelineId, 'pendingItems'], ImmutableList()).size, }); return mapStateToProps; @@ -77,6 +78,8 @@ const mapDispatchToProps = (dispatch, { timelineId }) => ({ dispatch(scrollTopTimeline(timelineId, false)); }, 100), + onLoadPending: () => dispatch(loadPending(timelineId)), + }); export default connect(makeMapStateToProps, mapDispatchToProps)(StatusList); diff --git a/app/javascript/flavours/glitch/reducers/notifications.js b/app/javascript/flavours/glitch/reducers/notifications.js index 5bbf9c822..d057f8f83 100644 --- a/app/javascript/flavours/glitch/reducers/notifications.js +++ b/app/javascript/flavours/glitch/reducers/notifications.js @@ -9,6 +9,7 @@ import { NOTIFICATIONS_FILTER_SET, NOTIFICATIONS_CLEAR, NOTIFICATIONS_SCROLL_TOP, + NOTIFICATIONS_LOAD_PENDING, NOTIFICATIONS_DELETE_MARKED_REQUEST, NOTIFICATIONS_DELETE_MARKED_SUCCESS, NOTIFICATION_MARK_FOR_DELETE, @@ -25,6 +26,7 @@ import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; import compareId from 'flavours/glitch/util/compare_id'; const initialState = ImmutableMap({ + pendingItems: ImmutableList(), items: ImmutableList(), hasMore: true, top: false, @@ -46,7 +48,11 @@ const notificationToMap = (state, notification) => ImmutableMap({ status: notification.status ? notification.status.id : null, }); -const normalizeNotification = (state, notification) => { +const normalizeNotification = (state, notification, usePendingItems) => { + if (usePendingItems) { + return state.update('pendingItems', list => list.unshift(notificationToMap(state, notification))); + } + const top = !shouldCountUnreadNotifications(state); if (top) { @@ -64,7 +70,7 @@ const normalizeNotification = (state, notification) => { }); }; -const expandNormalizedNotifications = (state, notifications, next) => { +const expandNormalizedNotifications = (state, notifications, next, usePendingItems) => { const top = !(shouldCountUnreadNotifications(state)); const lastReadId = state.get('lastReadId'); let items = ImmutableList(); @@ -75,7 +81,7 @@ const expandNormalizedNotifications = (state, notifications, next) => { return state.withMutations(mutable => { if (!items.isEmpty()) { - mutable.update('items', list => { + mutable.update(usePendingItems ? 'pendingItems' : 'items', list => { const lastIndex = 1 + list.findLastIndex( item => item !== null && (compareId(item.get('id'), items.last().get('id')) > 0 || item.get('id') === items.last().get('id')) ); @@ -105,7 +111,8 @@ const expandNormalizedNotifications = (state, notifications, next) => { }; const filterNotifications = (state, relationship) => { - return state.update('items', list => list.filterNot(item => item !== null && item.get('account') === relationship.id)); + const helper = list => list.filterNot(item => item !== null && item.get('account') === relationship.id); + return state.update('items', helper).update('pendingItems', helper); }; const clearUnread = (state) => { @@ -131,7 +138,8 @@ const deleteByStatus = (state, statusId) => { const deletedUnread = state.get('items').filter(item => item !== null && item.get('status') === statusId && compareId(item.get('id'), lastReadId) > 0); state = state.update('unread', unread => unread - deletedUnread.size); } - return state.update('items', list => list.filterNot(item => item !== null && item.get('status') === statusId)); + const helper = list => list.filterNot(item => item !== null && item.get('status') === statusId); + return state.update('items', helper).update('pendingItems', helper); }; const markForDelete = (state, notificationId, yes) => { @@ -192,6 +200,8 @@ export default function notifications(state = initialState, action) { return state.update('mounted', count => count - 1); case NOTIFICATIONS_SET_VISIBILITY: return updateVisibility(state, action.visibility); + case NOTIFICATIONS_LOAD_PENDING: + return state.update('items', list => state.get('pendingItems').concat(list.take(40))).set('pendingItems', ImmutableList()).set('unread', 0); case NOTIFICATIONS_EXPAND_REQUEST: case NOTIFICATIONS_DELETE_MARKED_REQUEST: return state.set('isLoading', true); @@ -203,20 +213,20 @@ export default function notifications(state = initialState, action) { case NOTIFICATIONS_SCROLL_TOP: return updateTop(state, action.top); case NOTIFICATIONS_UPDATE: - return normalizeNotification(state, action.notification); + return normalizeNotification(state, action.notification, action.usePendingItems); case NOTIFICATIONS_EXPAND_SUCCESS: - return expandNormalizedNotifications(state, action.notifications, action.next); + return expandNormalizedNotifications(state, action.notifications, action.next, action.usePendingItems); case ACCOUNT_BLOCK_SUCCESS: return filterNotifications(state, action.relationship); case ACCOUNT_MUTE_SUCCESS: return action.relationship.muting_notifications ? filterNotifications(state, action.relationship) : state; case NOTIFICATIONS_CLEAR: - return state.set('items', ImmutableList()).set('hasMore', false); + return state.set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('hasMore', false); case TIMELINE_DELETE: return deleteByStatus(state, action.id); case TIMELINE_DISCONNECT: return action.timeline === 'home' ? - state.update('items', items => items.first() ? items.unshift(null) : items) : + state.update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items) : state; case NOTIFICATION_MARK_FOR_DELETE: diff --git a/app/javascript/flavours/glitch/reducers/timelines.js b/app/javascript/flavours/glitch/reducers/timelines.js index 440b370e6..9b016a4c6 100644 --- a/app/javascript/flavours/glitch/reducers/timelines.js +++ b/app/javascript/flavours/glitch/reducers/timelines.js @@ -8,6 +8,7 @@ import { TIMELINE_SCROLL_TOP, TIMELINE_CONNECT, TIMELINE_DISCONNECT, + TIMELINE_LOAD_PENDING, } from 'flavours/glitch/actions/timelines'; import { ACCOUNT_BLOCK_SUCCESS, @@ -25,10 +26,11 @@ const initialTimeline = ImmutableMap({ top: true, isLoading: false, hasMore: true, + pendingItems: ImmutableList(), items: ImmutableList(), }); -const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, isLoadingRecent) => { +const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, isLoadingRecent, usePendingItems) => { return state.update(timeline, initialTimeline, map => map.withMutations(mMap => { mMap.set('isLoading', false); mMap.set('isPartial', isPartial); @@ -38,7 +40,7 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, is if (timeline.endsWith(':pinned')) { mMap.set('items', statuses.map(status => status.get('id'))); } else if (!statuses.isEmpty()) { - mMap.update('items', ImmutableList(), oldIds => { + mMap.update(usePendingItems ? 'pendingItems' : 'items', ImmutableList(), oldIds => { const newIds = statuses.map(status => status.get('id')); const lastIndex = oldIds.findLastIndex(id => id !== null && compareId(id, newIds.last()) >= 0) + 1; const firstIndex = oldIds.take(lastIndex).findLastIndex(id => id !== null && compareId(id, newIds.first()) > 0); @@ -56,7 +58,15 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, is })); }; -const updateTimeline = (state, timeline, status) => { +const updateTimeline = (state, timeline, status, usePendingItems) => { + if (usePendingItems) { + if (state.getIn([timeline, 'pendingItems'], ImmutableList()).includes(status.get('id')) || state.getIn([timeline, 'items'], ImmutableList()).includes(status.get('id'))) { + return state; + } + + return state.update(timeline, initialTimeline, map => map.update('pendingItems', list => list.unshift(status.get('id')))); + } + const top = state.getIn([timeline, 'top']); const ids = state.getIn([timeline, 'items'], ImmutableList()); const includesId = ids.includes(status.get('id')); @@ -77,8 +87,10 @@ const updateTimeline = (state, timeline, status) => { const deleteStatus = (state, id, accountId, references, exclude_account = null) => { state.keySeq().forEach(timeline => { - if (exclude_account === null || (timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`))) - state = state.updateIn([timeline, 'items'], list => list.filterNot(item => item === id)); + if (exclude_account === null || (timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`))) { + const helper = list => list.filterNot(item => item === id); + state = state.updateIn([timeline, 'items'], helper).updateIn([timeline, 'pendingItems'], helper); + } }); // Remove reblogs of deleted status @@ -108,11 +120,10 @@ const filterTimelines = (state, relationship, statuses) => { return state; }; -const filterTimeline = (timeline, state, relationship, statuses) => - state.updateIn([timeline, 'items'], ImmutableList(), list => - list.filterNot(statusId => - statuses.getIn([statusId, 'account']) === relationship.id - )); +const filterTimeline = (timeline, state, relationship, statuses) => { + const helper = list => list.filterNot(statusId => statuses.getIn([statusId, 'account']) === relationship.id); + return state.updateIn([timeline, 'items'], ImmutableList(), helper).updateIn([timeline, 'pendingItems'], ImmutableList(), helper); +}; const updateTop = (state, timeline, top) => { return state.update(timeline, initialTimeline, map => map.withMutations(mMap => { @@ -123,14 +134,17 @@ const updateTop = (state, timeline, top) => { export default function timelines(state = initialState, action) { switch(action.type) { + case TIMELINE_LOAD_PENDING: + return state.update(action.timeline, initialTimeline, map => + map.update('items', list => map.get('pendingItems').concat(list.take(40))).set('pendingItems', ImmutableList()).set('unread', 0)); case TIMELINE_EXPAND_REQUEST: return state.update(action.timeline, initialTimeline, map => map.set('isLoading', true)); case TIMELINE_EXPAND_FAIL: return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false)); case TIMELINE_EXPAND_SUCCESS: - return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial, action.isLoadingRecent); + return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial, action.isLoadingRecent, action.usePendingItems); case TIMELINE_UPDATE: - return updateTimeline(state, action.timeline, fromJS(action.status)); + return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems); case TIMELINE_DELETE: return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf); case TIMELINE_CLEAR: @@ -148,7 +162,7 @@ export default function timelines(state = initialState, action) { return state.update( action.timeline, initialTimeline, - map => map.set('online', false).update('items', items => items.first() ? items.unshift(null) : items) + map => map.set('online', false).update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items) ); default: return state; diff --git a/app/javascript/flavours/glitch/util/compare_id.js b/app/javascript/flavours/glitch/util/compare_id.js index aaff66481..66cf51c4b 100644 --- a/app/javascript/flavours/glitch/util/compare_id.js +++ b/app/javascript/flavours/glitch/util/compare_id.js @@ -1,10 +1,11 @@ -export default function compareId(id1, id2) { +export default function compareId (id1, id2) { if (id1 === id2) { return 0; } + if (id1.length === id2.length) { return id1 > id2 ? 1 : -1; } else { return id1.length > id2.length ? 1 : -1; } -} +}; diff --git a/app/javascript/flavours/glitch/util/initial_state.js b/app/javascript/flavours/glitch/util/initial_state.js index e8811a6ce..caaa79bb3 100644 --- a/app/javascript/flavours/glitch/util/initial_state.js +++ b/app/javascript/flavours/glitch/util/initial_state.js @@ -30,5 +30,6 @@ export const isStaff = getMeta('is_staff'); export const defaultContentType = getMeta('default_content_type'); export const forceSingleColumn = getMeta('advanced_layout') === false; export const useBlurhash = getMeta('use_blurhash'); +export const usePendingItems = getMeta('use_pending_items'); export default initialState; -- cgit From e9f88f40058a510da9d36feb57f5edf1d98908d9 Mon Sep 17 00:00:00 2001 From: Jakub Mendyk Date: Sun, 26 Aug 2018 16:39:37 +0200 Subject: [Glitch] Add messages informing that collections are empty Port 5129f6f2aa56afb21708aec552a798d062ccaff9 to glitch-soc Signed-off-by: Thibaut Girka --- .../flavours/glitch/features/blocks/index.js | 34 +++++++++--------- .../glitch/features/domain_blocks/index.js | 14 +++++--- .../glitch/features/favourited_statuses/index.js | 7 ++-- .../flavours/glitch/features/favourites/index.js | 21 ++++++----- .../glitch/features/follow_requests/index.js | 34 +++++++++--------- .../flavours/glitch/features/followers/index.js | 37 +++++++++---------- .../flavours/glitch/features/following/index.js | 42 ++++++++++------------ .../flavours/glitch/features/lists/index.js | 13 ++++--- .../flavours/glitch/features/mutes/index.js | 34 +++++++++--------- .../flavours/glitch/features/reblogs/index.js | 20 +++++++---- 10 files changed, 141 insertions(+), 115 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/blocks/index.js b/app/javascript/flavours/glitch/features/blocks/index.js index 386a0ce63..e995fb31f 100644 --- a/app/javascript/flavours/glitch/features/blocks/index.js +++ b/app/javascript/flavours/glitch/features/blocks/index.js @@ -1,14 +1,15 @@ import React from 'react'; import { connect } from 'react-redux'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { debounce } from 'lodash'; import PropTypes from 'prop-types'; import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; -import { ScrollContainer } from 'react-router-scroll-4'; +import ScrollableList from '../../components/scrollable_list'; import Column from 'flavours/glitch/features/ui/components/column'; import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim'; import AccountContainer from 'flavours/glitch/containers/account_container'; import { fetchBlocks, expandBlocks } from 'flavours/glitch/actions/blocks'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; const messages = defineMessages({ @@ -34,13 +35,9 @@ export default class Blocks extends ImmutablePureComponent { this.props.dispatch(fetchBlocks()); } - handleScroll = (e) => { - const { scrollTop, scrollHeight, clientHeight } = e.target; - - if (scrollTop === scrollHeight - clientHeight) { - this.props.dispatch(expandBlocks()); - } - } + handleLoadMore = debounce(() => { + this.props.dispatch(expandBlocks()); + }, 300, { leading: true }); shouldUpdateScroll = (prevRouterProps, { location }) => { if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false; @@ -58,16 +55,21 @@ export default class Blocks extends ImmutablePureComponent { ); } + const emptyMessage = ; + return ( - -
- {accountIds.map(id => - - )} -
-
+ + {accountIds.map(id => + + )} +
); } diff --git a/app/javascript/flavours/glitch/features/domain_blocks/index.js b/app/javascript/flavours/glitch/features/domain_blocks/index.js index 3b29e2a26..8b5496139 100644 --- a/app/javascript/flavours/glitch/features/domain_blocks/index.js +++ b/app/javascript/flavours/glitch/features/domain_blocks/index.js @@ -1,16 +1,16 @@ import React from 'react'; import { connect } from 'react-redux'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { debounce } from 'lodash'; import PropTypes from 'prop-types'; import LoadingIndicator from '../../components/loading_indicator'; import Column from '../ui/components/column'; import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import DomainContainer from '../../containers/domain_container'; import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { debounce } from 'lodash'; -import ScrollableList from '../../components/scrollable_list'; +import ScrollableList from 'flavours/glitch/components/scrollable_list'; const messages = defineMessages({ heading: { id: 'column.domain_blocks', defaultMessage: 'Hidden domains' }, @@ -51,10 +51,16 @@ export default class Blocks extends ImmutablePureComponent { ); } + const emptyMessage = ; + return ( - + {domains.map(domain => )} diff --git a/app/javascript/flavours/glitch/features/favourited_statuses/index.js b/app/javascript/flavours/glitch/features/favourited_statuses/index.js index 32bf4e71a..0dae7dd53 100644 --- a/app/javascript/flavours/glitch/features/favourited_statuses/index.js +++ b/app/javascript/flavours/glitch/features/favourited_statuses/index.js @@ -2,14 +2,14 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { debounce } from 'lodash'; import { fetchFavouritedStatuses, expandFavouritedStatuses } from 'flavours/glitch/actions/favourites'; import Column from 'flavours/glitch/features/ui/components/column'; import ColumnHeader from 'flavours/glitch/components/column_header'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import StatusList from 'flavours/glitch/components/status_list'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { debounce } from 'lodash'; const messages = defineMessages({ heading: { id: 'column.favourites', defaultMessage: 'Favourites' }, @@ -70,6 +70,8 @@ export default class Favourites extends ImmutablePureComponent { const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; const pinned = !!columnId; + const emptyMessage = ; + return ( ); diff --git a/app/javascript/flavours/glitch/features/favourites/index.js b/app/javascript/flavours/glitch/features/favourites/index.js index eb86636c3..16ce6ed10 100644 --- a/app/javascript/flavours/glitch/features/favourites/index.js +++ b/app/javascript/flavours/glitch/features/favourites/index.js @@ -4,12 +4,12 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; import { fetchFavourites } from 'flavours/glitch/actions/interactions'; -import { ScrollContainer } from 'react-router-scroll-4'; import AccountContainer from 'flavours/glitch/containers/account_container'; import Column from 'flavours/glitch/features/ui/components/column'; import ColumnHeader from 'flavours/glitch/components/column_header'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import ScrollableList from '../../components/scrollable_list'; const messages = defineMessages({ heading: { id: 'column.favourited_by', defaultMessage: 'Favourited by' }, @@ -64,6 +64,8 @@ export default class Favourites extends ImmutablePureComponent { ); } + const emptyMessage = ; + return ( - - -
- {accountIds.map(id => )} -
-
+ + {accountIds.map(id => + + )} +
); } diff --git a/app/javascript/flavours/glitch/features/follow_requests/index.js b/app/javascript/flavours/glitch/features/follow_requests/index.js index d0845769e..8c2e70ca4 100644 --- a/app/javascript/flavours/glitch/features/follow_requests/index.js +++ b/app/javascript/flavours/glitch/features/follow_requests/index.js @@ -2,14 +2,15 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { debounce } from 'lodash'; import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; -import { ScrollContainer } from 'react-router-scroll-4'; import Column from 'flavours/glitch/features/ui/components/column'; import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim'; import AccountAuthorizeContainer from './containers/account_authorize_container'; import { fetchFollowRequests, expandFollowRequests } from 'flavours/glitch/actions/accounts'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import ScrollableList from 'flavours/glitch/components/scrollable_list'; const messages = defineMessages({ heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' }, @@ -34,13 +35,9 @@ export default class FollowRequests extends ImmutablePureComponent { this.props.dispatch(fetchFollowRequests()); } - handleScroll = (e) => { - const { scrollTop, scrollHeight, clientHeight } = e.target; - - if (scrollTop === scrollHeight - clientHeight) { - this.props.dispatch(expandFollowRequests()); - } - } + handleLoadMore = debounce(() => { + this.props.dispatch(expandFollowRequests()); + }, 300, { leading: true }); shouldUpdateScroll = (prevRouterProps, { location }) => { if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false; @@ -58,17 +55,22 @@ export default class FollowRequests extends ImmutablePureComponent { ); } + const emptyMessage = ; + return ( - -
- {accountIds.map(id => - - )} -
-
+ + {accountIds.map(id => + + )} +
); } diff --git a/app/javascript/flavours/glitch/features/followers/index.js b/app/javascript/flavours/glitch/features/followers/index.js index 2e47ab9b9..0f50b727d 100644 --- a/app/javascript/flavours/glitch/features/followers/index.js +++ b/app/javascript/flavours/glitch/features/followers/index.js @@ -2,20 +2,21 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { debounce } from 'lodash'; import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; import { fetchAccount, fetchFollowers, expandFollowers, } from 'flavours/glitch/actions/accounts'; -import { ScrollContainer } from 'react-router-scroll-4'; +import { FormattedMessage } from 'react-intl'; import AccountContainer from 'flavours/glitch/containers/account_container'; import Column from 'flavours/glitch/features/ui/components/column'; import ProfileColumnHeader from 'flavours/glitch/features/account/components/profile_column_header'; import HeaderContainer from 'flavours/glitch/features/account_timeline/containers/header_container'; -import LoadMore from 'flavours/glitch/components/load_more'; import ImmutablePureComponent from 'react-immutable-pure-component'; import MissingIndicator from 'flavours/glitch/components/missing_indicator'; +import ScrollableList from 'flavours/glitch/components/scrollable_list'; const mapStateToProps = (state, props) => ({ isAccount: !!state.getIn(['accounts', props.params.accountId]), @@ -58,10 +59,10 @@ export default class Followers extends ImmutablePureComponent { } } - handleLoadMore = (e) => { + handleLoadMore = debounce(() => { e.preventDefault(); this.props.dispatch(expandFollowers(this.props.params.accountId)); - } + }, 300, { leading: true }); shouldUpdateScroll = (prevRouterProps, { location }) => { if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false; @@ -83,8 +84,6 @@ export default class Followers extends ImmutablePureComponent { ); } - let loadMore = null; - if (!accountIds) { return ( @@ -93,23 +92,25 @@ export default class Followers extends ImmutablePureComponent { ); } - if (hasMore) { - loadMore = ; - } + const emptyMessage = ; return ( - -
-
- - {accountIds.map(id => )} - {loadMore} -
-
-
+ + + + {accountIds.map(id => + + )} +
); } diff --git a/app/javascript/flavours/glitch/features/following/index.js b/app/javascript/flavours/glitch/features/following/index.js index ad1445f3a..b69358890 100644 --- a/app/javascript/flavours/glitch/features/following/index.js +++ b/app/javascript/flavours/glitch/features/following/index.js @@ -2,20 +2,21 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { debounce } from 'lodash'; import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; import { fetchAccount, fetchFollowing, expandFollowing, } from 'flavours/glitch/actions/accounts'; -import { ScrollContainer } from 'react-router-scroll-4'; +import { FormattedMessage } from 'react-intl'; import AccountContainer from 'flavours/glitch/containers/account_container'; import Column from 'flavours/glitch/features/ui/components/column'; import ProfileColumnHeader from 'flavours/glitch/features/account/components/profile_column_header'; import HeaderContainer from 'flavours/glitch/features/account_timeline/containers/header_container'; -import LoadMore from 'flavours/glitch/components/load_more'; import ImmutablePureComponent from 'react-immutable-pure-component'; import MissingIndicator from 'flavours/glitch/components/missing_indicator'; +import ScrollableList from 'flavours/glitch/components/scrollable_list'; const mapStateToProps = (state, props) => ({ isAccount: !!state.getIn(['accounts', props.params.accountId]), @@ -58,15 +59,10 @@ export default class Following extends ImmutablePureComponent { } } - handleLoadMore = (e) => { + handleLoadMore = debounce(() => { e.preventDefault(); this.props.dispatch(expandFollowing(this.props.params.accountId)); - } - - shouldUpdateScroll = (prevRouterProps, { location }) => { - if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false; - return !(location.state && location.state.mastodonModalOpen); - } + }, 300, { leading: true }); setRef = c => { this.column = c; @@ -83,8 +79,6 @@ export default class Following extends ImmutablePureComponent { ); } - let loadMore = null; - if (!accountIds) { return ( @@ -93,23 +87,25 @@ export default class Following extends ImmutablePureComponent { ); } - if (hasMore) { - loadMore = ; - } + const emptyMessage = ; return ( - -
-
- - {accountIds.map(id => )} - {loadMore} -
-
-
+ + + + {accountIds.map(id => + + )} +
); } diff --git a/app/javascript/flavours/glitch/features/lists/index.js b/app/javascript/flavours/glitch/features/lists/index.js index 8b0470c92..bf1b410bb 100644 --- a/app/javascript/flavours/glitch/features/lists/index.js +++ b/app/javascript/flavours/glitch/features/lists/index.js @@ -6,12 +6,13 @@ import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; import Column from 'flavours/glitch/features/ui/components/column'; import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim'; import { fetchLists } from 'flavours/glitch/actions/lists'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ColumnLink from 'flavours/glitch/features/ui/components/column_link'; import ColumnSubheading from 'flavours/glitch/features/ui/components/column_subheading'; import NewListForm from './components/new_list_form'; import { createSelector } from 'reselect'; +import ScrollableList from 'flavours/glitch/components/scrollable_list'; const messages = defineMessages({ heading: { id: 'column.lists', defaultMessage: 'Lists' }, @@ -62,13 +63,15 @@ export default class Lists extends ImmutablePureComponent { -
- - + + {lists.map(list => )} -
+
); } diff --git a/app/javascript/flavours/glitch/features/mutes/index.js b/app/javascript/flavours/glitch/features/mutes/index.js index bbcbea701..c398395e8 100644 --- a/app/javascript/flavours/glitch/features/mutes/index.js +++ b/app/javascript/flavours/glitch/features/mutes/index.js @@ -1,15 +1,16 @@ import React from 'react'; import { connect } from 'react-redux'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { debounce } from 'lodash'; import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; -import { ScrollContainer } from 'react-router-scroll-4'; import Column from 'flavours/glitch/features/ui/components/column'; import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim'; import AccountContainer from 'flavours/glitch/containers/account_container'; import { fetchMutes, expandMutes } from 'flavours/glitch/actions/mutes'; -import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import ScrollableList from 'flavours/glitch/components/scrollable_list'; const messages = defineMessages({ heading: { id: 'column.mutes', defaultMessage: 'Muted users' }, @@ -34,13 +35,9 @@ export default class Mutes extends ImmutablePureComponent { this.props.dispatch(fetchMutes()); } - handleScroll = (e) => { - const { scrollTop, scrollHeight, clientHeight } = e.target; - - if (scrollTop === scrollHeight - clientHeight) { - this.props.dispatch(expandMutes()); - } - } + handleLoadMore = debounce(() => { + this.props.dispatch(expandMutes()); + }, 300, { leading: true }); shouldUpdateScroll = (prevRouterProps, { location }) => { if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false; @@ -58,16 +55,21 @@ export default class Mutes extends ImmutablePureComponent { ); } + const emptyMessage = ; + return ( - -
- {accountIds.map(id => - - )} -
-
+ + {accountIds.map(id => + + )} +
); } diff --git a/app/javascript/flavours/glitch/features/reblogs/index.js b/app/javascript/flavours/glitch/features/reblogs/index.js index e007506b7..cb1dc5c29 100644 --- a/app/javascript/flavours/glitch/features/reblogs/index.js +++ b/app/javascript/flavours/glitch/features/reblogs/index.js @@ -4,12 +4,12 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; import { fetchReblogs } from 'flavours/glitch/actions/interactions'; -import { ScrollContainer } from 'react-router-scroll-4'; import AccountContainer from 'flavours/glitch/containers/account_container'; import Column from 'flavours/glitch/features/ui/components/column'; import ColumnHeader from 'flavours/glitch/components/column_header'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import ScrollableList from 'flavours/glitch/components/scrollable_list'; const messages = defineMessages({ heading: { id: 'column.reblogged_by', defaultMessage: 'Boosted by' }, @@ -64,6 +64,8 @@ export default class Reblogs extends ImmutablePureComponent { ); } + const emptyMessage = ; + return ( - -
- {accountIds.map(id => )} -
-
+ + {accountIds.map(id => + + )} +
); } -- cgit From b294b5956bf15c8c18c75d01c57ddfeb07270ff7 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Sat, 20 Jul 2019 15:07:24 +0200 Subject: Add empty column message to bookmarks column --- .../flavours/glitch/features/bookmarked_statuses/index.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/bookmarked_statuses/index.js b/app/javascript/flavours/glitch/features/bookmarked_statuses/index.js index 9468ad81d..c57aba37b 100644 --- a/app/javascript/flavours/glitch/features/bookmarked_statuses/index.js +++ b/app/javascript/flavours/glitch/features/bookmarked_statuses/index.js @@ -2,14 +2,14 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { debounce } from 'lodash'; import { fetchBookmarkedStatuses, expandBookmarkedStatuses } from 'flavours/glitch/actions/bookmarks'; import Column from 'flavours/glitch/features/ui/components/column'; import ColumnHeader from 'flavours/glitch/components/column_header'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import StatusList from 'flavours/glitch/components/status_list'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { debounce } from 'lodash'; const messages = defineMessages({ heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' }, @@ -70,6 +70,8 @@ export default class Bookmarks extends ImmutablePureComponent { const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; const pinned = !!columnId; + const emptyMessage = ; + return ( ); -- cgit From c614abb95dcb226edd60df52bd987adbe83d7b30 Mon Sep 17 00:00:00 2001 From: Jakub Mendyk Date: Wed, 29 Aug 2018 01:19:58 +0200 Subject: [Glitch] Fix followers/follows layout issues from #8418 Port ceed1ebe5b308a95f9c167c93d3bc0976937fb58 to glitch-soc Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/features/followers/index.js | 5 +++-- app/javascript/flavours/glitch/features/following/index.js | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/followers/index.js b/app/javascript/flavours/glitch/features/followers/index.js index 0f50b727d..b0a9efc21 100644 --- a/app/javascript/flavours/glitch/features/followers/index.js +++ b/app/javascript/flavours/glitch/features/followers/index.js @@ -98,13 +98,14 @@ export default class Followers extends ImmutablePureComponent { - - } + alwaysPrepend + alwaysShowScrollbar emptyMessage={emptyMessage} > {accountIds.map(id => diff --git a/app/javascript/flavours/glitch/features/following/index.js b/app/javascript/flavours/glitch/features/following/index.js index b69358890..baf3730d9 100644 --- a/app/javascript/flavours/glitch/features/following/index.js +++ b/app/javascript/flavours/glitch/features/following/index.js @@ -93,13 +93,14 @@ export default class Following extends ImmutablePureComponent { - - } + alwaysPrepend + alwaysShowScrollbar emptyMessage={emptyMessage} > {accountIds.map(id => -- cgit From bd7b1538f17f3aa5b73a691044cf64068f23a231 Mon Sep 17 00:00:00 2001 From: ThibG Date: Wed, 12 Dec 2018 22:32:44 +0100 Subject: [Glitch] Fix followers and followings on account profiles Partial fix from 55abff8af75b9a8ab541ec68a3603c77e1339e23 Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/features/followers/index.js | 1 - app/javascript/flavours/glitch/features/following/index.js | 1 - 2 files changed, 2 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/followers/index.js b/app/javascript/flavours/glitch/features/followers/index.js index b0a9efc21..704e0c92e 100644 --- a/app/javascript/flavours/glitch/features/followers/index.js +++ b/app/javascript/flavours/glitch/features/followers/index.js @@ -105,7 +105,6 @@ export default class Followers extends ImmutablePureComponent { shouldUpdateScroll={this.shouldUpdateScroll} prepend={} alwaysPrepend - alwaysShowScrollbar emptyMessage={emptyMessage} > {accountIds.map(id => diff --git a/app/javascript/flavours/glitch/features/following/index.js b/app/javascript/flavours/glitch/features/following/index.js index baf3730d9..46cc773e7 100644 --- a/app/javascript/flavours/glitch/features/following/index.js +++ b/app/javascript/flavours/glitch/features/following/index.js @@ -100,7 +100,6 @@ export default class Following extends ImmutablePureComponent { shouldUpdateScroll={this.shouldUpdateScroll} prepend={} alwaysPrepend - alwaysShowScrollbar emptyMessage={emptyMessage} > {accountIds.map(id => -- cgit From e58af0428775d6e8f98ce86cbb626e0abf6fa78b Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 16 Feb 2019 11:56:09 +0100 Subject: [Glitch] Fix mutes, blocks, domain blocks and follow requests not paginating Port ea7ad59af20af2aa6817b3b40dca34c8fba3373a to glitch-soc Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/features/blocks/index.js | 5 ++++- app/javascript/flavours/glitch/features/domain_blocks/index.js | 5 ++++- app/javascript/flavours/glitch/features/follow_requests/index.js | 5 ++++- app/javascript/flavours/glitch/features/mutes/index.js | 5 ++++- 4 files changed, 16 insertions(+), 4 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/blocks/index.js b/app/javascript/flavours/glitch/features/blocks/index.js index e995fb31f..1a7206f80 100644 --- a/app/javascript/flavours/glitch/features/blocks/index.js +++ b/app/javascript/flavours/glitch/features/blocks/index.js @@ -18,6 +18,7 @@ const messages = defineMessages({ const mapStateToProps = state => ({ accountIds: state.getIn(['user_lists', 'blocks', 'items']), + hasMore: !!state.getIn(['user_lists', 'blocks', 'next']), }); @connect(mapStateToProps) @@ -28,6 +29,7 @@ export default class Blocks extends ImmutablePureComponent { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, accountIds: ImmutablePropTypes.list, + hasMore: PropTypes.bool, intl: PropTypes.object.isRequired, }; @@ -45,7 +47,7 @@ export default class Blocks extends ImmutablePureComponent { } render () { - const { intl, accountIds } = this.props; + const { intl, accountIds, hasMore } = this.props; if (!accountIds) { return ( @@ -63,6 +65,7 @@ export default class Blocks extends ImmutablePureComponent { diff --git a/app/javascript/flavours/glitch/features/domain_blocks/index.js b/app/javascript/flavours/glitch/features/domain_blocks/index.js index 8b5496139..f7d93a48e 100644 --- a/app/javascript/flavours/glitch/features/domain_blocks/index.js +++ b/app/javascript/flavours/glitch/features/domain_blocks/index.js @@ -19,6 +19,7 @@ const messages = defineMessages({ const mapStateToProps = state => ({ domains: state.getIn(['domain_lists', 'blocks', 'items']), + hasMore: !!state.getIn(['domain_lists', 'blocks', 'next']), }); @connect(mapStateToProps) @@ -28,6 +29,7 @@ export default class Blocks extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, + hasMore: PropTypes.bool, domains: ImmutablePropTypes.list, intl: PropTypes.object.isRequired, }; @@ -41,7 +43,7 @@ export default class Blocks extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { intl, domains } = this.props; + const { intl, domains, hasMore } = this.props; if (!domains) { return ( @@ -59,6 +61,7 @@ export default class Blocks extends ImmutablePureComponent { {domains.map(domain => diff --git a/app/javascript/flavours/glitch/features/follow_requests/index.js b/app/javascript/flavours/glitch/features/follow_requests/index.js index 8c2e70ca4..eacf1e0ac 100644 --- a/app/javascript/flavours/glitch/features/follow_requests/index.js +++ b/app/javascript/flavours/glitch/features/follow_requests/index.js @@ -18,6 +18,7 @@ const messages = defineMessages({ const mapStateToProps = state => ({ accountIds: state.getIn(['user_lists', 'follow_requests', 'items']), + hasMore: !!state.getIn(['user_lists', 'follow_requests', 'next']), }); @connect(mapStateToProps) @@ -27,6 +28,7 @@ export default class FollowRequests extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, + hasMore: PropTypes.bool, accountIds: ImmutablePropTypes.list, intl: PropTypes.object.isRequired, }; @@ -45,7 +47,7 @@ export default class FollowRequests extends ImmutablePureComponent { } render () { - const { intl, accountIds } = this.props; + const { intl, accountIds, hasMore } = this.props; if (!accountIds) { return ( @@ -64,6 +66,7 @@ export default class FollowRequests extends ImmutablePureComponent { diff --git a/app/javascript/flavours/glitch/features/mutes/index.js b/app/javascript/flavours/glitch/features/mutes/index.js index c398395e8..35c15deec 100644 --- a/app/javascript/flavours/glitch/features/mutes/index.js +++ b/app/javascript/flavours/glitch/features/mutes/index.js @@ -18,6 +18,7 @@ const messages = defineMessages({ const mapStateToProps = state => ({ accountIds: state.getIn(['user_lists', 'mutes', 'items']), + hasMore: !!state.getIn(['user_lists', 'mutes', 'next']), }); @connect(mapStateToProps) @@ -27,6 +28,7 @@ export default class Mutes extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, + hasMore: PropTypes.bool, accountIds: ImmutablePropTypes.list, intl: PropTypes.object.isRequired, }; @@ -45,7 +47,7 @@ export default class Mutes extends ImmutablePureComponent { } render () { - const { intl, accountIds } = this.props; + const { intl, accountIds, hasMore } = this.props; if (!accountIds) { return ( @@ -63,6 +65,7 @@ export default class Mutes extends ImmutablePureComponent { -- cgit From 6db5669818cce459b9bb916665541b7b8f5d5155 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Sat, 20 Jul 2019 16:07:55 +0200 Subject: Clean up redundant shouldUpdateScroll definitions --- app/javascript/flavours/glitch/features/blocks/index.js | 6 ------ app/javascript/flavours/glitch/features/community_timeline/index.js | 5 ----- app/javascript/flavours/glitch/features/favourites/index.js | 6 ------ app/javascript/flavours/glitch/features/follow_requests/index.js | 6 ------ app/javascript/flavours/glitch/features/followers/index.js | 6 ------ app/javascript/flavours/glitch/features/following/index.js | 1 - app/javascript/flavours/glitch/features/mutes/index.js | 6 ------ app/javascript/flavours/glitch/features/public_timeline/index.js | 4 ---- app/javascript/flavours/glitch/features/reblogs/index.js | 6 ------ 9 files changed, 46 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/blocks/index.js b/app/javascript/flavours/glitch/features/blocks/index.js index 1a7206f80..2d0f10ae7 100644 --- a/app/javascript/flavours/glitch/features/blocks/index.js +++ b/app/javascript/flavours/glitch/features/blocks/index.js @@ -41,11 +41,6 @@ export default class Blocks extends ImmutablePureComponent { this.props.dispatch(expandBlocks()); }, 300, { leading: true }); - shouldUpdateScroll = (prevRouterProps, { location }) => { - if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false; - return !(location.state && location.state.mastodonModalOpen); - } - render () { const { intl, accountIds, hasMore } = this.props; @@ -66,7 +61,6 @@ export default class Blocks extends ImmutablePureComponent { scrollKey='blocks' onLoadMore={this.handleLoadMore} hasMore={hasMore} - shouldUpdateScroll={this.shouldUpdateScroll} emptyMessage={emptyMessage} > {accountIds.map(id => diff --git a/app/javascript/flavours/glitch/features/community_timeline/index.js b/app/javascript/flavours/glitch/features/community_timeline/index.js index 2c0fbff36..24126e5bc 100644 --- a/app/javascript/flavours/glitch/features/community_timeline/index.js +++ b/app/javascript/flavours/glitch/features/community_timeline/index.js @@ -99,10 +99,6 @@ export default class CommunityTimeline extends React.PureComponent { dispatch(expandCommunityTimeline({ maxId, onlyMedia })); } - shouldUpdateScroll = (prevRouterProps, { location }) => { - return !(location.state && location.state.mastodonModalOpen) - } - render () { const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props; const pinned = !!columnId; @@ -125,7 +121,6 @@ export default class CommunityTimeline extends React.PureComponent { } diff --git a/app/javascript/flavours/glitch/features/favourites/index.js b/app/javascript/flavours/glitch/features/favourites/index.js index 16ce6ed10..81f0b24c4 100644 --- a/app/javascript/flavours/glitch/features/favourites/index.js +++ b/app/javascript/flavours/glitch/features/favourites/index.js @@ -40,11 +40,6 @@ export default class Favourites extends ImmutablePureComponent { } } - shouldUpdateScroll = (prevRouterProps, { location }) => { - if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false; - return !(location.state && location.state.mastodonModalOpen); - } - handleHeaderClick = () => { this.column.scrollTop(); } @@ -76,7 +71,6 @@ export default class Favourites extends ImmutablePureComponent { /> {accountIds.map(id => diff --git a/app/javascript/flavours/glitch/features/follow_requests/index.js b/app/javascript/flavours/glitch/features/follow_requests/index.js index eacf1e0ac..89340320e 100644 --- a/app/javascript/flavours/glitch/features/follow_requests/index.js +++ b/app/javascript/flavours/glitch/features/follow_requests/index.js @@ -41,11 +41,6 @@ export default class FollowRequests extends ImmutablePureComponent { this.props.dispatch(expandFollowRequests()); }, 300, { leading: true }); - shouldUpdateScroll = (prevRouterProps, { location }) => { - if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false; - return !(location.state && location.state.mastodonModalOpen); - } - render () { const { intl, accountIds, hasMore } = this.props; @@ -67,7 +62,6 @@ export default class FollowRequests extends ImmutablePureComponent { scrollKey='follow_requests' onLoadMore={this.handleLoadMore} hasMore={hasMore} - shouldUpdateScroll={this.shouldUpdateScroll} emptyMessage={emptyMessage} > {accountIds.map(id => diff --git a/app/javascript/flavours/glitch/features/followers/index.js b/app/javascript/flavours/glitch/features/followers/index.js index 704e0c92e..c57a2b1a2 100644 --- a/app/javascript/flavours/glitch/features/followers/index.js +++ b/app/javascript/flavours/glitch/features/followers/index.js @@ -64,11 +64,6 @@ export default class Followers extends ImmutablePureComponent { this.props.dispatch(expandFollowers(this.props.params.accountId)); }, 300, { leading: true }); - shouldUpdateScroll = (prevRouterProps, { location }) => { - if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false; - return !(location.state && location.state.mastodonModalOpen); - } - setRef = c => { this.column = c; } @@ -102,7 +97,6 @@ export default class Followers extends ImmutablePureComponent { scrollKey='followers' hasMore={hasMore} onLoadMore={this.handleLoadMore} - shouldUpdateScroll={this.shouldUpdateScroll} prepend={} alwaysPrepend emptyMessage={emptyMessage} diff --git a/app/javascript/flavours/glitch/features/following/index.js b/app/javascript/flavours/glitch/features/following/index.js index 46cc773e7..b61f83988 100644 --- a/app/javascript/flavours/glitch/features/following/index.js +++ b/app/javascript/flavours/glitch/features/following/index.js @@ -97,7 +97,6 @@ export default class Following extends ImmutablePureComponent { scrollKey='following' hasMore={hasMore} onLoadMore={this.handleLoadMore} - shouldUpdateScroll={this.shouldUpdateScroll} prepend={} alwaysPrepend emptyMessage={emptyMessage} diff --git a/app/javascript/flavours/glitch/features/mutes/index.js b/app/javascript/flavours/glitch/features/mutes/index.js index 35c15deec..e5b5bb46d 100644 --- a/app/javascript/flavours/glitch/features/mutes/index.js +++ b/app/javascript/flavours/glitch/features/mutes/index.js @@ -41,11 +41,6 @@ export default class Mutes extends ImmutablePureComponent { this.props.dispatch(expandMutes()); }, 300, { leading: true }); - shouldUpdateScroll = (prevRouterProps, { location }) => { - if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false; - return !(location.state && location.state.mastodonModalOpen); - } - render () { const { intl, accountIds, hasMore } = this.props; @@ -66,7 +61,6 @@ export default class Mutes extends ImmutablePureComponent { scrollKey='mutes' onLoadMore={this.handleLoadMore} hasMore={hasMore} - shouldUpdateScroll={this.shouldUpdateScroll} emptyMessage={emptyMessage} > {accountIds.map(id => diff --git a/app/javascript/flavours/glitch/features/public_timeline/index.js b/app/javascript/flavours/glitch/features/public_timeline/index.js index 7fe472202..e5f5171aa 100644 --- a/app/javascript/flavours/glitch/features/public_timeline/index.js +++ b/app/javascript/flavours/glitch/features/public_timeline/index.js @@ -99,10 +99,6 @@ export default class PublicTimeline extends React.PureComponent { dispatch(expandPublicTimeline({ maxId, onlyMedia })); } - shouldUpdateScroll = (prevRouterProps, { location }) => { - return !(location.state && location.state.mastodonModalOpen) - } - render () { const { intl, columnId, hasUnread, multiColumn, onlyMedia } = this.props; const pinned = !!columnId; diff --git a/app/javascript/flavours/glitch/features/reblogs/index.js b/app/javascript/flavours/glitch/features/reblogs/index.js index cb1dc5c29..14f44a20a 100644 --- a/app/javascript/flavours/glitch/features/reblogs/index.js +++ b/app/javascript/flavours/glitch/features/reblogs/index.js @@ -40,11 +40,6 @@ export default class Reblogs extends ImmutablePureComponent { } } - shouldUpdateScroll = (prevRouterProps, { location }) => { - if ((((prevRouterProps || {}).location || {}).state || {}).mastodonModalOpen) return false; - return !(location.state && location.state.mastodonModalOpen); - } - handleHeaderClick = () => { this.column.scrollTop(); } @@ -77,7 +72,6 @@ export default class Reblogs extends ImmutablePureComponent { {accountIds.map(id => -- cgit From c1231a846ac7b4a83b5b5b05384bca670d66ccde Mon Sep 17 00:00:00 2001 From: ThibG Date: Sun, 21 Jul 2019 18:10:40 +0200 Subject: [Glitch] Play animated custom emoji on hover Port 7de8c51873b51d8450f7a6597a43d454964d0407 to glitch-soc --- .../flavours/glitch/components/display_name.js | 44 +++++++++++++++++++++- .../flavours/glitch/components/status_content.js | 32 ++++++++++++++++ .../glitch/features/account/components/header.js | 43 ++++++++++++++++++++- app/javascript/flavours/glitch/packs/public.js | 10 +++++ app/javascript/flavours/glitch/util/emoji/index.js | 2 +- 5 files changed, 128 insertions(+), 3 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/components/display_name.js b/app/javascript/flavours/glitch/components/display_name.js index 7626fb25c..9d8c4a775 100644 --- a/app/javascript/flavours/glitch/components/display_name.js +++ b/app/javascript/flavours/glitch/components/display_name.js @@ -2,6 +2,7 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import classNames from 'classnames'; +import { autoPlayGif } from 'flavours/glitch/util/initial_state'; export default class DisplayName extends React.PureComponent { @@ -14,6 +15,47 @@ export default class DisplayName extends React.PureComponent { handleClick: PropTypes.func, }; + _updateEmojis () { + const node = this.node; + + if (!node || autoPlayGif) { + return; + } + + const emojis = node.querySelectorAll('.custom-emoji'); + + for (var i = 0; i < emojis.length; i++) { + let emoji = emojis[i]; + if (emoji.classList.contains('status-emoji')) { + continue; + } + emoji.classList.add('status-emoji'); + + emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false); + emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false); + } + } + + componentDidMount () { + this._updateEmojis(); + } + + componentDidUpdate () { + this._updateEmojis(); + } + + handleEmojiMouseEnter = ({ target }) => { + target.src = target.getAttribute('data-original'); + } + + handleEmojiMouseLeave = ({ target }) => { + target.src = target.getAttribute('data-static'); + } + + setRef = (c) => { + this.node = c; + } + render() { const { account, className, inline, localDomain, others, onAccountClick } = this.props; @@ -58,7 +100,7 @@ export default class DisplayName extends React.PureComponent { } return ( - + {displayName} {inline ? ' ' : null} {suffix} diff --git a/app/javascript/flavours/glitch/components/status_content.js b/app/javascript/flavours/glitch/components/status_content.js index 07a0d1d5d..6e357fba0 100644 --- a/app/javascript/flavours/glitch/components/status_content.js +++ b/app/javascript/flavours/glitch/components/status_content.js @@ -5,6 +5,7 @@ import { isRtl } from 'flavours/glitch/util/rtl'; import { FormattedMessage } from 'react-intl'; import Permalink from './permalink'; import classnames from 'classnames'; +import { autoPlayGif } from 'flavours/glitch/util/initial_state'; export default class StatusContent extends React.PureComponent { @@ -57,12 +58,35 @@ export default class StatusContent extends React.PureComponent { } } + _updateStatusEmojis () { + const node = this.node; + + if (!node || autoPlayGif) { + return; + } + + const emojis = node.querySelectorAll('.custom-emoji'); + + for (var i = 0; i < emojis.length; i++) { + let emoji = emojis[i]; + if (emoji.classList.contains('status-emoji')) { + continue; + } + emoji.classList.add('status-emoji'); + + emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false); + emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false); + } + } + componentDidMount () { this._updateStatusLinks(); + this._updateStatusEmojis(); } componentDidUpdate () { this._updateStatusLinks(); + this._updateStatusEmojis(); if (this.props.onUpdate) this.props.onUpdate(); } @@ -86,6 +110,14 @@ export default class StatusContent extends React.PureComponent { } } + handleEmojiMouseEnter = ({ target }) => { + target.src = target.getAttribute('data-original'); + } + + handleEmojiMouseLeave = ({ target }) => { + target.src = target.getAttribute('data-static'); + } + handleMouseDown = (e) => { this.startXY = [e.clientX, e.clientY]; } diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js index 43c4f0d32..e9437c0a9 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.js +++ b/app/javascript/flavours/glitch/features/account/components/header.js @@ -71,6 +71,47 @@ class Header extends ImmutablePureComponent { window.open('/settings/profile', '_blank'); } + _updateEmojis () { + const node = this.node; + + if (!node || autoPlayGif) { + return; + } + + const emojis = node.querySelectorAll('.custom-emoji'); + + for (var i = 0; i < emojis.length; i++) { + let emoji = emojis[i]; + if (emoji.classList.contains('status-emoji')) { + continue; + } + emoji.classList.add('status-emoji'); + + emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false); + emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false); + } + } + + componentDidMount () { + this._updateEmojis(); + } + + componentDidUpdate () { + this._updateEmojis(); + } + + handleEmojiMouseEnter = ({ target }) => { + target.src = target.getAttribute('data-original'); + } + + handleEmojiMouseLeave = ({ target }) => { + target.src = target.getAttribute('data-static'); + } + + setRef = (c) => { + this.node = c; + } + render () { const { account, intl, domain, identity_proofs } = this.props; @@ -193,7 +234,7 @@ class Header extends ImmutablePureComponent { const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct'); return ( -
+
{info} diff --git a/app/javascript/flavours/glitch/packs/public.js b/app/javascript/flavours/glitch/packs/public.js index da0b4c8e0..9f88b0e04 100644 --- a/app/javascript/flavours/glitch/packs/public.js +++ b/app/javascript/flavours/glitch/packs/public.js @@ -4,6 +4,7 @@ import ready from 'flavours/glitch/util/ready'; function main() { const IntlMessageFormat = require('intl-messageformat').default; const { timeAgoString } = require('flavours/glitch/components/relative_timestamp'); + const { delegate } = require('rails-ujs'); const emojify = require('flavours/glitch/util/emoji').default; const { getLocale } = require('locales'); const { messages } = getLocale(); @@ -23,6 +24,12 @@ function main() { } }; + const getEmojiAnimationHandler = (swapTo) => { + return ({ target }) => { + target.src = target.getAttribute(swapTo); + }; + }; + ready(() => { const locale = document.documentElement.lang; @@ -94,6 +101,9 @@ function main() { document.head.appendChild(scrollbarWidthStyle); scrollbarWidthStyle.sheet.insertRule(`body.with-modals--active { margin-right: ${scrollbarWidth}px; }`, 0); } + + delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original')); + delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static')); }); } diff --git a/app/javascript/flavours/glitch/util/emoji/index.js b/app/javascript/flavours/glitch/util/emoji/index.js index e6fcaf0dc..2f40f9b08 100644 --- a/app/javascript/flavours/glitch/util/emoji/index.js +++ b/app/javascript/flavours/glitch/util/emoji/index.js @@ -29,7 +29,7 @@ const emojify = (str, customEmojis = {}) => { // if you want additional emoji handler, add statements below which set replacement and return true. if (shortname in customEmojis) { const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url; - replacement = `${shortname}`; + replacement = `${shortname}`; return true; } return false; -- cgit From 51411267fda00db576230a270a10e31992378c18 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 27 Jul 2019 05:49:50 +0200 Subject: [Glitch] Add search results pagination to web UI (#11409) Port 8a4674f2c3d89c998eb5438b96b7977dc2be3167 to glitch-soc Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/actions/search.js | 54 ++++++++++++++++++++-- .../features/compose/components/search_results.js | 18 +++++++- .../compose/containers/search_results_container.js | 4 +- app/javascript/flavours/glitch/reducers/search.js | 3 ++ .../flavours/glitch/styles/components/search.scss | 5 +- 5 files changed, 76 insertions(+), 8 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/actions/search.js b/app/javascript/flavours/glitch/actions/search.js index 9ce77b24b..a025f352a 100644 --- a/app/javascript/flavours/glitch/actions/search.js +++ b/app/javascript/flavours/glitch/actions/search.js @@ -10,6 +10,10 @@ export const SEARCH_FETCH_REQUEST = 'SEARCH_FETCH_REQUEST'; export const SEARCH_FETCH_SUCCESS = 'SEARCH_FETCH_SUCCESS'; export const SEARCH_FETCH_FAIL = 'SEARCH_FETCH_FAIL'; +export const SEARCH_EXPAND_REQUEST = 'SEARCH_EXPAND_REQUEST'; +export const SEARCH_EXPAND_SUCCESS = 'SEARCH_EXPAND_SUCCESS'; +export const SEARCH_EXPAND_FAIL = 'SEARCH_EXPAND_FAIL'; + export function changeSearch(value) { return { type: SEARCH_CHANGE, @@ -77,8 +81,50 @@ export function fetchSearchFail(error) { }; }; -export function showSearch() { - return { - type: SEARCH_SHOW, - }; +export const expandSearch = type => (dispatch, getState) => { + const value = getState().getIn(['search', 'value']); + const offset = getState().getIn(['search', 'results', type]).size; + + dispatch(expandSearchRequest()); + + api(getState).get('/api/v2/search', { + params: { + q: value, + type, + offset, + }, + }).then(({ data }) => { + if (data.accounts) { + dispatch(importFetchedAccounts(data.accounts)); + } + + if (data.statuses) { + dispatch(importFetchedStatuses(data.statuses)); + } + + dispatch(expandSearchSuccess(data, value, type)); + dispatch(fetchRelationships(data.accounts.map(item => item.id))); + }).catch(error => { + dispatch(expandSearchFail(error)); + }); }; + +export const expandSearchRequest = () => ({ + type: SEARCH_EXPAND_REQUEST, +}); + +export const expandSearchSuccess = (results, searchTerm, searchType) => ({ + type: SEARCH_EXPAND_SUCCESS, + results, + searchTerm, + searchType, +}); + +export const expandSearchFail = error => ({ + type: SEARCH_EXPAND_FAIL, + error, +}); + +export const showSearch = () => ({ + type: SEARCH_SHOW, +}); diff --git a/app/javascript/flavours/glitch/features/compose/components/search_results.js b/app/javascript/flavours/glitch/features/compose/components/search_results.js index dd99f3430..7220d8529 100644 --- a/app/javascript/flavours/glitch/features/compose/components/search_results.js +++ b/app/javascript/flavours/glitch/features/compose/components/search_results.js @@ -8,6 +8,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import Hashtag from 'flavours/glitch/components/hashtag'; import Icon from 'flavours/glitch/components/icon'; import { searchEnabled } from 'flavours/glitch/util/initial_state'; +import LoadMore from 'flavours/glitch/components/load_more'; const messages = defineMessages({ dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' }, @@ -20,15 +21,24 @@ class SearchResults extends ImmutablePureComponent { results: ImmutablePropTypes.map.isRequired, suggestions: ImmutablePropTypes.list.isRequired, fetchSuggestions: PropTypes.func.isRequired, + expandSearch: PropTypes.func.isRequired, dismissSuggestion: PropTypes.func.isRequired, searchTerm: PropTypes.string, intl: PropTypes.object.isRequired, }; componentDidMount () { - this.props.fetchSuggestions(); + if (this.props.searchTerm === '') { + this.props.fetchSuggestions(); + } } + handleLoadMoreAccounts = () => this.props.expandSearch('accounts'); + + handleLoadMoreStatuses = () => this.props.expandSearch('statuses'); + + handleLoadMoreHashtags = () => this.props.expandSearch('hashtags'); + render () { const { intl, results, suggestions, dismissSuggestion, searchTerm } = this.props; @@ -75,6 +85,8 @@ class SearchResults extends ImmutablePureComponent {
{results.get('accounts').map(accountId => )} + + {results.get('accounts').size >= 5 && } ); } @@ -86,6 +98,8 @@ class SearchResults extends ImmutablePureComponent {
{results.get('statuses').map(statusId => )} + + {results.get('statuses').size >= 5 && } ); } @@ -97,6 +111,8 @@ class SearchResults extends ImmutablePureComponent {
{results.get('hashtags').map(hashtag => )} + + {results.get('hashtags').size >= 5 && } ); } diff --git a/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js b/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js index e4d5f3420..1f714ff83 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js @@ -1,6 +1,7 @@ import { connect } from 'react-redux'; import SearchResults from '../components/search_results'; -import { fetchSuggestions, dismissSuggestion } from '../../../actions/suggestions'; +import { fetchSuggestions, dismissSuggestion } from 'mastodon/actions/suggestions'; +import { expandSearch } from 'mastodon/actions/search'; const mapStateToProps = state => ({ results: state.getIn(['search', 'results']), @@ -10,6 +11,7 @@ const mapStateToProps = state => ({ const mapDispatchToProps = dispatch => ({ fetchSuggestions: () => dispatch(fetchSuggestions()), + expandSearch: type => dispatch(expandSearch(type)), dismissSuggestion: account => dispatch(dismissSuggestion(account.get('id'))), }); diff --git a/app/javascript/flavours/glitch/reducers/search.js b/app/javascript/flavours/glitch/reducers/search.js index 1c32a5b9f..f4d99a99a 100644 --- a/app/javascript/flavours/glitch/reducers/search.js +++ b/app/javascript/flavours/glitch/reducers/search.js @@ -3,6 +3,7 @@ import { SEARCH_CLEAR, SEARCH_FETCH_SUCCESS, SEARCH_SHOW, + SEARCH_EXPAND_SUCCESS, } from 'flavours/glitch/actions/search'; import { COMPOSE_MENTION, @@ -42,6 +43,8 @@ export default function search(state = initialState, action) { statuses: ImmutableList(action.results.statuses.map(item => item.id)), hashtags: fromJS(action.results.hashtags), })).set('submitted', true).set('searchTerm', action.searchTerm); + case SEARCH_EXPAND_SUCCESS: + return state.updateIn(['results', action.searchType], list => list.concat(action.results[action.searchType].map(item => item.id))); default: return state; } diff --git a/app/javascript/flavours/glitch/styles/components/search.scss b/app/javascript/flavours/glitch/styles/components/search.scss index 117da362f..0e518997d 100644 --- a/app/javascript/flavours/glitch/styles/components/search.scss +++ b/app/javascript/flavours/glitch/styles/components/search.scss @@ -79,8 +79,9 @@ } .search-results__info { - padding: 10px; - color: $secondary-text-color; + padding: 20px; + color: $darker-text-color; + text-align: center; } .trends { -- cgit From a4a81c5434c73139f7c1d66f2687039237c5208a Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Mon, 29 Jul 2019 00:28:13 +0200 Subject: Fix crash in /web/lists Fixes #1181 --- app/javascript/flavours/glitch/features/lists/index.js | 2 ++ 1 file changed, 2 insertions(+) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/lists/index.js b/app/javascript/flavours/glitch/features/lists/index.js index bf1b410bb..ad5240bd5 100644 --- a/app/javascript/flavours/glitch/features/lists/index.js +++ b/app/javascript/flavours/glitch/features/lists/index.js @@ -57,6 +57,8 @@ export default class Lists extends ImmutablePureComponent { ); } + const emptyMessage = ; + return ( -- cgit From 49a28e69a520d5994352e231e9879a5093af5916 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Thu, 1 Aug 2019 14:35:17 +0200 Subject: Move decodeIDNA to app/javascript/flavours/glitch/util --- .../flavours/glitch/features/status/components/card.js | 10 +--------- app/javascript/flavours/glitch/util/idna.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 app/javascript/flavours/glitch/util/idna.js (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/status/components/card.js b/app/javascript/flavours/glitch/features/status/components/card.js index f974a87a1..108b6e3b2 100644 --- a/app/javascript/flavours/glitch/features/status/components/card.js +++ b/app/javascript/flavours/glitch/features/status/components/card.js @@ -4,15 +4,7 @@ import Immutable from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; import punycode from 'punycode'; import classnames from 'classnames'; - -const IDNA_PREFIX = 'xn--'; - -const decodeIDNA = domain => { - return domain - .split('.') - .map(part => part.indexOf(IDNA_PREFIX) === 0 ? punycode.decode(part.slice(IDNA_PREFIX.length)) : part) - .join('.'); -}; +import { decode as decodeIDNA } from 'flavours/glitch/util/idna'; const getHostname = url => { const parser = document.createElement('a'); diff --git a/app/javascript/flavours/glitch/util/idna.js b/app/javascript/flavours/glitch/util/idna.js new file mode 100644 index 000000000..efab5bacf --- /dev/null +++ b/app/javascript/flavours/glitch/util/idna.js @@ -0,0 +1,10 @@ +import punycode from 'punycode'; + +const IDNA_PREFIX = 'xn--'; + +export const decode = domain => { + return domain + .split('.') + .map(part => part.indexOf(IDNA_PREFIX) === 0 ? punycode.decode(part.slice(IDNA_PREFIX.length)) : part) + .join('.'); +}; -- cgit From df866a464d43ad718602a18b86c57b716f7bcf27 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Thu, 1 Aug 2019 15:01:09 +0200 Subject: Add options to highlight misleading links in statuses Fixes #1162 --- .../flavours/glitch/components/status.js | 1 + .../flavours/glitch/components/status_content.js | 107 +++++++++++++++++++++ .../glitch/features/local_settings/page/index.js | 16 +++ .../features/status/components/detailed_status.js | 1 + .../flavours/glitch/reducers/local_settings.js | 1 + .../flavours/glitch/styles/components/status.scss | 4 + 6 files changed, 130 insertions(+) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index 7c08ae4e8..06280d8d5 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -699,6 +699,7 @@ class Status extends ImmutablePureComponent { onExpandedToggle={this.handleExpandedToggle} parseClick={parseClick} disabled={!router} + linkRewriting={settings.get('link_rewriting')} /> {!isCollapsed || !(muted || !settings.getIn(['collapsed', 'show_action_bar'])) ? ( { + let linkTextParts = []; + + // Reconstruct visible text, as we do not have much control over how links + // from remote software look, and we can't rely on `innerText` because the + // `invisible` class does not set `display` to `none`. + + const walk = (node) => { + switch (node.nodeType) { + case Node.TEXT_NODE: + linkTextParts.push(node.textContent); + break; + case Node.ELEMENT_NODE: + if (node.classList.contains('invisible')) return; + const children = node.childNodes; + for (let i = 0; i < children.length; i++) { + walk(children[i]); + } + break; + } + }; + + walk(link); + + const linkText = linkTextParts.join(''); + const targetURL = new URL(link.href); + + // The following may not work with international domain names + if (linkText === targetURL.origin || linkText === targetURL.host || 'www.' + linkText === targetURL.host || linkText.startsWith(targetURL.origin + '/') || linkText.startsWith(targetURL.host + '/')) { + return false; + } + + // The link hasn't been recognized, maybe it features an international domain name + const hostname = decodeIDNA(targetURL.hostname); + const host = targetURL.host.replace(targetURL.hostname, hostname); + const origin = targetURL.origin.replace(targetURL.host, host); + if (linkText === origin || linkText === host || linkText.startsWith(origin + '/') || linkText.startsWith(host + '/')) { + return false; + } + + // If the link text looks like an URL or auto-generated link, it is misleading + return !checkUrlLike || linkRegex.test(linkText); +}; export default class StatusContent extends React.PureComponent { @@ -19,6 +82,11 @@ export default class StatusContent extends React.PureComponent { parseClick: PropTypes.func, disabled: PropTypes.bool, onUpdate: PropTypes.func, + linkRewriting: PropTypes.string, + }; + + static defaultProps = { + linkRewriting: 'tag', }; state = { @@ -27,6 +95,7 @@ export default class StatusContent extends React.PureComponent { _updateStatusLinks () { const node = this.contentsNode; + const { linkRewriting } = this.props; if (!node) { return; @@ -52,6 +121,44 @@ export default class StatusContent extends React.PureComponent { link.addEventListener('click', this.onLinkClick.bind(this), false); link.setAttribute('title', link.href); link.classList.add('unhandled-link'); + + if (linkRewriting === 'rewrite' && isLinkMisleading(link)) { + // Rewrite misleading links entirely + + while (link.firstChild) { + link.removeChild(link.firstChild); + } + + const prefix = (link.href.match(/https?:\/\/(www\.)?/) || [''])[0]; + const text = link.href.substr(prefix.length, 30); + const suffix = link.href.substr(prefix.length + 30); + const cutoff = !!suffix; + + const prefixTag = document.createElement('span'); + prefixTag.classList.add('invisible'); + prefixTag.textContent = prefix; + link.appendChild(prefixTag); + + const textTag = document.createElement('span'); + if (cutoff) { + textTag.classList.add('ellipsis'); + } + textTag.textContent = text; + link.appendChild(textTag); + + const suffixTag = document.createElement('span'); + suffixTag.classList.add('invisible'); + suffixTag.textContent = suffix; + link.appendChild(suffixTag); + } else if (linkRewriting === 'tag' && isLinkMisleading(link, false)) { + // Add a tag besides the link to display its origin + + const tag = document.createElement('span'); + tag.classList.add('link-origin-tag'); + tag.textContent = `[${new URL(link.href).host}]`; + link.insertAdjacentText('beforeend', ' '); + link.insertAdjacentElement('beforeend', tag); + } } link.setAttribute('target', '_blank'); diff --git a/app/javascript/flavours/glitch/features/local_settings/page/index.js b/app/javascript/flavours/glitch/features/local_settings/page/index.js index 910cb5346..3f11dc5e9 100644 --- a/app/javascript/flavours/glitch/features/local_settings/page/index.js +++ b/app/javascript/flavours/glitch/features/local_settings/page/index.js @@ -25,6 +25,9 @@ const messages = defineMessages({ filters_upstream: { id: 'settings.filtering_behavior.upstream', defaultMessage: 'Show "filtered" like vanilla Mastodon' }, filters_hide: { id: 'settings.filtering_behavior.hide', defaultMessage: 'Show "filtered" and add a button to display why' }, filters_cw: { id: 'settings.filtering_behavior.cw', defaultMessage: 'Still display the post, and add filtered words to content warning' }, + link_rewriting_none: { id: 'settings.link_rewriting.none', defaultMessage: 'Do not rewrite links' }, + link_rewriting_rewrite: { id: 'settings.link_rewriting.rewrite', defaultMessage: 'Rewrite links that may be misleading' }, + link_rewriting_tag: { id: 'settings.link_rewriting.tag', defaultMessage: 'Tag links with their target host unless it is already explicit' }, }); @injectIntl @@ -66,6 +69,19 @@ export default class LocalSettingsPage extends React.PureComponent { > + + +

diff --git a/app/javascript/flavours/glitch/reducers/local_settings.js b/app/javascript/flavours/glitch/reducers/local_settings.js index 6fd3d901b..b020bdfb9 100644 --- a/app/javascript/flavours/glitch/reducers/local_settings.js +++ b/app/javascript/flavours/glitch/reducers/local_settings.js @@ -22,6 +22,7 @@ const initialState = ImmutableMap({ hicolor_privacy_icons: false, show_content_type_choice: false, filtering_behavior: 'hide', + link_rewriting: 'tag', content_warnings : ImmutableMap({ auto_unfold : false, filter : null, diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss index 803494df6..67a0dfbb2 100644 --- a/app/javascript/flavours/glitch/styles/components/status.scss +++ b/app/javascript/flavours/glitch/styles/components/status.scss @@ -135,6 +135,10 @@ a.unhandled-link { color: lighten($ui-highlight-color, 8%); + + .link-origin-tag { + color: $gold-star; + } } .status__content__spoiler-link { -- cgit From ff0ceb28b3f1b19a6851a482f8203e434e50f167 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Thu, 1 Aug 2019 18:48:16 +0200 Subject: Remove link rewriting option as it is easily bypassable --- .../flavours/glitch/components/status.js | 2 +- .../flavours/glitch/components/status_content.js | 71 +++------------------- .../glitch/features/local_settings/page/index.js | 15 ++--- .../features/status/components/detailed_status.js | 2 +- .../flavours/glitch/reducers/local_settings.js | 2 +- 5 files changed, 17 insertions(+), 75 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index 06280d8d5..170efad04 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -699,7 +699,7 @@ class Status extends ImmutablePureComponent { onExpandedToggle={this.handleExpandedToggle} parseClick={parseClick} disabled={!router} - linkRewriting={settings.get('link_rewriting')} + tagLinks={settings.get('tag_misleading_links')} /> {!isCollapsed || !(muted || !settings.getIn(['collapsed', 'show_action_bar'])) ? ( { return (text === origin || text === host || text.startsWith(origin + '/') || text.startsWith(host + '/') || 'www.' + text === host || ('www.' + text).startsWith(host + '/')); } -// If `checkUrlLike` is true, consider only URL-like link texts to be misleading -const isLinkMisleading = (link, checkUrlLike = true) => { +const isLinkMisleading = (link) => { let linkTextParts = []; // Reconstruct visible text, as we do not have much control over how links @@ -69,12 +51,7 @@ const isLinkMisleading = (link, checkUrlLike = true) => { const host = targetURL.host.replace(targetURL.hostname, hostname); const origin = targetURL.origin.replace(targetURL.host, host); const text = linkText.normalize('NFKC'); - if (textMatchesTarget(text, origin, host) || textMatchesTarget(text.toLowerCase(), origin, host)) { - return false; - } - - // If the link text looks like an URL or auto-generated link, it is misleading - return !checkUrlLike || linkRegex.test(linkText); + return !(textMatchesTarget(text, origin, host) || textMatchesTarget(text.toLowerCase(), origin, host)); }; export default class StatusContent extends React.PureComponent { @@ -89,11 +66,11 @@ export default class StatusContent extends React.PureComponent { parseClick: PropTypes.func, disabled: PropTypes.bool, onUpdate: PropTypes.func, - linkRewriting: PropTypes.string, + tagLinks: PropTypes.bool, }; static defaultProps = { - linkRewriting: 'tag', + tagLinks: true, }; state = { @@ -102,7 +79,7 @@ export default class StatusContent extends React.PureComponent { _updateStatusLinks () { const node = this.contentsNode; - const { linkRewriting } = this.props; + const { tagLinks } = this.props; if (!node) { return; @@ -129,35 +106,7 @@ export default class StatusContent extends React.PureComponent { link.setAttribute('title', link.href); link.classList.add('unhandled-link'); - if (linkRewriting === 'rewrite' && isLinkMisleading(link)) { - // Rewrite misleading links entirely - - while (link.firstChild) { - link.removeChild(link.firstChild); - } - - const prefix = (link.href.match(/https?:\/\/(www\.)?/) || [''])[0]; - const text = link.href.substr(prefix.length, 30); - const suffix = link.href.substr(prefix.length + 30); - const cutoff = !!suffix; - - const prefixTag = document.createElement('span'); - prefixTag.classList.add('invisible'); - prefixTag.textContent = prefix; - link.appendChild(prefixTag); - - const textTag = document.createElement('span'); - if (cutoff) { - textTag.classList.add('ellipsis'); - } - textTag.textContent = text; - link.appendChild(textTag); - - const suffixTag = document.createElement('span'); - suffixTag.classList.add('invisible'); - suffixTag.textContent = suffix; - link.appendChild(suffixTag); - } else if (linkRewriting === 'tag' && isLinkMisleading(link, false)) { + if (tagLinks && isLinkMisleading(link)) { // Add a tag besides the link to display its origin const tag = document.createElement('span'); @@ -287,7 +236,7 @@ export default class StatusContent extends React.PureComponent { mediaIcon, parseClick, disabled, - linkRewriting, + tagLinks, } = this.props; const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden; @@ -362,7 +311,7 @@ export default class StatusContent extends React.PureComponent {
-
+
{media}
); diff --git a/app/javascript/flavours/glitch/features/local_settings/page/index.js b/app/javascript/flavours/glitch/features/local_settings/page/index.js index 3f11dc5e9..bd92a81c2 100644 --- a/app/javascript/flavours/glitch/features/local_settings/page/index.js +++ b/app/javascript/flavours/glitch/features/local_settings/page/index.js @@ -25,9 +25,6 @@ const messages = defineMessages({ filters_upstream: { id: 'settings.filtering_behavior.upstream', defaultMessage: 'Show "filtered" like vanilla Mastodon' }, filters_hide: { id: 'settings.filtering_behavior.hide', defaultMessage: 'Show "filtered" and add a button to display why' }, filters_cw: { id: 'settings.filtering_behavior.cw', defaultMessage: 'Still display the post, and add filtered words to content warning' }, - link_rewriting_none: { id: 'settings.link_rewriting.none', defaultMessage: 'Do not rewrite links' }, - link_rewriting_rewrite: { id: 'settings.link_rewriting.rewrite', defaultMessage: 'Rewrite links that may be misleading' }, - link_rewriting_tag: { id: 'settings.link_rewriting.tag', defaultMessage: 'Tag links with their target host unless it is already explicit' }, }); @injectIntl @@ -71,16 +68,12 @@ export default class LocalSettingsPage extends React.PureComponent { - + +

diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js index 29272c0ad..fa4ed2fd5 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js @@ -241,7 +241,7 @@ export default class DetailedStatus extends ImmutablePureComponent { onExpandedToggle={onToggleHidden} parseClick={this.parseClick} onUpdate={this.handleChildUpdate} - linkRewriting={settings.get('link_rewriting')} + tagLinks={settings.get('tag_misleading_links')} disabled /> diff --git a/app/javascript/flavours/glitch/reducers/local_settings.js b/app/javascript/flavours/glitch/reducers/local_settings.js index b020bdfb9..7477c5584 100644 --- a/app/javascript/flavours/glitch/reducers/local_settings.js +++ b/app/javascript/flavours/glitch/reducers/local_settings.js @@ -22,7 +22,7 @@ const initialState = ImmutableMap({ hicolor_privacy_icons: false, show_content_type_choice: false, filtering_behavior: 'hide', - link_rewriting: 'tag', + tag_misleading_links: true, content_warnings : ImmutableMap({ auto_unfold : false, filter : null, -- cgit From 8b57d704dc455d1ffb1c31bb0ff6a19d58e322ce Mon Sep 17 00:00:00 2001 From: ThibG Date: Sat, 3 Aug 2019 19:10:39 +0200 Subject: [Glitch] Disable list title validation button when list title is empty Port 089c6410208d294e7f1995e000bd796d4625246f to glitch-soc Signed-off-by: Thibaut Girka --- .../flavours/glitch/features/list_editor/components/edit_list_form.js | 2 +- .../flavours/glitch/features/lists/components/new_list_form.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/list_editor/components/edit_list_form.js b/app/javascript/flavours/glitch/features/list_editor/components/edit_list_form.js index 24aaf82ac..bf5a8de35 100644 --- a/app/javascript/flavours/glitch/features/list_editor/components/edit_list_form.js +++ b/app/javascript/flavours/glitch/features/list_editor/components/edit_list_form.js @@ -11,7 +11,7 @@ const messages = defineMessages({ const mapStateToProps = state => ({ value: state.getIn(['listEditor', 'title']), - disabled: !state.getIn(['listEditor', 'isChanged']), + disabled: !state.getIn(['listEditor', 'isChanged']) || !state.getIn(['listEditor', 'title']), }); const mapDispatchToProps = dispatch => ({ diff --git a/app/javascript/flavours/glitch/features/lists/components/new_list_form.js b/app/javascript/flavours/glitch/features/lists/components/new_list_form.js index 61fcbeaf9..eb5b6188a 100644 --- a/app/javascript/flavours/glitch/features/lists/components/new_list_form.js +++ b/app/javascript/flavours/glitch/features/lists/components/new_list_form.js @@ -66,7 +66,7 @@ export default class NewListForm extends React.PureComponent { Date: Sat, 3 Aug 2019 19:10:50 +0200 Subject: [Glitch] Change icon button styles to make hover/focus states more obvious Port c8fd82332749cd58f85dd398d32559a02499c945 to glitch-soc Signed-off-by: Thibaut Girka --- .../flavours/glitch/components/text_icon_button.js | 29 --------------- .../glitch/features/compose/components/options.js | 2 +- .../compose/components/text_icon_button.js | 43 ++++++++++++++++++++++ .../flavours/glitch/styles/components/index.scss | 36 +++++++++++++++--- 4 files changed, 74 insertions(+), 36 deletions(-) delete mode 100644 app/javascript/flavours/glitch/components/text_icon_button.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/text_icon_button.js (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/components/text_icon_button.js b/app/javascript/flavours/glitch/components/text_icon_button.js deleted file mode 100644 index 9c8ffab1f..000000000 --- a/app/javascript/flavours/glitch/components/text_icon_button.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -export default class TextIconButton extends React.PureComponent { - - static propTypes = { - label: PropTypes.string.isRequired, - title: PropTypes.string, - active: PropTypes.bool, - onClick: PropTypes.func.isRequired, - ariaControls: PropTypes.string, - }; - - handleClick = (e) => { - e.preventDefault(); - this.props.onClick(); - } - - render () { - const { label, title, active, ariaControls } = this.props; - - return ( - - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/compose/components/options.js b/app/javascript/flavours/glitch/features/compose/components/options.js index ed52b1997..92348b000 100644 --- a/app/javascript/flavours/glitch/features/compose/components/options.js +++ b/app/javascript/flavours/glitch/features/compose/components/options.js @@ -7,7 +7,7 @@ import spring from 'react-motion/lib/spring'; // Components. import IconButton from 'flavours/glitch/components/icon_button'; -import TextIconButton from 'flavours/glitch/components/text_icon_button'; +import TextIconButton from './text_icon_button'; import Dropdown from './dropdown'; import ImmutablePureComponent from 'react-immutable-pure-component'; diff --git a/app/javascript/flavours/glitch/features/compose/components/text_icon_button.js b/app/javascript/flavours/glitch/features/compose/components/text_icon_button.js new file mode 100644 index 000000000..7f2005060 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/components/text_icon_button.js @@ -0,0 +1,43 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const iconStyle = { + height: null, + lineHeight: '27px', + width: `${18 * 1.28571429}px`, +}; + +export default class TextIconButton extends React.PureComponent { + + static propTypes = { + label: PropTypes.string.isRequired, + title: PropTypes.string, + active: PropTypes.bool, + onClick: PropTypes.func.isRequired, + ariaControls: PropTypes.string, + }; + + handleClick = (e) => { + e.preventDefault(); + this.props.onClick(); + } + + render () { + const { label, title, active, ariaControls } = this.props; + + return ( + + ); + } + +} diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss index 9f96a3154..6942170f2 100644 --- a/app/javascript/flavours/glitch/styles/components/index.scss +++ b/app/javascript/flavours/glitch/styles/components/index.scss @@ -118,20 +118,29 @@ display: inline-block; padding: 0; color: $action-button-color; - border: none; + border: 0; + border-radius: 4px; background: transparent; cursor: pointer; - transition: color 100ms ease-in; + transition: all 100ms ease-in; + transition-property: background-color, color; &:hover, &:active, &:focus { color: lighten($action-button-color, 7%); - transition: color 200ms ease-out; + background-color: rgba($action-button-color, 0.15); + transition: all 200ms ease-out; + transition-property: background-color, color; + } + + &:focus { + background-color: rgba($action-button-color, 0.3); } &.disabled { color: darken($action-button-color, 13%); + background-color: transparent; cursor: default; } @@ -156,10 +165,16 @@ &:active, &:focus { color: darken($lighter-text-color, 7%); + background-color: rgba($lighter-text-color, 0.15); + } + + &:focus { + background-color: rgba($lighter-text-color, 0.3); } &.disabled { color: lighten($lighter-text-color, 7%); + background-color: transparent; } &.active { @@ -186,7 +201,8 @@ .text-icon-button { color: $lighter-text-color; - border: none; + border: 0; + border-radius: 4px; background: transparent; cursor: pointer; font-weight: 600; @@ -194,17 +210,25 @@ padding: 0 3px; line-height: 27px; outline: 0; - transition: color 100ms ease-in; + transition: all 100ms ease-in; + transition-property: background-color, color; &:hover, &:active, &:focus { color: darken($lighter-text-color, 7%); - transition: color 200ms ease-out; + background-color: rgba($lighter-text-color, 0.15); + transition: all 200ms ease-out; + transition-property: background-color, color; + } + + &:focus { + background-color: rgba($lighter-text-color, 0.3); } &.disabled { color: lighten($lighter-text-color, 20%); + background-color: transparent; cursor: default; } -- cgit From 68eb58b8058579f551b8aa94e800283cd0f93f7f Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Mon, 5 Aug 2019 14:25:48 +0200 Subject: Fix color of dropdown icons --- app/javascript/flavours/glitch/features/compose/components/dropdown.js | 1 + 1 file changed, 1 insertion(+) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown.js b/app/javascript/flavours/glitch/features/compose/components/dropdown.js index 8d982208f..764dcea69 100644 --- a/app/javascript/flavours/glitch/features/compose/components/dropdown.js +++ b/app/javascript/flavours/glitch/features/compose/components/dropdown.js @@ -182,6 +182,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent { className='value' disabled={disabled} icon={icon} + inverted onClick={handleToggle} size={18} style={{ -- cgit From 6d2b0fa3f0f32c874863e7fe035270dc31cdcc58 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 6 Aug 2019 13:57:45 +0200 Subject: Refactor composer Dropdown's component a bit to make it closer to upstream --- .../glitch/features/compose/components/dropdown.js | 155 +++++++++------------ 1 file changed, 63 insertions(+), 92 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown.js b/app/javascript/flavours/glitch/features/compose/components/dropdown.js index 764dcea69..6a5f4575e 100644 --- a/app/javascript/flavours/glitch/features/compose/components/dropdown.js +++ b/app/javascript/flavours/glitch/features/compose/components/dropdown.js @@ -12,33 +12,71 @@ import DropdownMenu from './dropdown_menu'; import { isUserTouching } from 'flavours/glitch/util/is_mobile'; import { assignHandlers } from 'flavours/glitch/util/react_helpers'; -// Handlers. -const handlers = { +// The component. +export default class ComposerOptionsDropdown extends React.PureComponent { - // Closes the dropdown. - handleClose () { - this.setState({ open: false }); - }, + static propTypes = { + active: PropTypes.bool, + disabled: PropTypes.bool, + icon: PropTypes.string, + items: PropTypes.arrayOf(PropTypes.shape({ + icon: PropTypes.string, + meta: PropTypes.node, + name: PropTypes.string.isRequired, + on: PropTypes.bool, + text: PropTypes.node, + })).isRequired, + onModalOpen: PropTypes.func, + onModalClose: PropTypes.func, + title: PropTypes.string, + value: PropTypes.string, + onChange: PropTypes.func, + }; + + state = { + needsModalUpdate: false, + open: false, + placement: 'bottom', + }; - // The enter key toggles the dropdown's open state, and the escape - // key closes it. - handleKeyDown ({ key }) { - const { - handleClose, - handleToggle, - } = this.handlers; - switch (key) { + // Toggles opening and closing the dropdown. + handleToggle = ({ target }) => { + const { onModalOpen } = this.props; + const { open } = this.state; + + if (isUserTouching()) { + if (this.state.open) { + this.props.onModalClose(); + } else { + const modal = this.handleMakeModal(); + if (modal && onModalOpen) { + onModalOpen(modal); + } + } + } else { + const { top } = target.getBoundingClientRect(); + this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' }); + this.setState({ open: !this.state.open }); + } + } + + handleKeyDown = (e) => { + switch (e.key) { case 'Enter': - handleToggle(key); + this.handleToggle(key); break; case 'Escape': - handleClose(); + this.handleClose(); break; } - }, + } + + handleClose = () => { + this.setState({ open: false }); + } // Creates an action modal object. - handleMakeModal () { + handleMakeModal = () => { const component = this; const { items, @@ -76,85 +114,37 @@ const handlers = { }) ), }; - }, - - // Toggles opening and closing the dropdown. - handleToggle ({ target }) { - const { handleMakeModal } = this.handlers; - const { onModalOpen } = this.props; - const { open } = this.state; - - // If this is a touch device, we open a modal instead of the - // dropdown. - if (isUserTouching()) { - - // This gets the modal to open. - const modal = handleMakeModal(); - - // If we can, we then open the modal. - if (modal && onModalOpen) { - onModalOpen(modal); - return; - } - } - - const { top } = target.getBoundingClientRect(); - this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' }); - // Otherwise, we just set our state to open. - this.setState({ open: !open }); - }, + } // If our modal is open and our props update, we need to also update // the modal. - handleUpdate () { - const { handleMakeModal } = this.handlers; + handleUpdate = () => { const { onModalOpen } = this.props; const { needsModalUpdate } = this.state; // Gets our modal object. - const modal = handleMakeModal(); + const modal = this.handleMakeModal(); // Reopens the modal with the new object. if (needsModalUpdate && modal && onModalOpen) { onModalOpen(modal); } - }, -}; - -// The component. -export default class ComposerOptionsDropdown extends React.PureComponent { - - // Constructor. - constructor (props) { - super(props); - assignHandlers(this, handlers); - this.state = { - needsModalUpdate: false, - open: false, - placement: 'bottom', - }; } // Updates our modal as necessary. componentDidUpdate (prevProps) { - const { handleUpdate } = this.handlers; const { items } = this.props; const { needsModalUpdate } = this.state; if (needsModalUpdate && items.find( (item, i) => item.on !== prevProps.items[i].on )) { - handleUpdate(); + this.handleUpdate(); this.setState({ needsModalUpdate: false }); } } // Rendering. render () { - const { - handleClose, - handleKeyDown, - handleToggle, - } = this.handlers; const { active, disabled, @@ -175,7 +165,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent { return (
@@ -209,22 +199,3 @@ export default class ComposerOptionsDropdown extends React.PureComponent { } } - -// Props. -ComposerOptionsDropdown.propTypes = { - active: PropTypes.bool, - disabled: PropTypes.bool, - icon: PropTypes.string, - items: PropTypes.arrayOf(PropTypes.shape({ - icon: PropTypes.string, - meta: PropTypes.node, - name: PropTypes.string.isRequired, - on: PropTypes.bool, - text: PropTypes.node, - })).isRequired, - onChange: PropTypes.func, - onModalClose: PropTypes.func, - onModalOpen: PropTypes.func, - title: PropTypes.string, - value: PropTypes.string, -}; -- cgit From d10f6036cfeebec5b2c160db8659d2c19d29fe9c Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 6 Aug 2019 14:18:09 +0200 Subject: Implement keyboard navigation in glitch-soc composer --- .../flavours/glitch/components/icon_button.js | 9 + .../glitch/features/compose/components/dropdown.js | 44 +++- .../features/compose/components/dropdown_menu.js | 254 ++++++++++++--------- 3 files changed, 194 insertions(+), 113 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/components/icon_button.js b/app/javascript/flavours/glitch/components/icon_button.js index c1e2f664c..521353238 100644 --- a/app/javascript/flavours/glitch/components/icon_button.js +++ b/app/javascript/flavours/glitch/components/icon_button.js @@ -13,6 +13,7 @@ export default class IconButton extends React.PureComponent { onClick: PropTypes.func, onMouseDown: PropTypes.func, onKeyDown: PropTypes.func, + onKeyPress: PropTypes.func, size: PropTypes.number, active: PropTypes.bool, pressed: PropTypes.bool, @@ -45,6 +46,12 @@ export default class IconButton extends React.PureComponent { } } + handleKeyPress = (e) => { + if (this.props.onKeyPress && !this.props.disabled) { + this.props.onKeyPress(e); + } + } + handleMouseDown = (e) => { if (!this.props.disabled && this.props.onMouseDown) { this.props.onMouseDown(e); @@ -121,6 +128,7 @@ export default class IconButton extends React.PureComponent { onClick={this.handleClick} onMouseDown={this.handleMouseDown} onKeyDown={this.handleKeyDown} + onKeyPress={this.handleKeyPress} style={style} tabIndex={tabIndex} disabled={disabled} @@ -142,6 +150,7 @@ export default class IconButton extends React.PureComponent { onClick={this.handleClick} onMouseDown={this.handleMouseDown} onKeyDown={this.handleKeyDown} + onKeyPress={this.handleKeyPress} style={style} tabIndex={tabIndex} disabled={disabled} diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown.js b/app/javascript/flavours/glitch/features/compose/components/dropdown.js index 6a5f4575e..60035b705 100644 --- a/app/javascript/flavours/glitch/features/compose/components/dropdown.js +++ b/app/javascript/flavours/glitch/features/compose/components/dropdown.js @@ -36,11 +36,12 @@ export default class ComposerOptionsDropdown extends React.PureComponent { state = { needsModalUpdate: false, open: false, + openedViaKeyboard: undefined, placement: 'bottom', }; // Toggles opening and closing the dropdown. - handleToggle = ({ target }) => { + handleToggle = ({ target, type }) => { const { onModalOpen } = this.props; const { open } = this.state; @@ -55,23 +56,52 @@ export default class ComposerOptionsDropdown extends React.PureComponent { } } else { const { top } = target.getBoundingClientRect(); + if (this.state.open && this.activeElement) { + this.activeElement.focus(); + } this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' }); - this.setState({ open: !this.state.open }); + this.setState({ open: !this.state.open, openedViaKeyboard: type !== 'click' }); } } handleKeyDown = (e) => { switch (e.key) { - case 'Enter': - this.handleToggle(key); - break; case 'Escape': this.handleClose(); break; } } + handleMouseDown = () => { + if (!this.state.open) { + this.activeElement = document.activeElement; + } + } + + handleButtonKeyDown = (e) => { + switch(e.key) { + case ' ': + case 'Enter': + this.handleMouseDown(); + break; + } + } + + handleKeyPress = (e) => { + switch(e.key) { + case ' ': + case 'Enter': + this.handleToggle(e); + e.stopPropagation(); + e.preventDefault(); + break; + } + } + handleClose = () => { + if (this.state.open && this.activeElement) { + this.activeElement.focus(); + } this.setState({ open: false }); } @@ -174,6 +204,9 @@ export default class ComposerOptionsDropdown extends React.PureComponent { icon={icon} inverted onClick={this.handleToggle} + onMouseDown={this.handleMouseDown} + onKeyDown={this.handleButtonKeyDown} + onKeyPress={this.handleKeyPress} size={18} style={{ height: null, @@ -192,6 +225,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent { onChange={onChange} onClose={this.handleClose} value={value} + openedViaKeyboard={this.state.openedViaKeyboard} />
diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js index 19d35a8f4..f812be7a9 100644 --- a/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js +++ b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js @@ -14,91 +14,6 @@ import { withPassive } from 'flavours/glitch/util/dom_helpers'; import Motion from 'flavours/glitch/util/optional_motion'; import { assignHandlers } from 'flavours/glitch/util/react_helpers'; -class ComposerOptionsDropdownContentItem extends ImmutablePureComponent { - - static propTypes = { - active: PropTypes.bool, - name: PropTypes.string, - onChange: PropTypes.func, - onClose: PropTypes.func, - options: PropTypes.shape({ - icon: PropTypes.string, - meta: PropTypes.node, - on: PropTypes.bool, - text: PropTypes.node, - }), - }; - - handleActivate = (e) => { - const { - name, - onChange, - onClose, - options: { on }, - } = this.props; - - // If the escape key was pressed, we close the dropdown. - if (e.key === 'Escape' && onClose) { - onClose(); - - // Otherwise, we both close the dropdown and change the value. - } else if (onChange && (!e.key || e.key === 'Enter')) { - e.preventDefault(); // Prevents change in focus on click - if ((on === null || typeof on === 'undefined') && onClose) { - onClose(); - } - onChange(name); - } - } - - // Rendering. - render () { - const { - active, - options: { - icon, - meta, - on, - text, - }, - } = this.props; - const computedClass = classNames('composer--options--dropdown--content--item', { - active, - lengthy: meta, - 'toggled-off': !on && on !== null && typeof on !== 'undefined', - 'toggled-on': on, - 'with-icon': icon, - }); - - let prefix = null; - - if (on !== null && typeof on !== 'undefined') { - prefix = ; - } else if (icon) { - prefix = - } - - // The result. - return ( -
- {prefix} - -
- {text} - {meta} -
-
- ); - } - -}; - // The spring to use with our motion. const springMotion = spring(1, { damping: 35, @@ -116,10 +31,11 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent on: PropTypes.bool, text: PropTypes.node, })), - onChange: PropTypes.func, - onClose: PropTypes.func, + onChange: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, style: PropTypes.object, value: PropTypes.string, + openedViaKeyboard: PropTypes.bool, }; static defaultProps = { @@ -128,14 +44,13 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent state = { mounted: false, + value: this.props.openedViaKeyboard ? this.props.items[0].name : undefined, }; // When the document is clicked elsewhere, we close the dropdown. - handleDocumentClick = ({ target }) => { - const { node } = this; - const { onClose } = this.props; - if (onClose && node && !node.contains(target)) { - onClose(); + handleDocumentClick = (e) => { + if (this.node && !this.node.contains(e.target)) { + this.props.onClose(); } } @@ -148,6 +63,11 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent componentDidMount () { document.addEventListener('click', this.handleDocumentClick, false); document.addEventListener('touchend', this.handleDocumentClick, withPassive); + if (this.focusedItem) { + this.focusedItem.focus(); + } else { + this.node.firstChild.focus(); + } this.setState({ mounted: true }); } @@ -157,6 +77,138 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent document.removeEventListener('touchend', this.handleDocumentClick, withPassive); } + handleClick = (e) => { + const name = e.currentTarget.getAttribute('data-index'); + + const { + onChange, + onClose, + items, + } = this.props; + + const { on } = this.props.items.find(item => item.name === name); + e.preventDefault(); // Prevents change in focus on click + if ((on === null || typeof on === 'undefined')) { + onClose(); + } + onChange(name); + } + + // Handle changes differently whether the dropdown is a list of options or actions + handleChange = (name) => { + if (this.props.value) { + this.props.onChange(name); + } else { + this.setState({ value: name }); + } + } + + handleKeyDown = e => { + const { items } = this.props; + const name = e.currentTarget.getAttribute('data-index'); + const index = items.findIndex(item => { + return (item.name === name); + }); + let element; + + switch(e.key) { + case 'Escape': + this.props.onClose(); + break; + case 'Enter': + case ' ': + this.handleClick(e); + break; + case 'ArrowDown': + element = this.node.childNodes[index + 1]; + if (element) { + element.focus(); + this.handleChange(element.getAttribute('data-index')); + } + break; + case 'ArrowUp': + element = this.node.childNodes[index - 1]; + if (element) { + element.focus(); + this.handleChange(element.getAttribute('data-index')); + } + break; + case 'Tab': + if (e.shiftKey) { + element = this.node.childNodes[index - 1] || this.node.lastChild; + } else { + element = this.node.childNodes[index + 1] || this.node.firstChild; + } + if (element) { + element.focus(); + this.handleChange(element.getAttribute('data-index')); + e.preventDefault(); + e.stopPropagation(); + } + break; + case 'Home': + element = this.node.firstChild; + if (element) { + element.focus(); + this.handleChange(element.getAttribute('data-index')); + } + break; + case 'End': + element = this.node.lastChild; + if (element) { + element.focus(); + this.handleChange(element.getAttribute('data-index')); + } + break; + } + } + + setFocusRef = c => { + this.focusedItem = c; + } + + renderItem = (item) => { + const { name, icon, meta, on, text } = item; + + const active = (name === (this.props.value || this.state.value)); + + const computedClass = classNames('composer--options--dropdown--content--item', { + active, + lengthy: meta, + 'toggled-off': !on && on !== null && typeof on !== 'undefined', + 'toggled-on': on, + 'with-icon': icon, + }); + + let prefix = null; + + if (on !== null && typeof on !== 'undefined') { + prefix = ; + } else if (icon) { + prefix = + } + + return ( +
+ {prefix} + +
+ {text} + {meta} +
+
+ ); + } + // Rendering. render () { const { mounted } = this.state; @@ -165,7 +217,6 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent onChange, onClose, style, - value, } = this.props; // The result. @@ -189,27 +240,14 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
- {items ? items.map( - ({ - name, - ...rest - }) => ( - - ) - ) : null} + {!!items && items.map(item => this.renderItem(item))}
)} -- cgit From 42588ed460c77c58b1c1ed616db2e81bdf1d6060 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 8 Aug 2019 08:56:55 +0200 Subject: [Glitch] Fix "cancel follow request" button having unreadable text in web UI Port e823b492147a7d7fca7a5a91dacc201fa2064e2c to glitch-soc Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/components/button.js | 2 +- app/javascript/flavours/glitch/features/account/components/header.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/components/button.js b/app/javascript/flavours/glitch/components/button.js index 16868010c..cd6528f58 100644 --- a/app/javascript/flavours/glitch/components/button.js +++ b/app/javascript/flavours/glitch/components/button.js @@ -12,9 +12,9 @@ export default class Button extends React.PureComponent { secondary: PropTypes.bool, size: PropTypes.number, className: PropTypes.string, + title: PropTypes.string, style: PropTypes.object, children: PropTypes.node, - title: PropTypes.string, }; static defaultProps = { diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js index e9437c0a9..b0072533c 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.js +++ b/app/javascript/flavours/glitch/features/account/components/header.js @@ -15,6 +15,7 @@ import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_cont const messages = defineMessages({ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, follow: { id: 'account.follow', defaultMessage: 'Follow' }, + cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Cancel follow request' }, requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' }, unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, @@ -141,7 +142,7 @@ class Header extends ImmutablePureComponent { if (!account.get('relationship')) { // Wait until the relationship is loaded actionBtn = ''; } else if (account.getIn(['relationship', 'requested'])) { - actionBtn = - - - - - + + + + + +
); } @@ -199,7 +200,7 @@ class ModifierPicker extends React.PureComponent { return (
- +
); @@ -344,6 +345,7 @@ class EmojiPickerMenu extends React.PureComponent { backgroundImageFn={backgroundImageFn} autoFocus emojiTooltip + native={useSystemEmojiFont} /> Date: Wed, 14 Aug 2019 04:07:32 +0200 Subject: [Glitch] Add media editing modal Port 23f7afa562c49b24e979505680463bc712b11d94 to glitch-soc Signed-off-by: Thibaut Girka --- .../glitch/features/compose/components/upload.js | 84 +------------- .../compose/containers/upload_container.js | 6 +- .../features/ui/components/focal_point_modal.js | 129 +++++++++++++++++---- .../flavours/glitch/features/video/index.js | 11 +- .../flavours/glitch/styles/components/media.scss | 5 + .../flavours/glitch/styles/components/modal.scss | 46 ++++++-- 6 files changed, 159 insertions(+), 122 deletions(-) (limited to 'app/javascript/flavours/glitch/features') diff --git a/app/javascript/flavours/glitch/features/compose/components/upload.js b/app/javascript/flavours/glitch/features/compose/components/upload.js index 84edf664e..f89145a52 100644 --- a/app/javascript/flavours/glitch/features/compose/components/upload.js +++ b/app/javascript/flavours/glitch/features/compose/components/upload.js @@ -4,18 +4,12 @@ import PropTypes from 'prop-types'; import Motion from 'flavours/glitch/util/optional_motion'; import spring from 'react-motion/lib/spring'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import Icon from 'flavours/glitch/components/icon'; import { isUserTouching } from 'flavours/glitch/util/is_mobile'; -const messages = defineMessages({ - description: { id: 'upload_form.description', defaultMessage: 'Describe for the visually impaired' }, -}); - -// The component. -export default @injectIntl -class Upload extends ImmutablePureComponent { +export default class Upload extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object, @@ -23,30 +17,10 @@ class Upload extends ImmutablePureComponent { static propTypes = { media: ImmutablePropTypes.map.isRequired, - intl: PropTypes.object.isRequired, onUndo: PropTypes.func.isRequired, - onDescriptionChange: PropTypes.func.isRequired, onOpenFocalPoint: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - }; - - state = { - hovered: false, - focused: false, - dirtyDescription: null, }; - handleKeyDown = (e) => { - if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { - this.handleSubmit(); - } - } - - handleSubmit = () => { - this.handleInputBlur(); - this.props.onSubmit(this.context.router.history); - } - handleUndoClick = e => { e.stopPropagation(); this.props.onUndo(this.props.media.get('id')); @@ -57,69 +31,21 @@ class Upload extends ImmutablePureComponent { this.props.onOpenFocalPoint(this.props.media.get('id')); } - handleInputChange = e => { - this.setState({ dirtyDescription: e.target.value }); - } - - handleMouseEnter = () => { - this.setState({ hovered: true }); - } - - handleMouseLeave = () => { - this.setState({ hovered: false }); - } - - handleInputFocus = () => { - this.setState({ focused: true }); - } - - handleClick = () => { - this.setState({ focused: true }); - } - - handleInputBlur = () => { - const { dirtyDescription } = this.state; - - this.setState({ focused: false, dirtyDescription: null }); - - if (dirtyDescription !== null) { - this.props.onDescriptionChange(this.props.media.get('id'), dirtyDescription); - } - } - render () { const { intl, media } = this.props; - const active = this.state.hovered || this.state.focused || isUserTouching(); - const description = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || ''; - const computedClass = classNames('composer--upload_form--item', { active }); const focusX = media.getIn(['meta', 'focus', 'x']); const focusY = media.getIn(['meta', 'focus', 'y']); const x = ((focusX / 2) + .5) * 100; const y = ((focusY / -2) + .5) * 100; return ( -
+
{({ scale }) => (
-
+
- {media.get('type') === 'image' && } -
- -
-