diff options
Diffstat (limited to 'app')
62 files changed, 686 insertions, 235 deletions
diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 7cfe8fe71..5983c0fbe 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -72,19 +72,4 @@ class Api::BaseController < ApplicationController def render_empty render json: {}, status: 200 end - - def set_maps(statuses) # rubocop:disable Style/AccessorMethodName - if current_account.nil? - @reblogs_map = {} - @favourites_map = {} - @mutes_map = {} - return - end - - status_ids = statuses.compact.flat_map { |s| [s.id, s.reblog_of_id] }.uniq - conversation_ids = statuses.compact.map(&:conversation_id).compact.uniq - @reblogs_map = Status.reblogs_map(status_ids, current_account) - @favourites_map = Status.favourites_map(status_ids, current_account) - @mutes_map = Status.mutes_map(conversation_ids, current_account) - end end diff --git a/app/controllers/api/v1/accounts/lists_controller.rb b/app/controllers/api/v1/accounts/lists_controller.rb new file mode 100644 index 000000000..a7ba89ce2 --- /dev/null +++ b/app/controllers/api/v1/accounts/lists_controller.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class Api::V1::Accounts::ListsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read } + before_action :require_user! + before_action :set_account + + respond_to :json + + def index + @lists = @account.lists.where(account: current_account) + render json: @lists, each_serializer: REST::ListSerializer + end + + private + + def set_account + @account = Account.find(params[:account_id]) + end +end diff --git a/app/controllers/api/v1/lists_controller.rb b/app/controllers/api/v1/lists_controller.rb index 9437373bd..180a91d81 100644 --- a/app/controllers/api/v1/lists_controller.rb +++ b/app/controllers/api/v1/lists_controller.rb @@ -1,18 +1,14 @@ # frozen_string_literal: true class Api::V1::ListsController < Api::BaseController - LISTS_LIMIT = 50 - before_action -> { doorkeeper_authorize! :read }, only: [:index, :show] before_action -> { doorkeeper_authorize! :write }, except: [:index, :show] before_action :require_user! before_action :set_list, except: [:index, :create] - after_action :insert_pagination_headers, only: :index - def index - @lists = List.where(account: current_account).paginate_by_max_id(limit_param(LISTS_LIMIT), params[:max_id], params[:since_id]) + @lists = List.where(account: current_account).all render json: @lists, each_serializer: REST::ListSerializer end @@ -44,36 +40,4 @@ class Api::V1::ListsController < Api::BaseController def list_params params.permit(:title) end - - def insert_pagination_headers - set_pagination_headers(next_path, prev_path) - end - - def next_path - if records_continue? - api_v1_lists_url pagination_params(max_id: pagination_max_id) - end - end - - def prev_path - unless @lists.empty? - api_v1_lists_url pagination_params(since_id: pagination_since_id) - end - end - - def pagination_max_id - @lists.last.id - end - - def pagination_since_id - @lists.first.id - end - - def records_continue? - @lists.size == limit_param(LISTS_LIMIT) - end - - def pagination_params(core_params) - params.permit(:limit).merge(core_params) - end end diff --git a/app/controllers/api/web/push_subscriptions_controller.rb b/app/controllers/api/web/push_subscriptions_controller.rb index d66237feb..52e250d02 100644 --- a/app/controllers/api/web/push_subscriptions_controller.rb +++ b/app/controllers/api/web/push_subscriptions_controller.rb @@ -28,6 +28,8 @@ class Api::Web::PushSubscriptionsController < Api::BaseController }, } + data.deep_merge!(params[:data]) if params[:data] + web_subscription = ::Web::PushSubscription.create!( endpoint: params[:subscription][:endpoint], key_p256dh: params[:subscription][:keys][:p256dh], diff --git a/app/controllers/concerns/rate_limit_headers.rb b/app/controllers/concerns/rate_limit_headers.rb index 36cb91075..b79c558d8 100644 --- a/app/controllers/concerns/rate_limit_headers.rb +++ b/app/controllers/concerns/rate_limit_headers.rb @@ -44,7 +44,8 @@ module RateLimitHeaders end def api_throttle_data - request.env['rack.attack.throttle_data']['api'] + most_limited_type, = request.env['rack.attack.throttle_data'].min_by { |_, v| v[:limit] } + request.env['rack.attack.throttle_data'][most_limited_type] end def request_time diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index abce85812..1d4cb8a57 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -10,6 +10,7 @@ module SettingsHelper eo: 'Esperanto', es: 'Español', fa: 'فارسی', + gl: 'Galego', fi: 'Suomi', fr: 'Français', he: 'עברית', diff --git a/app/javascript/mastodon/actions/favourites.js b/app/javascript/mastodon/actions/favourites.js index 09ce51fce..93094c526 100644 --- a/app/javascript/mastodon/actions/favourites.js +++ b/app/javascript/mastodon/actions/favourites.js @@ -10,6 +10,10 @@ export const FAVOURITED_STATUSES_EXPAND_FAIL = 'FAVOURITED_STATUSES_EXPAND_FA export function fetchFavouritedStatuses() { return (dispatch, getState) => { + if (getState().getIn(['status_lists', 'favourites', 'isLoading'])) { + return; + } + dispatch(fetchFavouritedStatusesRequest()); api(getState).get('/api/v1/favourites').then(response => { @@ -46,7 +50,7 @@ export function expandFavouritedStatuses() { return (dispatch, getState) => { const url = getState().getIn(['status_lists', 'favourites', 'next'], null); - if (url === null) { + if (url === null || getState().getIn(['status_lists', 'favourites', 'isLoading'])) { return; } diff --git a/app/javascript/mastodon/actions/push_notifications.js b/app/javascript/mastodon/actions/push_notifications.js index 55661d2b0..de06385f9 100644 --- a/app/javascript/mastodon/actions/push_notifications.js +++ b/app/javascript/mastodon/actions/push_notifications.js @@ -1,4 +1,5 @@ import axios from 'axios'; +import { pushNotificationsSetting } from '../settings'; export const SET_BROWSER_SUPPORT = 'PUSH_NOTIFICATIONS_SET_BROWSER_SUPPORT'; export const SET_SUBSCRIPTION = 'PUSH_NOTIFICATIONS_SET_SUBSCRIPTION'; @@ -42,11 +43,15 @@ export function saveSettings() { const state = getState().get('push_notifications'); const subscription = state.get('subscription'); const alerts = state.get('alerts'); + const data = { alerts }; axios.put(`/api/web/push_subscriptions/${subscription.get('id')}`, { - data: { - alerts, - }, + data, + }).then(() => { + const me = getState().getIn(['meta', 'me']); + if (me) { + pushNotificationsSetting.set(me, data); + } }); }; } diff --git a/app/javascript/mastodon/features/favourited_statuses/index.js b/app/javascript/mastodon/features/favourited_statuses/index.js index 1e1f5873c..67b107bc8 100644 --- a/app/javascript/mastodon/features/favourited_statuses/index.js +++ b/app/javascript/mastodon/features/favourited_statuses/index.js @@ -9,6 +9,7 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import StatusList from '../../components/status_list'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import { debounce } from 'lodash'; const messages = defineMessages({ heading: { id: 'column.favourites', defaultMessage: 'Favourites' }, @@ -16,6 +17,7 @@ const messages = defineMessages({ const mapStateToProps = state => ({ statusIds: state.getIn(['status_lists', 'favourites', 'items']), + isLoading: state.getIn(['status_lists', 'favourites', 'isLoading'], true), hasMore: !!state.getIn(['status_lists', 'favourites', 'next']), }); @@ -30,6 +32,7 @@ export default class Favourites extends ImmutablePureComponent { columnId: PropTypes.string, multiColumn: PropTypes.bool, hasMore: PropTypes.bool, + isLoading: PropTypes.bool, }; componentWillMount () { @@ -59,12 +62,12 @@ export default class Favourites extends ImmutablePureComponent { this.column = c; } - handleScrollToBottom = () => { + handleScrollToBottom = debounce(() => { this.props.dispatch(expandFavouritedStatuses()); - } + }, 300, { leading: true }) render () { - const { intl, statusIds, columnId, multiColumn, hasMore } = this.props; + const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; const pinned = !!columnId; return ( @@ -85,6 +88,7 @@ export default class Favourites extends ImmutablePureComponent { statusIds={statusIds} scrollKey={`favourited_statuses-${columnId}`} hasMore={hasMore} + isLoading={isLoading} onScrollToBottom={this.handleScrollToBottom} /> </Column> diff --git a/app/javascript/mastodon/features/status/components/card.js b/app/javascript/mastodon/features/status/components/card.js index d3e322c36..f7d980066 100644 --- a/app/javascript/mastodon/features/status/components/card.js +++ b/app/javascript/mastodon/features/status/components/card.js @@ -59,6 +59,8 @@ export default class Card extends React.PureComponent { renderLink () { const { card, maxDescription } = this.props; + const { width } = this.state; + const horizontal = card.get('width') > card.get('height') && (card.get('width') + 100 >= width); let image = ''; let provider = card.get('provider_name'); @@ -75,17 +77,15 @@ export default class Card extends React.PureComponent { provider = decodeIDNA(getHostname(card.get('url'))); } - const className = classnames('status-card', { - 'horizontal': card.get('width') > card.get('height'), - }); + const className = classnames('status-card', { horizontal }); return ( - <a href={card.get('url')} className={className} target='_blank' rel='noopener'> + <a href={card.get('url')} className={className} target='_blank' rel='noopener' ref={this.setRef}> {image} <div className='status-card__content'> <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong> - <p className='status-card__description'>{(card.get('description') || '').substring(0, maxDescription)}</p> + {!horizontal && <p className='status-card__description'>{(card.get('description') || '').substring(0, maxDescription)}</p>} <span className='status-card__host'>{provider}</span> </div> </a> diff --git a/app/javascript/mastodon/features/ui/components/video_modal.js b/app/javascript/mastodon/features/ui/components/video_modal.js index 1437deeb0..6a883759f 100644 --- a/app/javascript/mastodon/features/ui/components/video_modal.js +++ b/app/javascript/mastodon/features/ui/components/video_modal.js @@ -23,6 +23,7 @@ export default class VideoModal extends ImmutablePureComponent { src={media.get('url')} startTime={time} onCloseVideo={onClose} + detailed description={media.get('description')} /> </div> diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js index 003bf23a8..0ee8bb6c8 100644 --- a/app/javascript/mastodon/features/video/index.js +++ b/app/javascript/mastodon/features/video/index.js @@ -17,6 +17,18 @@ const messages = defineMessages({ exit_fullscreen: { id: 'video.exit_fullscreen', defaultMessage: 'Exit full screen' }, }); +const formatTime = secondsNum => { + let hours = Math.floor(secondsNum / 3600); + let minutes = Math.floor((secondsNum - (hours * 3600)) / 60); + let seconds = secondsNum - (hours * 3600) - (minutes * 60); + + if (hours < 10) hours = '0' + hours; + if (minutes < 10) minutes = '0' + minutes; + if (seconds < 10) seconds = '0' + seconds; + + return (hours === '00' ? '' : `${hours}:`) + `${minutes}:${seconds}`; +}; + const findElementPosition = el => { let box; @@ -83,11 +95,13 @@ export default class Video extends React.PureComponent { startTime: PropTypes.number, onOpenVideo: PropTypes.func, onCloseVideo: PropTypes.func, + detailed: PropTypes.bool, intl: PropTypes.object.isRequired, }; state = { - progress: 0, + currentTime: 0, + duration: 0, paused: true, dragging: false, fullscreen: false, @@ -117,7 +131,10 @@ export default class Video extends React.PureComponent { } handleTimeUpdate = () => { - this.setState({ progress: 100 * (this.video.currentTime / this.video.duration) }); + this.setState({ + currentTime: Math.floor(this.video.currentTime), + duration: Math.floor(this.video.duration), + }); } handleMouseDown = e => { @@ -143,8 +160,10 @@ export default class Video extends React.PureComponent { handleMouseMove = throttle(e => { const { x } = getPointerPosition(this.seek, e); - this.video.currentTime = this.video.duration * x; - this.setState({ progress: x * 100 }); + const currentTime = Math.floor(this.video.duration * x); + + this.video.currentTime = currentTime; + this.setState({ currentTime }); }, 60); togglePlay = () => { @@ -226,11 +245,12 @@ export default class Video extends React.PureComponent { } render () { - const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl, alt } = this.props; - const { progress, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state; + const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed } = this.props; + const { currentTime, duration, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state; + const progress = (currentTime / duration) * 100; return ( - <div className={classNames('video-player', { inactive: !revealed, inline: width && height && !fullscreen, fullscreen })} style={{ width, height }} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> + <div className={classNames('video-player', { inactive: !revealed, detailed, inline: width && height && !fullscreen, fullscreen })} style={{ width, height }} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> <video ref={this.setVideoRef} src={src} @@ -267,16 +287,27 @@ export default class Video extends React.PureComponent { /> </div> - <div className='video-player__buttons left'> - <button aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button> - <button aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button> - {!onCloseVideo && <button aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></button>} - </div> - - <div className='video-player__buttons right'> - {(!fullscreen && onOpenVideo) && <button aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><i className='fa fa-fw fa-expand' /></button>} - {onCloseVideo && <button aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><i className='fa fa-fw fa-times' /></button>} - <button aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><i className={classNames('fa fa-fw', { 'fa-arrows-alt': !fullscreen, 'fa-compress': fullscreen })} /></button> + <div className='video-player__buttons-bar'> + <div className='video-player__buttons left'> + <button aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button> + <button aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button> + + {!onCloseVideo && <button aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></button>} + + {(detailed || fullscreen) && + <span> + <span className='video-player__time-current'>{formatTime(currentTime)}</span> + <span className='video-player__time-sep'>/</span> + <span className='video-player__time-total'>{formatTime(duration)}</span> + </span> + } + </div> + + <div className='video-player__buttons right'> + {(!fullscreen && onOpenVideo) && <button aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><i className='fa fa-fw fa-expand' /></button>} + {onCloseVideo && <button aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><i className='fa fa-fw fa-compress' /></button>} + <button aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><i className={classNames('fa fa-fw', { 'fa-arrows-alt': !fullscreen, 'fa-compress': fullscreen })} /></button> + </div> </div> </div> </div> diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index a984b38d2..ec66a0027 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "تحميل ...", "media_gallery.toggle_visible": "عرض / إخفاء", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index d20120b11..1c04b3bfa 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Зареждане...", "media_gallery.toggle_visible": "Toggle visibility", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index bfa931fc0..f705937fd 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Carregant...", "media_gallery.toggle_visible": "Alternar visibilitat", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index b6d9e27a7..6354f18b6 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Wird geladen …", "media_gallery.toggle_visible": "Sichtbarkeit umschalten", diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index f5154634c..0f766af6a 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -135,7 +135,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Loading...", "media_gallery.toggle_visible": "Toggle visibility", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 619c7320a..9e66c379f 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Ŝarganta…", "media_gallery.toggle_visible": "Baskuli videblecon", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 411615744..6122a79ab 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Cargando…", "media_gallery.toggle_visible": "Cambiar visibilidad", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index aa5c21feb..75057a7dd 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "بارگیری...", "media_gallery.toggle_visible": "تغییر پیدایی", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index db9319e2e..4ddc1cca7 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Ladataan...", "media_gallery.toggle_visible": "Toggle visibility", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 9b9469bc2..a7a8876d0 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -63,8 +63,8 @@ "confirmations.block.message": "Confirmez-vous le blocage de {name} ?", "confirmations.delete.confirm": "Supprimer", "confirmations.delete.message": "Confirmez-vous la suppression de ce pouet ?", - "confirmations.delete_list.confirm": "Delete", - "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", + "confirmations.delete_list.confirm": "Supprimer", + "confirmations.delete_list.message": "Êtes-vous sûr de vouloir supprimer définitivement cette liste ?", "confirmations.domain_block.confirm": "Masquer le domaine entier", "confirmations.domain_block.message": "Êtes-vous vraiment, vraiment sûr⋅e de vouloir bloquer {domain} en entier ? Dans la plupart des cas, quelques blocages ou masquages ciblés sont suffisants et préférables.", "confirmations.mute.confirm": "Masquer", @@ -114,27 +114,27 @@ "keyboard_shortcuts.description": "Description", "keyboard_shortcuts.down": "descendre dans la liste", "keyboard_shortcuts.enter": "to open status", - "keyboard_shortcuts.favourite": "to favourite", - "keyboard_shortcuts.heading": "Keyboard Shortcuts", - "keyboard_shortcuts.hotkey": "Hotkey", - "keyboard_shortcuts.legend": "to display this legend", - "keyboard_shortcuts.mention": "to mention author", - "keyboard_shortcuts.reply": "to reply", + "keyboard_shortcuts.favourite": "vers les favoris", + "keyboard_shortcuts.heading": "Raccourcis clavier", + "keyboard_shortcuts.hotkey": "Raccourci", + "keyboard_shortcuts.legend": "pour afficher cette légende", + "keyboard_shortcuts.mention": "pour mentionner l'auteur", + "keyboard_shortcuts.reply": "pour répondre", "keyboard_shortcuts.search": "to focus search", - "keyboard_shortcuts.toot": "to start a brand new toot", + "keyboard_shortcuts.toot": "pour démarrer un tout nouveau pouet", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", "lightbox.close": "Fermer", "lightbox.next": "Suivant", "lightbox.previous": "Précédent", - "lists.account.add": "Add to list", - "lists.account.remove": "Remove from list", - "lists.delete": "Delete list", - "lists.edit": "Edit list", - "lists.new.create": "Add list", - "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", - "lists.subheading": "Your lists", + "lists.account.add": "Ajouter à la liste", + "lists.account.remove": "Supprimer de la liste", + "lists.delete": "Effacer la liste", + "lists.edit": "Éditer la liste", + "lists.new.create": "Ajouter une liste", + "lists.new.title_placeholder": "Titre de la nouvelle liste", + "lists.search": "Rechercher parmi les gens que vous suivez", + "lists.subheading": "Vos listes", "loading_indicator.label": "Chargement…", "media_gallery.toggle_visible": "Modifier la visibilité", "missing_indicator.label": "Non trouvé", @@ -145,8 +145,8 @@ "navigation_bar.favourites": "Favoris", "navigation_bar.follow_requests": "Demandes de suivi", "navigation_bar.info": "Plus d’informations", - "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts", - "navigation_bar.lists": "Lists", + "navigation_bar.keyboard_shortcuts": "Raccourcis clavier", + "navigation_bar.lists": "Listes", "navigation_bar.logout": "Déconnexion", "navigation_bar.mutes": "Comptes masqués", "navigation_bar.pins": "Pouets épinglés", @@ -241,7 +241,7 @@ "tabs_bar.home": "Accueil", "tabs_bar.local_timeline": "Fil public local", "tabs_bar.notifications": "Notifications", - "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", + "ui.beforeunload": "Votre brouillon sera perdu si vous quittez Mastodon.", "upload_area.title": "Glissez et déposez pour envoyer", "upload_button.label": "Joindre un média", "upload_form.description": "Décrire pour les malvoyants", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json new file mode 100644 index 000000000..bb0b1a9fd --- /dev/null +++ b/app/javascript/mastodon/locales/gl.json @@ -0,0 +1,259 @@ +{ + "account.block": "Bloquear @{name}", + "account.block_domain": "Ocultar calquer contido de {domain}", + "account.disclaimer_full": "A información inferior podería mostrar un perfil incompleto da usuaria.", + "account.edit_profile": "Editar perfil", + "account.follow": "Seguir", + "account.followers": "Seguidoras", + "account.follows": "Seguindo", + "account.follows_you": "Séguena", + "account.hide_reblogs": "Ocultar repeticións de @{name}", + "account.media": "Medios", + "account.mention": "Mencionar @{name}", + "account.moved_to": "{name} marchou a:", + "account.mute": "Acalar @{name}", + "account.mute_notifications": "Acalar as notificacións de @{name}", + "account.posts": "Publicacións", + "account.report": "Informar sobre @{name}", + "account.requested": "Agardando aceptación. Pulse para cancelar a solicitude de seguimento", + "account.share": "Compartir o perfil de @{name}", + "account.show_reblogs": "Mostrar repeticións de @{name}", + "account.unblock": "Desbloquear @{name}", + "account.unblock_domain": "Non ocultar {domain}", + "account.unfollow": "Non seguir", + "account.unmute": "Non acalar @{name}", + "account.unmute_notifications": "Desbloquear as notificacións de @{name}", + "account.view_full_profile": "Ver o perfil completo", + "boost_modal.combo": "Pulse {combo} para saltar esto a próxima vez", + "bundle_column_error.body": "Houbo un fallo mentras se cargaba este compoñente.", + "bundle_column_error.retry": "Inténteo de novo", + "bundle_column_error.title": "Fallo na rede", + "bundle_modal_error.close": "Pechar", + "bundle_modal_error.message": "Algo fallou mentras se cargaba este compoñente.", + "bundle_modal_error.retry": "Inténteo de novo", + "column.blocks": "Usuarias bloqueadas", + "column.community": "Liña temporal local", + "column.favourites": "Favoritas", + "column.follow_requests": "Peticións de seguimento", + "column.home": "Inicio", + "column.lists": "Lists", + "column.mutes": "Usuarias acaladas", + "column.notifications": "Notificacións", + "column.pins": "Mensaxes fixadas", + "column.public": "Liña temporal federada", + "column_back_button.label": "Atrás", + "column_header.hide_settings": "Agochar axustes", + "column_header.moveLeft_settings": "Mover a columna hacia a esquerda", + "column_header.moveRight_settings": "Mover a columna hacia a dereita", + "column_header.pin": "Fixar", + "column_header.show_settings": "Mostras axustes", + "column_header.unpin": "Soltar", + "column_subheading.navigation": "Navegación", + "column_subheading.settings": "Axustes", + "compose_form.lock_disclaimer": "A súa conta non está {locked}. Calquera pode seguila para ver as súas mensaxes só-para-seguidoras.", + "compose_form.lock_disclaimer.lock": "bloqueado", + "compose_form.placeholder": "A qué andas?", + "compose_form.publish": "Toot", + "compose_form.publish_loud": "{publish}!", + "compose_form.sensitive": "Marcar medios como sensibles", + "compose_form.spoiler": "Agochar texto detrás de un aviso", + "compose_form.spoiler_placeholder": "Escriba o aviso aquí", + "confirmation_modal.cancel": "Cancelar", + "confirmations.block.confirm": "Bloquear", + "confirmations.block.message": "Está segura de querer bloquear a {name}?", + "confirmations.delete.confirm": "Borrar", + "confirmations.delete.message": "Está segura de que quere eliminar este estado?", + "confirmations.delete_list.confirm": "Delete", + "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", + "confirmations.domain_block.confirm": "Agochar un dominio completo", + "confirmations.domain_block.message": "Realmente está segura de que quere bloquear por completo o dominio {domain}? Normalmente é suficiente, e preferible, bloquear de xeito selectivo varios elementos.", + "confirmations.mute.confirm": "Acalar", + "confirmations.mute.message": "Está segura de que quere acalar a {name}?", + "confirmations.unfollow.confirm": "Deixar de seguir", + "confirmations.unfollow.message": "Quere deixar de seguir a {name}?", + "embed.instructions": "Copie o código inferior para incrustar no seu sitio web este estado.", + "embed.preview": "Así será mostrado:", + "emoji_button.activity": "Actividade", + "emoji_button.custom": "Personalizado", + "emoji_button.flags": "Marcas", + "emoji_button.food": "Comida e Bebida", + "emoji_button.label": "Insertar emoji", + "emoji_button.nature": "Natureza", + "emoji_button.not_found": "Sen emojos!! (╯°□°)╯︵ ┻━┻", + "emoji_button.objects": "Obxetos", + "emoji_button.people": "Xente", + "emoji_button.recent": "Utilizadas con frecuencia", + "emoji_button.search": "Buscar...", + "emoji_button.search_results": "Resultados da busca", + "emoji_button.symbols": "Símbolos", + "emoji_button.travel": "Viaxes e Lugares", + "empty_column.community": "A liña temporal local está baldeira. Escriba algo de xeito público para que rule!", + "empty_column.hashtag": "Aínda non hai nada con esta etiqueta.", + "empty_column.home": "A súa liña temporal de inicio está baldeira! Visite {public} ou utilice a busca para atopar outras usuarias.", + "empty_column.home.public_timeline": "a liña temporal pública", + "empty_column.list": "Aínda non hai nada en esta lista.", + "empty_column.notifications": "Aínda non ten notificacións. Interactúe con outras para iniciar unha conversa.", + "empty_column.public": "Nada por aquí! Escriba algo de xeito público, ou siga manualmente usuarias de outras instancias para ir enchéndoa", + "follow_request.authorize": "Autorizar", + "follow_request.reject": "Rexeitar", + "getting_started.appsshort": "Aplicacións", + "getting_started.faq": "PMF", + "getting_started.heading": "Comezando", + "getting_started.open_source_notice": "Mastodon é software de código aberto. Pode contribuír ou informar de fallos en GitHub en {github}.", + "getting_started.userguide": "Guía de usuaria", + "home.column_settings.advanced": "Avanzado", + "home.column_settings.basic": "Básico", + "home.column_settings.filter_regex": "Filtrar expresións regulares", + "home.column_settings.show_reblogs": "Mostrar repeticións", + "home.column_settings.show_replies": "Mostrar respostas", + "home.settings": "Axustes da columna", + "keyboard_shortcuts.back": "voltar atrás", + "keyboard_shortcuts.boost": "repetir", + "keyboard_shortcuts.column": "destacar un estado en unha das columnas", + "keyboard_shortcuts.compose": "Foco no área de escritura", + "keyboard_shortcuts.description": "Descrición", + "keyboard_shortcuts.down": "ir hacia abaixo na lista", + "keyboard_shortcuts.enter": "abrir estado", + "keyboard_shortcuts.favourite": "marcar como favorito", + "keyboard_shortcuts.heading": "Atallos do teclado", + "keyboard_shortcuts.hotkey": "Tecla de acceso directo", + "keyboard_shortcuts.legend": "para mostrar esta lenda", + "keyboard_shortcuts.mention": "para mencionar o autor", + "keyboard_shortcuts.reply": "para responder", + "keyboard_shortcuts.search": "para centrar a busca", + "keyboard_shortcuts.toot": "escribir un toot novo", + "keyboard_shortcuts.unfocus": "quitar o foco do área de escritura/busca", + "keyboard_shortcuts.up": "ir hacia arriba na lista", + "lightbox.close": "Fechar", + "lightbox.next": "Seguinte", + "lightbox.previous": "Anterior", + "lists.account.add": "Add to list", + "lists.account.remove": "Remove from list", + "lists.delete": "Delete list", + "lists.edit": "Edit list", + "lists.new.create": "Add list", + "lists.new.title_placeholder": "New list title", + "lists.search": "Search among people you follow", + "lists.subheading": "Your lists", + "loading_indicator.label": "Cargando...", + "media_gallery.toggle_visible": "Dar visibilidade", + "missing_indicator.label": "Non atopado", + "mute_modal.hide_notifications": "Esconder notificacións deste usuario?", + "navigation_bar.blocks": "Usuarios bloqueados", + "navigation_bar.community_timeline": "Liña temporal local", + "navigation_bar.edit_profile": "Editar perfil", + "navigation_bar.favourites": "Favoritas", + "navigation_bar.follow_requests": "Peticións de seguimento", + "navigation_bar.info": "Sobre esta instancia", + "navigation_bar.keyboard_shortcuts": "Atallos do teclado", + "navigation_bar.lists": "Lists", + "navigation_bar.logout": "Sair", + "navigation_bar.mutes": "Usuarias acaladas", + "navigation_bar.pins": "Mensaxes fixadas", + "navigation_bar.preferences": "Preferencias", + "navigation_bar.public_timeline": "Liña temporal federada", + "notification.favourite": "{name} marcou como favorito o seu estado", + "notification.follow": "{name} está a seguila", + "notification.mention": "{name} mencionoute", + "notification.reblog": "{name} promocionou o seu estado", + "notifications.clear": "Limpar notificacións", + "notifications.clear_confirmation": "Estás seguro de que queres limpar permanentemente todas as túas notificacións?", + "notifications.column_settings.alert": "Notificacións de escritorio", + "notifications.column_settings.favourite": "Favoritas:", + "notifications.column_settings.follow": "Novos seguidores:", + "notifications.column_settings.mention": "Mencións:", + "notifications.column_settings.push": "Enviar notificacións", + "notifications.column_settings.push_meta": "Este aparello", + "notifications.column_settings.reblog": "Promocións:", + "notifications.column_settings.show": "Mostrar en columna", + "notifications.column_settings.sound": "Reproducir son", + "onboarding.done": "Feito", + "onboarding.next": "Seguinte", + "onboarding.page_five.public_timelines": "A liña de tempo local mostra as publicacións públicas de todos en {domain}. A liña de tempo federada mostra as publicacións públicas de todos os que as persoas en {domain} seguen. Estas son as Liñas de tempo públicas, unha boa forma de descubrir novas persoas.", + "onboarding.page_four.home": "A liña de tempo local mostra as publicacións das persoas que segues.", + "onboarding.page_four.notifications": "A columna de notificacións mostra cando alguén interactúa contigo.", + "onboarding.page_one.federation": "Mastodon é unha rede de servidores independentes que se unen para facer unha rede social máis grande. Chamamos instancias a estes servidores.", + "onboarding.page_one.handle": "Estás en {domain}, polo que o teu nome de usuario completo é {handle}", + "onboarding.page_one.welcome": "Benvido a Mastodon!", + "onboarding.page_six.admin": "O administrador da túa instancia é {admin}.", + "onboarding.page_six.almost_done": "Case feito...", + "onboarding.page_six.appetoot": "Que tootes ben!", + "onboarding.page_six.apps_available": "Hai {apps} dispoñíbeis para iOS, Android e outras plataformas.", + "onboarding.page_six.github": "Mastodon é un software gratuito e de código aberto. Pode informar de erros, solicitar novas funcionalidades ou contribuír ao código en {github}.", + "onboarding.page_six.guidelines": "directrices da comunidade", + "onboarding.page_six.read_guidelines": "Por favor, le as {guidelines} do {domain}!", + "onboarding.page_six.various_app": "aplicacións móbiles", + "onboarding.page_three.profile": "Edita o teu perfil para cambiar o teu avatar, bio e nome. Alí, tamén atoparás outras preferencias.", + "onboarding.page_three.search": "Utilice a barra de busca para atopar xente e descubrir etiquetas, como {illustration} e {introductions}. Para atopar unha usuaria que non está en esta instancia utilice o seu enderezo completo.", + "onboarding.page_two.compose": "Escriba mensaxes desde a columna de composición. Pode subir imaxes, mudar as opcións de intimidade e engadir avisos sobre o contido coas iconas inferiores.", + "onboarding.skip": "Saltar", + "privacy.change": "Axustar a intimidade do estado", + "privacy.direct.long": "Enviar exclusivamente as usuarias mencionadas", + "privacy.direct.short": "Directa", + "privacy.private.long": "Enviar só as seguidoras", + "privacy.private.short": "Só-seguidoras", + "privacy.public.long": "Publicar na liña temporal pública", + "privacy.public.short": "Pública", + "privacy.unlisted.long": "Non publicar en liñas temporais públicas", + "privacy.unlisted.short": "Non listada", + "relative_time.days": "{number}d", + "relative_time.hours": "{number}h", + "relative_time.just_now": "agora", + "relative_time.minutes": "{number}m", + "relative_time.seconds": "{number}s", + "reply_indicator.cancel": "Cancelar", + "report.placeholder": "Comentarios adicionais", + "report.submit": "Enviar", + "report.target": "Informar {target}", + "search.placeholder": "Buscar", + "search_popout.search_format": "Formato de busca avanzada", + "search_popout.tips.hashtag": "etiqueta", + "search_popout.tips.status": "estado", + "search_popout.tips.text": "Texto simple devolve coincidencias con nomes públicos, nomes de usuaria e etiquetas", + "search_popout.tips.user": "usuaria", + "search_results.total": "{count, number} {count,plural,one {result} outros {results}}", + "standalone.public_title": "Ollada dentro...", + "status.cannot_reblog": "Esta mensaxe non pode ser promocionada", + "status.delete": "Eliminar", + "status.embed": "Incrustar", + "status.favourite": "Favorita", + "status.load_more": "Cargar máis", + "status.media_hidden": "Medios ocultos", + "status.mention": "Mencionar @{name}", + "status.more": "Máis", + "status.mute_conversation": "Acalar conversa", + "status.open": "Expandir este estado", + "status.pin": "Fixar no perfil", + "status.reblog": "Promocionar", + "status.reblogged_by": "{name} promocionado", + "status.reply": "Resposta", + "status.replyAll": "Resposta a conversa", + "status.report": "Informar @{name}", + "status.sensitive_toggle": "Pulse para ver", + "status.sensitive_warning": "Contido sensible", + "status.share": "Compartir", + "status.show_less": "Mostrar menos", + "status.show_more": "Mostrar máis", + "status.unmute_conversation": "Non acalar a conversa", + "status.unpin": "Despegar do perfil", + "tabs_bar.compose": "Compoñer", + "tabs_bar.federated_timeline": "Federado", + "tabs_bar.home": "Inicio", + "tabs_bar.local_timeline": "Local", + "tabs_bar.notifications": "Notificacións", + "ui.beforeunload": "O borrador perderase se sae de Mastodon.", + "upload_area.title": "Arrastre e solte para subir", + "upload_button.label": "Engadir medios", + "upload_form.description": "Describa para deficientes visuais", + "upload_form.undo": "Desfacer", + "upload_progress.label": "Subindo...", + "video.close": "Pechar video", + "video.exit_fullscreen": "Saír da pantalla completa", + "video.expand": "Expandir vídeo", + "video.fullscreen": "Pantalla completa", + "video.hide": "Agochar vídeo", + "video.mute": "Acalar son", + "video.pause": "Pausar", + "video.play": "Reproducir", + "video.unmute": "Permitir son" +} diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index ec1e30dd5..5444c8e34 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "טוען...", "media_gallery.toggle_visible": "נראה\\בלתי נראה", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index c21482670..f70c66223 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Učitavam...", "media_gallery.toggle_visible": "Preklopi vidljivost", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 71dd810b6..7cb816fe9 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Betöltés...", "media_gallery.toggle_visible": "Toggle visibility", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index 744423e78..429b77182 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Tunggu sebentar...", "media_gallery.toggle_visible": "Tampil/Sembunyikan", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index b1523e626..3e5c8edb9 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Kargante...", "media_gallery.toggle_visible": "Chanjar videbleso", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 9a2d320fd..e2ad1632a 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Carico...", "media_gallery.toggle_visible": "Imposta visibilità", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 125618bf2..40ad66a7f 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -24,7 +24,7 @@ "account.unmute": "ミュート解除", "account.unmute_notifications": "@{name}さんからの通知を受け取らない", "account.view_full_profile": "全ての情報を見る", - "boost_modal.combo": "次からは{combo}を押せば、これをスキップできます。", + "boost_modal.combo": "次からは{combo}を押せば、これをスキップできます", "bundle_column_error.body": "コンポーネントの読み込み中に問題が発生しました。", "bundle_column_error.retry": "再試行", "bundle_column_error.title": "ネットワークエラー", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 3f47baa76..472a52a99 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "불러오는 중...", "media_gallery.toggle_visible": "표시 전환", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 26e86308d..c290ed767 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -107,22 +107,22 @@ "home.column_settings.show_reblogs": "Boosts tonen", "home.column_settings.show_replies": "Reacties tonen", "home.settings": "Kolom-instellingen", - "keyboard_shortcuts.back": "om terug te navigeren", + "keyboard_shortcuts.back": "om terug te gaan", "keyboard_shortcuts.boost": "om te boosten", - "keyboard_shortcuts.column": "om te focussen op een status in één van de kolommen", - "keyboard_shortcuts.compose": "om te focussen op het toot tekstvak", - "keyboard_shortcuts.description": "Description", + "keyboard_shortcuts.column": "om op een toot te focussen in één van de kolommen", + "keyboard_shortcuts.compose": "om het tekstvak voor toots te focussen", + "keyboard_shortcuts.description": "Omschrijving", "keyboard_shortcuts.down": "om naar beneden door de lijst te bewegen", "keyboard_shortcuts.enter": "to open status", - "keyboard_shortcuts.favourite": "om het te markeren als favoriet", - "keyboard_shortcuts.heading": "Keyboard Shortcuts", + "keyboard_shortcuts.favourite": "om als favoriet te markeren", + "keyboard_shortcuts.heading": "Sneltoetsen", "keyboard_shortcuts.hotkey": "Sneltoets", "keyboard_shortcuts.legend": "om deze legenda weer te geven", "keyboard_shortcuts.mention": "om de auteur te vermelden", - "keyboard_shortcuts.reply": "om te antwoorden", - "keyboard_shortcuts.search": "om te focussen op zoeken", + "keyboard_shortcuts.reply": "om te reageren", + "keyboard_shortcuts.search": "om het zoekvak te focussen", "keyboard_shortcuts.toot": "om een nieuwe toot te starten", - "keyboard_shortcuts.unfocus": "om te ontfocussen van het toot tekstvak/zoeken", + "keyboard_shortcuts.unfocus": "om het tekst- en zoekvak te ontfocussen", "keyboard_shortcuts.up": "om omhoog te bewegen in de lijst", "lightbox.close": "Sluiten", "lightbox.next": "Volgende", @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Laden…", "media_gallery.toggle_visible": "Media wel/niet tonen", @@ -204,7 +204,7 @@ "reply_indicator.cancel": "Annuleren", "report.placeholder": "Extra opmerkingen", "report.submit": "Verzenden", - "report.target": "Rapporteren van", + "report.target": "Rapporteer {target}", "search.placeholder": "Zoeken", "search_popout.search_format": "Geavanceerd zoeken", "search_popout.tips.hashtag": "hashtag", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index 233b6c946..bf2b6259a 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Laster...", "media_gallery.toggle_visible": "Veksle synlighet", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index ec7202ff6..f8b4751d6 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -10,22 +10,22 @@ "account.hide_reblogs": "Rescondre los partages de @{name}", "account.media": "Mèdias", "account.mention": "Mencionar @{name}", - "account.moved_to": "{name} a mudat los catons a : ", + "account.moved_to": "{name} a mudat los catons a :", "account.mute": "Rescondre @{name}", - "account.mute_notifications": "Mute notifications from @{name}", + "account.mute_notifications": "Rescondre las notificacions de @{name}", "account.posts": "Estatuts", "account.report": "Senhalar @{name}", - "account.requested": "Invitacion mandada. Clicatz per anullar.", + "account.requested": "Invitacion mandada. Clicatz per anullar", "account.share": "Partejar lo perfil a @{name}", "account.show_reblogs": "Mostrar los partages de @{name}", "account.unblock": "Desblocar @{name}", "account.unblock_domain": "Desblocar {domain}", "account.unfollow": "Quitar de sègre", "account.unmute": "Quitar de rescondre @{name}", - "account.unmute_notifications": "Unmute notifications from @{name}", + "account.unmute_notifications": "Mostrar las notificacions de @{name}", "account.view_full_profile": "Veire lo perfil complet", "boost_modal.combo": "Podètz botar {combo} per passar aquò lo còp que ven", - "bundle_column_error.body": "Quicòm a fach meuca pendent lo cargament d’aqueste compausant.", + "bundle_column_error.body": "Quicòm a fach mèuca pendent lo cargament d’aqueste compausant.", "bundle_column_error.retry": "Tornar ensajar", "bundle_column_error.title": "Error de ret", "bundle_modal_error.close": "Tampar", @@ -36,7 +36,7 @@ "column.favourites": "Favorits", "column.follow_requests": "Demandas d’abonament", "column.home": "Acuèlh", - "column.lists": "Lists", + "column.lists": "Listas", "column.mutes": "Personas rescondudas", "column.notifications": "Notificacions", "column.pins": "Tuts penjats", @@ -63,8 +63,8 @@ "confirmations.block.message": "Sètz segur de voler blocar {name} ?", "confirmations.delete.confirm": "Escafar", "confirmations.delete.message": "Sètz segur de voler escafar l’estatut ?", - "confirmations.delete_list.confirm": "Delete", - "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", + "confirmations.delete_list.confirm": "Suprimir", + "confirmations.delete_list.message": "Sètz segur de voler suprimir aquesta lista per totjorn ?", "confirmations.domain_block.confirm": "Amagar tot lo domeni", "confirmations.domain_block.message": "Sètz segur segur de voler blocar completament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.", "confirmations.mute.confirm": "Rescondre", @@ -72,7 +72,7 @@ "confirmations.unfollow.confirm": "Quitar de sègre", "confirmations.unfollow.message": "Volètz vertadièrament quitar de sègre {name} ?", "embed.instructions": "Embarcar aqueste estatut per lo far veire sus un site Internet en copiar lo còdi çai-jos.", - "embed.preview": "Semblarà aquò : ", + "embed.preview": "Semblarà aquò :", "emoji_button.activity": "Activitats", "emoji_button.custom": "Personalizats", "emoji_button.flags": "Drapèus", @@ -84,16 +84,16 @@ "emoji_button.people": "Gents", "emoji_button.recent": "Sovent utilizats", "emoji_button.search": "Cercar…", - "emoji_button.search_results": "Resultat de recèrca", + "emoji_button.search_results": "Resultats de recèrca", "emoji_button.symbols": "Simbòls", "emoji_button.travel": "Viatges & lòcs", "empty_column.community": "Lo flux public local es void. Escrivètz quicòm per lo garnir !", - "empty_column.hashtag": "I a pas encara de contengut ligat a aqueste hashtag", + "empty_column.hashtag": "I a pas encara de contengut ligat a aquesta etiqueta.", "empty_column.home": "Vòstre flux d’acuèlh es void. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.", "empty_column.home.public_timeline": "lo flux public", "empty_column.list": "I a pas res dins la lista pel moment.", "empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualqu’un per començar una conversacion.", - "empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo flux public.", + "empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo flux public", "follow_request.authorize": "Autorizar", "follow_request.reject": "Regetar", "getting_started.appsshort": "Apps", @@ -116,7 +116,7 @@ "keyboard_shortcuts.enter": "per dobrir los estatuts", "keyboard_shortcuts.favourite": "per apondre als favorits", "keyboard_shortcuts.heading": "Acorchis clavièr", - "keyboard_shortcuts.hotkey": "Clau", + "keyboard_shortcuts.hotkey": "Acorchis", "keyboard_shortcuts.legend": "per mostrar aquesta legenda", "keyboard_shortcuts.mention": "per mencionar l’autor", "keyboard_shortcuts.reply": "per respondre", @@ -127,35 +127,35 @@ "lightbox.close": "Tampar", "lightbox.next": "Seguent", "lightbox.previous": "Precedent", - "lists.account.add": "Add to list", - "lists.account.remove": "Remove from list", - "lists.delete": "Delete list", - "lists.edit": "Edit list", - "lists.new.create": "Add list", - "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", - "lists.subheading": "Your lists", + "lists.account.add": "Ajustar a la lista", + "lists.account.remove": "Levar de la lista", + "lists.delete": "Suprimir la lista", + "lists.edit": "Modificar la lista", + "lists.new.create": "Ajustar una lista", + "lists.new.title_placeholder": "Títol de la nòva lista", + "lists.search": "Cercar demest lo monde que seguètz", + "lists.subheading": "Vòstras listas", "loading_indicator.label": "Cargament…", "media_gallery.toggle_visible": "Modificar la visibilitat", "missing_indicator.label": "Pas trobat", - "mute_modal.hide_notifications": "Hide notifications from this user?", + "mute_modal.hide_notifications": "Rescondre las notificacions d’aquesta persona ?", "navigation_bar.blocks": "Personas blocadas", "navigation_bar.community_timeline": "Flux public local", "navigation_bar.edit_profile": "Modificar lo perfil", "navigation_bar.favourites": "Favorits", - "navigation_bar.follow_requests": "Demandas d'abonament", + "navigation_bar.follow_requests": "Demandas d’abonament", "navigation_bar.info": "Mai informacions", "navigation_bar.keyboard_shortcuts": "Acorchis clavièr", - "navigation_bar.lists": "Lists", + "navigation_bar.lists": "Listas", "navigation_bar.logout": "Desconnexion", "navigation_bar.mutes": "Personas rescondudas", "navigation_bar.pins": "Tuts penjats", "navigation_bar.preferences": "Preferéncias", "navigation_bar.public_timeline": "Flux public global", - "notification.favourite": "{name} a ajustat a sos favorits :", + "notification.favourite": "{name} a ajustat a sos favorits", "notification.follow": "{name} vos sèc", - "notification.mention": "{name} vos a mencionat :", - "notification.reblog": "{name} a partejat vòstre estatut :", + "notification.mention": "{name} vos a mencionat", + "notification.reblog": "{name} a partejat vòstre estatut", "notifications.clear": "Escafar", "notifications.clear_confirmation": "Volètz vertadièrament escafar totas vòstras las notificacions ?", "notifications.column_settings.alert": "Notificacions localas", @@ -171,7 +171,7 @@ "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 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.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 construire 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.welcome": "Benvengut a Mastodon !", @@ -209,7 +209,7 @@ "search_popout.search_format": "Format recèrca avançada", "search_popout.tips.hashtag": "etiqueta", "search_popout.tips.status": "estatut", - "search_popout.tips.text": "Tèxt brut tòrna escais, noms d’utilizaire e etiquetas correspondents", + "search_popout.tips.text": "Lo tèxt brut tòrna escais, noms d’utilizaire e etiquetas correspondents", "search_popout.tips.user": "utilizaire", "search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}", "standalone.public_title": "Una ulhada dedins…", @@ -225,7 +225,7 @@ "status.open": "Desplegar aqueste estatut", "status.pin": "Penjar al perfil", "status.reblog": "Partejar", - "status.reblogged_by": "{name} a partejat :", + "status.reblogged_by": "{name} a partejat", "status.reply": "Respondre", "status.replyAll": "Respondre a la conversacion", "status.report": "Senhalar @{name}", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 1df27d536..6bac65865 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -109,21 +109,21 @@ "home.settings": "Configurações de colunas", "keyboard_shortcuts.back": "para navegar de volta", "keyboard_shortcuts.boost": "para compartilhar", - "keyboard_shortcuts.column": "to focus a status in one of the columns", + "keyboard_shortcuts.column": "Focar um status em uma das colunas", "keyboard_shortcuts.compose": "to focus the compose textarea", "keyboard_shortcuts.description": "Description", - "keyboard_shortcuts.down": "to move down in the list", + "keyboard_shortcuts.down": "para mover para baixo na lista", "keyboard_shortcuts.enter": "to open status", - "keyboard_shortcuts.favourite": "to favourite", + "keyboard_shortcuts.favourite": "para adicionar aos favoritos", "keyboard_shortcuts.heading": "Keyboard Shortcuts", - "keyboard_shortcuts.hotkey": "Hotkey", - "keyboard_shortcuts.legend": "to display this legend", - "keyboard_shortcuts.mention": "to mention author", - "keyboard_shortcuts.reply": "to reply", - "keyboard_shortcuts.search": "to focus search", - "keyboard_shortcuts.toot": "to start a brand new toot", - "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", - "keyboard_shortcuts.up": "to move up in the list", + "keyboard_shortcuts.hotkey": "Atalho", + "keyboard_shortcuts.legend": "para mostrar essa legenda", + "keyboard_shortcuts.mention": "para mencionar o autor", + "keyboard_shortcuts.reply": "para responder", + "keyboard_shortcuts.search": "para focar a pesquisa", + "keyboard_shortcuts.toot": "para compor um novo toot", + "keyboard_shortcuts.unfocus": "para remover o foco da área de composição/pesquisa", + "keyboard_shortcuts.up": "para mover para cima na lista", "lightbox.close": "Fechar", "lightbox.next": "Próximo", "lightbox.previous": "Anterior", @@ -133,19 +133,19 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Carregando...", "media_gallery.toggle_visible": "Esconder/Mostrar", "missing_indicator.label": "Não encontrado", - "mute_modal.hide_notifications": "Hide notifications from this user?", + "mute_modal.hide_notifications": "Esconder notificações deste usuário?", "navigation_bar.blocks": "Usuários bloqueados", "navigation_bar.community_timeline": "Local", "navigation_bar.edit_profile": "Editar perfil", "navigation_bar.favourites": "Favoritos", "navigation_bar.follow_requests": "Seguidores pendentes", "navigation_bar.info": "Mais informações", - "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts", + "navigation_bar.keyboard_shortcuts": "Atalhos de teclado", "navigation_bar.lists": "Lists", "navigation_bar.logout": "Sair", "navigation_bar.mutes": "Usuários silenciados", @@ -220,7 +220,7 @@ "status.load_more": "Carregar mais", "status.media_hidden": "Mídia escondida", "status.mention": "Mencionar @{name}", - "status.more": "More", + "status.more": "Mais", "status.mute_conversation": "Silenciar conversa", "status.open": "Expandir", "status.pin": "Fixar no perfil", @@ -241,7 +241,7 @@ "tabs_bar.home": "Página inicial", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificações", - "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", + "ui.beforeunload": "Seu rascunho será perdido se você sair do Mastodon.", "upload_area.title": "Arraste e solte para enviar", "upload_button.label": "Adicionar mídia", "upload_form.description": "Descreva a imagem para deficientes visuais", diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json index 3d3e19571..728fb3a10 100644 --- a/app/javascript/mastodon/locales/pt.json +++ b/app/javascript/mastodon/locales/pt.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "A carregar...", "media_gallery.toggle_visible": "Esconder/Mostrar", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 0aef2d9df..e9925b675 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Загрузка...", "media_gallery.toggle_visible": "Показать/скрыть", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 53090452f..9d9646509 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Laddar...", "media_gallery.toggle_visible": "Växla synlighet", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 2f064a193..cc18a6096 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Loading...", "media_gallery.toggle_visible": "Toggle visibility", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index be8103d1c..c51f3e417 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Yükleniyor...", "media_gallery.toggle_visible": "Görünürlüğü değiştir", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 273661462..86c0ce76d 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Завантаження...", "media_gallery.toggle_visible": "Показати/приховати", diff --git a/app/javascript/mastodon/locales/whitelist_gl.json b/app/javascript/mastodon/locales/whitelist_gl.json new file mode 100644 index 000000000..0d4f101c7 --- /dev/null +++ b/app/javascript/mastodon/locales/whitelist_gl.json @@ -0,0 +1,2 @@ +[ +] diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index dbb9584c6..15a68c915 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "載入中...", "media_gallery.toggle_visible": "打開或關上", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 0b05a83cd..1bdc883a8 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "讀取中...", "media_gallery.toggle_visible": "切換可見性", diff --git a/app/javascript/mastodon/middleware/sounds.js b/app/javascript/mastodon/middleware/sounds.js index 3d1e3eaba..9f1bc02b9 100644 --- a/app/javascript/mastodon/middleware/sounds.js +++ b/app/javascript/mastodon/middleware/sounds.js @@ -15,7 +15,7 @@ const play = audio => { if (typeof audio.fastSeek === 'function') { audio.fastSeek(0); } else { - audio.seek(0); + audio.currentTime = 0; } } diff --git a/app/javascript/mastodon/reducers/status_lists.js b/app/javascript/mastodon/reducers/status_lists.js index c4aeb338f..6c5f33557 100644 --- a/app/javascript/mastodon/reducers/status_lists.js +++ b/app/javascript/mastodon/reducers/status_lists.js @@ -1,6 +1,10 @@ import { + FAVOURITED_STATUSES_FETCH_REQUEST, FAVOURITED_STATUSES_FETCH_SUCCESS, + FAVOURITED_STATUSES_FETCH_FAIL, + FAVOURITED_STATUSES_EXPAND_REQUEST, FAVOURITED_STATUSES_EXPAND_SUCCESS, + FAVOURITED_STATUSES_EXPAND_FAIL, } from '../actions/favourites'; import { PINNED_STATUSES_FETCH_SUCCESS, @@ -30,6 +34,7 @@ const normalizeList = (state, listType, statuses, next) => { return state.update(listType, listMap => listMap.withMutations(map => { map.set('next', next); map.set('loaded', true); + map.set('isLoading', false); map.set('items', ImmutableList(statuses.map(item => item.id))); })); }; @@ -37,6 +42,7 @@ const normalizeList = (state, listType, statuses, next) => { const appendToList = (state, listType, statuses, next) => { return state.update(listType, listMap => listMap.withMutations(map => { map.set('next', next); + map.set('isLoading', false); map.set('items', map.get('items').concat(statuses.map(item => item.id))); })); }; @@ -55,6 +61,12 @@ const removeOneFromList = (state, listType, status) => { export default function statusLists(state = initialState, action) { switch(action.type) { + case FAVOURITED_STATUSES_FETCH_REQUEST: + case FAVOURITED_STATUSES_EXPAND_REQUEST: + return state.setIn(['favourites', 'isLoading'], true); + case FAVOURITED_STATUSES_FETCH_FAIL: + case FAVOURITED_STATUSES_EXPAND_FAIL: + return state.setIn(['favourites', 'isLoading'], false); case FAVOURITED_STATUSES_FETCH_SUCCESS: return normalizeList(state, 'favourites', action.statuses, action.next); case FAVOURITED_STATUSES_EXPAND_SUCCESS: diff --git a/app/javascript/mastodon/settings.js b/app/javascript/mastodon/settings.js new file mode 100644 index 000000000..dbd969cb1 --- /dev/null +++ b/app/javascript/mastodon/settings.js @@ -0,0 +1,46 @@ +export default class Settings { + + constructor(keyBase = null) { + this.keyBase = keyBase; + } + + generateKey(id) { + return this.keyBase ? [this.keyBase, `id${id}`].join('.') : id; + } + + set(id, data) { + const key = this.generateKey(id); + try { + const encodedData = JSON.stringify(data); + localStorage.setItem(key, encodedData); + return data; + } catch (e) { + return null; + } + } + + get(id) { + const key = this.generateKey(id); + try { + const rawData = localStorage.getItem(key); + return JSON.parse(rawData); + } catch (e) { + return null; + } + } + + remove(id) { + const data = this.get(id); + if (data) { + const key = this.generateKey(id); + try { + localStorage.removeItem(key); + } catch (e) { + } + } + return data; + } + +} + +export const pushNotificationsSetting = new Settings('mastodon_push_notification_data'); diff --git a/app/javascript/mastodon/web_push_subscription.js b/app/javascript/mastodon/web_push_subscription.js index 3dbed09ea..17aca4060 100644 --- a/app/javascript/mastodon/web_push_subscription.js +++ b/app/javascript/mastodon/web_push_subscription.js @@ -1,6 +1,7 @@ import axios from 'axios'; import { store } from './containers/mastodon'; import { setBrowserSupport, setSubscription, clearSubscription } from './actions/push_notifications'; +import { pushNotificationsSetting } from './settings'; // Taken from https://www.npmjs.com/package/web-push const urlBase64ToUint8Array = (base64String) => { @@ -35,16 +36,33 @@ const subscribe = (registration) => const unsubscribe = ({ registration, subscription }) => subscription ? subscription.unsubscribe().then(() => registration) : registration; -const sendSubscriptionToBackend = (subscription) => - axios.post('/api/web/push_subscriptions', { - subscription, - }).then(response => response.data); +const sendSubscriptionToBackend = (subscription) => { + const params = { subscription }; + + const me = store.getState().getIn(['meta', 'me']); + if (me) { + const data = pushNotificationsSetting.get(me); + if (data) { + params.data = data; + } + } + + return axios.post('/api/web/push_subscriptions', params).then(response => response.data); +}; // Last one checks for payload support: https://web-push-book.gauntface.com/chapter-06/01-non-standards-browsers/#no-payload const supportsPushNotifications = ('serviceWorker' in navigator && 'PushManager' in window && 'getKey' in PushSubscription.prototype); export function register () { store.dispatch(setBrowserSupport(supportsPushNotifications)); + const me = store.getState().getIn(['meta', 'me']); + + if (me && !pushNotificationsSetting.get(me)) { + const alerts = store.getState().getIn(['push_notifications', 'alerts']); + if (alerts) { + pushNotificationsSetting.set(me, { alerts: alerts }); + } + } if (supportsPushNotifications) { if (!getApplicationServerKey()) { @@ -79,6 +97,9 @@ export function register () { // it means that the backend subscription is valid (and was set during hydration) if (!(subscription instanceof PushSubscription)) { store.dispatch(setSubscription(subscription)); + if (me) { + pushNotificationsSetting.set(me, { alerts: subscription.alerts }); + } } }) .catch(error => { @@ -90,6 +111,9 @@ export function register () { // Clear alerts and hide UI settings store.dispatch(clearSubscription()); + if (me) { + pushNotificationsSetting.remove(me); + } try { getRegistration() diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 17322264e..da789ba06 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -803,7 +803,7 @@ .emojione { width: 24px; height: 24px; - margin: -3px 0 0; + margin: -1px 0 0; } } @@ -2273,14 +2273,19 @@ button.icon-button.active i.fa-retweet { .status-card__image-image { border-radius: 4px 4px 0 0; } + + .status-card__title { + white-space: inherit; + } } .status-card__image-image { border-radius: 4px 0 0 4px; display: block; - height: auto; margin: 0; width: 100%; + height: 100%; + object-fit: cover; } .load-more { @@ -3998,6 +4003,7 @@ button.icon-button.active i.fa-retweet { position: relative; background: $base-shadow-color; max-width: 100%; + border-radius: 4px; video { height: 100%; @@ -4032,8 +4038,8 @@ button.icon-button.active i.fa-retweet { left: 0; right: 0; box-sizing: border-box; - background: linear-gradient(0deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 60%, transparent); - padding: 0 10px; + background: linear-gradient(0deg, rgba($base-shadow-color, 0.85) 0, rgba($base-shadow-color, 0.45) 60%, transparent); + padding: 0 15px; opacity: 0; transition: opacity .1s ease; @@ -4086,40 +4092,67 @@ button.icon-button.active i.fa-retweet { } } - &__buttons { + &__buttons-bar { + display: flex; + justify-content: space-between; padding-bottom: 10px; + } + + &__buttons { font-size: 16px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; &.left { - float: left; - button { - padding-right: 10px; + padding-left: 0; } } &.right { - float: right; - button { - padding-left: 10px; + padding-right: 0; } } button { background: transparent; - padding: 0; + padding: 2px 10px; + font-size: 16px; border: 0; - color: $white; + color: rgba($white, 0.75); &:active, &:hover, &:focus { - color: $ui-highlight-color; + color: $white; } } } + &__time-sep, + &__time-total, + &__time-current { + font-size: 14px; + font-weight: 500; + } + + &__time-current { + color: $white; + margin-left: 10px; + } + + &__time-sep { + display: inline-block; + margin: 0 6px; + } + + &__time-sep, + &__time-total { + color: $white; + } + &__seek { cursor: pointer; height: 24px; @@ -4129,6 +4162,7 @@ button.icon-button.active i.fa-retweet { content: ""; width: 100%; background: rgba($white, 0.35); + border-radius: 4px; display: block; position: absolute; height: 4px; @@ -4140,8 +4174,9 @@ button.icon-button.active i.fa-retweet { display: block; position: absolute; height: 4px; + border-radius: 4px; top: 10px; - background: $ui-highlight-color; + background: lighten($ui-highlight-color, 8%); } &__buffer { @@ -4158,7 +4193,8 @@ button.icon-button.active i.fa-retweet { top: 6px; margin-left: -6px; transition: opacity .1s ease; - background: $ui-highlight-color; + background: lighten($ui-highlight-color, 8%); + box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2); pointer-events: none; &.active { @@ -4172,6 +4208,16 @@ button.icon-button.active i.fa-retweet { } } } + + &.detailed, + &.fullscreen { + .video-player__buttons { + button { + padding-top: 10px; + padding-bottom: 10px; + } + } + } } .media-spoiler-video { diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 31e0abe39..3a985c19b 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -20,11 +20,13 @@ class ActivityPub::Activity::Create < ActivityPub::Activity private def process_status + media_attachments = process_attachments + ApplicationRecord.transaction do @status = Status.create!(status_params) process_tags(@status) - process_attachments(@status) + attach_media(@status, media_attachments) end resolve_thread(@status) @@ -105,22 +107,36 @@ class ActivityPub::Activity::Create < ActivityPub::Activity emoji.save end - def process_attachments(status) + def process_attachments return if @object['attachment'].nil? + media_attachments = [] + as_array(@object['attachment']).each do |attachment| next if unsupported_media_type?(attachment['mediaType']) || attachment['url'].blank? href = Addressable::URI.parse(attachment['url']).normalize.to_s - media_attachment = MediaAttachment.create(status: status, account: status.account, remote_url: href, description: attachment['name'].presence) + media_attachment = MediaAttachment.create(account: @account, remote_url: href, description: attachment['name'].presence) + media_attachments << media_attachment next if skip_download? media_attachment.file_remote_url = href media_attachment.save end + + media_attachments rescue Addressable::URI::InvalidURIError => e Rails.logger.debug e + + media_attachments + end + + def attach_media(status, media_attachments) + return if media_attachments.blank? + + media = MediaAttachment.where(status_id: nil, id: media_attachments.take(4).map(&:id)) + media.update(status_id: status.id) end def resolve_thread(status) diff --git a/app/lib/ostatus/activity/creation.rb b/app/lib/ostatus/activity/creation.rb index 3418e2420..f210e134a 100644 --- a/app/lib/ostatus/activity/creation.rb +++ b/app/lib/ostatus/activity/creation.rb @@ -26,6 +26,8 @@ class OStatus::Activity::Creation < OStatus::Activity::Base cached_reblog = reblog status = nil + media_attachments = save_media + ApplicationRecord.transaction do status = Status.create!( uri: id, @@ -44,7 +46,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base save_mentions(status) save_hashtags(status) - save_media(status) + attach_media(status, media_attachments) save_emojis(status) end @@ -126,18 +128,20 @@ class OStatus::Activity::Creation < OStatus::Activity::Base ProcessHashtagsService.new.call(parent, tags) end - def save_media(parent) - do_not_download = DomainBlock.find_by(domain: parent.account.domain)&.reject_media? + def save_media + do_not_download = DomainBlock.find_by(domain: @account.domain)&.reject_media? + media_attachments = [] @xml.xpath('./xmlns:link[@rel="enclosure"]', xmlns: OStatus::TagManager::XMLNS).each do |link| next unless link['href'] - media = MediaAttachment.where(status: parent, remote_url: link['href']).first_or_initialize(account: parent.account, status: parent, remote_url: link['href']) + media = MediaAttachment.where(status: nil, remote_url: link['href']).first_or_initialize(account: @account, status: nil, remote_url: link['href']) parsed_url = Addressable::URI.parse(link['href']).normalize next if !%w(http https).include?(parsed_url.scheme) || parsed_url.host.empty? media.save + media_attachments << media next if do_not_download @@ -148,6 +152,15 @@ class OStatus::Activity::Creation < OStatus::Activity::Base next end end + + media_attachments + end + + def attach_media(parent, media_attachments) + return if media_attachments.blank? + + media = MediaAttachment.where(status_id: nil, id: media_attachments.take(4).map(&:id)) + media.update(status_id: parent.id) end def save_emojis(parent) diff --git a/app/models/account.rb b/app/models/account.rb index 48b17bbb8..c75ea028e 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -287,6 +287,7 @@ class Account < ApplicationRecord FROM accounts WHERE #{query} @@ #{textsearch} AND accounts.suspended = false + AND accounts.moved_to_account_id IS NULL ORDER BY rank DESC LIMIT ? SQL @@ -312,6 +313,7 @@ class Account < ApplicationRecord WHERE accounts.id IN (SELECT * FROM first_degree) AND #{query} @@ #{textsearch} AND accounts.suspended = false + AND accounts.moved_to_account_id IS NULL GROUP BY accounts.id ORDER BY rank DESC LIMIT ? @@ -327,6 +329,7 @@ class Account < ApplicationRecord LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = ?) OR (accounts.id = f.target_account_id AND f.account_id = ?) WHERE #{query} @@ #{textsearch} AND accounts.suspended = false + AND accounts.moved_to_account_id IS NULL GROUP BY accounts.id ORDER BY rank DESC LIMIT ? diff --git a/app/models/list.rb b/app/models/list.rb index 910864b26..be85c3b87 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -4,7 +4,7 @@ # Table name: lists # # id :integer not null, primary key -# account_id :integer +# account_id :integer not null # title :string default(""), not null # created_at :datetime not null # updated_at :datetime not null @@ -13,6 +13,8 @@ class List < ApplicationRecord include Paginable + PER_ACCOUNT_LIMIT = 50 + belongs_to :account has_many :list_accounts, inverse_of: :list, dependent: :destroy @@ -20,6 +22,10 @@ class List < ApplicationRecord validates :title, presence: true + validates_each :account_id, on: :create do |record, _attr, value| + record.errors.add(:base, I18n.t('lists.errors.limit')) if List.where(account_id: value).count >= PER_ACCOUNT_LIMIT + end + before_destroy :clean_feed_manager private diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index 5baddba8a..716b82243 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -33,7 +33,7 @@ class PreviewCard < ApplicationRecord has_and_belongs_to_many :statuses - has_attached_file :image, styles: { original: '280x280>' }, convert_options: { all: '-quality 80 -strip' } + has_attached_file :image, styles: { original: '400x400>' }, convert_options: { all: '-quality 80 -strip' } include Attachmentable include Remotable diff --git a/app/models/tag.rb b/app/models/tag.rb index 0fa08e157..dc2c8d129 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -23,7 +23,7 @@ class Tag < ApplicationRecord class << self def search_for(term, limit = 5) - pattern = sanitize_sql_like(term) + '%' + pattern = sanitize_sql_like(term.strip) + '%' Tag.where('lower(name) like lower(?)', pattern).order(:name).limit(limit) end end diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb index a289ceac4..3be110665 100644 --- a/app/services/account_search_service.rb +++ b/app/services/account_search_service.rb @@ -4,7 +4,7 @@ class AccountSearchService < BaseService attr_reader :query, :limit, :options, :account def call(query, limit, account = nil, options = {}) - @query = query + @query = query.strip @limit = limit @options = options @account = account diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb index cec96d927..7f4518ea7 100644 --- a/app/services/fetch_link_card_service.rb +++ b/app/services/fetch_link_card_service.rb @@ -38,7 +38,7 @@ class FetchLinkCardService < BaseService @card ||= PreviewCard.new(url: @url) res = Request.new(:head, @url).perform - return if res.code != 200 || res.mime_type != 'text/html' + return if res.code != 405 && (res.code != 200 || res.mime_type != 'text/html') attempt_oembed || attempt_opengraph end diff --git a/app/services/resolve_remote_account_service.rb b/app/services/resolve_remote_account_service.rb index 3293fe40f..d7d0be210 100644 --- a/app/services/resolve_remote_account_service.rb +++ b/app/services/resolve_remote_account_service.rb @@ -44,7 +44,7 @@ class ResolveRemoteAccountService < BaseService if lock.acquired? @account = Account.find_remote(@username, @domain) - if activitypub_ready? + if activitypub_ready? || @account&.activitypub? handle_activitypub else handle_ostatus diff --git a/app/validators/status_pin_validator.rb b/app/validators/status_pin_validator.rb index 9760e1138..64da04120 100644 --- a/app/validators/status_pin_validator.rb +++ b/app/validators/status_pin_validator.rb @@ -2,9 +2,9 @@ class StatusPinValidator < ActiveModel::Validator def validate(pin) - pin.errors.add(:status, I18n.t('statuses.pin_errors.reblog')) if pin.status.reblog? - pin.errors.add(:status, I18n.t('statuses.pin_errors.ownership')) if pin.account_id != pin.status.account_id - pin.errors.add(:status, I18n.t('statuses.pin_errors.private')) unless %w(public unlisted).include?(pin.status.visibility) - pin.errors.add(:status, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count > 4 + pin.errors.add(:base, I18n.t('statuses.pin_errors.reblog')) if pin.status.reblog? + pin.errors.add(:base, I18n.t('statuses.pin_errors.ownership')) if pin.account_id != pin.status.account_id + pin.errors.add(:base, I18n.t('statuses.pin_errors.private')) unless %w(public unlisted).include?(pin.status.visibility) + pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count > 4 end end diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml index b488bd9ba..d88ec8280 100644 --- a/app/views/stream_entries/_detailed_status.html.haml +++ b/app/views/stream_entries/_detailed_status.html.haml @@ -17,16 +17,16 @@ %p{ style: 'margin-bottom: 0' }< %span.p-summary> #{Formatter.instance.format_spoiler(status)} %a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more') - .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }< - = Formatter.instance.format(status, custom_emojify: true) - - if !status.media_attachments.empty? - - if status.media_attachments.first.video? - - video = status.media_attachments.first - %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive?, width: 670, height: 380) }}< - - else - %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 380, sensitive: status.sensitive?, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }}< - - elsif status.preview_cards.first - %div{ data: { component: 'Card', props: Oj.dump('maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_cards.first, serializer: REST::PreviewCardSerializer).as_json) }}< + .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status, custom_emojify: true) + + - if !status.media_attachments.empty? + - if status.media_attachments.first.video? + - video = status.media_attachments.first + %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive?, width: 670, height: 380, detailed: true) }}< + - else + %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 380, sensitive: status.sensitive?, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }}< + - elsif status.preview_cards.first + %div{ data: { component: 'Card', props: Oj.dump('maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_cards.first, serializer: REST::PreviewCardSerializer).as_json) }} .detailed-status__meta %data.dt-published{ value: status.created_at.to_time.iso8601 } diff --git a/app/views/stream_entries/show.html.haml b/app/views/stream_entries/show.html.haml index 895a61247..b52334a28 100644 --- a/app/views/stream_entries/show.html.haml +++ b/app/views/stream_entries/show.html.haml @@ -1,3 +1,6 @@ +- content_for :page_title do + = t('statuses.title', name: display_name(@account), quote: truncate(@stream_entry.activity.text, length: 50, omission: '…')) + - content_for :header_tags do - if @account.user&.setting_noindex %meta{ name: 'robots', content: 'noindex' }/ diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml index e05fe1c39..03f19e20a 100644 --- a/app/views/tags/show.html.haml +++ b/app/views/tags/show.html.haml @@ -19,8 +19,11 @@ %p= t 'about.about_hashtag_html', hashtag: @tag.name .cta - = link_to t('auth.login'), new_user_session_path, class: 'button button-secondary' - = link_to t('about.learn_more'), root_url, class: 'button button-alternative' + - if user_signed_in? + = link_to t('settings.back'), root_path, class: 'button button-secondary' + - else + = link_to t('auth.login'), new_user_session_path, class: 'button button-secondary' + = link_to t('about.learn_more'), about_path, class: 'button button-alternative' .features-list .features-list__row |