Merge pull request #142 from glitch-soc/sync/upstream-1.6.0rc4
Merge with 1.6.0rc4 STRAP IN BUCKAWOO HERE WE GO AGAIN
This commit is contained in:
		
						commit
						c9df53044a
					
				
							
								
								
									
										1
									
								
								Gemfile
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								Gemfile
									
									
									
									
									
								
							| @ -24,6 +24,7 @@ gem 'addressable', '~> 2.5' | |||||||
| gem 'bootsnap' | gem 'bootsnap' | ||||||
| gem 'browser' | gem 'browser' | ||||||
| gem 'charlock_holmes', '~> 0.7.5' | gem 'charlock_holmes', '~> 0.7.5' | ||||||
|  | gem 'iso-639' | ||||||
| gem 'cld3', '~> 3.1' | gem 'cld3', '~> 3.1' | ||||||
| gem 'devise', '~> 4.2' | gem 'devise', '~> 4.2' | ||||||
| gem 'devise-two-factor', '~> 3.0' | gem 'devise-two-factor', '~> 3.0' | ||||||
|  | |||||||
| @ -225,6 +225,7 @@ GEM | |||||||
|       terminal-table (>= 1.5.1) |       terminal-table (>= 1.5.1) | ||||||
|     idn-ruby (0.1.0) |     idn-ruby (0.1.0) | ||||||
|     ipaddress (0.8.3) |     ipaddress (0.8.3) | ||||||
|  |     iso-639 (0.2.8) | ||||||
|     jmespath (1.3.1) |     jmespath (1.3.1) | ||||||
|     json (2.1.0) |     json (2.1.0) | ||||||
|     json-ld (2.1.5) |     json-ld (2.1.5) | ||||||
| @ -560,6 +561,7 @@ DEPENDENCIES | |||||||
|   httplog (~> 0.99) |   httplog (~> 0.99) | ||||||
|   i18n-tasks (~> 0.9) |   i18n-tasks (~> 0.9) | ||||||
|   idn-ruby |   idn-ruby | ||||||
|  |   iso-639 | ||||||
|   json-ld-preloaded (~> 2.2.1) |   json-ld-preloaded (~> 2.2.1) | ||||||
|   kaminari (~> 1.0) |   kaminari (~> 1.0) | ||||||
|   letter_opener (~> 1.4) |   letter_opener (~> 1.4) | ||||||
|  | |||||||
| @ -30,6 +30,7 @@ module SettingsHelper | |||||||
|     th: 'ภาษาไทย', |     th: 'ภาษาไทย', | ||||||
|     tr: 'Türkçe', |     tr: 'Türkçe', | ||||||
|     uk: 'Українська', |     uk: 'Українська', | ||||||
|  |     zh: '中文', | ||||||
|     'zh-CN': '简体中文', |     'zh-CN': '简体中文', | ||||||
|     'zh-HK': '繁體中文(香港)', |     'zh-HK': '繁體中文(香港)', | ||||||
|     'zh-TW': '繁體中文(臺灣)', |     'zh-TW': '繁體中文(臺灣)', | ||||||
| @ -39,6 +40,10 @@ module SettingsHelper | |||||||
|     HUMAN_LOCALES[locale] |     HUMAN_LOCALES[locale] | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def filterable_languages | ||||||
|  |     I18n.available_locales.map { |locale| locale.to_s.split('-').first.to_sym }.uniq | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   def hash_to_object(hash) |   def hash_to_object(hash) | ||||||
|     HashObject.new(hash) |     HashObject.new(hash) | ||||||
|   end |   end | ||||||
|  | |||||||
| @ -1,6 +1,11 @@ | |||||||
| import api from '../api'; | import api from '../api'; | ||||||
| 
 | 
 | ||||||
| import { updateTimeline } from './timelines'; | import { | ||||||
|  |   updateTimeline, | ||||||
|  |   refreshHomeTimeline, | ||||||
|  |   refreshCommunityTimeline, | ||||||
|  |   refreshPublicTimeline, | ||||||
|  | } from './timelines'; | ||||||
| 
 | 
 | ||||||
