diff options
author | Reverite <github@reverite.sh> | 2019-09-07 13:03:02 -0700 |
---|---|---|
committer | Reverite <github@reverite.sh> | 2019-09-07 13:03:02 -0700 |
commit | 2486f5f825ae861cdf97529f64dc4e658ddab279 (patch) | |
tree | eebbd191c621baa43a93b26d97d4766cf7ebf9cd /app/javascript/flavours/glitch/components | |
parent | 54bf56d9e019e29c1509a2c5a0f23e4f1df65c90 (diff) | |
parent | 286bf110c3e69042049968440f1eb99372a7e0e6 (diff) |
Merge branch 'glitch' into production
Diffstat (limited to 'app/javascript/flavours/glitch/components')
9 files changed, 136 insertions, 32 deletions
diff --git a/app/javascript/flavours/glitch/components/autosuggest_hashtag.js b/app/javascript/flavours/glitch/components/autosuggest_hashtag.js new file mode 100644 index 000000000..648987dfd --- /dev/null +++ b/app/javascript/flavours/glitch/components/autosuggest_hashtag.js @@ -0,0 +1,28 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { shortNumberFormat } from 'flavours/glitch/util/numbers'; +import { FormattedMessage } from 'react-intl'; + +export default class AutosuggestHashtag extends React.PureComponent { + + static propTypes = { + tag: PropTypes.shape({ + name: PropTypes.string.isRequired, + url: PropTypes.string, + history: PropTypes.array, + }).isRequired, + }; + + render () { + const { tag } = this.props; + const weeklyUses = tag.history && shortNumberFormat(tag.history.reduce((total, day) => total + (day.uses * 1), 0)); + + return ( + <div className='autosuggest-hashtag'> + <div className='autosuggest-hashtag__name'>#<strong>{tag.name}</strong></div> + {tag.history !== undefined && <div className='autosuggest-hashtag__uses'><FormattedMessage id='autosuggest_hashtag.per_week' defaultMessage='{count} per week' values={{ count: weeklyUses }} /></div>} + </div> + ); + } + +} diff --git a/app/javascript/flavours/glitch/components/autosuggest_input.js b/app/javascript/flavours/glitch/components/autosuggest_input.js index 5fc952d8e..1ef7ee216 100644 --- a/app/javascript/flavours/glitch/components/autosuggest_input.js +++ b/app/javascript/flavours/glitch/components/autosuggest_input.js @@ -1,6 +1,7 @@ import React from 'react'; import AutosuggestAccountContainer from 'flavours/glitch/features/compose/containers/autosuggest_account_container'; import AutosuggestEmoji from './autosuggest_emoji'; +import AutosuggestHashtag from './autosuggest_hashtag'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import { isRtl } from 'flavours/glitch/util/rtl'; @@ -167,15 +168,15 @@ export default class AutosuggestInput extends ImmutablePureComponent { const { selectedSuggestion } = this.state; let inner, key; - if (typeof suggestion === 'object') { + if (suggestion.type === 'emoji') { inner = <AutosuggestEmoji emoji={suggestion} />; key = suggestion.id; - } else if (suggestion[0] === '#') { - inner = suggestion; - key = suggestion; - } else { - inner = <AutosuggestAccountContainer id={suggestion} />; - key = suggestion; + } else if (suggestion.type ==='hashtag') { + inner = <AutosuggestHashtag tag={suggestion} />; + key = suggestion.name; + } else if (suggestion.type === 'account') { + inner = <AutosuggestAccountContainer id={suggestion.id} />; + key = suggestion.id; } return ( diff --git a/app/javascript/flavours/glitch/components/autosuggest_textarea.js b/app/javascript/flavours/glitch/components/autosuggest_textarea.js index bbe0ffcbe..ec2fbbe4b 100644 --- a/app/javascript/flavours/glitch/components/autosuggest_textarea.js +++ b/app/javascript/flavours/glitch/components/autosuggest_textarea.js @@ -1,6 +1,7 @@ import React from 'react'; import AutosuggestAccountContainer from 'flavours/glitch/features/compose/containers/autosuggest_account_container'; import AutosuggestEmoji from './autosuggest_emoji'; +import AutosuggestHashtag from './autosuggest_hashtag'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import { isRtl } from 'flavours/glitch/util/rtl'; @@ -173,15 +174,15 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { const { selectedSuggestion } = this.state; let inner, key; - if (typeof suggestion === 'object') { + if (suggestion.type === 'emoji') { inner = <AutosuggestEmoji emoji={suggestion} />; key = suggestion.id; - } else if (suggestion[0] === '#') { - inner = suggestion; - key = suggestion; - } else { - inner = <AutosuggestAccountContainer id={suggestion} />; - key = suggestion; + } else if (suggestion.type === 'hashtag') { + inner = <AutosuggestHashtag tag={suggestion} />; + key = suggestion.name; + } else if (suggestion.type === 'account') { + inner = <AutosuggestAccountContainer id={suggestion.id} />; + key = suggestion.id; } return ( diff --git a/app/javascript/flavours/glitch/components/hashtag.js b/app/javascript/flavours/glitch/components/hashtag.js index d75edd994..d42bee0e9 100644 --- a/app/javascript/flavours/glitch/components/hashtag.js +++ b/app/javascript/flavours/glitch/components/hashtag.js @@ -12,11 +12,11 @@ const Hashtag = ({ hashtag }) => ( #<span>{hashtag.get('name')}</span> </Permalink> - <FormattedMessage id='trends.count_by_accounts' defaultMessage='{count} {rawCount, plural, one {person} other {people}} talking' values={{ rawCount: hashtag.getIn(['history', 0, 'accounts']), count: <strong>{shortNumberFormat(hashtag.getIn(['history', 0, 'accounts']))}</strong> }} /> + <FormattedMessage id='trends.count_by_accounts' defaultMessage='{count} {rawCount, plural, one {person} other {people}} talking' values={{ rawCount: hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1, count: <strong>{shortNumberFormat(hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1)}</strong> }} /> </div> <div className='trends__item__current'> - {shortNumberFormat(hashtag.getIn(['history', 0, 'uses']))} + {shortNumberFormat(hashtag.getIn(['history', 0, 'uses']) * 1 + hashtag.getIn(['history', 1, 'uses']) * 1)} </div> <div className='trends__item__sparkline'> diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js index 04d3ce751..28b369d09 100644 --- a/app/javascript/flavours/glitch/components/media_gallery.js +++ b/app/javascript/flavours/glitch/components/media_gallery.js @@ -179,7 +179,7 @@ class Item extends React.PureComponent { if (attachment.get('type') === 'unknown') { return ( <div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}> - <a className='media-gallery__item-thumbnail' href={attachment.get('remote_url')} target='_blank' style={{ cursor: 'pointer' }} title={attachment.get('description')}> + <a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} target='_blank' style={{ cursor: 'pointer' }} title={attachment.get('description')}> <canvas width={32} height={32} ref={this.setCanvasRef} className='media-gallery__preview' /> </a> </div> @@ -329,7 +329,8 @@ export default class MediaGallery extends React.PureComponent { render () { const { media, intl, sensitive, letterbox, fullwidth, defaultWidth } = this.props; const { visible } = this.state; - const size = media.take(4).size; + const size = media.take(4).size; + const uncached = media.every(attachment => attachment.get('type') === 'unknown'); const width = this.state.width || defaultWidth; @@ -350,10 +351,16 @@ export default class MediaGallery extends React.PureComponent { if (this.isStandaloneEligible()) { children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} displayWidth={width} visible={visible} />; } else { - children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} letterbox={letterbox} displayWidth={width} visible={visible} />); + children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} letterbox={letterbox} displayWidth={width} visible={visible || uncached} />); } - if (visible) { + if (uncached) { + spoilerButton = ( + <button type='button' disabled className='spoiler-button__overlay'> + <span className='spoiler-button__overlay__label'><FormattedMessage id='status.uncached_media_warning' defaultMessage='Not available' /></span> + </button> + ); + } else if (visible) { spoilerButton = <IconButton title={intl.formatMessage(messages.toggle_visible)} icon='eye-slash' overlay onClick={this.handleOpen} />; } else { spoilerButton = ( @@ -365,7 +372,7 @@ export default class MediaGallery extends React.PureComponent { return ( <div className={computedClass} style={style} ref={this.handleRef}> - <div className={classNames('spoiler-button', { 'spoiler-button--minified': visible })}> + <div className={classNames('spoiler-button', { 'spoiler-button--minified': visible && !uncached, 'spoiler-button--click-thru': uncached })}> {spoilerButton} {visible && sensitive && ( <span className='sensitive-marker'> diff --git a/app/javascript/flavours/glitch/components/poll.js b/app/javascript/flavours/glitch/components/poll.js index 690f9ae5a..36c4b236c 100644 --- a/app/javascript/flavours/glitch/components/poll.js +++ b/app/javascript/flavours/glitch/components/poll.js @@ -4,11 +4,11 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; -import { vote, fetchPoll } from 'mastodon/actions/polls'; -import Motion from 'mastodon/features/ui/util/optional_motion'; +import { vote, fetchPoll } from 'flavours/glitch/actions/polls'; +import Motion from 'flavours/glitch/util/optional_motion'; import spring from 'react-motion/lib/spring'; import escapeTextContentForBrowser from 'escape-html'; -import emojify from 'mastodon/features/emoji/emoji'; +import emojify from 'flavours/glitch/util/emoji'; import RelativeTimestamp from './relative_timestamp'; const messages = defineMessages({ diff --git a/app/javascript/flavours/glitch/components/radio_button.js b/app/javascript/flavours/glitch/components/radio_button.js new file mode 100644 index 000000000..0496fa286 --- /dev/null +++ b/app/javascript/flavours/glitch/components/radio_button.js @@ -0,0 +1,35 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +export default class RadioButton extends React.PureComponent { + + static propTypes = { + value: PropTypes.string.isRequired, + checked: PropTypes.bool, + name: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, + label: PropTypes.node.isRequired, + }; + + render () { + const { name, value, checked, onChange, label } = this.props; + + return ( + <label className='radio-button'> + <input + name={name} + type='radio' + value={value} + checked={checked} + onChange={onChange} + /> + + <span className={classNames('radio-button__input', { checked })} /> + + <span>{label}</span> + </label> + ); + } + +} diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index 88994c2ac..e7bf1f4d0 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -10,7 +10,7 @@ import AttachmentList from './attachment_list'; import Card from '../features/status/components/card'; import { injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { MediaGallery, Video } from 'flavours/glitch/util/async-components'; +import { MediaGallery, Video, Audio } from 'flavours/glitch/util/async-components'; import { HotKeys } from 'react-hotkeys'; import NotificationOverlayContainer from 'flavours/glitch/features/notifications/containers/overlay_container'; import classNames from 'classnames'; @@ -443,11 +443,15 @@ class Status extends ImmutablePureComponent { } renderLoadingMediaGallery () { - return <div className='media_gallery' style={{ height: '110px' }} />; + return <div className='media-gallery' style={{ height: '110px' }} />; } renderLoadingVideoPlayer () { - return <div className='media-spoiler-video' style={{ height: '110px' }} />; + return <div className='video-player' style={{ height: '110px' }} />; + } + + renderLoadingAudioPlayer () { + return <div className='audio-player' style={{ height: '110px' }} />; } render () { @@ -561,7 +565,24 @@ class Status extends ImmutablePureComponent { media={status.get('media_attachments')} /> ); - } else if (['video', 'audio'].includes(attachments.getIn([0, 'type']))) { + } else if (attachments.getIn([0, 'type']) === 'audio') { + const attachment = status.getIn(['media_attachments', 0]); + + media = ( + <Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} > + {Component => ( + <Component + src={attachment.get('url')} + alt={attachment.get('description')} + duration={attachment.getIn(['meta', 'original', 'duration'], 0)} + peaks={[0]} + height={70} + /> + )} + </Bundle> + ); + mediaIcon = 'music'; + } else if (attachments.getIn([0, 'type']) === 'video') { const attachment = status.getIn(['media_attachments', 0]); media = ( @@ -584,7 +605,7 @@ class Status extends ImmutablePureComponent { />)} </Bundle> ); - mediaIcon = attachment.get('type') === 'video' ? 'video-camera' : 'music'; + mediaIcon = 'video-camera'; } else { // Media type is 'image' or 'gifv' media = ( <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}> @@ -702,6 +723,7 @@ class Status extends ImmutablePureComponent { parseClick={parseClick} disabled={!router} tagLinks={settings.get('tag_misleading_links')} + rewriteMentions={settings.get('rewrite_mentions')} /> {!isCollapsed || !(muted || !settings.getIn(['collapsed', 'show_action_bar'])) ? ( <StatusActionBar diff --git a/app/javascript/flavours/glitch/components/status_content.js b/app/javascript/flavours/glitch/components/status_content.js index 95a4fe3fa..c34464fde 100644 --- a/app/javascript/flavours/glitch/components/status_content.js +++ b/app/javascript/flavours/glitch/components/status_content.js @@ -67,10 +67,12 @@ export default class StatusContent extends React.PureComponent { disabled: PropTypes.bool, onUpdate: PropTypes.func, tagLinks: PropTypes.bool, + rewriteMentions: PropTypes.string, }; static defaultProps = { tagLinks: true, + rewriteMentions: 'no', }; state = { @@ -79,7 +81,7 @@ export default class StatusContent extends React.PureComponent { _updateStatusLinks () { const node = this.contentsNode; - const { tagLinks } = this.props; + const { tagLinks, rewriteMentions } = this.props; if (!node) { return; @@ -99,6 +101,13 @@ export default class StatusContent extends React.PureComponent { if (mention) { link.addEventListener('click', this.onMentionClick.bind(this, mention), false); link.setAttribute('title', mention.get('acct')); + if (rewriteMentions !== 'no') { + while (link.firstChild) link.removeChild(link.firstChild); + link.appendChild(document.createTextNode('@')); + const acctSpan = document.createElement('span'); + acctSpan.textContent = rewriteMentions === 'acct' ? mention.get('acct') : mention.get('username'); + link.appendChild(acctSpan); + } } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) { link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false); } else { @@ -203,7 +212,7 @@ export default class StatusContent extends React.PureComponent { let element = e.target; while (element) { - if (element.localName === 'button' || element.localName === 'video' || element.localName === 'a' || element.localName === 'label') { + if (['button', 'video', 'a', 'label', 'wave'].includes(element.localName)) { return; } element = element.parentNode; @@ -242,6 +251,7 @@ export default class StatusContent extends React.PureComponent { parseClick, disabled, tagLinks, + rewriteMentions, } = this.props; const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden; @@ -340,7 +350,7 @@ export default class StatusContent extends React.PureComponent { > <div ref={this.setContentsRef} - key={`contents-${tagLinks}`} + key={`contents-${tagLinks}-${rewriteMentions}`} dangerouslySetInnerHTML={content} lang={status.get('language')} className='status__content__text' |