diff options
Diffstat (limited to 'app/javascript/flavours/glitch/features')
15 files changed, 108 insertions, 57 deletions
diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js index 47c074ec3..9a5f2fd62 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.js +++ b/app/javascript/flavours/glitch/features/account/components/header.js @@ -141,6 +141,17 @@ class Header extends ImmutablePureComponent { } } + handleShare = () => { + const { account } = this.props; + + navigator.share({ + text: `${titleFromAccount(account)}\n${account.get('note_plain')}`, + url: account.get('url'), + }).catch((e) => { + if (e.name !== 'AbortError') console.error(e); + }); + } + render () { const { account, hidden, intl, domain } = this.props; const { signedIn } = this.context.identity; diff --git a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js index a16ee4806..f7a7fd467 100644 --- a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js +++ b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js @@ -2,7 +2,6 @@ import Blurhash from 'flavours/glitch/components/blurhash'; import classNames from 'classnames'; import Icon from 'flavours/glitch/components/icon'; import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/initial_state'; -import { isIOS } from 'flavours/glitch/is_mobile'; import PropTypes from 'prop-types'; import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; @@ -109,7 +108,8 @@ export default class MediaItem extends ImmutablePureComponent { src={attachment.get('url')} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} - autoPlay={!isIOS() && autoPlayGif} + autoPlay={autoPlayGif} + playsInline loop muted /> diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.js b/app/javascript/flavours/glitch/features/account_timeline/index.js index b735af0ac..b79082f00 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/index.js +++ b/app/javascript/flavours/glitch/features/account_timeline/index.js @@ -25,7 +25,13 @@ const emptyList = ImmutableList(); const mapStateToProps = (state, { params: { acct, id, tagged }, withReplies = false }) => { const accountId = id || state.getIn(['accounts_map', normalizeForLookup(acct)]); - if (!accountId) { + if (accountId === null) { + return { + isLoading: false, + isAccount: false, + statusIds: emptyList, + }; + } else if (!accountId) { return { isLoading: true, statusIds: emptyList, 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 516648f4b..abdd247a0 100644 --- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js +++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js @@ -356,10 +356,8 @@ class ComposeForm extends ImmutablePureComponent { <OptionsContainer advancedOptions={advancedOptions} disabled={isSubmitting} - onChangeVisibility={onChangeVisibility} onToggleSpoiler={spoilersAlwaysOn ? null : onChangeSpoilerness} onUpload={onPaste} - privacy={privacy} isEditing={isEditing} sensitive={sensitive || (spoilersAlwaysOn && spoilerText && spoilerText.length > 0)} spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler} diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown.js b/app/javascript/flavours/glitch/features/compose/components/dropdown.js index 6b6d3de94..3de198c45 100644 --- a/app/javascript/flavours/glitch/features/compose/components/dropdown.js +++ b/app/javascript/flavours/glitch/features/compose/components/dropdown.js @@ -9,13 +9,13 @@ import IconButton from 'flavours/glitch/components/icon_button'; import DropdownMenu from './dropdown_menu'; // Utils. -import { isUserTouching } from 'flavours/glitch/is_mobile'; import { assignHandlers } from 'flavours/glitch/utils/react_helpers'; // The component. export default class ComposerOptionsDropdown extends React.PureComponent { static propTypes = { + isUserTouching: PropTypes.func, disabled: PropTypes.bool, icon: PropTypes.string, items: PropTypes.arrayOf(PropTypes.shape({ @@ -49,7 +49,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent { const { onModalOpen } = this.props; const { open } = this.state; - if (isUserTouching()) { + if (this.props.isUserTouching && this.props.isUserTouching()) { if (this.state.open) { this.props.onModalClose(); } else { diff --git a/app/javascript/flavours/glitch/features/compose/components/options.js b/app/javascript/flavours/glitch/features/compose/components/options.js index c6278f4cb..b5276c371 100644 --- a/app/javascript/flavours/glitch/features/compose/components/options.js +++ b/app/javascript/flavours/glitch/features/compose/components/options.js @@ -10,8 +10,8 @@ import { connect } from 'react-redux'; // Components. import IconButton from 'flavours/glitch/components/icon_button'; import TextIconButton from './text_icon_button'; -import Dropdown from './dropdown'; -import PrivacyDropdown from './privacy_dropdown'; +import DropdownContainer from '../containers/dropdown_container'; +import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; import LanguageDropdown from '../containers/language_dropdown_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -126,15 +126,11 @@ class ComposerOptions extends ImmutablePureComponent { hasPoll: PropTypes.bool, intl: PropTypes.object.isRequired, onChangeAdvancedOption: PropTypes.func, - onChangeVisibility: PropTypes.func, onChangeContentType: PropTypes.func, onTogglePoll: PropTypes.func, onDoodleOpen: PropTypes.func, - onModalClose: PropTypes.func, - onModalOpen: PropTypes.func, onToggleSpoiler: PropTypes.func, onUpload: PropTypes.func, - privacy: PropTypes.string, contentType: PropTypes.string, resetFileKey: PropTypes.number, spoiler: PropTypes.bool, @@ -195,12 +191,8 @@ class ComposerOptions extends ImmutablePureComponent { hasPoll, onChangeAdvancedOption, onChangeContentType, - onChangeVisibility, onTogglePoll, - onModalClose, - onModalOpen, onToggleSpoiler, - privacy, resetFileKey, spoiler, showContentTypeChoice, @@ -239,7 +231,7 @@ class ComposerOptions extends ImmutablePureComponent { multiple style={{ display: 'none' }} /> - <Dropdown + <DropdownContainer disabled={disabled || !allowMedia} icon='paperclip' items={[ @@ -255,8 +247,6 @@ class ComposerOptions extends ImmutablePureComponent { }, ]} onChange={this.handleClickAttach} - onModalClose={onModalClose} - onModalOpen={onModalOpen} title={formatMessage(messages.attach)} /> {!!pollLimits && ( @@ -275,15 +265,9 @@ class ComposerOptions extends ImmutablePureComponent { /> )} <hr /> - <PrivacyDropdown - disabled={disabled || isEditing} - onChange={onChangeVisibility} - onModalClose={onModalClose} - onModalOpen={onModalOpen} - value={privacy} - /> + <PrivacyDropdownContainer disabled={disabled || isEditing} /> {showContentTypeChoice && ( - <Dropdown + <DropdownContainer disabled={disabled} icon={(contentTypeItems[contentType.split('/')[1]] || {}).icon} items={[ @@ -292,8 +276,6 @@ class ComposerOptions extends ImmutablePureComponent { contentTypeItems.markdown, ]} onChange={onChangeContentType} - onModalClose={onModalClose} - onModalOpen={onModalOpen} title={formatMessage(messages.content_type)} value={contentType} /> @@ -308,7 +290,7 @@ class ComposerOptions extends ImmutablePureComponent { /> )} <LanguageDropdown /> - <Dropdown + <DropdownContainer disabled={disabled || isEditing} icon='ellipsis-h' items={advancedOptions ? [ @@ -325,8 +307,6 @@ class ComposerOptions extends ImmutablePureComponent { ] : null} onChange={onChangeAdvancedOption} renderItemContents={this.renderToggleItemContents} - onModalClose={onModalClose} - onModalOpen={onModalOpen} title={formatMessage(messages.advanced_options_icon_title)} closeOnChange={false} /> diff --git a/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js b/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js index 14364b0a0..02cf72289 100644 --- a/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js +++ b/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js @@ -32,7 +32,7 @@ class PrivacyDropdown extends React.PureComponent { }; render () { - const { value, onChange, onModalOpen, onModalClose, disabled, noDirect, container, intl: { formatMessage } } = this.props; + const { value, onChange, onModalOpen, onModalClose, disabled, noDirect, container, isUserTouching, intl: { formatMessage } } = this.props; // We predefine our privacy items so that we can easily pick the // dropdown icon later. @@ -75,6 +75,7 @@ class PrivacyDropdown extends React.PureComponent { icon={(privacyItems[value] || {}).icon} items={items} onChange={onChange} + isUserTouching={isUserTouching} onModalClose={onModalClose} onModalOpen={onModalOpen} title={formatMessage(messages.change_privacy)} diff --git a/app/javascript/flavours/glitch/features/compose/containers/dropdown_container.js b/app/javascript/flavours/glitch/features/compose/containers/dropdown_container.js new file mode 100644 index 000000000..3ac16505f --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/containers/dropdown_container.js @@ -0,0 +1,12 @@ +import { connect } from 'react-redux'; +import { isUserTouching } from 'flavours/glitch/is_mobile'; +import { openModal, closeModal } from 'flavours/glitch/actions/modal'; +import Dropdown from '../components/dropdown'; + +const mapDispatchToProps = dispatch => ({ + isUserTouching, + onModalOpen: props => dispatch(openModal('ACTIONS', props)), + onModalClose: () => dispatch(closeModal()), +}); + +export default connect(null, mapDispatchToProps)(Dropdown); diff --git a/app/javascript/flavours/glitch/features/compose/containers/options_container.js b/app/javascript/flavours/glitch/features/compose/containers/options_container.js index c792aa582..6c3db8173 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/options_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/options_container.js @@ -6,7 +6,7 @@ import { addPoll, removePoll, } from 'flavours/glitch/actions/compose'; -import { closeModal, openModal } from 'flavours/glitch/actions/modal'; +import { openModal } from 'flavours/glitch/actions/modal'; function mapStateToProps (state) { const spoilersAlwaysOn = state.getIn(['local_settings', 'always_show_spoilers_field']); @@ -48,14 +48,6 @@ const mapDispatchToProps = (dispatch) => ({ onDoodleOpen() { dispatch(openModal('DOODLE', { noEsc: true })); }, - - onModalClose() { - dispatch(closeModal()); - }, - - onModalOpen(props) { - dispatch(openModal('ACTIONS', props)); - }, }); export default connect(mapStateToProps, mapDispatchToProps)(Options); diff --git a/app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js b/app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js new file mode 100644 index 000000000..5591d89c4 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js @@ -0,0 +1,23 @@ +import { connect } from 'react-redux'; +import PrivacyDropdown from '../components/privacy_dropdown'; +import { changeComposeVisibility } from 'flavours/glitch/actions/compose'; +import { openModal, closeModal } from 'flavours/glitch/actions/modal'; +import { isUserTouching } from 'flavours/glitch/is_mobile'; + +const mapStateToProps = state => ({ + value: state.getIn(['compose', 'privacy']), +}); + +const mapDispatchToProps = dispatch => ({ + + onChange (value) { + dispatch(changeComposeVisibility(value)); + }, + + isUserTouching, + onModalOpen: props => dispatch(openModal('ACTIONS', props)), + onModalClose: () => dispatch(closeModal()), + +}); + +export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown); diff --git a/app/javascript/flavours/glitch/features/emoji/emoji.js b/app/javascript/flavours/glitch/features/emoji/emoji.js index 50a399114..4f33200b6 100644 --- a/app/javascript/flavours/glitch/features/emoji/emoji.js +++ b/app/javascript/flavours/glitch/features/emoji/emoji.js @@ -19,8 +19,6 @@ const emojiFilename = (filename) => { return borderedEmoji.includes(filename) ? (filename + '_border') : filename; }; -const domParser = new DOMParser(); - const emojifyTextNode = (node, customEmojis) => { let str = node.textContent; @@ -39,7 +37,7 @@ const emojifyTextNode = (node, customEmojis) => { } } - let rend, replacement = ''; + let rend, replacement = null; if (i === str.length) { break; } else if (str[i] === ':') { @@ -51,7 +49,14 @@ const emojifyTextNode = (node, 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 = `<img draggable="false" class="emojione custom-emoji" alt="${shortname}" title="${shortname}" src="${filename}" data-original="${customEmojis[shortname].url}" data-static="${customEmojis[shortname].static_url}" />`; + replacement = document.createElement('img'); + replacement.setAttribute('draggable', false); + replacement.setAttribute('class', 'emojione custom-emoji'); + replacement.setAttribute('alt', shortname); + replacement.setAttribute('title', shortname); + replacement.setAttribute('src', filename); + replacement.setAttribute('data-original', customEmojis[shortname].url); + replacement.setAttribute('data-static', customEmojis[shortname].static_url); return true; } return false; @@ -59,7 +64,12 @@ const emojifyTextNode = (node, customEmojis) => { } else if (!useSystemEmojiFont) { // matched to unicode emoji const { filename, shortCode } = unicodeMapping[match]; const title = shortCode ? `:${shortCode}:` : ''; - replacement = `<img draggable="false" class="emojione" alt="${match}" title="${title}" src="${assetHost}/emoji/${emojiFilename(filename)}.svg" />`; + replacement = document.createElement('img'); + replacement.setAttribute('draggable', false); + replacement.setAttribute('class', 'emojione'); + replacement.setAttribute('alt', match); + replacement.setAttribute('title', title); + replacement.setAttribute('src', `${assetHost}/emoji/${emojiFilename(filename)}.svg`); rend = i + match.length; // If the matched character was followed by VS15 (for selecting text presentation), skip it. if (str.codePointAt(rend) === 65038) { @@ -69,9 +79,8 @@ const emojifyTextNode = (node, customEmojis) => { fragment.append(document.createTextNode(str.slice(0, i))); if (replacement) { - fragment.append(domParser.parseFromString(replacement, 'text/html').documentElement.getElementsByTagName('img')[0]); + fragment.append(replacement); } - node.textContent = str.slice(0, i); str = str.slice(rend); } diff --git a/app/javascript/flavours/glitch/features/explore/index.js b/app/javascript/flavours/glitch/features/explore/index.js index 24fa26eec..ba435d7e3 100644 --- a/app/javascript/flavours/glitch/features/explore/index.js +++ b/app/javascript/flavours/glitch/features/explore/index.js @@ -24,6 +24,16 @@ const mapStateToProps = state => ({ isSearching: state.getIn(['search', 'submitted']) || !showTrends, }); +// Fix strange bug on Safari where <span> (rendered by FormattedMessage) disappears +// after clicking around Explore top bar (issue #20885). +// Removing width=100% from <a> also fixes it, as well as replacing <span> with <div> +// We're choosing to wrap span with div to keep the changes local only to this tool bar. +const WrapFormattedMessage = ({ children, ...props }) => <div><FormattedMessage {...props}>{children}</FormattedMessage></div>; +WrapFormattedMessage.propTypes = { + children: PropTypes.any, +}; + + export default @connect(mapStateToProps) @injectIntl class Explore extends React.PureComponent { @@ -47,7 +57,7 @@ class Explore extends React.PureComponent { this.column = c; } - render () { + render() { const { intl, multiColumn, isSearching } = this.props; const { signedIn } = this.context.identity; @@ -70,10 +80,10 @@ class Explore extends React.PureComponent { ) : ( <React.Fragment> <div className='account__section-headline'> - <NavLink exact to='/explore'><FormattedMessage id='explore.trending_statuses' defaultMessage='Posts' /></NavLink> - <NavLink exact to='/explore/tags'><FormattedMessage id='explore.trending_tags' defaultMessage='Hashtags' /></NavLink> - <NavLink exact to='/explore/links'><FormattedMessage id='explore.trending_links' defaultMessage='News' /></NavLink> - {signedIn && <NavLink exact to='/explore/suggestions'><FormattedMessage id='explore.suggested_follows' defaultMessage='For you' /></NavLink>} + <NavLink exact to='/explore'><WrapFormattedMessage id='explore.trending_statuses' defaultMessage='Posts' /></NavLink> + <NavLink exact to='/explore/tags'><WrapFormattedMessage id='explore.trending_tags' defaultMessage='Hashtags' /></NavLink> + <NavLink exact to='/explore/links'><WrapFormattedMessage id='explore.trending_links' defaultMessage='News' /></NavLink> + {signedIn && <NavLink exact to='/explore/suggestions'><WrapFormattedMessage id='explore.suggested_follows' defaultMessage='For you' /></NavLink>} </div> <Switch> diff --git a/app/javascript/flavours/glitch/features/getting_started/components/trends.js b/app/javascript/flavours/glitch/features/getting_started/components/trends.js index 5158f6689..d45934d6e 100644 --- a/app/javascript/flavours/glitch/features/getting_started/components/trends.js +++ b/app/javascript/flavours/glitch/features/getting_started/components/trends.js @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag'; import { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router-dom'; export default class Trends extends ImmutablePureComponent { @@ -36,7 +37,11 @@ export default class Trends extends ImmutablePureComponent { return ( <div className='getting-started__trends'> - <h4><FormattedMessage id='trends.trending_now' defaultMessage='Trending now' /></h4> + <h4> + <Link to={'/explore/tags'}> + <FormattedMessage id='trends.trending_now' defaultMessage='Trending now' /> + </Link> + </h4> {trends.take(1).map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)} </div> diff --git a/app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js b/app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js index 4dec7d154..739c5ebae 100644 --- a/app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js +++ b/app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js @@ -50,6 +50,8 @@ export default class LocalSettingsPage extends React.PureComponent { <a href={href} className={finalClassName} + title={title} + aria-label={title} > {iconElem} <span>{title}</span> </a> @@ -60,6 +62,8 @@ export default class LocalSettingsPage extends React.PureComponent { role='button' tabIndex='0' className={finalClassName} + title={title} + aria-label={title} > {iconElem} <span>{title}</span> </a> diff --git a/app/javascript/flavours/glitch/features/ui/components/header.js b/app/javascript/flavours/glitch/features/ui/components/header.js index 6c2fb40ba..891f7fc07 100644 --- a/app/javascript/flavours/glitch/features/ui/components/header.js +++ b/app/javascript/flavours/glitch/features/ui/components/header.js @@ -36,7 +36,7 @@ class Header extends React.PureComponent { if (signedIn) { content = ( <> - {location.pathname !== '/publish' && <Link to='/publish' className='button'><FormattedMessage id='compose_form.publish' defaultMessage='Publish' /></Link>} + {location.pathname !== '/publish' && <Link to='/publish' className='button'><FormattedMessage id='compose_form.publish_form' defaultMessage='Publish' /></Link>} <Account /> </> ); |