| export const COMPOSE_CHANGE          = 'COMPOSE_CHANGE'; | export const COMPOSE_CHANGE          = 'COMPOSE_CHANGE'; | ||||||
| export const COMPOSE_SUBMIT_REQUEST  = 'COMPOSE_SUBMIT_REQUEST'; | export const COMPOSE_SUBMIT_REQUEST  = 'COMPOSE_SUBMIT_REQUEST'; | ||||||
| @ -98,16 +103,20 @@ export function submitCompose() { | |||||||
|       dispatch(submitComposeSuccess({ ...response.data })); |       dispatch(submitComposeSuccess({ ...response.data })); | ||||||
| 
 | 
 | ||||||
|       // To make the app more responsive, immediately get the status into the columns
 |       // To make the app more responsive, immediately get the status into the columns
 | ||||||
|       dispatch(updateTimeline('home', { ...response.data })); | 
 | ||||||
|  |       const insertOrRefresh = (timelineId, refreshAction) => { | ||||||
|  |         if (getState().getIn(['timelines', timelineId, 'online'])) { | ||||||
|  |           dispatch(updateTimeline(timelineId, { ...response.data })); | ||||||
|  |         } else if (getState().getIn(['timelines', timelineId, 'loaded'])) { | ||||||
|  |           dispatch(refreshAction()); | ||||||
|  |         } | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       insertOrRefresh('home', refreshHomeTimeline); | ||||||
| 
 | 
 | ||||||
|       if (response.data.in_reply_to_id === null && response.data.visibility === 'public') { |       if (response.data.in_reply_to_id === null && response.data.visibility === 'public') { | ||||||
|         if (getState().getIn(['timelines', 'community', 'loaded'])) { |         insertOrRefresh('community', refreshCommunityTimeline); | ||||||
|           dispatch(updateTimeline('community', { ...response.data })); |         insertOrRefresh('public', refreshPublicTimeline); | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (getState().getIn(['timelines', 'public', 'loaded'])) { |  | ||||||
|           dispatch(updateTimeline('public', { ...response.data })); |  | ||||||
|         } |  | ||||||
|       } |       } | ||||||
|     }).catch(function (error) { |     }).catch(function (error) { | ||||||
|       dispatch(submitComposeFail(error)); |       dispatch(submitComposeFail(error)); | ||||||
|  | |||||||
							
								
								
									
										39
									
								
								app/javascript/mastodon/actions/pin_statuses.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								app/javascript/mastodon/actions/pin_statuses.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | |||||||
|  | import api from '../api'; | ||||||
|  | 
 | ||||||
|  | export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST'; | ||||||
|  | export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS'; | ||||||
|  | export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL'; | ||||||
|  | 
 | ||||||
|  | export function fetchPinnedStatuses() { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     dispatch(fetchPinnedStatusesRequest()); | ||||||
|  | 
 | ||||||
|  |     const accountId = getState().getIn(['meta', 'me']); | ||||||
|  |     api(getState).get(`/api/v1/accounts/${accountId}/statuses`, { params: { pinned: true } }).then(response => { | ||||||
|  |       dispatch(fetchPinnedStatusesSuccess(response.data, null)); | ||||||
|  |     }).catch(error => { | ||||||
|  |       dispatch(fetchPinnedStatusesFail(error)); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchPinnedStatusesRequest() { | ||||||
|  |   return { | ||||||
|  |     type: PINNED_STATUSES_FETCH_REQUEST, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchPinnedStatusesSuccess(statuses, next) { | ||||||
|  |   return { | ||||||
|  |     type: PINNED_STATUSES_FETCH_SUCCESS, | ||||||
|  |     statuses, | ||||||
|  |     next, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchPinnedStatusesFail(error) { | ||||||
|  |   return { | ||||||
|  |     type: PINNED_STATUSES_FETCH_FAIL, | ||||||
|  |     error, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
| @ -27,6 +27,10 @@ export default class ScrollableList extends PureComponent { | |||||||
|     trackScroll: true, |     trackScroll: true, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  |   state = { | ||||||
|  |     lastMouseMove: null, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   intersectionObserverWrapper = new IntersectionObserverWrapper(); |   intersectionObserverWrapper = new IntersectionObserverWrapper(); | ||||||
| 
 | 
 | ||||||
|   handleScroll = throttle(() => { |   handleScroll = throttle(() => { | ||||||
| @ -47,6 +51,14 @@ export default class ScrollableList extends PureComponent { | |||||||
|     trailing: true, |     trailing: true, | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|  |   handleMouseMove = throttle(() => { | ||||||
|  |     this._lastMouseMove = new Date(); | ||||||
|  |   }, 300); | ||||||
|  | 
 | ||||||
|  |   handleMouseLeave = () => { | ||||||
|  |     this._lastMouseMove = null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   componentDidMount () { |   componentDidMount () { | ||||||
|     this.attachScrollListener(); |     this.attachScrollListener(); | ||||||
|     this.attachIntersectionObserver(); |     this.attachIntersectionObserver(); | ||||||
| @ -56,17 +68,20 @@ export default class ScrollableList extends PureComponent { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentDidUpdate (prevProps) { |   componentDidUpdate (prevProps) { | ||||||
|  |     const someItemInserted = React.Children.count(prevProps.children) > 0 && | ||||||
|  |       React.Children.count(prevProps.children) < React.Children.count(this.props.children) && | ||||||
|  |       this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props); | ||||||
|  | 
 | ||||||
|     // Reset the scroll position when a new child comes in in order not to
 |     // Reset the scroll position when a new child comes in in order not to
 | ||||||
|     // jerk the scrollbar around if you're already scrolled down the page.
 |     // jerk the scrollbar around if you're already scrolled down the page.
 | ||||||
|     if (React.Children.count(prevProps.children) < React.Children.count(this.props.children) && this._oldScrollPosition && this.node.scrollTop > 0) { |     if (someItemInserted && this._oldScrollPosition && this.node.scrollTop > 0) { | ||||||
|       if (this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props)) { |       const newScrollTop = this.node.scrollHeight - this._oldScrollPosition; | ||||||
|         const newScrollTop = this.node.scrollHeight - this._oldScrollPosition; | 
 | ||||||
|         if (this.node.scrollTop !== newScrollTop) { |       if (this.node.scrollTop !== newScrollTop) { | ||||||
|           this.node.scrollTop = newScrollTop; |         this.node.scrollTop = newScrollTop; | ||||||
|         } |  | ||||||
|       } else { |  | ||||||
|         this._oldScrollPosition = this.node.scrollHeight - this.node.scrollTop; |  | ||||||
|       } |       } | ||||||
|  |     } else { | ||||||
|  |       this._oldScrollPosition = this.node.scrollHeight - this.node.scrollTop; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -114,6 +129,10 @@ export default class ScrollableList extends PureComponent { | |||||||
|     this.props.onScrollToBottom(); |     this.props.onScrollToBottom(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   _recentlyMoved () { | ||||||
|  |     return this._lastMouseMove !== null && ((new Date()) - this._lastMouseMove < 600); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   handleKeyDown = (e) => { |   handleKeyDown = (e) => { | ||||||
|     if (['PageDown', 'PageUp'].includes(e.key) || (e.ctrlKey && ['End', 'Home'].includes(e.key))) { |     if (['PageDown', 'PageUp'].includes(e.key) || (e.ctrlKey && ['End', 'Home'].includes(e.key))) { | ||||||
|       const article = (() => { |       const article = (() => { | ||||||
| @ -149,7 +168,7 @@ export default class ScrollableList extends PureComponent { | |||||||
| 
 | 
 | ||||||
|     if (isLoading || childrenCount > 0 || !emptyMessage) { |     if (isLoading || childrenCount > 0 || !emptyMessage) { | ||||||
|       scrollableArea = ( |       scrollableArea = ( | ||||||
|         <div className='scrollable' ref={this.setRef}> |         <div className='scrollable' ref={this.setRef} onMouseMove={this.handleMouseMove} onMouseLeave={this.handleMouseLeave}> | ||||||
|           <div role='feed' className='item-list' onKeyDown={this.handleKeyDown}> |           <div role='feed' className='item-list' onKeyDown={this.handleKeyDown}> | ||||||
|             {prepend} |             {prepend} | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -137,7 +137,7 @@ export default class StatusActionBar extends ImmutablePureComponent { | |||||||
| 
 | 
 | ||||||
|     menu.push(null); |     menu.push(null); | ||||||
| 
 | 
 | ||||||
|     if (withDismiss) { |     if (status.getIn(['account', 'id']) === me || withDismiss) { | ||||||
|       menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick }); |       menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick }); | ||||||
|       menu.push(null); |       menu.push(null); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -149,29 +149,29 @@ export default class VideoPlayer extends React.PureComponent { | |||||||
|     if (!this.state.visible) { |     if (!this.state.visible) { | ||||||
|       if (sensitive) { |       if (sensitive) { | ||||||
|         return ( |         return ( | ||||||
|           <div role='button' tabIndex='0' style={{ width: `${width}px`, height: `${height}px`, marginTop: '8px' }} className='media-spoiler__video' onClick={this.handleVisibility}> |           <button style={{ width: `${width}px`, height: `${height}px`, marginTop: '8px' }} className='media-spoiler' onClick={this.handleVisibility}> | ||||||
|             {spoilerButton} |             {spoilerButton} | ||||||
|             <span className='media-spoiler__warning'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span> |             <span className='media-spoiler__warning'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span> | ||||||
|             <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> |             <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> | ||||||
|           </div> |           </button> | ||||||
|         ); |         ); | ||||||
|       } else { |       } else { | ||||||
|         return ( |         return ( | ||||||
|           <div role='button' tabIndex='0' style={{ width: `${width}px`, height: `${height}px`, marginTop: '8px' }} className='media-spoiler__video' onClick={this.handleVisibility}> |           <button style={{ width: `${width}px`, height: `${height}px`, marginTop: '8px' }} className='media-spoiler' onClick={this.handleVisibility}> | ||||||
|             {spoilerButton} |             {spoilerButton} | ||||||
|             <span className='media-spoiler__warning'><FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' /></span> |             <span className='media-spoiler__warning'><FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' /></span> | ||||||
|             <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> |             <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> | ||||||
|           </div> |           </button> | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (this.state.preview && !autoplay) { |     if (this.state.preview && !autoplay) { | ||||||
|       return ( |       return ( | ||||||
|         <div role='button' tabIndex='0' className='media-spoiler-video' style={{ width: `${width}px`, height: `${height}px`, backgroundImage: `url(${media.get('preview_url')})` }} onClick={this.handleOpen}> |         <button className='media-spoiler-video' style={{ width: `${width}px`, height: `${height}px`, backgroundImage: `url(${media.get('preview_url')})` }} onClick={this.handleOpen}> | ||||||
|           {spoilerButton} |           {spoilerButton} | ||||||
|           <div className='media-spoiler-video-play-icon'><i className='fa fa-play' /></div> |           <div className='media-spoiler-video-play-icon'><i className='fa fa-play' /></div> | ||||||
|         </div> |         </button> | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -26,6 +26,7 @@ const messages = defineMessages({ | |||||||
|   mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, |   mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, | ||||||
|   info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' }, |   info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' }, | ||||||
|   show_me_around: { id: 'getting_started.onboarding', defaultMessage: 'Show me around' }, |   show_me_around: { id: 'getting_started.onboarding', defaultMessage: 'Show me around' }, | ||||||
|  |   pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' }, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = state => ({ | const mapStateToProps = state => ({ | ||||||
| @ -79,15 +80,16 @@ export default class GettingStarted extends ImmutablePureComponent { | |||||||
| 
 | 
 | ||||||
|     navItems = navItems.concat([ |     navItems = navItems.concat([ | ||||||
|       <ColumnLink key='4' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />, |       <ColumnLink key='4' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />, | ||||||
|  |       <ColumnLink key='5' icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />, | ||||||
|     ]); |     ]); | ||||||
| 
 | 
 | ||||||
|     if (me.get('locked')) { |     if (me.get('locked')) { | ||||||
|       navItems.push(<ColumnLink key='5' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />); |       navItems.push(<ColumnLink key='6' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     navItems = navItems.concat([ |     navItems = navItems.concat([ | ||||||
|       <ColumnLink key='6' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />, |       <ColumnLink key='7' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />, | ||||||
|       <ColumnLink key='7' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />, |       <ColumnLink key='8' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />, | ||||||
|     ]); |     ]); | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|  | |||||||
							
								
								
									
										59
									
								
								app/javascript/mastodon/features/pinned_statuses/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								app/javascript/mastodon/features/pinned_statuses/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import { connect } from 'react-redux'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
|  | import { fetchPinnedStatuses } from '../../actions/pin_statuses'; | ||||||
|  | import Column from '../ui/components/column'; | ||||||
|  | import ColumnBackButtonSlim from '../../components/column_back_button_slim'; | ||||||
|  | import StatusList from '../../components/status_list'; | ||||||
|  | import { defineMessages, injectIntl } from 'react-intl'; | ||||||
|  | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
|  | 
 | ||||||
|  | const messages = defineMessages({ | ||||||
|  |   heading: { id: 'column.pins', defaultMessage: 'Pinned toot' }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const mapStateToProps = state => ({ | ||||||
|  |   statusIds: state.getIn(['status_lists', 'pins', 'items']), | ||||||
|  |   hasMore: !!state.getIn(['status_lists', 'pins', 'next']), | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | @connect(mapStateToProps) | ||||||
|  | @injectIntl | ||||||
|  | export default class PinnedStatuses extends ImmutablePureComponent { | ||||||
|  | 
 | ||||||
|  |   static propTypes = { | ||||||
|  |     dispatch: PropTypes.func.isRequired, | ||||||
|  |     statusIds: ImmutablePropTypes.list.isRequired, | ||||||
|  |     intl: PropTypes.object.isRequired, | ||||||
|  |     hasMore: PropTypes.bool.isRequired, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   componentWillMount () { | ||||||
|  |     this.props.dispatch(fetchPinnedStatuses()); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   handleHeaderClick = () => { | ||||||
|  |     this.column.scrollTop(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setRef = c => { | ||||||
|  |     this.column = c; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   render () { | ||||||
|  |     const { intl, statusIds, hasMore } = this.props; | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |       <Column icon='thumb-tack' heading={intl.formatMessage(messages.heading)} ref={this.setRef}> | ||||||
|  |         <ColumnBackButtonSlim /> | ||||||
|  |         <StatusList | ||||||
|  |           statusIds={statusIds} | ||||||
|  |           scrollKey='pinned_statuses' | ||||||
|  |           hasMore={hasMore} | ||||||
|  |         /> | ||||||
|  |       </Column> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -33,7 +33,8 @@ export default class EmbedModal extends ImmutablePureComponent { | |||||||
|       iframeDocument.close(); |       iframeDocument.close(); | ||||||
| 
 | 
 | ||||||
|       iframeDocument.body.style.margin = 0; |       iframeDocument.body.style.margin = 0; | ||||||
|       this.iframe.height = iframeDocument.body.scrollHeight + 'px'; |       this.iframe.width  = iframeDocument.body.scrollWidth; | ||||||
|  |       this.iframe.height = iframeDocument.body.scrollHeight; | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -71,7 +72,6 @@ export default class EmbedModal extends ImmutablePureComponent { | |||||||
| 
 | 
 | ||||||
|           <iframe |           <iframe | ||||||
|             className='embed-modal__iframe' |             className='embed-modal__iframe' | ||||||
|             scrolling='no' |  | ||||||
|             frameBorder='0' |             frameBorder='0' | ||||||
|             ref={this.setIframeRef} |             ref={this.setIframeRef} | ||||||
|             title='preview' |             title='preview' | ||||||
|  | |||||||
| @ -36,6 +36,7 @@ import { | |||||||
|   FavouritedStatuses, |   FavouritedStatuses, | ||||||
|   Blocks, |   Blocks, | ||||||
|   Mutes, |   Mutes, | ||||||
|  |   PinnedStatuses, | ||||||
| } from './util/async-components'; | } from './util/async-components'; | ||||||
| 
 | 
 | ||||||
| // Dummy import, to make sure that <Status /> ends up in the application bundle.
 | // Dummy import, to make sure that <Status /> ends up in the application bundle.
 | ||||||
| @ -235,6 +236,7 @@ export default class UI extends React.PureComponent { | |||||||
| 
 | 
 | ||||||
|             <WrappedRoute path='/notifications' component={Notifications} content={children} /> |             <WrappedRoute path='/notifications' component={Notifications} content={children} /> | ||||||
|             <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} /> |             <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} /> | ||||||
|  |             <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} /> | ||||||
| 
 | 
 | ||||||
|             <WrappedRoute path='/statuses/new' component={Compose} content={children} /> |             <WrappedRoute path='/statuses/new' component={Compose} content={children} /> | ||||||
|             <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} /> |             <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} /> | ||||||
|  | |||||||
| @ -34,6 +34,10 @@ export function GettingStarted () { | |||||||
|   return import(/* webpackChunkName: "features/getting_started" */'../../getting_started'); |   return import(/* webpackChunkName: "features/getting_started" */'../../getting_started'); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export function PinnedStatuses () { | ||||||
|  |   return import(/* webpackChunkName: "features/pinned_statuses" */'../../pinned_statuses'); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export function AccountTimeline () { | export function AccountTimeline () { | ||||||
|   return import(/* webpackChunkName: "features/account_timeline" */'../../account_timeline'); |   return import(/* webpackChunkName: "features/account_timeline" */'../../account_timeline'); | ||||||
| } | } | ||||||
|  | |||||||
| @ -34,6 +34,7 @@ | |||||||
|   "column.mutes": "Muted users", |   "column.mutes": "Muted users", | ||||||
|   "column.notifications": "Notifications", |   "column.notifications": "Notifications", | ||||||
|   "column.public": "Federated timeline", |   "column.public": "Federated timeline", | ||||||
|  |   "column.pins": "Pinned toots", | ||||||
|   "column_back_button.label": "Back", |   "column_back_button.label": "Back", | ||||||
|   "column_header.hide_settings": "Hide settings", |   "column_header.hide_settings": "Hide settings", | ||||||
|   "column_header.moveLeft_settings": "Move column to the left", |   "column_header.moveLeft_settings": "Move column to the left", | ||||||
| @ -111,6 +112,7 @@ | |||||||
|   "navigation_bar.mutes": "Muted users", |   "navigation_bar.mutes": "Muted users", | ||||||
|   "navigation_bar.preferences": "Preferences", |   "navigation_bar.preferences": "Preferences", | ||||||
|   "navigation_bar.public_timeline": "Federated timeline", |   "navigation_bar.public_timeline": "Federated timeline", | ||||||
|  |   "navigation_bar.pins": "Pinned toots", | ||||||
|   "notification.favourite": "{name} favourited your status", |   "notification.favourite": "{name} favourited your status", | ||||||
|   "notification.follow": "{name} followed you", |   "notification.follow": "{name} followed you", | ||||||
|   "notification.mention": "{name} mentioned you", |   "notification.mention": "{name} mentioned you", | ||||||
|  | |||||||
| @ -34,6 +34,7 @@ | |||||||
|   "column.mutes": "Comptes masqués", |   "column.mutes": "Comptes masqués", | ||||||
|   "column.notifications": "Notifications", |   "column.notifications": "Notifications", | ||||||
|   "column.public": "Fil public global", |   "column.public": "Fil public global", | ||||||
|  |   "column.pins": "Pouets épinglés", | ||||||
|   "column_back_button.label": "Retour", |   "column_back_button.label": "Retour", | ||||||
|   "column_header.hide_settings": "Masquer les paramètres", |   "column_header.hide_settings": "Masquer les paramètres", | ||||||
|   "column_header.moveLeft_settings": "Déplacer la colonne vers la gauche", |   "column_header.moveLeft_settings": "Déplacer la colonne vers la gauche", | ||||||
| @ -62,9 +63,9 @@ | |||||||
|   "confirmations.mute.confirm": "Masquer", |   "confirmations.mute.confirm": "Masquer", | ||||||
|   "confirmations.mute.message": "Confirmez vous le masquage de {name} ?", |   "confirmations.mute.message": "Confirmez vous le masquage de {name} ?", | ||||||
|   "confirmations.unfollow.confirm": "Ne plus suivre", |   "confirmations.unfollow.confirm": "Ne plus suivre", | ||||||
|   "confirmations.unfollow.message": "Vous voulez-vous arrêter de suivre {name} ?", |   "confirmations.unfollow.message": "Voulez-vous arrêter de suivre {name} ?", | ||||||
|   "embed.instructions": "Embed this status on your website by copying the code below.", |   "embed.instructions": "Intégrez ce statut à votre site en copiant ce code ci-dessous.", | ||||||
|   "embed.preview": "Here is what it will look like:", |   "embed.preview": "Il apparaîtra comme cela : ", | ||||||
|   "emoji_button.activity": "Activités", |   "emoji_button.activity": "Activités", | ||||||
|   "emoji_button.flags": "Drapeaux", |   "emoji_button.flags": "Drapeaux", | ||||||
|   "emoji_button.food": "Boire et manger", |   "emoji_button.food": "Boire et manger", | ||||||
| @ -111,6 +112,7 @@ | |||||||
|   "navigation_bar.mutes": "Comptes masqués", |   "navigation_bar.mutes": "Comptes masqués", | ||||||
|   "navigation_bar.preferences": "Préférences", |   "navigation_bar.preferences": "Préférences", | ||||||
|   "navigation_bar.public_timeline": "Fil public global", |   "navigation_bar.public_timeline": "Fil public global", | ||||||
|  |   "navigation_bar.pins": "Pouets épinglés", | ||||||
|   "notification.favourite": "{name} a ajouté à ses favoris :", |   "notification.favourite": "{name} a ajouté à ses favoris :", | ||||||
|   "notification.follow": "{name} vous suit.", |   "notification.follow": "{name} vous suit.", | ||||||
|   "notification.mention": "{name} vous a mentionné⋅e :", |   "notification.mention": "{name} vous a mentionné⋅e :", | ||||||
|  | |||||||
| @ -34,6 +34,7 @@ | |||||||
|   "column.mutes": "ミュートしたユーザー", |   "column.mutes": "ミュートしたユーザー", | ||||||
|   "column.notifications": "通知", |   "column.notifications": "通知", | ||||||
|   "column.public": "連合タイムライン", |   "column.public": "連合タイムライン", | ||||||
|  |   "column.pins": "固定されたトゥート", | ||||||
|   "column_back_button.label": "戻る", |   "column_back_button.label": "戻る", | ||||||
|   "column_header.hide_settings": "設定を隠す", |   "column_header.hide_settings": "設定を隠す", | ||||||
|   "column_header.moveLeft_settings": "カラムを左に移動する", |   "column_header.moveLeft_settings": "カラムを左に移動する", | ||||||
| @ -111,6 +112,7 @@ | |||||||
|   "navigation_bar.mutes": "ミュートしたユーザー", |   "navigation_bar.mutes": "ミュートしたユーザー", | ||||||
|   "navigation_bar.preferences": "ユーザー設定", |   "navigation_bar.preferences": "ユーザー設定", | ||||||
|   "navigation_bar.public_timeline": "連合タイムライン", |   "navigation_bar.public_timeline": "連合タイムライン", | ||||||
|  |   "navigation_bar.pins": "固定されたトゥート", | ||||||
|   "notification.favourite": "{name}さんがあなたのトゥートをお気に入りに登録しました", |   "notification.favourite": "{name}さんがあなたのトゥートをお気に入りに登録しました", | ||||||
|   "notification.follow": "{name}さんにフォローされました", |   "notification.follow": "{name}さんにフォローされました", | ||||||
|   "notification.mention": "{name}さんがあなたに返信しました", |   "notification.mention": "{name}さんがあなたに返信しました", | ||||||
|  | |||||||
| @ -34,6 +34,7 @@ | |||||||
|   "column.mutes": "뮤트 중인 사용자", |   "column.mutes": "뮤트 중인 사용자", | ||||||
|   "column.notifications": "알림", |   "column.notifications": "알림", | ||||||
|   "column.public": "연합 타임라인", |   "column.public": "연합 타임라인", | ||||||
|  |   "column.pins": "고정된 Toot", | ||||||
|   "column_back_button.label": "돌아가기", |   "column_back_button.label": "돌아가기", | ||||||
|   "column_header.hide_settings": "Hide settings", |   "column_header.hide_settings": "Hide settings", | ||||||
|   "column_header.moveLeft_settings": "Move column to the left", |   "column_header.moveLeft_settings": "Move column to the left", | ||||||
| @ -111,6 +112,7 @@ | |||||||
|   "navigation_bar.mutes": "뮤트 중인 사용자", |   "navigation_bar.mutes": "뮤트 중인 사용자", | ||||||
|   "navigation_bar.preferences": "사용자 설정", |   "navigation_bar.preferences": "사용자 설정", | ||||||
|   "navigation_bar.public_timeline": "연합 타임라인", |   "navigation_bar.public_timeline": "연합 타임라인", | ||||||
|  |   "navigation_bar.pins": "고정된 Toot", | ||||||
|   "notification.favourite": "{name}님이 즐겨찾기 했습니다", |   "notification.favourite": "{name}님이 즐겨찾기 했습니다", | ||||||
|   "notification.follow": "{name}님이 나를 팔로우 했습니다", |   "notification.follow": "{name}님이 나를 팔로우 했습니다", | ||||||
|   "notification.mention": "{name}님이 답글을 보냈습니다", |   "notification.mention": "{name}님이 답글을 보냈습니다", | ||||||
|  | |||||||
| @ -34,6 +34,7 @@ | |||||||
|   "column.mutes": "Personas en silenci", |   "column.mutes": "Personas en silenci", | ||||||
|   "column.notifications": "Notificacions", |   "column.notifications": "Notificacions", | ||||||
|   "column.public": "Flux public global", |   "column.public": "Flux public global", | ||||||
|  |   "column.pins": "Tuts penjats", | ||||||
|   "column_back_button.label": "Tornar", |   "column_back_button.label": "Tornar", | ||||||
|   "column_header.hide_settings": "Amagar los paramètres", |   "column_header.hide_settings": "Amagar los paramètres", | ||||||
|   "column_header.moveLeft_settings": "Desplaçar la colomna a man drecha", |   "column_header.moveLeft_settings": "Desplaçar la colomna a man drecha", | ||||||
| @ -87,7 +88,7 @@ | |||||||
|   "getting_started.appsshort": "Apps", |   "getting_started.appsshort": "Apps", | ||||||
|   "getting_started.faq": "FAQ", |   "getting_started.faq": "FAQ", | ||||||
|   "getting_started.heading": "Per començar", |   "getting_started.heading": "Per començar", | ||||||
|   "getting_started.open_source_notice": "Mastodon es un logicial liure. Podètz contribuir e mandar vòstres comentaris e rapòrt de bug via{github} sus GitHub.", |   "getting_started.open_source_notice": "Mastodon es un logicial liure. Podètz contribuir e mandar vòstres comentaris e rapòrt de bug via {github} sus GitHub.", | ||||||
|   "getting_started.userguide": "Guida d’utilizacion", |   "getting_started.userguide": "Guida d’utilizacion", | ||||||
|   "home.column_settings.advanced": "Avançat", |   "home.column_settings.advanced": "Avançat", | ||||||
|   "home.column_settings.basic": "Basic", |   "home.column_settings.basic": "Basic", | ||||||
| @ -111,6 +112,7 @@ | |||||||
|   "navigation_bar.mutes": "Personas rescondudas", |   "navigation_bar.mutes": "Personas rescondudas", | ||||||
|   "navigation_bar.preferences": "Preferéncias", |   "navigation_bar.preferences": "Preferéncias", | ||||||
|   "navigation_bar.public_timeline": "Flux public global", |   "navigation_bar.public_timeline": "Flux public global", | ||||||
|  |   "navigation_bar.pins": "Tuts penjats", | ||||||
|   "notification.favourite": "{name} a ajustat a sos favorits :", |   "notification.favourite": "{name} a ajustat a sos favorits :", | ||||||
|   "notification.follow": "{name} vos sèc", |   "notification.follow": "{name} vos sèc", | ||||||
|   "notification.mention": "{name} vos a mencionat :", |   "notification.mention": "{name} vos a mencionat :", | ||||||
| @ -126,21 +128,21 @@ | |||||||
|   "notifications.column_settings.reblog": "Partatges :", |   "notifications.column_settings.reblog": "Partatges :", | ||||||
|   "notifications.column_settings.show": "Mostrar dins la colomna", |   "notifications.column_settings.show": "Mostrar dins la colomna", | ||||||
|   "notifications.column_settings.sound": "Emetre un son", |   "notifications.column_settings.sound": "Emetre un son", | ||||||
|   "onboarding.done": "Fach", |   "onboarding.done": "Sortir", | ||||||
|   "onboarding.next": "Seguent", |   "onboarding.next": "Seguent", | ||||||
|   "onboarding.page_five.public_timelines": "Lo flux local mòstra los estatuts publics del monde de vòstra instància, aquí {domain}. Lo flux federat mòstra los estatuts publics de tot lo mond sus {domain} sègon. Son los fluxes publics, un bon biais de trobar de mond.", |   "onboarding.page_five.public_timelines": "Lo flux local mòstra los estatuts publics del monde de vòstra instància, aquí {domain}. Lo flux federat mòstra los estatuts publics de la gent que los de {domain} sègon. Son los fluxes publics, un bon biais de trobar de mond.", | ||||||
|   "onboarding.page_four.home": "Lo flux d’acuèlh mòstra los estatuts del mond que seguètz.", |   "onboarding.page_four.home": "Lo flux d’acuèlh mòstra los estatuts del mond que seguètz.", | ||||||
|   "onboarding.page_four.notifications": "La colomna de notificacions vos fa veire quand qualqu’un interagís amb vos", |   "onboarding.page_four.notifications": "La colomna de notificacions vos fa veire quand qualqu’un interagís amb vos", | ||||||
|   "onboarding.page_one.federation": "Mastodon es un malhum de servidors independents que comunican per bastir un malhum ma larg. Òm los apèla instàncias.", |   "onboarding.page_one.federation": "Mastodon es un malhum de servidors independents que comunican per bastir un malhum mai larg. Òm los apèla instàncias.", | ||||||
|   "onboarding.page_one.handle": "Sètz sus {domain}, doncas vòstre identificant complet es {handle}", |   "onboarding.page_one.handle": "Sètz sus {domain}, doncas vòstre identificant complet es {handle}", | ||||||
|   "onboarding.page_one.welcome": "Benvengut a Mastodon !", |   "onboarding.page_one.welcome": "Benvengut a Mastodon !", | ||||||
|   "onboarding.page_six.admin": "Vòstre administrator d’instància es {admin}.", |   "onboarding.page_six.admin": "Vòstre administrator d’instància es {admin}.", | ||||||
|   "onboarding.page_six.almost_done": "Gaireben acabat…", |   "onboarding.page_six.almost_done": "Gaireben acabat…", | ||||||
|   "onboarding.page_six.appetoot": "Bon Appetut!", |   "onboarding.page_six.appetoot": "Bon Appetut !", | ||||||
|   "onboarding.page_six.apps_available": "I a d’aplicacions per mobil per iOS, Android e mai.", |   "onboarding.page_six.apps_available": "I a d’aplicacions per mobil per iOS, Android e mai.", | ||||||
|   "onboarding.page_six.github": "Mastodon es un logicial liure e open-source.  Podètz senhalar de bugs, demandar de foncionalitats e contribuir al còdi sus {github}.", |   "onboarding.page_six.github": "Mastodon es un logicial liure e open-source.  Podètz senhalar de bugs, demandar de foncionalitats e contribuir al còdi sus {github}.", | ||||||
|   "onboarding.page_six.guidelines": "guida de la comunitat", |   "onboarding.page_six.guidelines": "guida de la comunitat", | ||||||
|   "onboarding.page_six.read_guidelines": "Mercés de legir la {guidelines} a {domain} !", |   "onboarding.page_six.read_guidelines": "Mercés de legir la {guidelines} de {domain} !", | ||||||
|   "onboarding.page_six.various_app": "aplicacions per mobil", |   "onboarding.page_six.various_app": "aplicacions per mobil", | ||||||
|   "onboarding.page_three.profile": "Modificatz vòstre perfil per cambiar vòstre avatar, bio e escais-nom. I a enlà totas las preferéncias.", |   "onboarding.page_three.profile": "Modificatz vòstre perfil per cambiar vòstre avatar, bio e escais-nom. I a enlà totas las preferéncias.", | ||||||
|   "onboarding.page_three.search": "Emplegatz la barra de recèrca per trobar de mond e engachatz las etiquetas coma {illustration} e {introductions}. Per trobar una persona d’una autra instància, picatz son identificant complet.", |   "onboarding.page_three.search": "Emplegatz la barra de recèrca per trobar de mond e engachatz las etiquetas coma {illustration} e {introductions}. Per trobar una persona d’una autra instància, picatz son identificant complet.", | ||||||
| @ -183,7 +185,7 @@ | |||||||
|   "status.show_less": "Tornar plegar", |   "status.show_less": "Tornar plegar", | ||||||
|   "status.show_more": "Desplegar", |   "status.show_more": "Desplegar", | ||||||
|   "status.unmute_conversation": "Conversacions amb silenci levat", |   "status.unmute_conversation": "Conversacions amb silenci levat", | ||||||
|   "status.unpin": "Despenjar del perfil", |   "status.unpin": "Tirar del perfil", | ||||||
|   "tabs_bar.compose": "Compausar", |   "tabs_bar.compose": "Compausar", | ||||||
|   "tabs_bar.federated_timeline": "Flux public global", |   "tabs_bar.federated_timeline": "Flux public global", | ||||||
|   "tabs_bar.home": "Acuèlh", |   "tabs_bar.home": "Acuèlh", | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ | |||||||
|   "account.mute": "Wycisz @{name}", |   "account.mute": "Wycisz @{name}", | ||||||
|   "account.posts": "Wpisy", |   "account.posts": "Wpisy", | ||||||
|   "account.report": "Zgłoś @{name}", |   "account.report": "Zgłoś @{name}", | ||||||
|   "account.requested": "Oczekująca prośba", |   "account.requested": "Oczekująca prośba, kliknij aby anulować", | ||||||
|   "account.share": "Udostępnij profil @{name}", |   "account.share": "Udostępnij profil @{name}", | ||||||
|   "account.unblock": "Odblokuj @{name}", |   "account.unblock": "Odblokuj @{name}", | ||||||
|   "account.unblock_domain": "Odblokuj domenę {domain}", |   "account.unblock_domain": "Odblokuj domenę {domain}", | ||||||
| @ -33,6 +33,7 @@ | |||||||
|   "column.home": "Strona główna", |   "column.home": "Strona główna", | ||||||
|   "column.mutes": "Wyciszeni użytkownicy", |   "column.mutes": "Wyciszeni użytkownicy", | ||||||
|   "column.notifications": "Powiadomienia", |   "column.notifications": "Powiadomienia", | ||||||
|  |   "column.pins": "Przypięte wpisy", | ||||||
|   "column.public": "Globalna oś czasu", |   "column.public": "Globalna oś czasu", | ||||||
|   "column_back_button.label": "Wróć", |   "column_back_button.label": "Wróć", | ||||||
|   "column_header.hide_settings": "Ukryj ustawienia", |   "column_header.hide_settings": "Ukryj ustawienia", | ||||||
| @ -109,6 +110,7 @@ | |||||||
|   "navigation_bar.info": "Szczegółowe informacje", |   "navigation_bar.info": "Szczegółowe informacje", | ||||||
|   "navigation_bar.logout": "Wyloguj", |   "navigation_bar.logout": "Wyloguj", | ||||||
|   "navigation_bar.mutes": "Wyciszeni użytkownicy", |   "navigation_bar.mutes": "Wyciszeni użytkownicy", | ||||||
|  |   "navigation_bar.pins": "Przypięte wpisy", | ||||||
|   "navigation_bar.preferences": "Preferencje", |   "navigation_bar.preferences": "Preferencje", | ||||||
|   "navigation_bar.public_timeline": "Oś czasu federacji", |   "navigation_bar.public_timeline": "Oś czasu federacji", | ||||||
|   "notification.favourite": "{name} dodał Twój status do ulubionych", |   "notification.favourite": "{name} dodał Twój status do ulubionych", | ||||||
| @ -153,8 +155,8 @@ | |||||||
|   "privacy.private.short": "Tylko dla śledzących", |   "privacy.private.short": "Tylko dla śledzących", | ||||||
|   "privacy.public.long": "Widoczny na publicznych osiach czasu", |   "privacy.public.long": "Widoczny na publicznych osiach czasu", | ||||||
|   "privacy.public.short": "Publiczny", |   "privacy.public.short": "Publiczny", | ||||||
|   "privacy.unlisted.long": "Niewidoczne na publicznych osiach czasu", |   "privacy.unlisted.long": "Niewidoczny na publicznych osiach czasu", | ||||||
|   "privacy.unlisted.short": "Niewidoczne", |   "privacy.unlisted.short": "Niewidoczny", | ||||||
|   "reply_indicator.cancel": "Anuluj", |   "reply_indicator.cancel": "Anuluj", | ||||||
|   "report.placeholder": "Dodatkowe komentarze", |   "report.placeholder": "Dodatkowe komentarze", | ||||||
|   "report.submit": "Wyślij", |   "report.submit": "Wyślij", | ||||||
|  | |||||||
| @ -28,7 +28,7 @@ export default function reports(state = initialState, action) { | |||||||
|       if (state.getIn(['new', 'account_id']) !== action.account.get('id')) { |       if (state.getIn(['new', 'account_id']) !== action.account.get('id')) { | ||||||
|         map.setIn(['new', 'status_ids'], action.status ? ImmutableSet([action.status.getIn(['reblog', 'id'], action.status.get('id'))]) : ImmutableSet()); |         map.setIn(['new', 'status_ids'], action.status ? ImmutableSet([action.status.getIn(['reblog', 'id'], action.status.get('id'))]) : ImmutableSet()); | ||||||
|         map.setIn(['new', 'comment'], ''); |         map.setIn(['new', 'comment'], ''); | ||||||
|       } else { |       } else if (action.status) { | ||||||
|         map.updateIn(['new', 'status_ids'], ImmutableSet(), set => set.add(action.status.getIn(['reblog', 'id'], action.status.get('id')))); |         map.updateIn(['new', 'status_ids'], ImmutableSet(), set => set.add(action.status.getIn(['reblog', 'id'], action.status.get('id')))); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|  | |||||||
| @ -2,10 +2,15 @@ import { | |||||||
|   FAVOURITED_STATUSES_FETCH_SUCCESS, |   FAVOURITED_STATUSES_FETCH_SUCCESS, | ||||||
|   FAVOURITED_STATUSES_EXPAND_SUCCESS, |   FAVOURITED_STATUSES_EXPAND_SUCCESS, | ||||||
| } from '../actions/favourites'; | } from '../actions/favourites'; | ||||||
|  | import { | ||||||
|  |   PINNED_STATUSES_FETCH_SUCCESS, | ||||||
|  | } from '../actions/pin_statuses'; | ||||||
| import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; | import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; | ||||||
| import { | import { | ||||||
|   FAVOURITE_SUCCESS, |   FAVOURITE_SUCCESS, | ||||||
|   UNFAVOURITE_SUCCESS, |   UNFAVOURITE_SUCCESS, | ||||||
|  |   PIN_SUCCESS, | ||||||
|  |   UNPIN_SUCCESS, | ||||||
| } from '../actions/interactions'; | } from '../actions/interactions'; | ||||||
| 
 | 
 | ||||||
| const initialState = ImmutableMap({ | const initialState = ImmutableMap({ | ||||||
| @ -14,6 +19,11 @@ const initialState = ImmutableMap({ | |||||||
|     loaded: false, |     loaded: false, | ||||||
|     items: ImmutableList(), |     items: ImmutableList(), | ||||||
|   }), |   }), | ||||||
|  |   pins: ImmutableMap({ | ||||||
|  |     next: null, | ||||||
|  |     loaded: false, | ||||||
|  |     items: ImmutableList(), | ||||||
|  |   }), | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const normalizeList = (state, listType, statuses, next) => { | const normalizeList = (state, listType, statuses, next) => { | ||||||
| @ -53,6 +63,12 @@ export default function statusLists(state = initialState, action) { | |||||||
|     return prependOneToList(state, 'favourites', action.status); |     return prependOneToList(state, 'favourites', action.status); | ||||||
|   case UNFAVOURITE_SUCCESS: |   case UNFAVOURITE_SUCCESS: | ||||||
|     return removeOneFromList(state, 'favourites', action.status); |     return removeOneFromList(state, 'favourites', action.status); | ||||||
|  |   case PINNED_STATUSES_FETCH_SUCCESS: | ||||||
|  |     return normalizeList(state, 'pins', action.statuses, action.next); | ||||||
|  |   case PIN_SUCCESS: | ||||||
|  |     return prependOneToList(state, 'pins', action.status); | ||||||
|  |   case UNPIN_SUCCESS: | ||||||
|  |     return removeOneFromList(state, 'pins', action.status); | ||||||
|   default: |   default: | ||||||
|     return state; |     return state; | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -36,6 +36,9 @@ import { | |||||||
|   FAVOURITED_STATUSES_FETCH_SUCCESS, |   FAVOURITED_STATUSES_FETCH_SUCCESS, | ||||||
|   FAVOURITED_STATUSES_EXPAND_SUCCESS, |   FAVOURITED_STATUSES_EXPAND_SUCCESS, | ||||||
| } from '../actions/favourites'; | } from '../actions/favourites'; | ||||||
|  | import { | ||||||
|  |   PINNED_STATUSES_FETCH_SUCCESS, | ||||||
|  | } from '../actions/pin_statuses'; | ||||||
| import { SEARCH_FETCH_SUCCESS } from '../actions/search'; | import { SEARCH_FETCH_SUCCESS } from '../actions/search'; | ||||||
| import emojify from '../emoji'; | import emojify from '../emoji'; | ||||||
| import { Map as ImmutableMap, fromJS } from 'immutable'; | import { Map as ImmutableMap, fromJS } from 'immutable'; | ||||||
| @ -138,6 +141,7 @@ export default function statuses(state = initialState, action) { | |||||||
|   case NOTIFICATIONS_EXPAND_SUCCESS: |   case NOTIFICATIONS_EXPAND_SUCCESS: | ||||||
|   case FAVOURITED_STATUSES_FETCH_SUCCESS: |   case FAVOURITED_STATUSES_FETCH_SUCCESS: | ||||||
|   case FAVOURITED_STATUSES_EXPAND_SUCCESS: |   case FAVOURITED_STATUSES_EXPAND_SUCCESS: | ||||||
|  |   case PINNED_STATUSES_FETCH_SUCCESS: | ||||||
|   case SEARCH_FETCH_SUCCESS: |   case SEARCH_FETCH_SUCCESS: | ||||||
|     return normalizeStatuses(state, action.statuses); |     return normalizeStatuses(state, action.statuses); | ||||||
|   case TIMELINE_DELETE: |   case TIMELINE_DELETE: | ||||||
|  | |||||||
| @ -1,5 +1,22 @@ | |||||||
| import loadPolyfills from '../mastodon/load_polyfills'; | import loadPolyfills from '../mastodon/load_polyfills'; | ||||||
| import { processBio } from '../glitch/util/bio_metadata'; | import { processBio } from '../glitch/util/bio_metadata'; | ||||||
|  | import ready from '../mastodon/ready'; | ||||||
|  | 
 | ||||||
|  | window.addEventListener('message', e => { | ||||||
|  |   const data = e.data || {}; | ||||||
|  | 
 | ||||||
|  |   if (!window.parent || data.type !== 'setHeight') { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ready(() => { | ||||||
|  |     window.parent.postMessage({ | ||||||
|  |       type: 'setHeight', | ||||||
|  |       id: data.id, | ||||||
|  |       height: document.getElementsByTagName('html')[0].scrollHeight, | ||||||
|  |     }, '*'); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| 
 | 
 | ||||||
| function main() { | function main() { | ||||||
|   const { length } = require('stringz'); |   const { length } = require('stringz'); | ||||||
| @ -7,13 +24,13 @@ function main() { | |||||||
|   const { delegate } = require('rails-ujs'); |   const { delegate } = require('rails-ujs'); | ||||||
|   const emojify = require('../mastodon/emoji').default; |   const emojify = require('../mastodon/emoji').default; | ||||||
|   const { getLocale } = require('../mastodon/locales'); |   const { getLocale } = require('../mastodon/locales'); | ||||||
|   const ready = require('../mastodon/ready').default; |  | ||||||
| 
 |  | ||||||
|   const { localeData } = getLocale(); |   const { localeData } = getLocale(); | ||||||
|  | 
 | ||||||
|   localeData.forEach(IntlRelativeFormat.__addLocaleData); |   localeData.forEach(IntlRelativeFormat.__addLocaleData); | ||||||
| 
 | 
 | ||||||
|   ready(() => { |   ready(() => { | ||||||
|     const locale = document.documentElement.lang; |     const locale = document.documentElement.lang; | ||||||
|  | 
 | ||||||
|     const dateTimeFormat = new Intl.DateTimeFormat(locale, { |     const dateTimeFormat = new Intl.DateTimeFormat(locale, { | ||||||
|       year: 'numeric', |       year: 'numeric', | ||||||
|       month: 'long', |       month: 'long', | ||||||
| @ -21,6 +38,7 @@ function main() { | |||||||
|       hour: 'numeric', |       hour: 'numeric', | ||||||
|       minute: 'numeric', |       minute: 'numeric', | ||||||
|     }); |     }); | ||||||
|  | 
 | ||||||
|     const relativeFormat = new IntlRelativeFormat(locale); |     const relativeFormat = new IntlRelativeFormat(locale); | ||||||
| 
 | 
 | ||||||
|     [].forEach.call(document.querySelectorAll('.emojify'), (content) => { |     [].forEach.call(document.querySelectorAll('.emojify'), (content) => { | ||||||
| @ -30,12 +48,14 @@ function main() { | |||||||
|     [].forEach.call(document.querySelectorAll('time.formatted'), (content) => { |     [].forEach.call(document.querySelectorAll('time.formatted'), (content) => { | ||||||
|       const datetime = new Date(content.getAttribute('datetime')); |       const datetime = new Date(content.getAttribute('datetime')); | ||||||
|       const formattedDate = dateTimeFormat.format(datetime); |       const formattedDate = dateTimeFormat.format(datetime); | ||||||
|  | 
 | ||||||
|       content.title = formattedDate; |       content.title = formattedDate; | ||||||
|       content.textContent = formattedDate; |       content.textContent = formattedDate; | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     [].forEach.call(document.querySelectorAll('time.time-ago'), (content) => { |     [].forEach.call(document.querySelectorAll('time.time-ago'), (content) => { | ||||||
|       const datetime = new Date(content.getAttribute('datetime')); |       const datetime = new Date(content.getAttribute('datetime')); | ||||||
|  | 
 | ||||||
|       content.title = dateTimeFormat.format(datetime); |       content.title = dateTimeFormat.format(datetime); | ||||||
|       content.textContent = relativeFormat.format(datetime); |       content.textContent = relativeFormat.format(datetime); | ||||||
|     }); |     }); | ||||||
| @ -46,10 +66,6 @@ function main() { | |||||||
|         window.open(e.target.href, 'mastodon-intent', 'width=400,height=400,resizable=no,menubar=no,status=no,scrollbars=yes'); |         window.open(e.target.href, 'mastodon-intent', 'width=400,height=400,resizable=no,menubar=no,status=no,scrollbars=yes'); | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
| 
 |  | ||||||
|     if (window.parent) { |  | ||||||
|       window.parent.postMessage(['setHeight', document.getElementsByTagName('html')[0].scrollHeight], '*'); |  | ||||||
|     } |  | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   delegate(document, '.video-player video', 'click', ({ target }) => { |   delegate(document, '.video-player video', 'click', ({ target }) => { | ||||||
| @ -78,6 +94,7 @@ function main() { | |||||||
| 
 | 
 | ||||||
|   delegate(document, '.status__content__spoiler-link', 'click', ({ target }) => { |   delegate(document, '.status__content__spoiler-link', 'click', ({ target }) => { | ||||||
|     const contentEl = target.parentNode.parentNode.querySelector('.e-content'); |     const contentEl = target.parentNode.parentNode.querySelector('.e-content'); | ||||||
|  | 
 | ||||||
|     if (contentEl.style.display === 'block') { |     if (contentEl.style.display === 'block') { | ||||||
|       contentEl.style.display = 'none'; |       contentEl.style.display = 'none'; | ||||||
|       target.parentNode.style.marginBottom = 0; |       target.parentNode.style.marginBottom = 0; | ||||||
| @ -85,11 +102,13 @@ function main() { | |||||||
|       contentEl.style.display = 'block'; |       contentEl.style.display = 'block'; | ||||||
|       target.parentNode.style.marginBottom = null; |       target.parentNode.style.marginBottom = null; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     return false; |     return false; | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   delegate(document, '.account_display_name', 'input', ({ target }) => { |   delegate(document, '.account_display_name', 'input', ({ target }) => { | ||||||
|     const nameCounter = document.querySelector('.name-counter'); |     const nameCounter = document.querySelector('.name-counter'); | ||||||
|  | 
 | ||||||
|     if (nameCounter) { |     if (nameCounter) { | ||||||
|       nameCounter.textContent = 30 - length(target.value); |       nameCounter.textContent = 30 - length(target.value); | ||||||
|     } |     } | ||||||
| @ -97,6 +116,7 @@ function main() { | |||||||
| 
 | 
 | ||||||
|   delegate(document, '.account_note', 'input', ({ target }) => { |   delegate(document, '.account_note', 'input', ({ target }) => { | ||||||
|     const noteCounter = document.querySelector('.note-counter'); |     const noteCounter = document.querySelector('.note-counter'); | ||||||
|  | 
 | ||||||
|     if (noteCounter) { |     if (noteCounter) { | ||||||
|       const noteWithoutMetadata = processBio(target.value).text; |       const noteWithoutMetadata = processBio(target.value).text; | ||||||
|       noteCounter.textContent = 500 - length(noteWithoutMetadata); |       noteCounter.textContent = 500 - length(noteWithoutMetadata); | ||||||
| @ -107,6 +127,7 @@ function main() { | |||||||
|     const avatar = document.querySelector('.card.compact .avatar img'); |     const avatar = document.querySelector('.card.compact .avatar img'); | ||||||
|     const [file] = target.files || []; |     const [file] = target.files || []; | ||||||
|     const url = URL.createObjectURL(file); |     const url = URL.createObjectURL(file); | ||||||
|  | 
 | ||||||
|     avatar.src = url; |     avatar.src = url; | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
| @ -114,6 +135,7 @@ function main() { | |||||||
|     const header = document.querySelector('.card.compact'); |     const header = document.querySelector('.card.compact'); | ||||||
|     const [file] = target.files || []; |     const [file] = target.files || []; | ||||||
|     const url = URL.createObjectURL(file); |     const url = URL.createObjectURL(file); | ||||||
|  | 
 | ||||||
|     header.style.backgroundImage = `url(${url})`; |     header.style.backgroundImage = `url(${url})`; | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  | |||||||
| @ -190,11 +190,15 @@ | |||||||
| 
 | 
 | ||||||
| .filters { | .filters { | ||||||
|   display: flex; |   display: flex; | ||||||
|   margin-bottom: 20px; |   flex-wrap: wrap; | ||||||
| 
 | 
 | ||||||
|   .filter-subset { |   .filter-subset { | ||||||
|     flex: 0 0 auto; |     flex: 0 0 auto; | ||||||
|     margin-right: 40px; |     margin: 0 40px 10px 0; | ||||||
|  | 
 | ||||||
|  |     &:last-child { | ||||||
|  |       margin-bottom: 20px; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     ul { |     ul { | ||||||
|       margin-top: 5px; |       margin-top: 5px; | ||||||
|  | |||||||
| @ -45,6 +45,7 @@ body { | |||||||
|   &.embed { |   &.embed { | ||||||
|     background: transparent; |     background: transparent; | ||||||
|     margin: 0; |     margin: 0; | ||||||
|  |     padding-bottom: 0; | ||||||
| 
 | 
 | ||||||
|     .container { |     .container { | ||||||
|       position: absolute; |       position: absolute; | ||||||
|  | |||||||
| @ -2716,22 +2716,6 @@ button.icon-button.active i.fa-retweet { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .media-spoiler__video { |  | ||||||
|   align-items: center; |  | ||||||
|   background: $base-overlay-background; |  | ||||||
|   color: $primary-text-color; |  | ||||||
|   cursor: pointer; |  | ||||||
|   display: flex; |  | ||||||
|   flex-direction: column; |  | ||||||
|   border: 0; |  | ||||||
|   width: 100%; |  | ||||||
|   height: 100%; |  | ||||||
|   justify-content: center; |  | ||||||
|   position: relative; |  | ||||||
|   text-align: center; |  | ||||||
|   z-index: 100; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .media-spoiler__warning { | .media-spoiler__warning { | ||||||
|   display: block; |   display: block; | ||||||
|   font-size: 14px; |   font-size: 14px; | ||||||
| @ -4302,6 +4286,9 @@ button.icon-button.active i.fa-retweet { | |||||||
|     margin-left: -68px; |     margin-left: -68px; | ||||||
|     width: calc(100% + 80px); |     width: calc(100% + 80px); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   border: 0; | ||||||
|  |   display: block; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .media-spoiler-video-play-icon { | .media-spoiler-video-play-icon { | ||||||
|  | |||||||
| @ -3,7 +3,6 @@ | |||||||
|   max-width: 100%; |   max-width: 100%; | ||||||
|   border-spacing: 0; |   border-spacing: 0; | ||||||
|   border-collapse: collapse; |   border-collapse: collapse; | ||||||
|   margin-bottom: 20px; |  | ||||||
| 
 | 
 | ||||||
|   th, |   th, | ||||||
|   td { |   td { | ||||||
| @ -43,19 +42,17 @@ | |||||||
|     font-weight: 500; |     font-weight: 500; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   &.inline-table { |   &.inline-table > tbody > tr:nth-child(odd) > td, | ||||||
|     td, |   &.inline-table > tbody > tr:nth-child(odd) > th { | ||||||
|     th { |     background: transparent; | ||||||
|       padding: 8px 2px; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     & > tbody > tr:nth-child(odd) > td, |  | ||||||
|     & > tbody > tr:nth-child(odd) > th { |  | ||||||
|       background: transparent; |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .table-wrapper { | ||||||
|  |   overflow: auto; | ||||||
|  |   margin-bottom: 20px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| samp { | samp { | ||||||
|   font-family: 'mastodon-font-monospace', monospace; |   font-family: 'mastodon-font-monospace', monospace; | ||||||
| } | } | ||||||
|  | |||||||
| @ -96,12 +96,14 @@ class ActivityPub::TagManager | |||||||
|       when 'Account' |       when 'Account' | ||||||
|         klass.find_local(uri_to_local_id(uri, :username)) |         klass.find_local(uri_to_local_id(uri, :username)) | ||||||
|       else |       else | ||||||
|         klass.find_by(id: uri_to_local_id(uri)) |         StatusFinder.new(uri).status | ||||||
|       end |       end | ||||||
|     elsif ::TagManager.instance.local_id?(uri) |     elsif ::TagManager.instance.local_id?(uri) | ||||||
|       klass.find_by(id: ::TagManager.instance.unique_tag_to_local_id(uri, klass.to_s)) |       klass.find_by(id: ::TagManager.instance.unique_tag_to_local_id(uri, klass.to_s)) | ||||||
|     else |     else | ||||||
|       klass.find_by(uri: uri.split('#').first) |       klass.find_by(uri: uri.split('#').first) | ||||||
|     end |     end | ||||||
|  |   rescue ActiveRecord::RecordNotFound | ||||||
|  |     nil | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  | |||||||
| @ -20,7 +20,16 @@ class LanguageDetector | |||||||
|   private |   private | ||||||
| 
 | 
 | ||||||
|   def detected_language_code |   def detected_language_code | ||||||
|     result.language.to_sym if detected_language_reliable? |     iso6391(result.language).to_sym if detected_language_reliable? | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def iso6391(bcp47) | ||||||
|  |     iso639 = bcp47.split('-').first | ||||||
|  | 
 | ||||||
|  |     # CLD3 returns grandfathered language code for Hebrew | ||||||
|  |     return 'he' if iso639 == 'iw' | ||||||
|  | 
 | ||||||
|  |     ISO_639.find(iso639).alpha2 | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def result |   def result | ||||||
|  | |||||||
| @ -22,6 +22,8 @@ class Report < ApplicationRecord | |||||||
|   scope :unresolved, -> { where(action_taken: false) } |   scope :unresolved, -> { where(action_taken: false) } | ||||||
|   scope :resolved,   -> { where(action_taken: true) } |   scope :resolved,   -> { where(action_taken: true) } | ||||||
| 
 | 
 | ||||||
|  |   validates :comment, length: { maximum: 1000 } | ||||||
|  | 
 | ||||||
|   def statuses |   def statuses | ||||||
|     Status.where(id: status_ids).includes(:account, :media_attachments, :mentions) |     Status.where(id: status_ids).includes(:account, :media_attachments, :mentions) | ||||||
|   end |   end | ||||||
|  | |||||||
| @ -63,8 +63,8 @@ class Status < ApplicationRecord | |||||||
|   default_scope { recent } |   default_scope { recent } | ||||||
| 
 | 
 | ||||||
|   scope :recent, -> { reorder(id: :desc) } |   scope :recent, -> { reorder(id: :desc) } | ||||||
|   scope :remote, -> { where.not(uri: nil) } |   scope :remote, -> { where(local: false).or(where.not(uri: nil)) } | ||||||
|   scope :local, -> { where(uri: nil) } |   scope :local,  -> { where(local: true).or(where(uri: nil)) } | ||||||
| 
 | 
 | ||||||
|   scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') } |   scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') } | ||||||
|   scope :without_reblogs, -> { where('statuses.reblog_of_id IS NULL') } |   scope :without_reblogs, -> { where('statuses.reblog_of_id IS NULL') } | ||||||
|  | |||||||
| @ -40,12 +40,12 @@ class OEmbedSerializer < ActiveModel::Serializer | |||||||
|     attributes = { |     attributes = { | ||||||
|       src: embed_short_account_status_url(object.account, object), |       src: embed_short_account_status_url(object.account, object), | ||||||
|       class: 'mastodon-embed', |       class: 'mastodon-embed', | ||||||
|       style: 'max-width: 100%; border: none;', |       style: 'max-width: 100%; border: 0', | ||||||
|       width: width, |       width: width, | ||||||
|       height: height, |       height: height, | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     content_tag :iframe, nil, attributes |     content_tag(:iframe, nil, attributes) + content_tag(:script, nil, src: full_asset_url('embed.js'), async: true) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def width |   def width | ||||||
|  | |||||||
| @ -8,11 +8,12 @@ class ActivityPub::ProcessAccountService < BaseService | |||||||
|   def call(username, domain, json) |   def call(username, domain, json) | ||||||
|     return if json['inbox'].blank? |     return if json['inbox'].blank? | ||||||
| 
 | 
 | ||||||
|     @json     = json |     @json        = json | ||||||
|     @uri      = @json['id'] |     @uri         = @json['id'] | ||||||
|     @username = username |     @username    = username | ||||||
|     @domain   = domain |     @domain      = domain | ||||||
|     @account  = Account.find_by(uri: @uri) |     @account     = Account.find_by(uri: @uri) | ||||||
|  |     @collections = {} | ||||||
| 
 | 
 | ||||||
|     create_account  if @account.nil? |     create_account  if @account.nil? | ||||||
|     upgrade_account if @account.ostatus? |     upgrade_account if @account.ostatus? | ||||||
| @ -47,11 +48,14 @@ class ActivityPub::ProcessAccountService < BaseService | |||||||
|     @account.url                 = url || @uri |     @account.url                 = url || @uri | ||||||
|     @account.display_name        = @json['name'] || '' |     @account.display_name        = @json['name'] || '' | ||||||
|     @account.note                = @json['summary'] || '' |     @account.note                = @json['summary'] || '' | ||||||
|     @account.avatar_remote_url   = image_url('icon') |     @account.avatar_remote_url   = image_url('icon')  unless skip_download? | ||||||
|     @account.header_remote_url   = image_url('image') |     @account.header_remote_url   = image_url('image') unless skip_download? | ||||||
|     @account.public_key          = public_key || '' |     @account.public_key          = public_key || '' | ||||||
|     @account.locked              = @json['manuallyApprovesFollowers'] || false |     @account.locked              = @json['manuallyApprovesFollowers'] || false | ||||||
|     @account.save! |     @account.statuses_count      = outbox_total_items    if outbox_total_items.present? | ||||||
|  |     @account.following_count     = following_total_items if following_total_items.present? | ||||||
|  |     @account.followers_count     = followers_total_items if followers_total_items.present? | ||||||
|  |     @account.save_with_optional_media! | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def upgrade_account |   def upgrade_account | ||||||
| @ -88,6 +92,33 @@ class ActivityPub::ProcessAccountService < BaseService | |||||||
|     value['href'] |     value['href'] | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def outbox_total_items | ||||||
|  |     collection_total_items('outbox') | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def following_total_items | ||||||
|  |     collection_total_items('following') | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def followers_total_items | ||||||
|  |     collection_total_items('followers') | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def collection_total_items(type) | ||||||
|  |     return if @json[type].blank? | ||||||
|  |     return @collections[type] if @collections.key?(type) | ||||||
|  | 
 | ||||||
|  |     collection = fetch_resource(@json[type]) | ||||||
|  | 
 | ||||||
|  |     @collections[type] = collection.is_a?(Hash) && collection['totalItems'].present? && collection['totalItems'].is_a?(Numeric) ? collection['totalItems'] : nil | ||||||
|  |   rescue HTTP::Error, OpenSSL::SSL::SSLError | ||||||
|  |     @collections[type] = nil | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def skip_download? | ||||||
|  |     @account.suspended? || domain_block&.reject_media? | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   def auto_suspend? |   def auto_suspend? | ||||||
|     domain_block && domain_block.suspend? |     domain_block && domain_block.suspend? | ||||||
|   end |   end | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ class FollowService < BaseService | |||||||
|     raise ActiveRecord::RecordNotFound if target_account.nil? || target_account.id == source_account.id || target_account.suspended? |     raise ActiveRecord::RecordNotFound if target_account.nil? || target_account.id == source_account.id || target_account.suspended? | ||||||
|     raise Mastodon::NotPermittedError  if target_account.blocking?(source_account) || source_account.blocking?(target_account) |     raise Mastodon::NotPermittedError  if target_account.blocking?(source_account) || source_account.blocking?(target_account) | ||||||
| 
 | 
 | ||||||
|     return if source_account.following?(target_account) |     return if source_account.following?(target_account) || source_account.requested?(target_account) | ||||||
| 
 | 
 | ||||||
|     if target_account.locked? || target_account.activitypub? |     if target_account.locked? || target_account.activitypub? | ||||||
|       request_follow(source_account, target_account) |       request_follow(source_account, target_account) | ||||||
|  | |||||||
| @ -27,9 +27,10 @@ class PostStatusService < BaseService | |||||||
|                                         thread: in_reply_to, |                                         thread: in_reply_to, | ||||||
|                                         sensitive: options[:sensitive], |                                         sensitive: options[:sensitive], | ||||||
|                                         spoiler_text: options[:spoiler_text] || '', |                                         spoiler_text: options[:spoiler_text] || '', | ||||||
|                                         visibility: options[:visibility], |                                         visibility: options[:visibility] || account.user&.setting_default_privacy, | ||||||
|                                         language: detect_language_for(text, account), |                                         language: detect_language_for(text, account), | ||||||
|                                         application: options[:application]) |                                         application: options[:application]) | ||||||
|  | 
 | ||||||
|       attach_media(status, media) |       attach_media(status, media) | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -4,16 +4,19 @@ class UnsubscribeService < BaseService | |||||||
|   def call(account) |   def call(account) | ||||||
|     return if account.hub_url.blank? |     return if account.hub_url.blank? | ||||||
| 
 | 
 | ||||||
|     @account  = account |     @account = account | ||||||
|     @response = build_request.perform |  | ||||||
| 
 | 
 | ||||||
|     Rails.logger.debug "PuSH unsubscribe for #{@account.acct} failed: #{@response.status}" unless @response.status.success? |     begin | ||||||
|  |       @response = build_request.perform | ||||||
|  | 
 | ||||||
|  |       Rails.logger.debug "PuSH unsubscribe for #{@account.acct} failed: #{@response.status}" unless @response.status.success? | ||||||
|  |     rescue HTTP::Error, OpenSSL::SSL::SSLError => e | ||||||
|  |       Rails.logger.debug "PuSH unsubscribe for #{@account.acct} failed: #{e}" | ||||||
|  |     end | ||||||
| 
 | 
 | ||||||
|     @account.secret = '' |     @account.secret = '' | ||||||
|     @account.subscription_expires_at = nil |     @account.subscription_expires_at = nil | ||||||
|     @account.save! |     @account.save! | ||||||
|   rescue HTTP::Error, OpenSSL::SSL::SSLError |  | ||||||
|     Rails.logger.debug "PuSH subscription request for #{@account.acct} could not be made due to HTTP or SSL error" |  | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   private |   private | ||||||
|  | |||||||
| @ -1,16 +1,17 @@ | |||||||
| %table.table | .table-wrapper | ||||||
|   %tbody |   %table.table | ||||||
|     %tr |     %tbody | ||||||
|       %td= t('admin.accounts.show.created_reports') |  | ||||||
|       %td= link_to pluralize(account.reports.count, t('admin.accounts.show.report')), admin_reports_path(account_id: account.id) |  | ||||||
|     %tr |  | ||||||
|       %td= t('admin.accounts.show.targeted_reports') |  | ||||||
|       %td= link_to pluralize(account.targeted_reports.count, t('admin.accounts.show.report')), admin_reports_path(target_account_id: account.id) |  | ||||||
|     - if account.silenced? || account.suspended? |  | ||||||
|       %tr |       %tr | ||||||
|         %td= t('admin.accounts.moderation.title') |         %td= t('admin.accounts.show.created_reports') | ||||||
|         %td |         %td= link_to pluralize(account.reports.count, t('admin.accounts.show.report')), admin_reports_path(account_id: account.id) | ||||||
|           - if account.silenced? |       %tr | ||||||
|             %p= t('admin.accounts.moderation.silenced') |         %td= t('admin.accounts.show.targeted_reports') | ||||||
|           - if account.suspended? |         %td= link_to pluralize(account.targeted_reports.count, t('admin.accounts.show.report')), admin_reports_path(target_account_id: account.id) | ||||||
|             %p= t('admin.accounts.moderation.suspended') |       - if account.silenced? || account.suspended? | ||||||
|  |         %tr | ||||||
|  |           %td= t('admin.accounts.moderation.title') | ||||||
|  |           %td | ||||||
|  |             - if account.silenced? | ||||||
|  |               %p= t('admin.accounts.moderation.silenced') | ||||||
|  |             - if account.suspended? | ||||||
|  |               %p= t('admin.accounts.moderation.suspended') | ||||||
|  | |||||||
| @ -50,16 +50,17 @@ | |||||||
|       %button= t('admin.accounts.search') |       %button= t('admin.accounts.search') | ||||||
|       = link_to t('admin.accounts.reset'), admin_accounts_path, class: 'button negative' |       = link_to t('admin.accounts.reset'), admin_accounts_path, class: 'button negative' | ||||||
| 
 | 
 | ||||||
| %table.table | .table-wrapper | ||||||
|   %thead |   %table.table | ||||||
|     %tr |     %thead | ||||||
|       %th= t('admin.accounts.username') |       %tr | ||||||
|       %th= t('admin.accounts.domain') |         %th= t('admin.accounts.username') | ||||||
|       %th= t('admin.accounts.protocol') |         %th= t('admin.accounts.domain') | ||||||
|       %th= t('admin.accounts.confirmed') |         %th= t('admin.accounts.protocol') | ||||||
|       %th= fa_icon 'paper-plane-o' |         %th= t('admin.accounts.confirmed') | ||||||
|       %th |         %th= fa_icon 'paper-plane-o' | ||||||
|   %tbody |         %th | ||||||
|     = render @accounts |     %tbody | ||||||
|  |       = render @accounts | ||||||
| 
 | 
 | ||||||
| = paginate @accounts | = paginate @accounts | ||||||
|  | |||||||
| @ -1,86 +1,86 @@ | |||||||
| - content_for :page_title do | - content_for :page_title do | ||||||
|   = @account.acct |   = @account.acct | ||||||
| 
 | 
 | ||||||
| %table.table | .table-wrapper | ||||||
|   %tbody |   %table.table | ||||||
|     %tr |     %tbody | ||||||
|       %th= t('admin.accounts.username') |       %tr | ||||||
|       %td= @account.username |         %th= t('admin.accounts.username') | ||||||
|     %tr |         %td= @account.username | ||||||
|       %th= t('admin.accounts.domain') |       %tr | ||||||
|       %td= @account.domain |         %th= t('admin.accounts.domain') | ||||||
|     %tr |         %td= @account.domain | ||||||
|       %th= t('admin.accounts.display_name') |       %tr | ||||||
|       %td= @account.display_name |         %th= t('admin.accounts.display_name') | ||||||
|  |         %td= @account.display_name | ||||||
| 
 | 
 | ||||||
|     - if @account.local? |       - if @account.local? | ||||||
|       %tr |  | ||||||
|         %th= t('admin.accounts.email') |  | ||||||
|         %td= @account.user_email |  | ||||||
|       %tr |  | ||||||
|         %th= t('admin.accounts.most_recent_ip') |  | ||||||
|         %td= @account.user_current_sign_in_ip |  | ||||||
|       %tr |  | ||||||
|         %th= t('admin.accounts.most_recent_activity') |  | ||||||
|         %td |  | ||||||
|           - if @account.user_current_sign_in_at |  | ||||||
|             %time.formatted{ datetime: @account.user_current_sign_in_at.iso8601, title: l(@account.user_current_sign_in_at) } |  | ||||||
|               = l @account.user_current_sign_in_at |  | ||||||
|           - else |  | ||||||
|             Never |  | ||||||
|     - else |  | ||||||
|       %tr |  | ||||||
|         %th= t('admin.accounts.profile_url') |  | ||||||
|         %td= link_to @account.url, @account.url |  | ||||||
|       %tr |  | ||||||
|         %th= t('admin.accounts.protocol') |  | ||||||
|         %td= @account.protocol.humanize |  | ||||||
| 
 |  | ||||||
|       - if @account.ostatus? |  | ||||||
|         %tr |         %tr | ||||||
|           %th= t('admin.accounts.feed_url') |           %th= t('admin.accounts.email') | ||||||
|           %td= link_to @account.remote_url, @account.remote_url |           %td= @account.user_email | ||||||
|         %tr |         %tr | ||||||
|           %th= t('admin.accounts.push_subscription_expires') |           %th= t('admin.accounts.most_recent_ip') | ||||||
|  |           %td= @account.user_current_sign_in_ip | ||||||
|  |         %tr | ||||||
|  |           %th= t('admin.accounts.most_recent_activity') | ||||||
|           %td |           %td | ||||||
|             - if @account.subscribed? |             - if @account.user_current_sign_in_at | ||||||
|               %time.formatted{ datetime: @account.subscription_expires_at.iso8601, title: l(@account.subscription_expires_at) } |               %time.formatted{ datetime: @account.user_current_sign_in_at.iso8601, title: l(@account.user_current_sign_in_at) } | ||||||
|                 = l @account.subscription_expires_at |                 = l @account.user_current_sign_in_at | ||||||
|             - else |             - else | ||||||
|               = t('admin.accounts.not_subscribed') |               Never | ||||||
|  |       - else | ||||||
|         %tr |         %tr | ||||||
|           %th= t('admin.accounts.salmon_url') |           %th= t('admin.accounts.profile_url') | ||||||
|           %td= link_to @account.salmon_url, @account.salmon_url |           %td= link_to @account.url, @account.url | ||||||
|       - elsif @account.activitypub? |  | ||||||
|         %tr |         %tr | ||||||
|           %th= t('admin.accounts.inbox_url') |           %th= t('admin.accounts.protocol') | ||||||
|           %td= link_to @account.inbox_url, @account.inbox_url |           %td= @account.protocol.humanize | ||||||
|         %tr |  | ||||||
|           %th= t('admin.accounts.outbox_url') |  | ||||||
|           %td= link_to @account.outbox_url, @account.outbox_url |  | ||||||
| 
 | 
 | ||||||
|     %tr |         - if @account.ostatus? | ||||||
|       %th= t('admin.accounts.follows') |           %tr | ||||||
|       %td= @account.following_count |             %th= t('admin.accounts.feed_url') | ||||||
|     %tr |             %td= link_to @account.remote_url, @account.remote_url | ||||||
|       %th= t('admin.accounts.followers') |           %tr | ||||||
|       %td= @account.followers_count |             %th= t('admin.accounts.push_subscription_expires') | ||||||
|     %tr |             %td | ||||||
|       %th= t('admin.accounts.statuses') |               - if @account.subscribed? | ||||||
|       %td= link_to @account.statuses_count, admin_account_statuses_path(@account.id) |                 %time.formatted{ datetime: @account.subscription_expires_at.iso8601, title: l(@account.subscription_expires_at) } | ||||||
|     %tr |                   = l @account.subscription_expires_at | ||||||
|       %th= t('admin.accounts.media_attachments') |               - else | ||||||
|       %td |                 = t('admin.accounts.not_subscribed') | ||||||
|         = link_to @account.media_attachments.count, admin_account_statuses_path(@account.id, { media: true }) |           %tr | ||||||
|         = surround '(', ')' do |             %th= t('admin.accounts.salmon_url') | ||||||
|           = number_to_human_size @account.media_attachments.sum('file_file_size') |             %td= link_to @account.salmon_url, @account.salmon_url | ||||||
|     %tr |         - elsif @account.activitypub? | ||||||
|       %th= t('.created_reports') |           %tr | ||||||
|       %td= link_to pluralize(@account.reports.count, t('.report')), admin_reports_path(account_id: @account.id) |             %th= t('admin.accounts.inbox_url') | ||||||
|     %tr |             %td= link_to @account.inbox_url, @account.inbox_url | ||||||
|       %th= t('.targeted_reports') |           %tr | ||||||
|       %td= link_to pluralize(@account.targeted_reports.count, t('.report')), admin_reports_path(target_account_id: @account.id) |             %th= t('admin.accounts.outbox_url') | ||||||
|  |             %td= link_to @account.outbox_url, @account.outbox_url | ||||||
| 
 | 
 | ||||||
|  |       %tr | ||||||
|  |         %th= t('admin.accounts.follows') | ||||||
|  |         %td= @account.following_count | ||||||
|  |       %tr | ||||||
|  |         %th= t('admin.accounts.followers') | ||||||
|  |         %td= @account.followers_count | ||||||
|  |       %tr | ||||||
|  |         %th= t('admin.accounts.statuses') | ||||||
|  |         %td= link_to @account.statuses_count, admin_account_statuses_path(@account.id) | ||||||
|  |       %tr | ||||||
|  |         %th= t('admin.accounts.media_attachments') | ||||||
|  |         %td | ||||||
|  |           = link_to @account.media_attachments.count, admin_account_statuses_path(@account.id, { media: true }) | ||||||
|  |           = surround '(', ')' do | ||||||
|  |             = number_to_human_size @account.media_attachments.sum('file_file_size') | ||||||
|  |       %tr | ||||||
|  |         %th= t('.created_reports') | ||||||
|  |         %td= link_to pluralize(@account.reports.count, t('.report')), admin_reports_path(account_id: @account.id) | ||||||
|  |       %tr | ||||||
|  |         %th= t('.targeted_reports') | ||||||
|  |         %td= link_to pluralize(@account.targeted_reports.count, t('.report')), admin_reports_path(target_account_id: @account.id) | ||||||
| 
 | 
 | ||||||
| %div{ style: 'float: right' } | %div{ style: 'float: right' } | ||||||
|   - if @account.local? |   - if @account.local? | ||||||
|  | |||||||
| @ -1,15 +1,16 @@ | |||||||
| - content_for :page_title do | - content_for :page_title do | ||||||
|   = t('admin.domain_blocks.title') |   = t('admin.domain_blocks.title') | ||||||
| 
 | 
 | ||||||
| %table.table | .table-wrapper | ||||||
|   %thead |   %table.table | ||||||
|     %tr |     %thead | ||||||
|       %th= t('admin.domain_blocks.domain') |       %tr | ||||||
|       %th= t('admin.domain_blocks.severity') |         %th= t('admin.domain_blocks.domain') | ||||||
|       %th= t('admin.domain_blocks.reject_media') |         %th= t('admin.domain_blocks.severity') | ||||||
|       %th |         %th= t('admin.domain_blocks.reject_media') | ||||||
|   %tbody |         %th | ||||||
|     = render @domain_blocks |     %tbody | ||||||
|  |       = render @domain_blocks | ||||||
| 
 | 
 | ||||||
| = paginate @domain_blocks | = paginate @domain_blocks | ||||||
| = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path, class: 'button' | = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path, class: 'button' | ||||||
|  | |||||||
| @ -1,12 +1,13 @@ | |||||||
| - content_for :page_title do | - content_for :page_title do | ||||||
|   = t('admin.instances.title') |   = t('admin.instances.title') | ||||||
| 
 | 
 | ||||||
| %table.table | .table-wrapper | ||||||
|   %thead |   %table.table | ||||||
|     %tr |     %thead | ||||||
|       %th= t('admin.instances.domain_name') |       %tr | ||||||
|       %th= t('admin.instances.account_count') |         %th= t('admin.instances.domain_name') | ||||||
|   %tbody |         %th= t('admin.instances.account_count') | ||||||
|     = render @instances |     %tbody | ||||||
|  |       = render @instances | ||||||
| 
 | 
 | ||||||
| = paginate paginated_instances | = paginate paginated_instances | ||||||
|  | |||||||
| @ -10,17 +10,18 @@ | |||||||
| 
 | 
 | ||||||
| = form_tag do | = form_tag do | ||||||
| 
 | 
 | ||||||
|   %table.table |   .table-wrapper | ||||||
|     %thead |     %table.table | ||||||
|       %tr |       %thead | ||||||
|         -# %th |         %tr | ||||||
|         %th= t('admin.reports.id') |           -# %th | ||||||
|         %th= t('admin.reports.target') |           %th= t('admin.reports.id') | ||||||
|         %th= t('admin.reports.reported_by') |           %th= t('admin.reports.target') | ||||||
|         %th= t('admin.reports.comment.label') |           %th= t('admin.reports.reported_by') | ||||||
|         %th= t('admin.reports.report_contents') |           %th= t('admin.reports.comment.label') | ||||||
|         %th |           %th= t('admin.reports.report_contents') | ||||||
|     %tbody |           %th | ||||||
|       = render @reports |       %tbody | ||||||
|  |         = render @reports | ||||||
| 
 | 
 | ||||||
| = paginate @reports | = paginate @reports | ||||||
|  | |||||||
| @ -1,15 +1,16 @@ | |||||||
| - content_for :page_title do | - content_for :page_title do | ||||||
|   = t('admin.subscriptions.title') |   = t('admin.subscriptions.title') | ||||||
| 
 | 
 | ||||||
| %table.table | .table-wrapper | ||||||
|   %thead |   %table.table | ||||||
|     %tr |     %thead | ||||||
|       %th= t('admin.subscriptions.topic') |       %tr | ||||||
|       %th= t('admin.subscriptions.callback_url') |         %th= t('admin.subscriptions.topic') | ||||||
|       %th= t('admin.subscriptions.confirmed') |         %th= t('admin.subscriptions.callback_url') | ||||||
|       %th= t('admin.subscriptions.expires_in') |         %th= t('admin.subscriptions.confirmed') | ||||||
|       %th= t('admin.subscriptions.last_delivery') |         %th= t('admin.subscriptions.expires_in') | ||||||
|   %tbody |         %th= t('admin.subscriptions.last_delivery') | ||||||
|     = render @subscriptions |     %tbody | ||||||
|  |       = render @subscriptions | ||||||
| 
 | 
 | ||||||
| = paginate @subscriptions | = paginate @subscriptions | ||||||
|  | |||||||
| @ -1,28 +1,29 @@ | |||||||
| %h6= t 'sessions.title' | %h6= t 'sessions.title' | ||||||
| %p.muted-hint= t 'sessions.explanation' | %p.muted-hint= t 'sessions.explanation' | ||||||
| 
 | 
 | ||||||
| %table.table.inline-table | .table-wrapper | ||||||
|   %thead |   %table.table.inline-table | ||||||
|     %tr |     %thead | ||||||
|       %th= t 'sessions.browser' |  | ||||||
|       %th= t 'sessions.ip' |  | ||||||
|       %th= t 'sessions.activity' |  | ||||||
|       %td |  | ||||||
|   %tbody |  | ||||||
|     - @sessions.each do |session| |  | ||||||
|       %tr |       %tr | ||||||
|  |         %th= t 'sessions.browser' | ||||||
|  |         %th= t 'sessions.ip' | ||||||
|  |         %th= t 'sessions.activity' | ||||||
|         %td |         %td | ||||||
|           %span{ title: session.user_agent }< |     %tbody | ||||||
|             = fa_icon "#{session_device_icon(session)} fw", 'aria-label' => session_device_icon(session) |       - @sessions.each do |session| | ||||||
|             = ' ' |         %tr | ||||||
|             = t 'sessions.description', browser: t("sessions.browsers.#{session.browser}"), platform: t("sessions.platforms.#{session.platform}") |           %td | ||||||
|         %td |             %span{ title: session.user_agent }< | ||||||
|           %samp= session.ip |               = fa_icon "#{session_device_icon(session)} fw", 'aria-label' => session_device_icon(session) | ||||||
|         %td |               = ' ' | ||||||
|           - if current_session.session_id == session.session_id |               = t 'sessions.description', browser: t("sessions.browsers.#{session.browser}"), platform: t("sessions.platforms.#{session.platform}") | ||||||
|             = t 'sessions.current_session' |           %td | ||||||
|           - else |             %samp= session.ip | ||||||
|             %time.time-ago{ datetime: session.updated_at.iso8601, title: l(session.updated_at) }= l(session.updated_at) |           %td | ||||||
|         %td |             - if current_session.session_id == session.session_id | ||||||
|           - if current_session.session_id != session.session_id |               = t 'sessions.current_session' | ||||||
|             = table_link_to 'times', t('sessions.revoke'), settings_session_path(session), method: :delete |             - else | ||||||
|  |               %time.time-ago{ datetime: session.updated_at.iso8601, title: l(session.updated_at) }= l(session.updated_at) | ||||||
|  |           %td | ||||||
|  |             - if current_session.session_id != session.session_id | ||||||
|  |               = table_link_to 'times', t('sessions.revoke'), settings_session_path(session), method: :delete | ||||||
|  | |||||||
| @ -1,23 +1,24 @@ | |||||||
| - content_for :page_title do | - content_for :page_title do | ||||||
|   = t('doorkeeper.authorized_applications.index.title') |   = t('doorkeeper.authorized_applications.index.title') | ||||||
| 
 | 
 | ||||||
| %table.table | .table-wrapper | ||||||
|   %thead |   %table.table | ||||||
|     %tr |     %thead | ||||||
|       %th= t('doorkeeper.authorized_applications.index.application') |  | ||||||
|       %th= t('doorkeeper.authorized_applications.index.scopes') |  | ||||||
|       %th= t('doorkeeper.authorized_applications.index.created_at') |  | ||||||
|       %th |  | ||||||
|   %tbody |  | ||||||
|     - @applications.each do |application| |  | ||||||
|       %tr |       %tr | ||||||
|         %td |         %th= t('doorkeeper.authorized_applications.index.application') | ||||||
|           - if application.website.blank? |         %th= t('doorkeeper.authorized_applications.index.scopes') | ||||||
|             = application.name |         %th= t('doorkeeper.authorized_applications.index.created_at') | ||||||
|           - else |         %th | ||||||
|             = link_to application.name, application.website, target: '_blank', rel: 'noopener' |     %tbody | ||||||
|         %th!= application.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.join('<br />') |       - @applications.each do |application| | ||||||
|         %td= l application.created_at |         %tr | ||||||
|         %td |           %td | ||||||
|           - unless application.superapp? |             - if application.website.blank? | ||||||
|             = table_link_to 'times', t('doorkeeper.authorized_applications.buttons.revoke'), oauth_authorized_application_path(application), method: :delete, data: { confirm: t('doorkeeper.authorized_applications.confirmations.revoke') } |               = application.name | ||||||
|  |             - else | ||||||
|  |               = link_to application.name, application.website, target: '_blank', rel: 'noopener' | ||||||
|  |           %th!= application.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.join('<br />') | ||||||
|  |           %td= l application.created_at | ||||||
|  |           %td | ||||||
|  |             - unless application.superapp? | ||||||
|  |               = table_link_to 'times', t('doorkeeper.authorized_applications.buttons.revoke'), oauth_authorized_application_path(application), method: :delete, data: { confirm: t('doorkeeper.authorized_applications.confirmations.revoke') } | ||||||
|  | |||||||
| @ -1,19 +1,20 @@ | |||||||
| - content_for :page_title do | - content_for :page_title do | ||||||
|   = t('doorkeeper.applications.index.title') |   = t('doorkeeper.applications.index.title') | ||||||
| 
 | 
 | ||||||
| %table.table | .table-wrapper | ||||||
|   %thead |   %table.table | ||||||
|     %tr |     %thead | ||||||
|       %th= t('doorkeeper.applications.index.application') |  | ||||||
|       %th= t('doorkeeper.applications.index.scopes') |  | ||||||
|       %th |  | ||||||
|   %tbody |  | ||||||
|     - @applications.each do |application| |  | ||||||
|       %tr |       %tr | ||||||
|         %td= link_to application.name, settings_application_path(application) |         %th= t('doorkeeper.applications.index.application') | ||||||
|         %th= application.scopes |         %th= t('doorkeeper.applications.index.scopes') | ||||||
|         %td |         %th | ||||||
|           = table_link_to 'times', t('doorkeeper.applications.index.delete'), settings_application_path(application), method: :delete, data: { confirm: t('doorkeeper.applications.confirmations.destroy') } |     %tbody | ||||||
|  |       - @applications.each do |application| | ||||||
|  |         %tr | ||||||
|  |           %td= link_to application.name, settings_application_path(application) | ||||||
|  |           %th= application.scopes | ||||||
|  |           %td | ||||||
|  |             = table_link_to 'times', t('doorkeeper.applications.index.delete'), settings_application_path(application), method: :delete, data: { confirm: t('doorkeeper.applications.confirmations.destroy') } | ||||||
| 
 | 
 | ||||||
| = paginate @applications | = paginate @applications | ||||||
| = link_to t('doorkeeper.applications.index.new'), new_settings_application_path, class: 'button' | = link_to t('doorkeeper.applications.index.new'), new_settings_application_path, class: 'button' | ||||||
|  | |||||||
| @ -3,22 +3,23 @@ | |||||||
| 
 | 
 | ||||||
| %p.hint= t('applications.warning') | %p.hint= t('applications.warning') | ||||||
| 
 | 
 | ||||||
| %table.table | .table-wrapper | ||||||
|   %tbody |   %table.table | ||||||
|     %tr   |     %tbody | ||||||
|       %th= t('doorkeeper.applications.show.application_id') |       %tr   | ||||||
|       %td |         %th= t('doorkeeper.applications.show.application_id') | ||||||
|         %code= @application.uid |         %td | ||||||
|     %tr |           %code= @application.uid | ||||||
|       %th= t('doorkeeper.applications.show.secret') |       %tr | ||||||
|       %td |         %th= t('doorkeeper.applications.show.secret') | ||||||
|         %code= @application.secret |         %td | ||||||
|     %tr |           %code= @application.secret | ||||||
|       %th{ rowspan: 2}= t('applications.your_token') |       %tr | ||||||
|       %td |         %th{ rowspan: 2}= t('applications.your_token') | ||||||
|         %code= current_user.token_for_app(@application).token |         %td | ||||||
|     %tr |           %code= current_user.token_for_app(@application).token | ||||||
|       %td= table_link_to 'refresh', t('applications.regenerate_token'), regenerate_settings_application_path(@application), method: :post |       %tr | ||||||
|  |         %td= table_link_to 'refresh', t('applications.regenerate_token'), regenerate_settings_application_path(@application), method: :post | ||||||
| 
 | 
 | ||||||
| %hr/ | %hr/ | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,21 +1,22 @@ | |||||||
| - content_for :page_title do | - content_for :page_title do | ||||||
|   = t('settings.export') |   = t('settings.export') | ||||||
| 
 | 
 | ||||||
| %table.table | .table-wrapper | ||||||
|   %tbody |   %table.table | ||||||
|     %tr |     %tbody | ||||||
|       %th= t('exports.storage') |       %tr | ||||||
|       %td= number_to_human_size @export.total_storage |         %th= t('exports.storage') | ||||||
|       %td |         %td= number_to_human_size @export.total_storage | ||||||
|     %tr |         %td | ||||||
|       %th= t('exports.follows') |       %tr | ||||||
|       %td= @export.total_follows |         %th= t('exports.follows') | ||||||
|       %td= table_link_to 'download', t('exports.csv'), settings_exports_follows_path(format: :csv) |         %td= @export.total_follows | ||||||
|     %tr |         %td= table_link_to 'download', t('exports.csv'), settings_exports_follows_path(format: :csv) | ||||||
|       %th= t('exports.blocks') |       %tr | ||||||
|       %td= @export.total_blocks |         %th= t('exports.blocks') | ||||||
|       %td= table_link_to 'download', t('exports.csv'), settings_exports_blocks_path(format: :csv) |         %td= @export.total_blocks | ||||||
|     %tr |         %td= table_link_to 'download', t('exports.csv'), settings_exports_blocks_path(format: :csv) | ||||||
|       %th= t('exports.mutes') |       %tr | ||||||
|       %td= @export.total_mutes |         %th= t('exports.mutes') | ||||||
|       %td= table_link_to 'download', t('exports.csv'), settings_exports_mutes_path(format: :csv) |         %td= @export.total_mutes | ||||||
|  |         %td= table_link_to 'download', t('exports.csv'), settings_exports_mutes_path(format: :csv) | ||||||
|  | |||||||
| @ -12,20 +12,21 @@ | |||||||
|   %p= t('followers.explanation_html') |   %p= t('followers.explanation_html') | ||||||
|   %p= t('followers.true_privacy_html') |   %p= t('followers.true_privacy_html') | ||||||
| 
 | 
 | ||||||
|   %table.table |   .table-wrapper | ||||||
|     %thead |     %table.table | ||||||
|       %tr |       %thead | ||||||
|         %th |  | ||||||
|         %th= t('followers.domain') |  | ||||||
|         %th= t('followers.followers_count') |  | ||||||
|     %tbody |  | ||||||
|       - @domains.each do |domain| |  | ||||||
|         %tr |         %tr | ||||||
|           %td |           %th | ||||||
|             = check_box_tag 'select[]', domain.domain, false, disabled: !@account.locked? unless domain.domain.nil? |           %th= t('followers.domain') | ||||||
|           %td |           %th= t('followers.followers_count') | ||||||
|             %samp= domain.domain.presence || Rails.configuration.x.local_domain |       %tbody | ||||||
|           %td= number_with_delimiter domain.accounts_from_domain |         - @domains.each do |domain| | ||||||
|  |           %tr | ||||||
|  |             %td | ||||||
|  |               = check_box_tag 'select[]', domain.domain, false, disabled: !@account.locked? unless domain.domain.nil? | ||||||
|  |             %td | ||||||
|  |               %samp= domain.domain.presence || Rails.configuration.x.local_domain | ||||||
|  |             %td= number_with_delimiter domain.accounts_from_domain | ||||||
| 
 | 
 | ||||||
|   .action-pagination |   .action-pagination | ||||||
|     .actions |     .actions | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ | |||||||
|       selected: I18n.locale |       selected: I18n.locale | ||||||
| 
 | 
 | ||||||
|     = f.input :filtered_languages, |     = f.input :filtered_languages, | ||||||
|       collection: I18n.available_locales, |       collection: filterable_languages, | ||||||
|       wrapper: :with_block_label, |       wrapper: :with_block_label, | ||||||
|       include_blank: false, |       include_blank: false, | ||||||
|       label_method: lambda { |locale| human_locale(locale) }, |       label_method: lambda { |locale| human_locale(locale) }, | ||||||
|  | |||||||
| @ -1,8 +1,8 @@ | |||||||
| <p>Bonjorn <%= @resource.email %> !<p> | <p>Bonjorn <%= @resource.email %> ! <p> | ||||||
| 
 | 
 | ||||||
| <p>Venètz de vos crear un compte sus <%= @instance %> e vos mercegem :)</p> | <p>Venètz de vos crear un compte sus <%= @instance %> e vos mercegem :)</p> | ||||||
| 
 | 
 | ||||||
| <p>Per confirmar vòstre inscripcion, mercés de clicar sul ligam seguent : <br> | <p>Per confirmar vòstre inscripcion, mercés de clicar sul ligam seguent : <br> | ||||||
| <%= link_to 'Confirmar mon compte', confirmation_url(@resource, confirmation_token: @token) %></p> | <%= link_to 'Confirmar mon compte', confirmation_url(@resource, confirmation_token: @token) %></p> | ||||||
| 
 | 
 | ||||||
| <p>Aprèp vòstra primièra connexion, poiretz accedir a la documentacion de l’aisina.</p> | <p>Aprèp vòstra primièra connexion, poiretz accedir a la documentacion de l’aisina.</p> | ||||||
|  | |||||||
| @ -1,8 +1,8 @@ | |||||||
| Bonjorn <%= @resource.email %> ! | Bonjorn <%= @resource.email %> !  | ||||||
| 
 | 
 | ||||||
| Venètz de vos crear un compte sus <%= @instance %> e vos mercegem :) | Venètz de vos crear un compte sus <%= @instance %> e vos mercegem :) | ||||||
| 
 | 
 | ||||||
| er confirmar vòstre inscripcion, mercés de clicar sul ligam seguent : | er confirmar vòstre inscripcion, mercés de clicar sul ligam seguent :  | ||||||
| <%= link_to 'Confirmar mon compte', confirmation_url(@resource, confirmation_token: @token) %> | <%= link_to 'Confirmar mon compte', confirmation_url(@resource, confirmation_token: @token) %> | ||||||
| 
 | 
 | ||||||
| Aprèp vòstra primièra connexion, poiretz accedir a la documentacion de l’aisina. | Aprèp vòstra primièra connexion, poiretz accedir a la documentacion de l’aisina. | ||||||
|  | |||||||
| @ -1,3 +1,3 @@ | |||||||
| <p>Bonjorn <%= @resource.email %> !</p> | <p>Bonjorn <%= @resource.email %> ! </p> | ||||||
| 
 | 
 | ||||||
| <p>Vos contactem per vos avisar que vòstre senhal per Mastodon es ben estat cambiat.</p> | <p>Vos contactem per vos avisar qu’avèm ben cambiat vòstre senhal Mastodon.</p> | ||||||
|  | |||||||
| @ -1,3 +1,3 @@ | |||||||
| Bonjorn <%= @resource.email %> ! | Bonjorn <%= @resource.email %> !  | ||||||
| 
 | 
 | ||||||
| Vos contactem per vos avisar que vòstre senhal per Mastodon es ben estat cambiat. | Vos contactem per vos avisar qu’avèm ben cambiat vòstre senhal Mastodon. | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| <p>Bonjorn <%= @resource.email %> !</p> | <p>Bonjorn <%= @resource.email %> ! </p> | ||||||
| 
 | 
 | ||||||
| <p>Qualqu’un a demandat una reĩnicializacion de vòstre senhal per Mastodon. Podètz realizar la reĩnicializacion en clicant sul ligam çai-jos.</p> | <p>Qualqu’un a demandat una reĩnicializacion de vòstre senhal per Mastodon. Podètz realizar la reĩnicializacion en clicant sul ligam çai-jos.</p> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| Bonjorn <%= @resource.email %> ! | Bonjorn <%= @resource.email %> !  | ||||||
| 
 | 
 | ||||||
| Qualqu’un a demandat una reĩnicializacion de vòstre senhal per Mastodon. Podètz realizar la reĩnicializacion en clicant sul ligam çai-jos.</p> | Qualqu’un a demandat una reĩnicializacion de vòstre senhal per Mastodon. Podètz realizar la reĩnicializacion en clicant sul ligam çai-jos.</p> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -9,6 +9,9 @@ end | |||||||
| 
 | 
 | ||||||
| Sidekiq.configure_server do |config| | Sidekiq.configure_server do |config| | ||||||
|   config.redis = redis_params |   config.redis = redis_params | ||||||
|  |   config.client_middleware do |chain| | ||||||
|  |     chain.add Mastodon::UniqueRetryJobMiddleware | ||||||
|  |   end | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| Sidekiq.configure_client do |config| | Sidekiq.configure_client do |config| | ||||||
|  | |||||||
| @ -451,11 +451,11 @@ pl: | |||||||
|     show_more: Pokaż więcej |     show_more: Pokaż więcej | ||||||
|     visibilities: |     visibilities: | ||||||
|       private: Tylko dla śledzących |       private: Tylko dla śledzących | ||||||
|       private_long: Widoczny tylko dla osób, które Cię śledzą |       private_long: Widoczne tylko dla osób, które Cię śledzą | ||||||
|       public: Publiczny |       public: Publiczne | ||||||
|       public_long: Widoczny dla wszystkich użytkowników |       public_long: Widoczne dla wszystkich użytkowników | ||||||
|       unlisted: Niewypisany |       unlisted: Niewypisane | ||||||
|       unlisted_long: Widoczny dla wszystkich, ale nie wyświetlany na publicznych osiach czasu |       unlisted_long: Widoczne dla wszystkich, ale nie wyświetlane na publicznych osiach czasu | ||||||
|   stream_entries: |   stream_entries: | ||||||
|     click_to_show: Naciśnij aby wyświetlić |     click_to_show: Naciśnij aby wyświetlić | ||||||
|     pinned: Przypięty wpis |     pinned: Przypięty wpis | ||||||
|  | |||||||
| @ -21,7 +21,7 @@ module Mastodon | |||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def flags |     def flags | ||||||
|       'rc2' |       'rc4' | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def to_a |     def to_a | ||||||
|  | |||||||
| @ -47,7 +47,7 @@ namespace :mastodon do | |||||||
|     confirm = STDIN.gets.chomp |     confirm = STDIN.gets.chomp | ||||||
|     puts |     puts | ||||||
| 
 | 
 | ||||||
|     if confirm.casecmp?('y') |     if confirm.casecmp('y').zero? | ||||||
|       password = SecureRandom.hex |       password = SecureRandom.hex | ||||||
|       user = User.new(email: email, password: password, account_attributes: { username: username }) |       user = User.new(email: email, password: password, account_attributes: { username: username }) | ||||||
|       if user.save |       if user.save | ||||||
| @ -289,13 +289,13 @@ namespace :mastodon do | |||||||
|       puts 'Delete records and associated files from deprecated preview cards? [y/N]: ' |       puts 'Delete records and associated files from deprecated preview cards? [y/N]: ' | ||||||
|       confirm = STDIN.gets.chomp |       confirm = STDIN.gets.chomp | ||||||
| 
 | 
 | ||||||
|       if confirm.casecmp?('y') |       if confirm.casecmp('y').zero? | ||||||
|         DeprecatedPreviewCard.in_batches.destroy_all |         DeprecatedPreviewCard.in_batches.destroy_all | ||||||
| 
 | 
 | ||||||
|         puts 'Drop deprecated preview cards table? [y/N]: ' |         puts 'Drop deprecated preview cards table? [y/N]: ' | ||||||
|         confirm = STDIN.gets.chomp |         confirm = STDIN.gets.chomp | ||||||
| 
 | 
 | ||||||
|         if confirm.casecmp?('y') |         if confirm.casecmp('y').zero? | ||||||
|           ActiveRecord::Migration.drop_table :deprecated_preview_cards |           ActiveRecord::Migration.drop_table :deprecated_preview_cards | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|  | |||||||
							
								
								
									
										43
									
								
								public/embed.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								public/embed.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | |||||||
|  | (function() { | ||||||
|  |   'use strict'; | ||||||
|  | 
 | ||||||
|  |   var ready = function(loaded) { | ||||||
|  |     if (['interactive', 'complete'].indexOf(document.readyState) !== -1) { | ||||||
|  |       loaded(); | ||||||
|  |     } else { | ||||||
|  |       document.addEventListener('DOMContentLoaded', loaded); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   ready(function() { | ||||||
|  |     var iframes = []; | ||||||
|  | 
 | ||||||
|  |     window.addEventListener('message', function(e) { | ||||||
|  |       var data = e.data || {}; | ||||||
|  | 
 | ||||||
|  |       if (data.type !== 'setHeight' || !iframes[data.id]) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       iframes[data.id].height = data.height; | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     [].forEach.call(document.querySelectorAll('iframe.mastodon-embed'), function(iframe) { | ||||||
|  |       iframe.scrolling      = 'no'; | ||||||
|  |       iframe.style.overflow = 'hidden'; | ||||||
|  | 
 | ||||||
|  |       iframes.push(iframe); | ||||||
|  | 
 | ||||||
|  |       var id = iframes.length - 1; | ||||||
|  | 
 | ||||||
|  |       iframe.onload = function() { | ||||||
|  |         iframe.contentWindow.postMessage({ | ||||||
|  |           type: 'setHeight', | ||||||
|  |           id: id, | ||||||
|  |         }, '*'); | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       iframe.onload(); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | })(); | ||||||
| @ -4,10 +4,10 @@ require 'rails_helper' | |||||||
| 
 | 
 | ||||||
| describe SettingsHelper do | describe SettingsHelper do | ||||||
|   describe 'the HUMAN_LOCALES constant' do |   describe 'the HUMAN_LOCALES constant' do | ||||||
|     it 'has the same number of keys as I18n locales exist' do |     it 'includes all I18n locales' do | ||||||
|       options = I18n.available_locales |       options = I18n.available_locales | ||||||
| 
 | 
 | ||||||
|       expect(described_class::HUMAN_LOCALES.keys).to eq(options) |       expect(described_class::HUMAN_LOCALES.keys).to include(*options) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,12 +2,16 @@ require 'rails_helper' | |||||||
| 
 | 
 | ||||||
| RSpec.describe ActivityPub::Activity::Update do | RSpec.describe ActivityPub::Activity::Update do | ||||||
|   let!(:sender) { Fabricate(:account) } |   let!(:sender) { Fabricate(:account) } | ||||||
|    | 
 | ||||||
|   before do |   before do | ||||||
|  |     stub_request(:get, actor_json[:outbox]).to_return(status: 404) | ||||||
|  |     stub_request(:get, actor_json[:followers]).to_return(status: 404) | ||||||
|  |     stub_request(:get, actor_json[:following]).to_return(status: 404) | ||||||
|  | 
 | ||||||
|     sender.update!(uri: ActivityPub::TagManager.instance.uri_for(sender)) |     sender.update!(uri: ActivityPub::TagManager.instance.uri_for(sender)) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   let(:modified_sender) do  |   let(:modified_sender) do | ||||||
|     sender.dup.tap do |modified_sender| |     sender.dup.tap do |modified_sender| | ||||||
|       modified_sender.display_name = 'Totally modified now' |       modified_sender.display_name = 'Totally modified now' | ||||||
|     end |     end | ||||||
|  | |||||||
| @ -91,9 +91,35 @@ RSpec.describe ActivityPub::TagManager do | |||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe '#uri_to_resource' do |   describe '#uri_to_resource' do | ||||||
|     it 'returns the local resource' do |     it 'returns the local account' do | ||||||
|       account = Fabricate(:account) |       account = Fabricate(:account) | ||||||
|       expect(subject.uri_to_resource(subject.uri_for(account), Account)).to eq account |       expect(subject.uri_to_resource(subject.uri_for(account), Account)).to eq account | ||||||
|     end |     end | ||||||
|  | 
 | ||||||
|  |     it 'returns the remote account by matching URI without fragment part' do | ||||||
|  |       account = Fabricate(:account, uri: 'https://example.com/123') | ||||||
|  |       expect(subject.uri_to_resource('https://example.com/123#456', Account)).to eq account | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'returns the local status for ActivityPub URI' do | ||||||
|  |       status = Fabricate(:status) | ||||||
|  |       expect(subject.uri_to_resource(subject.uri_for(status), Status)).to eq status | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'returns the local status for OStatus tag: URI' do | ||||||
|  |       status = Fabricate(:status) | ||||||
|  |       expect(subject.uri_to_resource(::TagManager.instance.uri_for(status), Status)).to eq status | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'returns the local status for OStatus StreamEntry URL' do | ||||||
|  |       status = Fabricate(:status) | ||||||
|  |       stream_entry_url = account_stream_entry_url(status.account, status.stream_entry) | ||||||
|  |       expect(subject.uri_to_resource(stream_entry_url, Status)).to eq status | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'returns the remote status by matching URI without fragment part' do | ||||||
|  |       status = Fabricate(:status, uri: 'https://example.com/123') | ||||||
|  |       expect(subject.uri_to_resource('https://example.com/123#456', Status)).to eq status | ||||||
|  |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  | |||||||
| @ -21,4 +21,18 @@ describe Report do | |||||||
|       expect(report.media_attachments).to eq [media_attachment] |       expect(report.media_attachments).to eq [media_attachment] | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  | 
 | ||||||
|  |   describe 'validatiions' do | ||||||
|  |     it 'has a valid fabricator' do | ||||||
|  |       report = Fabricate(:report) | ||||||
|  |       report.valid? | ||||||
|  |       expect(report).to be_valid | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'is invalid if comment is longer than 1000 characters' do | ||||||
|  |       report = Fabricate.build(:report, comment: Faker::Lorem.characters(1001)) | ||||||
|  |       report.valid? | ||||||
|  |       expect(report).to model_have_error_on_field(:comment) | ||||||
|  |     end | ||||||
|  |   end | ||||||
| end | end | ||||||
|  | |||||||
| @ -41,7 +41,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService do | |||||||
| 
 | 
 | ||||||
|       before do |       before do | ||||||
|         actor[:inbox] = nil |         actor[:inbox] = nil | ||||||
|          | 
 | ||||||
|         stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor)) |         stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor)) | ||||||
|         stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) |         stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) | ||||||
|       end |       end | ||||||
|  | |||||||
| @ -26,7 +26,7 @@ RSpec.describe UnsubscribeService do | |||||||
|     stub_request(:post, 'http://hub.example.com/').to_raise(HTTP::Error) |     stub_request(:post, 'http://hub.example.com/').to_raise(HTTP::Error) | ||||||
|     subject.call(account) |     subject.call(account) | ||||||
| 
 | 
 | ||||||
|     expect(logger).to have_received(:debug).with(/PuSH subscription request for bob@example.com could not be made due to HTTP or SSL error/) |     expect(logger).to have_received(:debug).with(/unsubscribe for bob@example.com failed/) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def stub_logger |   def stub_logger | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user