diff --git a/.github/workflows/test-js.yml b/.github/workflows/test-js.yml index 6a1cacb3f..1c4958550 100644 --- a/.github/workflows/test-js.yml +++ b/.github/workflows/test-js.yml @@ -9,6 +9,8 @@ on: - '.nvmrc' - '**/*.js' - '**/*.jsx' + - '**/*.ts' + - '**/*.tsx' - '**/*.snap' - '.github/workflows/test-js.yml' @@ -19,6 +21,8 @@ on: - '.nvmrc' - '**/*.js' - '**/*.jsx' + - '**/*.ts' + - '**/*.tsx' - '**/*.snap' - '.github/workflows/test-js.yml' diff --git a/Gemfile b/Gemfile index cc94c878a..3c4ad3555 100644 --- a/Gemfile +++ b/Gemfile @@ -120,7 +120,7 @@ end group :test do gem 'capybara', '~> 3.39' gem 'climate_control' - gem 'faker', '~> 3.1' + gem 'faker', '~> 3.2' gem 'json-schema', '~> 3.0' gem 'rack-test', '~> 2.1' gem 'rails-controller-testing', '~> 1.0' diff --git a/Gemfile.lock b/Gemfile.lock index 6ce9afdf7..2a67abf27 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -243,7 +243,7 @@ GEM tzinfo excon (0.95.0) fabrication (2.30.0) - faker (3.1.1) + faker (3.2.0) i18n (>= 1.8.11, < 2) faraday (1.10.3) faraday-em_http (~> 1.0) @@ -348,19 +348,19 @@ GEM ipaddress (0.8.3) jmespath (1.6.2) json (2.6.3) - json-canonicalization (0.3.0) + json-canonicalization (0.3.1) json-jwt (1.15.3) activesupport (>= 4.2) aes_key_wrap bindata httpclient - json-ld (3.2.3) + json-ld (3.2.4) htmlentities (~> 4.3) json-canonicalization (~> 0.3) link_header (~> 0.0, >= 0.0.8) multi_json (~> 1.15) - rack (~> 2.2) - rdf (~> 3.2, >= 3.2.9) + rack (>= 2.2, < 4) + rdf (~> 3.2, >= 3.2.10) json-ld-preloaded (3.2.2) json-ld (~> 3.2) rdf (~> 3.2) @@ -479,7 +479,7 @@ GEM openssl-signature_algorithm (1.3.0) openssl (> 2.0) orm_adapter (0.5.0) - ox (2.14.14) + ox (2.14.16) parallel (1.22.1) parser (3.2.2.0) ast (~> 2.4.1) @@ -487,7 +487,7 @@ GEM pastel (0.8.0) tty-color (~> 0.5) pg (1.4.6) - pghero (3.3.1) + pghero (3.3.2) activerecord (>= 6) pkg-config (1.5.1) posix-spawn (0.3.15) @@ -557,7 +557,7 @@ GEM thor (~> 1.0) rainbow (3.1.1) rake (13.0.6) - rdf (3.2.9) + rdf (3.2.10) link_header (~> 0.0, >= 0.0.8) rdf-normalize (0.5.1) rdf (~> 3.2) @@ -799,7 +799,7 @@ DEPENDENCIES dotenv-rails (~> 2.8) ed25519 (~> 1.3) fabrication (~> 2.30) - faker (~> 3.1) + faker (~> 3.2) fast_blank (~> 1.0) fastimage fog-core (<= 2.4.0) diff --git a/app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb b/app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb new file mode 100644 index 000000000..5d9fcc82c --- /dev/null +++ b/app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +class Api::V1::Admin::Trends::Links::PreviewCardProvidersController < Api::BaseController + include Authorization + + LIMIT = 100 + + before_action -> { authorize_if_got_token! :'admin:read' }, only: :index + before_action -> { authorize_if_got_token! :'admin:write' }, except: :index + before_action :set_providers, only: :index + + after_action :verify_authorized + after_action :insert_pagination_headers, only: :index + + PAGINATION_PARAMS = %i(limit).freeze + + def index + authorize :preview_card_provider, :index? + + render json: @providers, each_serializer: REST::Admin::Trends::Links::PreviewCardProviderSerializer + end + + def approve + authorize :preview_card_provider, :review? + + provider = PreviewCardProvider.find(params[:id]) + provider.update(trendable: true, reviewed_at: Time.now.utc) + render json: provider, serializer: REST::Admin::Trends::Links::PreviewCardProviderSerializer + end + + def reject + authorize :preview_card_provider, :review? + + provider = PreviewCardProvider.find(params[:id]) + provider.update(trendable: false, reviewed_at: Time.now.utc) + render json: provider, serializer: REST::Admin::Trends::Links::PreviewCardProviderSerializer + end + + private + + def set_providers + @providers = PreviewCardProvider.all.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + api_v1_admin_trends_links_preview_card_providers_url(pagination_params(max_id: pagination_max_id)) if records_continue? + end + + def prev_path + api_v1_admin_trends_links_preview_card_providers_url(pagination_params(min_id: pagination_since_id)) unless @providers.empty? + end + + def pagination_max_id + @providers.last.id + end + + def pagination_since_id + @providers.first.id + end + + def records_continue? + @providers.size == limit_param(LIMIT) + end + + def pagination_params(core_params) + params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params) + end +end diff --git a/app/controllers/api/v1/admin/trends/links_controller.rb b/app/controllers/api/v1/admin/trends/links_controller.rb index cc6388980..7f4ca4828 100644 --- a/app/controllers/api/v1/admin/trends/links_controller.rb +++ b/app/controllers/api/v1/admin/trends/links_controller.rb @@ -1,7 +1,36 @@ # frozen_string_literal: true class Api::V1::Admin::Trends::LinksController < Api::V1::Trends::LinksController - before_action -> { authorize_if_got_token! :'admin:read' } + include Authorization + + before_action -> { authorize_if_got_token! :'admin:read' }, only: :index + before_action -> { authorize_if_got_token! :'admin:write' }, except: :index + + after_action :verify_authorized, except: :index + + def index + if current_user&.can?(:manage_taxonomies) + render json: @links, each_serializer: REST::Admin::Trends::LinkSerializer + else + super + end + end + + def approve + authorize :preview_card, :review? + + link = PreviewCard.find(params[:id]) + link.update(trendable: true) + render json: link, serializer: REST::Admin::Trends::LinkSerializer + end + + def reject + authorize :preview_card, :review? + + link = PreviewCard.find(params[:id]) + link.update(trendable: false) + render json: link, serializer: REST::Admin::Trends::LinkSerializer + end private diff --git a/app/controllers/api/v1/admin/trends/statuses_controller.rb b/app/controllers/api/v1/admin/trends/statuses_controller.rb index c39f77363..34b6580df 100644 --- a/app/controllers/api/v1/admin/trends/statuses_controller.rb +++ b/app/controllers/api/v1/admin/trends/statuses_controller.rb @@ -1,7 +1,36 @@ # frozen_string_literal: true class Api::V1::Admin::Trends::StatusesController < Api::V1::Trends::StatusesController - before_action -> { authorize_if_got_token! :'admin:read' } + include Authorization + + before_action -> { authorize_if_got_token! :'admin:read' }, only: :index + before_action -> { authorize_if_got_token! :'admin:write' }, except: :index + + after_action :verify_authorized, except: :index + + def index + if current_user&.can?(:manage_taxonomies) + render json: @statuses, each_serializer: REST::Admin::Trends::StatusSerializer + else + super + end + end + + def approve + authorize [:admin, :status], :review? + + status = Status.find(params[:id]) + status.update(trendable: true) + render json: status, serializer: REST::Admin::Trends::StatusSerializer + end + + def reject + authorize [:admin, :status], :review? + + status = Status.find(params[:id]) + status.update(trendable: false) + render json: status, serializer: REST::Admin::Trends::StatusSerializer + end private diff --git a/app/controllers/api/v1/admin/trends/tags_controller.rb b/app/controllers/api/v1/admin/trends/tags_controller.rb index e77df3021..2eeea9522 100644 --- a/app/controllers/api/v1/admin/trends/tags_controller.rb +++ b/app/controllers/api/v1/admin/trends/tags_controller.rb @@ -1,7 +1,12 @@ # frozen_string_literal: true class Api::V1::Admin::Trends::TagsController < Api::V1::Trends::TagsController - before_action -> { authorize_if_got_token! :'admin:read' } + include Authorization + + before_action -> { authorize_if_got_token! :'admin:read' }, only: :index + before_action -> { authorize_if_got_token! :'admin:write' }, except: :index + + after_action :verify_authorized, except: :index def index if current_user&.can?(:manage_taxonomies) @@ -11,6 +16,22 @@ class Api::V1::Admin::Trends::TagsController < Api::V1::Trends::TagsController end end + def approve + authorize :tag, :review? + + tag = Tag.find(params[:id]) + tag.update(trendable: true, reviewed_at: Time.now.utc) + render json: tag, serializer: REST::Admin::TagSerializer + end + + def reject + authorize :tag, :review? + + tag = Tag.find(params[:id]) + tag.update(trendable: false, reviewed_at: Time.now.utc) + render json: tag, serializer: REST::Admin::TagSerializer + end + private def enabled? diff --git a/app/controllers/auth/setup_controller.rb b/app/controllers/auth/setup_controller.rb index db5a866f2..3ee35d141 100644 --- a/app/controllers/auth/setup_controller.rb +++ b/app/controllers/auth/setup_controller.rb @@ -11,15 +11,7 @@ class Auth::SetupController < ApplicationController skip_before_action :require_functional! - def show - flash.now[:notice] = begin - if @user.pending? - I18n.t('devise.registrations.signed_up_but_pending') - else - I18n.t('devise.registrations.signed_up_but_unconfirmed') - end - end - end + def show; end def update # This allows updating the e-mail without entering a password as is required @@ -27,14 +19,13 @@ class Auth::SetupController < ApplicationController # that were not confirmed yet if @user.update(user_params) - redirect_to auth_setup_path, notice: I18n.t('devise.confirmations.send_instructions') + @user.resend_confirmation_instructions unless @user.confirmed? + redirect_to auth_setup_path, notice: I18n.t('auth.setup.new_confirmation_instructions_sent') else render :show end end - helper_method :missing_email? - private def require_unconfirmed_or_pending! @@ -53,10 +44,6 @@ class Auth::SetupController < ApplicationController params.require(:user).permit(:email) end - def missing_email? - truthy_param?(:missing_email) - end - def set_pack use_pack 'auth' end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 2cac2de59..1228ce36c 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -117,6 +117,10 @@ module ApplicationHelper content_tag(:i, nil, attributes.merge(class: class_names.join(' '))) end + def check_icon + content_tag(:svg, tag(:path, 'fill-rule': 'evenodd', 'clip-rule': 'evenodd', d: 'M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z'), xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 20 20', fill: 'currentColor') + end + def visibility_icon(status) if status.public_visibility? fa_icon('globe', title: I18n.t('statuses.visibilities.public')) diff --git a/app/javascript/flavours/glitch/components/animated_number.jsx b/app/javascript/flavours/glitch/components/animated_number.jsx deleted file mode 100644 index dd21d97f0..000000000 --- a/app/javascript/flavours/glitch/components/animated_number.jsx +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ShortNumber from 'mastodon/components/short_number'; -import TransitionMotion from 'react-motion/lib/TransitionMotion'; -import spring from 'react-motion/lib/spring'; -import { reduceMotion } from 'flavours/glitch/initial_state'; - -const obfuscatedCount = count => { - if (count < 0) { - return 0; - } else if (count <= 1) { - return count; - } else { - return '1+'; - } -}; - -export default class AnimatedNumber extends React.PureComponent { - - static propTypes = { - value: PropTypes.number.isRequired, - obfuscate: PropTypes.bool, - }; - - state = { - direction: 1, - }; - - componentWillReceiveProps (nextProps) { - if (nextProps.value > this.props.value) { - this.setState({ direction: 1 }); - } else if (nextProps.value < this.props.value) { - this.setState({ direction: -1 }); - } - } - - willEnter = () => { - const { direction } = this.state; - - return { y: -1 * direction }; - }; - - willLeave = () => { - const { direction } = this.state; - - return { y: spring(1 * direction, { damping: 35, stiffness: 400 }) }; - }; - - render () { - const { value, obfuscate } = this.props; - const { direction } = this.state; - - if (reduceMotion) { - return obfuscate ? obfuscatedCount(value) : ; - } - - const styles = [{ - key: `${value}`, - data: value, - style: { y: spring(0, { damping: 35, stiffness: 400 }) }, - }]; - - return ( - - {items => ( - - {items.map(({ key, data, style }) => ( - 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : } - ))} - - )} - - ); - } - -} diff --git a/app/javascript/flavours/glitch/components/animated_number.tsx b/app/javascript/flavours/glitch/components/animated_number.tsx new file mode 100644 index 000000000..1673ff41b --- /dev/null +++ b/app/javascript/flavours/glitch/components/animated_number.tsx @@ -0,0 +1,58 @@ +import React, { useCallback, useState } from 'react'; +import ShortNumber from './short_number'; +import { TransitionMotion, spring } from 'react-motion'; +import { reduceMotion } from '../initial_state'; + +const obfuscatedCount = (count: number) => { + if (count < 0) { + return 0; + } else if (count <= 1) { + return count; + } else { + return '1+'; + } +}; + +type Props = { + value: number; + obfuscate?: boolean; +} +export const AnimatedNumber: React.FC = ({ + value, + obfuscate, +})=> { + const [previousValue, setPreviousValue] = useState(value); + const [direction, setDirection] = useState<1|-1>(1); + + if (previousValue !== value) { + setPreviousValue(value); + setDirection(value > previousValue ? 1 : -1); + } + + const willEnter = useCallback(() => ({ y: -1 * direction }), [direction]); + const willLeave = useCallback(() => ({ y: spring(1 * direction, { damping: 35, stiffness: 400 }) }), [direction]); + + if (reduceMotion) { + return obfuscate ? <>{obfuscatedCount(value)} : ; + } + + const styles = [{ + key: `${value}`, + data: value, + style: { y: spring(0, { damping: 35, stiffness: 400 }) }, + }]; + + return ( + + {items => ( + + {items.map(({ key, data, style }) => ( + 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : } + ))} + + )} + + ); +}; + +export default AnimatedNumber; diff --git a/app/javascript/flavours/glitch/components/gifv.jsx b/app/javascript/flavours/glitch/components/gifv.jsx deleted file mode 100644 index 1ce7e7c29..000000000 --- a/app/javascript/flavours/glitch/components/gifv.jsx +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -export default class GIFV extends React.PureComponent { - - static propTypes = { - src: PropTypes.string.isRequired, - alt: PropTypes.string, - lang: PropTypes.string, - width: PropTypes.number, - height: PropTypes.number, - onClick: PropTypes.func, - }; - - state = { - loading: true, - }; - - handleLoadedData = () => { - this.setState({ loading: false }); - }; - - componentWillReceiveProps (nextProps) { - if (nextProps.src !== this.props.src) { - this.setState({ loading: true }); - } - } - - handleClick = e => { - const { onClick } = this.props; - - if (onClick) { - e.stopPropagation(); - onClick(); - } - }; - - render () { - const { src, width, height, alt, lang } = this.props; - const { loading } = this.state; - - return ( -
- {loading && ( - - )} - -
- ); - } - -} diff --git a/app/javascript/flavours/glitch/components/gifv.tsx b/app/javascript/flavours/glitch/components/gifv.tsx new file mode 100644 index 000000000..8968170c5 --- /dev/null +++ b/app/javascript/flavours/glitch/components/gifv.tsx @@ -0,0 +1,68 @@ +import React, { useCallback, useState } from 'react'; + +type Props = { + src: string; + key: string; + alt?: string; + lang?: string; + width: number; + height: number; + onClick?: () => void; +} + +export const GIFV: React.FC = ({ + src, + alt, + lang, + width, + height, + onClick, +})=> { + const [loading, setLoading] = useState(true); + + const handleLoadedData: React.ReactEventHandler = useCallback(() => { + setLoading(false); + }, [setLoading]); + + const handleClick: React.MouseEventHandler = useCallback((e) => { + if (onClick) { + e.stopPropagation(); + onClick(); + } + }, [onClick]); + + return ( +
+ {loading && ( + + )} + +
+ ); +}; + +export default GIFV; diff --git a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx index 63a331086..06984f3ad 100644 --- a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx +++ b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx @@ -203,7 +203,7 @@ class Conversation extends ImmutablePureComponent { parseClick={this.parseClick} expanded={isExpanded} onExpandedToggle={this.handleShowMore} - collapsable + collapsible media={media} /> diff --git a/app/javascript/flavours/glitch/features/status/index.jsx b/app/javascript/flavours/glitch/features/status/index.jsx index c220d761f..5d1160039 100644 --- a/app/javascript/flavours/glitch/features/status/index.jsx +++ b/app/javascript/flavours/glitch/features/status/index.jsx @@ -63,6 +63,7 @@ const messages = defineMessages({ redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.' }, revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' }, hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' }, + statusTitleWithAttachments: { id: 'status.title.with_attachments', defaultMessage: '{user} posted {attachmentCount, plural, one {an attachment} other {{attachmentCount} attachments}}' }, detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' }, replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, @@ -161,13 +162,14 @@ const truncate = (str, num) => { } }; -const titleFromStatus = status => { +const titleFromStatus = (intl, status) => { const displayName = status.getIn(['account', 'display_name']); const username = status.getIn(['account', 'username']); - const prefix = displayName.trim().length === 0 ? username : displayName; + const user = displayName.trim().length === 0 ? username : displayName; const text = status.get('search_index'); + const attachmentCount = status.get('media_attachments').size; - return `${prefix}: "${truncate(text, 30)}"`; + return text ? `${user}: "${truncate(text, 30)}"` : intl.formatMessage(messages.statusTitleWithAttachments, { user, attachmentCount }); }; class Status extends ImmutablePureComponent { @@ -710,7 +712,7 @@ class Status extends ImmutablePureComponent { - {titleFromStatus(status)} + {titleFromStatus(intl, status)} diff --git a/app/javascript/flavours/glitch/features/ui/components/filter_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/filter_modal.jsx index 2d49312e5..440a6ac4b 100644 --- a/app/javascript/flavours/glitch/features/ui/components/filter_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/filter_modal.jsx @@ -131,4 +131,4 @@ class FilterModal extends ImmutablePureComponent { } -export default connect(injectIntl(FilterModal)); +export default connect()(injectIntl(FilterModal)); diff --git a/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.jsx index a5637d31c..78aee8dfe 100644 --- a/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.jsx @@ -371,7 +371,7 @@ class FocalPointModal extends ImmutablePureComponent { {focals && (
{media.get('type') === 'image' && } - {media.get('type') === 'gifv' && } + {media.get('type') === 'gifv' && }
diff --git a/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx index fd2bd43cf..6ca96b743 100644 --- a/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx @@ -188,7 +188,7 @@ class MediaModal extends ImmutablePureComponent { src={image.get('url')} width={width} height={height} - key={image.get('preview_url')} + key={image.get('url')} alt={image.get('description')} lang={language} onClick={this.toggleNavigation} diff --git a/app/javascript/flavours/glitch/packs/public.jsx b/app/javascript/flavours/glitch/packs/public.jsx index 335a0710d..93b249bb4 100644 --- a/app/javascript/flavours/glitch/packs/public.jsx +++ b/app/javascript/flavours/glitch/packs/public.jsx @@ -2,6 +2,15 @@ import 'packs/public-path'; import loadPolyfills from 'flavours/glitch/load_polyfills'; import ready from 'flavours/glitch/ready'; import loadKeyboardExtensions from 'flavours/glitch/load_keyboard_extensions'; +import axios from 'axios'; +import { throttle } from 'lodash'; +import { defineMessages } from 'react-intl'; + +const messages = defineMessages({ + usernameTaken: { id: 'username.taken', defaultMessage: 'That username is taken. Try another' }, + passwordExceedsLength: { id: 'password_confirmation.exceeds_maxlength', defaultMessage: 'Password confirmation exceeds the maximum password length' }, + passwordDoesNotMatch: { id: 'password_confirmation.mismatching', defaultMessage: 'Password confirmation does not match' }, +}); function main() { const IntlMessageFormat = require('intl-messageformat').default; @@ -9,7 +18,7 @@ function main() { const { delegate } = require('@rails/ujs'); const emojify = require('flavours/glitch/features/emoji/emoji').default; const { getLocale } = require('locales'); - const { messages } = getLocale(); + const { localeData } = getLocale(); const React = require('react'); const ReactDOM = require('react-dom'); const { createBrowserHistory } = require('history'); @@ -54,6 +63,11 @@ function main() { hour12: false, }); + const formatMessage = ({ id, defaultMessage }, values) => { + const messageFormat = new IntlMessageFormat(localeData[id] || defaultMessage, locale); + return messageFormat.format(values); + }; + [].forEach.call(document.querySelectorAll('.emojify'), (content) => { content.innerHTML = emojify(content.innerHTML); }); @@ -73,7 +87,7 @@ function main() { date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear(); }; - const todayFormat = new IntlMessageFormat(messages['relative_format.today'] || 'Today at {time}', locale); + const todayFormat = new IntlMessageFormat(localeData['relative_format.today'] || 'Today at {time}', locale); [].forEach.call(document.querySelectorAll('time.relative-formatted'), (content) => { const datetime = new Date(content.getAttribute('datetime')); @@ -99,7 +113,7 @@ function main() { const timeGiven = content.getAttribute('datetime').includes('T'); content.title = timeGiven ? dateTimeFormat.format(datetime) : dateFormat.format(datetime); content.textContent = timeAgoString({ - formatMessage: ({ id, defaultMessage }, values) => (new IntlMessageFormat(messages[id] || defaultMessage, locale)).format(values), + formatMessage, formatDate: (date, options) => (new Intl.DateTimeFormat(locale, options)).format(date), }, datetime, now, now.getFullYear(), timeGiven); }); @@ -128,17 +142,19 @@ function main() { scrollToDetailedStatus(); } - delegate(document, '#registration_user_password_confirmation,#registration_user_password', 'input', () => { - const password = document.getElementById('registration_user_password'); - const confirmation = document.getElementById('registration_user_password_confirmation'); - if (confirmation.value && confirmation.value.length > password.maxLength) { - confirmation.setCustomValidity((new IntlMessageFormat(messages['password_confirmation.exceeds_maxlength'] || 'Password confirmation exceeds the maximum password length', locale)).format()); - } else if (password.value && password.value !== confirmation.value) { - confirmation.setCustomValidity((new IntlMessageFormat(messages['password_confirmation.mismatching'] || 'Password confirmation does not match', locale)).format()); + delegate(document, '#user_account_attributes_username', 'input', throttle(() => { + const username = document.getElementById('user_account_attributes_username'); + + if (username.value && username.value.length > 0) { + axios.get('/api/v1/accounts/lookup', { params: { acct: username.value } }).then(() => { + username.setCustomValidity(formatMessage(messages.usernameTaken)); + }).catch(() => { + username.setCustomValidity(''); + }); } else { - confirmation.setCustomValidity(''); + username.setCustomValidity(''); } - }); + }, 500, { leading: false, trailing: true })); delegate(document, '#user_password,#user_password_confirmation', 'input', () => { const password = document.getElementById('user_password'); @@ -146,9 +162,9 @@ function main() { if (!confirmation) return; if (confirmation.value && confirmation.value.length > password.maxLength) { - confirmation.setCustomValidity((new IntlMessageFormat(messages['password_confirmation.exceeds_maxlength'] || 'Password confirmation exceeds the maximum password length', locale)).format()); + confirmation.setCustomValidity(formatMessage(messages.passwordExceedsLength)); } else if (password.value && password.value !== confirmation.value) { - confirmation.setCustomValidity((new IntlMessageFormat(messages['password_confirmation.mismatching'] || 'Password confirmation does not match', locale)).format()); + confirmation.setCustomValidity(formatMessage(messages.passwordDoesNotMatch)); } else { confirmation.setCustomValidity(''); } @@ -162,10 +178,10 @@ function main() { if (statusEl.dataset.spoiler === 'expanded') { statusEl.dataset.spoiler = 'folded'; - this.textContent = (new IntlMessageFormat(messages['status.show_more'] || 'Show more', locale)).format(); + this.textContent = (new IntlMessageFormat(localeData['status.show_more'] || 'Show more', locale)).format(); } else { statusEl.dataset.spoiler = 'expanded'; - this.textContent = (new IntlMessageFormat(messages['status.show_less'] || 'Show less', locale)).format(); + this.textContent = (new IntlMessageFormat(localeData['status.show_less'] || 'Show less', locale)).format(); } return false; @@ -173,7 +189,7 @@ function main() { [].forEach.call(document.querySelectorAll('.status__content__spoiler-link'), (spoilerLink) => { const statusEl = spoilerLink.parentNode.parentNode; - const message = (statusEl.dataset.spoiler === 'expanded') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more'); + const message = (statusEl.dataset.spoiler === 'expanded') ? (localeData['status.show_less'] || 'Show less') : (localeData['status.show_more'] || 'Show more'); spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format(); }); }); diff --git a/app/javascript/flavours/glitch/styles/forms.scss b/app/javascript/flavours/glitch/styles/forms.scss index bb44d1bac..f69e8f276 100644 --- a/app/javascript/flavours/glitch/styles/forms.scss +++ b/app/javascript/flavours/glitch/styles/forms.scss @@ -1118,3 +1118,89 @@ code { white-space: nowrap; } } + +.progress-tracker { + display: flex; + align-items: center; + padding-bottom: 30px; + margin-bottom: 30px; + + li { + flex: 0 0 auto; + position: relative; + } + + .separator { + height: 2px; + background: $ui-base-lighter-color; + flex: 1 1 auto; + + &.completed { + background: $highlight-text-color; + } + } + + .circle { + box-sizing: border-box; + position: relative; + width: 30px; + height: 30px; + border-radius: 50%; + border: 2px solid $ui-base-lighter-color; + flex: 0 0 auto; + display: flex; + align-items: center; + justify-content: center; + + svg { + width: 16px; + } + } + + .label { + position: absolute; + font-size: 14px; + font-weight: 500; + color: $secondary-text-color; + padding-top: 10px; + text-align: center; + width: 100px; + left: 50%; + transform: translateX(-50%); + } + + li:first-child .label { + left: auto; + inset-inline-start: 0; + text-align: start; + transform: none; + } + + li:last-child .label { + left: auto; + inset-inline-end: 0; + text-align: end; + transform: none; + } + + .active .circle { + border-color: $highlight-text-color; + + &::before { + content: ''; + width: 10px; + height: 10px; + border-radius: 50%; + background: $highlight-text-color; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + } + } + + .completed .circle { + border-color: $highlight-text-color; + background: $highlight-text-color; + } +} diff --git a/app/javascript/mastodon/components/__tests__/__snapshots__/avatar_overlay-test.jsx.snap b/app/javascript/mastodon/components/__tests__/__snapshots__/avatar_overlay-test.jsx.snap index f8385357a..fbd44ecc5 100644 --- a/app/javascript/mastodon/components/__tests__/__snapshots__/avatar_overlay-test.jsx.snap +++ b/app/javascript/mastodon/components/__tests__/__snapshots__/avatar_overlay-test.jsx.snap @@ -3,6 +3,8 @@ exports[`
{ - if (count < 0) { - return 0; - } else if (count <= 1) { - return count; - } else { - return '1+'; - } -}; - -export default class AnimatedNumber extends React.PureComponent { - - static propTypes = { - value: PropTypes.number.isRequired, - obfuscate: PropTypes.bool, - }; - - state = { - direction: 1, - }; - - componentWillReceiveProps (nextProps) { - if (nextProps.value > this.props.value) { - this.setState({ direction: 1 }); - } else if (nextProps.value < this.props.value) { - this.setState({ direction: -1 }); - } - } - - willEnter = () => { - const { direction } = this.state; - - return { y: -1 * direction }; - }; - - willLeave = () => { - const { direction } = this.state; - - return { y: spring(1 * direction, { damping: 35, stiffness: 400 }) }; - }; - - render () { - const { value, obfuscate } = this.props; - const { direction } = this.state; - - if (reduceMotion) { - return obfuscate ? obfuscatedCount(value) : ; - } - - const styles = [{ - key: `${value}`, - data: value, - style: { y: spring(0, { damping: 35, stiffness: 400 }) }, - }]; - - return ( - - {items => ( - - {items.map(({ key, data, style }) => ( - 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : } - ))} - - )} - - ); - } - -} diff --git a/app/javascript/mastodon/components/animated_number.tsx b/app/javascript/mastodon/components/animated_number.tsx new file mode 100644 index 000000000..1673ff41b --- /dev/null +++ b/app/javascript/mastodon/components/animated_number.tsx @@ -0,0 +1,58 @@ +import React, { useCallback, useState } from 'react'; +import ShortNumber from './short_number'; +import { TransitionMotion, spring } from 'react-motion'; +import { reduceMotion } from '../initial_state'; + +const obfuscatedCount = (count: number) => { + if (count < 0) { + return 0; + } else if (count <= 1) { + return count; + } else { + return '1+'; + } +}; + +type Props = { + value: number; + obfuscate?: boolean; +} +export const AnimatedNumber: React.FC = ({ + value, + obfuscate, +})=> { + const [previousValue, setPreviousValue] = useState(value); + const [direction, setDirection] = useState<1|-1>(1); + + if (previousValue !== value) { + setPreviousValue(value); + setDirection(value > previousValue ? 1 : -1); + } + + const willEnter = useCallback(() => ({ y: -1 * direction }), [direction]); + const willLeave = useCallback(() => ({ y: spring(1 * direction, { damping: 35, stiffness: 400 }) }), [direction]); + + if (reduceMotion) { + return obfuscate ? <>{obfuscatedCount(value)} : ; + } + + const styles = [{ + key: `${value}`, + data: value, + style: { y: spring(0, { damping: 35, stiffness: 400 }) }, + }]; + + return ( + + {items => ( + + {items.map(({ key, data, style }) => ( + 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : } + ))} + + )} + + ); +}; + +export default AnimatedNumber; diff --git a/app/javascript/mastodon/components/avatar_overlay.jsx b/app/javascript/mastodon/components/avatar_overlay.jsx deleted file mode 100644 index 034e8ba56..000000000 --- a/app/javascript/mastodon/components/avatar_overlay.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { autoPlayGif } from '../initial_state'; -import Avatar from './avatar'; - -export default class AvatarOverlay extends React.PureComponent { - - static propTypes = { - account: ImmutablePropTypes.map.isRequired, - friend: ImmutablePropTypes.map.isRequired, - animate: PropTypes.bool, - size: PropTypes.number, - baseSize: PropTypes.number, - overlaySize: PropTypes.number, - }; - - static defaultProps = { - animate: autoPlayGif, - size: 46, - baseSize: 36, - overlaySize: 24, - }; - - state = { - hovering: false, - }; - - handleMouseEnter = () => { - if (this.props.animate) return; - this.setState({ hovering: true }); - }; - - handleMouseLeave = () => { - if (this.props.animate) return; - this.setState({ hovering: false }); - }; - - render() { - const { account, friend, animate, size, baseSize, overlaySize } = this.props; - const { hovering } = this.state; - - return ( -
-
-
-
- ); - } - -} diff --git a/app/javascript/mastodon/components/avatar_overlay.tsx b/app/javascript/mastodon/components/avatar_overlay.tsx new file mode 100644 index 000000000..5c65a928c --- /dev/null +++ b/app/javascript/mastodon/components/avatar_overlay.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import type { Account } from '../../types/resources'; +import { useHovering } from '../../hooks/useHovering'; +import { autoPlayGif } from '../initial_state'; + +type Props = { + account: Account; + friend: Account; + size?: number; + baseSize?: number; + overlaySize?: number; +}; + +export const AvatarOverlay: React.FC = ({ + account, + friend, + size = 46, + baseSize = 36, + overlaySize = 24, +}) => { + const { hovering, handleMouseEnter, handleMouseLeave } = useHovering(autoPlayGif); + const accountSrc = hovering ? account?.get('avatar') : account?.get('avatar_static'); + const friendSrc = hovering ? friend?.get('avatar') : friend?.get('avatar_static'); + + return ( +
+
+
+ {accountSrc && {account?.get('acct')}} +
+
+
+
+ {friendSrc && {friend?.get('acct')}} +
+
+
+ ); +}; + +export default AvatarOverlay; diff --git a/app/javascript/mastodon/components/gifv.jsx b/app/javascript/mastodon/components/gifv.jsx deleted file mode 100644 index 1ce7e7c29..000000000 --- a/app/javascript/mastodon/components/gifv.jsx +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -export default class GIFV extends React.PureComponent { - - static propTypes = { - src: PropTypes.string.isRequired, - alt: PropTypes.string, - lang: PropTypes.string, - width: PropTypes.number, - height: PropTypes.number, - onClick: PropTypes.func, - }; - - state = { - loading: true, - }; - - handleLoadedData = () => { - this.setState({ loading: false }); - }; - - componentWillReceiveProps (nextProps) { - if (nextProps.src !== this.props.src) { - this.setState({ loading: true }); - } - } - - handleClick = e => { - const { onClick } = this.props; - - if (onClick) { - e.stopPropagation(); - onClick(); - } - }; - - render () { - const { src, width, height, alt, lang } = this.props; - const { loading } = this.state; - - return ( -
- {loading && ( - - )} - -
- ); - } - -} diff --git a/app/javascript/mastodon/components/gifv.tsx b/app/javascript/mastodon/components/gifv.tsx new file mode 100644 index 000000000..8968170c5 --- /dev/null +++ b/app/javascript/mastodon/components/gifv.tsx @@ -0,0 +1,68 @@ +import React, { useCallback, useState } from 'react'; + +type Props = { + src: string; + key: string; + alt?: string; + lang?: string; + width: number; + height: number; + onClick?: () => void; +} + +export const GIFV: React.FC = ({ + src, + alt, + lang, + width, + height, + onClick, +})=> { + const [loading, setLoading] = useState(true); + + const handleLoadedData: React.ReactEventHandler = useCallback(() => { + setLoading(false); + }, [setLoading]); + + const handleClick: React.MouseEventHandler = useCallback((e) => { + if (onClick) { + e.stopPropagation(); + onClick(); + } + }, [onClick]); + + return ( +
+ {loading && ( + + )} + +
+ ); +}; + +export default GIFV; diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index 923dc892d..60a77a39c 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -541,7 +541,7 @@ class Status extends ImmutablePureComponent { expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} onTranslate={this.handleTranslate} - collapsable + collapsible onCollapsedToggle={this.handleCollapsedToggle} /> diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx index fb953b9dd..60f820bc5 100644 --- a/app/javascript/mastodon/components/status_content.jsx +++ b/app/javascript/mastodon/components/status_content.jsx @@ -65,7 +65,7 @@ class StatusContent extends React.PureComponent { onExpandedToggle: PropTypes.func, onTranslate: PropTypes.func, onClick: PropTypes.func, - collapsable: PropTypes.bool, + collapsible: PropTypes.bool, onCollapsedToggle: PropTypes.func, languages: ImmutablePropTypes.map, intl: PropTypes.object, @@ -112,10 +112,10 @@ class StatusContent extends React.PureComponent { } if (status.get('collapsed', null) === null && onCollapsedToggle) { - const { collapsable, onClick } = this.props; + const { collapsible, onClick } = this.props; const collapsed = - collapsable + collapsible && onClick && node.clientHeight > MAX_HEIGHT && status.get('spoiler_text').length === 0; diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx index d0dbffe65..11f2790bf 100644 --- a/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx +++ b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx @@ -165,7 +165,7 @@ class Conversation extends ImmutablePureComponent { onClick={this.handleClick} expanded={!lastStatus.get('hidden')} onExpandedToggle={this.handleShowMore} - collapsable + collapsible /> {lastStatus.get('media_attachments').size > 0 && ( diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index b547741f7..900b19c31 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -69,6 +69,7 @@ const messages = defineMessages({ redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' }, revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' }, hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' }, + statusTitleWithAttachments: { id: 'status.title.with_attachments', defaultMessage: '{user} posted {attachmentCount, plural, one {an attachment} other {{attachmentCount} attachments}}' }, detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' }, replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, @@ -166,13 +167,14 @@ const truncate = (str, num) => { } }; -const titleFromStatus = status => { +const titleFromStatus = (intl, status) => { const displayName = status.getIn(['account', 'display_name']); const username = status.getIn(['account', 'username']); - const prefix = displayName.trim().length === 0 ? username : displayName; + const user = displayName.trim().length === 0 ? username : displayName; const text = status.get('search_index'); + const attachmentCount = status.get('media_attachments').size; - return `${prefix}: "${truncate(text, 30)}"`; + return text ? `${user}: "${truncate(text, 30)}"` : intl.formatMessage(messages.statusTitleWithAttachments, { user, attachmentCount }); }; class Status extends ImmutablePureComponent { @@ -670,7 +672,7 @@ class Status extends ImmutablePureComponent { - {titleFromStatus(status)} + {titleFromStatus(intl, status)} diff --git a/app/javascript/mastodon/features/ui/components/filter_modal.jsx b/app/javascript/mastodon/features/ui/components/filter_modal.jsx index 32ebaf7b7..8d77fb3df 100644 --- a/app/javascript/mastodon/features/ui/components/filter_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/filter_modal.jsx @@ -131,4 +131,4 @@ class FilterModal extends ImmutablePureComponent { } -export default connect(injectIntl(FilterModal)); +export default connect()(injectIntl(FilterModal)); diff --git a/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx b/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx index 11c4c5237..2a1e4c8bb 100644 --- a/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx @@ -383,7 +383,7 @@ class FocalPointModal extends ImmutablePureComponent { {focals && (
{media.get('type') === 'image' && } - {media.get('type') === 'gifv' && } + {media.get('type') === 'gifv' && }
diff --git a/app/javascript/mastodon/features/ui/components/media_modal.jsx b/app/javascript/mastodon/features/ui/components/media_modal.jsx index 52bd75453..ec6ddc0e1 100644 --- a/app/javascript/mastodon/features/ui/components/media_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/media_modal.jsx @@ -186,7 +186,7 @@ class MediaModal extends ImmutablePureComponent { src={image.get('url')} width={width} height={height} - key={image.get('preview_url')} + key={image.get('url')} alt={image.get('description')} lang={language} onClick={this.toggleNavigation} diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 1351945eb..6d6683808 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -3732,6 +3732,10 @@ "defaultMessage": "Show less for all", "id": "status.show_less_all" }, + { + "defaultMessage": "{user} posted {attachmentCount, plural, one {an attachment} other {{attachmentCount} attachments}}", + "id": "status.title.with_attachments" + }, { "defaultMessage": "Detailed conversation view", "id": "status.detailed_status" @@ -4354,5 +4358,22 @@ } ], "path": "app/javascript/mastodon/features/video/index.json" + }, + { + "descriptors": [ + { + "defaultMessage": "That username is taken. Try another", + "id": "username.taken" + }, + { + "defaultMessage": "Password confirmation exceeds the maximum password length", + "id": "password_confirmation.exceeds_maxlength" + }, + { + "defaultMessage": "Password confirmation does not match", + "id": "password_confirmation.mismatching" + } + ], + "path": "app/javascript/packs/public.json" } ] \ No newline at end of file diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index ae2d5a999..31fa3ca3a 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -443,6 +443,8 @@ "notifications_permission_banner.enable": "Enable desktop notifications", "notifications_permission_banner.how_to_control": "To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled.", "notifications_permission_banner.title": "Never miss a thing", + "password_confirmation.exceeds_maxlength": "Password confirmation exceeds the maximum password length", + "password_confirmation.mismatching": "Password confirmation does not match", "picture_in_picture.restore": "Put it back", "poll.closed": "Closed", "poll.refresh": "Refresh", @@ -598,6 +600,7 @@ "status.show_more": "Show more", "status.show_more_all": "Show more for all", "status.show_original": "Show original", + "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {{attachmentCount} attachments}}", "status.translate": "Translate", "status.translated_from_with": "Translated from {lang} using {provider}", "status.uncached_media_warning": "Not available", @@ -650,6 +653,7 @@ "upload_modal.preview_label": "Preview ({ratio})", "upload_progress.label": "Uploading...", "upload_progress.processing": "Processing…", + "username.taken": "That username is taken. Try another", "video.close": "Close video", "video.download": "Download file", "video.exit_fullscreen": "Exit full screen", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 0085563d2..886976823 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -597,6 +597,7 @@ "status.show_more": "Mostrar más", "status.show_more_all": "Mostrar más para todo", "status.show_original": "Mostrar original", + "status.title.with_attachments": "{user} publicó {attachmentCount, plural, one {un archivo adjunto} other {{attachmentCount} archivos adjuntos}}", "status.translate": "Traducir", "status.translated_from_with": "Traducido del {lang} usando {provider}", "status.uncached_media_warning": "No disponible", diff --git a/app/javascript/packs/public.jsx b/app/javascript/packs/public.jsx index ad6bf237f..606ddc3bf 100644 --- a/app/javascript/packs/public.jsx +++ b/app/javascript/packs/public.jsx @@ -4,6 +4,15 @@ import ready from '../mastodon/ready'; import { start } from '../mastodon/common'; import loadKeyboardExtensions from '../mastodon/load_keyboard_extensions'; import 'cocoon-js-vanilla'; +import axios from 'axios'; +import { throttle } from 'lodash'; +import { defineMessages } from 'react-intl'; + +const messages = defineMessages({ + usernameTaken: { id: 'username.taken', defaultMessage: 'That username is taken. Try another' }, + passwordExceedsLength: { id: 'password_confirmation.exceeds_maxlength', defaultMessage: 'Password confirmation exceeds the maximum password length' }, + passwordDoesNotMatch: { id: 'password_confirmation.mismatching', defaultMessage: 'Password confirmation does not match' }, +}); start(); @@ -13,7 +22,7 @@ function main() { const { delegate } = require('@rails/ujs'); const emojify = require('../mastodon/features/emoji/emoji').default; const { getLocale } = require('../mastodon/locales'); - const { messages } = getLocale(); + const { localeData } = getLocale(); const React = require('react'); const ReactDOM = require('react-dom'); const { createBrowserHistory } = require('history'); @@ -58,6 +67,11 @@ function main() { hour12: false, }); + const formatMessage = ({ id, defaultMessage }, values) => { + const messageFormat = new IntlMessageFormat(localeData[id] || defaultMessage, locale); + return messageFormat.format(values); + }; + [].forEach.call(document.querySelectorAll('.emojify'), (content) => { content.innerHTML = emojify(content.innerHTML); }); @@ -77,7 +91,7 @@ function main() { date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear(); }; - const todayFormat = new IntlMessageFormat(messages['relative_format.today'] || 'Today at {time}', locale); + const todayFormat = new IntlMessageFormat(localeData['relative_format.today'] || 'Today at {time}', locale); [].forEach.call(document.querySelectorAll('time.relative-formatted'), (content) => { const datetime = new Date(content.getAttribute('datetime')); @@ -103,7 +117,7 @@ function main() { const timeGiven = content.getAttribute('datetime').includes('T'); content.title = timeGiven ? dateTimeFormat.format(datetime) : dateFormat.format(datetime); content.textContent = timeAgoString({ - formatMessage: ({ id, defaultMessage }, values) => (new IntlMessageFormat(messages[id] || defaultMessage, locale)).format(values), + formatMessage, formatDate: (date, options) => (new Intl.DateTimeFormat(locale, options)).format(date), }, datetime, now, now.getFullYear(), timeGiven); }); @@ -133,17 +147,19 @@ function main() { scrollToDetailedStatus(); } - delegate(document, '#registration_user_password_confirmation,#registration_user_password', 'input', () => { - const password = document.getElementById('registration_user_password'); - const confirmation = document.getElementById('registration_user_password_confirmation'); - if (confirmation.value && confirmation.value.length > password.maxLength) { - confirmation.setCustomValidity((new IntlMessageFormat(messages['password_confirmation.exceeds_maxlength'] || 'Password confirmation exceeds the maximum password length', locale)).format()); - } else if (password.value && password.value !== confirmation.value) { - confirmation.setCustomValidity((new IntlMessageFormat(messages['password_confirmation.mismatching'] || 'Password confirmation does not match', locale)).format()); + delegate(document, '#user_account_attributes_username', 'input', throttle(() => { + const username = document.getElementById('user_account_attributes_username'); + + if (username.value && username.value.length > 0) { + axios.get('/api/v1/accounts/lookup', { params: { acct: username.value } }).then(() => { + username.setCustomValidity(formatMessage(messages.usernameTaken)); + }).catch(() => { + username.setCustomValidity(''); + }); } else { - confirmation.setCustomValidity(''); + username.setCustomValidity(''); } - }); + }, 500, { leading: false, trailing: true })); delegate(document, '#user_password,#user_password_confirmation', 'input', () => { const password = document.getElementById('user_password'); @@ -151,9 +167,9 @@ function main() { if (!confirmation) return; if (confirmation.value && confirmation.value.length > password.maxLength) { - confirmation.setCustomValidity((new IntlMessageFormat(messages['password_confirmation.exceeds_maxlength'] || 'Password confirmation exceeds the maximum password length', locale)).format()); + confirmation.setCustomValidity(formatMessage(messages.passwordExceedsLength)); } else if (password.value && password.value !== confirmation.value) { - confirmation.setCustomValidity((new IntlMessageFormat(messages['password_confirmation.mismatching'] || 'Password confirmation does not match', locale)).format()); + confirmation.setCustomValidity(formatMessage(messages.passwordDoesNotMatch)); } else { confirmation.setCustomValidity(''); } @@ -167,10 +183,10 @@ function main() { if (statusEl.dataset.spoiler === 'expanded') { statusEl.dataset.spoiler = 'folded'; - this.textContent = (new IntlMessageFormat(messages['status.show_more'] || 'Show more', locale)).format(); + this.textContent = (new IntlMessageFormat(localeData['status.show_more'] || 'Show more', locale)).format(); } else { statusEl.dataset.spoiler = 'expanded'; - this.textContent = (new IntlMessageFormat(messages['status.show_less'] || 'Show less', locale)).format(); + this.textContent = (new IntlMessageFormat(localeData['status.show_less'] || 'Show less', locale)).format(); } return false; @@ -178,7 +194,7 @@ function main() { [].forEach.call(document.querySelectorAll('.status__content__spoiler-link'), (spoilerLink) => { const statusEl = spoilerLink.parentNode.parentNode; - const message = (statusEl.dataset.spoiler === 'expanded') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more'); + const message = (statusEl.dataset.spoiler === 'expanded') ? (localeData['status.show_less'] || 'Show less') : (localeData['status.show_more'] || 'Show more'); spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format(); }); }); diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 7d4bde5e9..dc52bcd87 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -1112,3 +1112,89 @@ code { white-space: nowrap; } } + +.progress-tracker { + display: flex; + align-items: center; + padding-bottom: 30px; + margin-bottom: 30px; + + li { + flex: 0 0 auto; + position: relative; + } + + .separator { + height: 2px; + background: $ui-base-lighter-color; + flex: 1 1 auto; + + &.completed { + background: $highlight-text-color; + } + } + + .circle { + box-sizing: border-box; + position: relative; + width: 30px; + height: 30px; + border-radius: 50%; + border: 2px solid $ui-base-lighter-color; + flex: 0 0 auto; + display: flex; + align-items: center; + justify-content: center; + + svg { + width: 16px; + } + } + + .label { + position: absolute; + font-size: 14px; + font-weight: 500; + color: $secondary-text-color; + padding-top: 10px; + text-align: center; + width: 100px; + left: 50%; + transform: translateX(-50%); + } + + li:first-child .label { + left: auto; + inset-inline-start: 0; + text-align: start; + transform: none; + } + + li:last-child .label { + left: auto; + inset-inline-end: 0; + text-align: end; + transform: none; + } + + .active .circle { + border-color: $highlight-text-color; + + &::before { + content: ''; + width: 10px; + height: 10px; + border-radius: 50%; + background: $highlight-text-color; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + } + } + + .completed .circle { + border-color: $highlight-text-color; + background: $highlight-text-color; + } +} diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb index ab73826ab..c428fd30d 100644 --- a/app/mailers/notification_mailer.rb +++ b/app/mailers/notification_mailer.rb @@ -14,7 +14,7 @@ class NotificationMailer < ApplicationMailer locale_for_account(@me) do thread_by_conversation(@status.conversation) - mail to: @me.user.email, subject: I18n.t('notification_mailer.mention.subject', name: @status.account.acct) + mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.mention.subject', name: @status.account.acct) end end @@ -25,7 +25,7 @@ class NotificationMailer < ApplicationMailer return unless @me.user.functional? locale_for_account(@me) do - mail to: @me.user.email, subject: I18n.t('notification_mailer.follow.subject', name: @account.acct) + mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.follow.subject', name: @account.acct) end end @@ -38,7 +38,7 @@ class NotificationMailer < ApplicationMailer locale_for_account(@me) do thread_by_conversation(@status.conversation) - mail to: @me.user.email, subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct) + mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct) end end @@ -51,7 +51,7 @@ class NotificationMailer < ApplicationMailer locale_for_account(@me) do thread_by_conversation(@status.conversation) - mail to: @me.user.email, subject: I18n.t('notification_mailer.reblog.subject', name: @account.acct) + mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.reblog.subject', name: @account.acct) end end @@ -62,7 +62,7 @@ class NotificationMailer < ApplicationMailer return unless @me.user.functional? locale_for_account(@me) do - mail to: @me.user.email, subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct) + mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct) end end diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb index 1666ea883..55d34e85c 100644 --- a/app/models/account_filter.rb +++ b/app/models/account_filter.rb @@ -55,7 +55,7 @@ class AccountFilter when 'by_domain' Account.where(domain: value.to_s.strip) when 'username' - Account.matches_username(value.to_s.strip) + Account.matches_username(value.to_s.strip.delete_prefix('@')) when 'display_name' Account.matches_display_name(value.to_s.strip) when 'email' diff --git a/app/models/preview_card_provider.rb b/app/models/preview_card_provider.rb index 1dd95fc91..9f5f6d3cb 100644 --- a/app/models/preview_card_provider.rb +++ b/app/models/preview_card_provider.rb @@ -18,6 +18,7 @@ # class PreviewCardProvider < ApplicationRecord + include Paginable include DomainNormalizable include Attachmentable diff --git a/app/serializers/rest/admin/trends/link_serializer.rb b/app/serializers/rest/admin/trends/link_serializer.rb new file mode 100644 index 000000000..c93e6c178 --- /dev/null +++ b/app/serializers/rest/admin/trends/link_serializer.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class REST::Admin::Trends::LinkSerializer < REST::Trends::LinkSerializer + attributes :id, :requires_review + + def requires_review + object.requires_review? + end +end diff --git a/app/serializers/rest/admin/trends/links/preview_card_provider_serializer.rb b/app/serializers/rest/admin/trends/links/preview_card_provider_serializer.rb new file mode 100644 index 000000000..fba0259fb --- /dev/null +++ b/app/serializers/rest/admin/trends/links/preview_card_provider_serializer.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class REST::Admin::Trends::Links::PreviewCardProviderSerializer < ActiveModel::Serializer + attributes :id, :domain, :trendable, :reviewed_at, + :requested_review_at, :requires_review + + def requires_review + object.requires_review? + end +end diff --git a/app/serializers/rest/admin/trends/status_serializer.rb b/app/serializers/rest/admin/trends/status_serializer.rb new file mode 100644 index 000000000..e46be30ab --- /dev/null +++ b/app/serializers/rest/admin/trends/status_serializer.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class REST::Admin::Trends::StatusSerializer < REST::StatusSerializer + attributes :requires_review + + def requires_review + object.requires_review? + end +end diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index 069f370cf..ad9e6e3d6 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -7,6 +7,7 @@ class NotifyService < BaseService admin.report admin.sign_up update + poll ).freeze def call(recipient, type, activity) diff --git a/app/views/auth/registrations/new.html.haml b/app/views/auth/registrations/new.html.haml index 0d8fd800f..4df0f95d5 100644 --- a/app/views/auth/registrations/new.html.haml +++ b/app/views/auth/registrations/new.html.haml @@ -5,6 +5,8 @@ = render partial: 'shared/og', locals: { description: description_for_sign_up } = simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { novalidate: false }) do |f| + = render 'auth/shared/progress', stage: 'details' + %h1.title= t('auth.sign_up.title', domain: site_hostname) %p.lead= t('auth.sign_up.preamble') @@ -18,7 +20,7 @@ .fields-group = f.simple_fields_for :account do |ff| = ff.input :display_name, wrapper: :with_label, label: false, required: false, input_html: { 'aria-label': t('simple_form.labels.defaults.display_name'), autocomplete: 'off', placeholder: t('simple_form.labels.defaults.display_name') } - = ff.input :username, wrapper: :with_label, label: false, required: true, input_html: { 'aria-label': t('simple_form.labels.defaults.username'), autocomplete: 'off', placeholder: t('simple_form.labels.defaults.username'), pattern: '[a-zA-Z0-9_]+', maxlength: 30 }, append: "@#{site_hostname}", hint: false + = ff.input :username, wrapper: :with_label, label: false, required: true, input_html: { 'aria-label': t('simple_form.labels.defaults.username'), autocomplete: 'off', placeholder: t('simple_form.labels.defaults.username'), pattern: '[a-zA-Z0-9_]+', maxlength: 30 }, append: "@#{site_hostname}" = f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label': t('simple_form.labels.defaults.email'), autocomplete: 'username' }, hint: false = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label': t('simple_form.labels.defaults.password'), autocomplete: 'new-password', minlength: User.password_length.first, maxlength: User.password_length.last }, hint: false = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label': t('simple_form.labels.defaults.confirm_password'), autocomplete: 'new-password' }, hint: false @@ -26,9 +28,11 @@ = f.input :website, as: :url, wrapper: :with_label, label: t('simple_form.labels.defaults.honeypot', label: 'Website'), required: false, input_html: { 'aria-label': t('simple_form.labels.defaults.honeypot', label: 'Website'), autocomplete: 'off' } - if approved_registrations? && !@invite.present? + %p.lead= t('auth.sign_up.manual_review', domain: site_hostname) + .fields-group = f.simple_fields_for :invite_request, resource.invite_request || resource.build_invite_request do |invite_request_fields| - = invite_request_fields.input :text, as: :text, wrapper: :with_block_label, required: Setting.require_invite_text + = invite_request_fields.input :text, as: :text, wrapper: :with_block_label, required: Setting.require_invite_text, label: false, hint: false = hidden_field_tag :accept, params[:accept] diff --git a/app/views/auth/registrations/rules.html.haml b/app/views/auth/registrations/rules.html.haml index 8e7a90cbe..aa16ab957 100644 --- a/app/views/auth/registrations/rules.html.haml +++ b/app/views/auth/registrations/rules.html.haml @@ -5,6 +5,8 @@ = render partial: 'shared/og', locals: { description: description_for_sign_up } .simple_form + = render 'auth/shared/progress', stage: 'rules' + %h1.title= t('auth.rules.title') %p.lead= t('auth.rules.preamble', domain: site_hostname) diff --git a/app/views/auth/setup/show.html.haml b/app/views/auth/setup/show.html.haml index 1a6611ceb..913b0c913 100644 --- a/app/views/auth/setup/show.html.haml +++ b/app/views/auth/setup/show.html.haml @@ -1,20 +1,22 @@ - content_for :page_title do = t('auth.setup.title') -- if missing_email? - = simple_form_for(@user, url: auth_setup_path) do |f| - = render 'shared/error_messages', object: @user += simple_form_for(@user, url: auth_setup_path) do |f| + = render 'auth/shared/progress', stage: 'confirm' - .fields-group - %p.hint= t('auth.setup.email_below_hint_html') + %h1.title= t('auth.setup.title') + %p.lead= t('auth.setup.email_settings_hint_html', email: content_tag(:strong, @user.email)) - .fields-group - = f.input :email, required: true, hint: false, input_html: { 'aria-label': t('simple_form.labels.defaults.email'), autocomplete: 'off' } + = render 'shared/error_messages', object: @user - .actions - = f.submit t('admin.accounts.change_email.label'), class: 'button' -- else - .simple_form - %p.hint= t('auth.setup.email_settings_hint_html', email: content_tag(:strong, @user.email)) + %p.lead + %strong= t('auth.setup.link_not_received') + %p.lead= t('auth.setup.email_below_hint_html') + + .fields-group + = f.input :email, required: true, hint: false, input_html: { 'aria-label': t('simple_form.labels.defaults.email'), autocomplete: 'off' } + + .actions + = f.submit t('auth.resend_confirmation'), class: 'button' .form-footer= render 'auth/shared/links' diff --git a/app/views/auth/shared/_links.html.haml b/app/views/auth/shared/_links.html.haml index f078e2f7e..757ef0a09 100644 --- a/app/views/auth/shared/_links.html.haml +++ b/app/views/auth/shared/_links.html.haml @@ -14,5 +14,5 @@ - if controller_name != 'confirmations' && (!user_signed_in? || !current_user.confirmed? || current_user.unconfirmed_email.present?) %li= link_to t('auth.didnt_get_confirmation'), new_user_confirmation_path - - if user_signed_in? && controller_name != 'setup' + - if user_signed_in? %li= link_to t('auth.logout'), destroy_user_session_path, data: { method: :delete } diff --git a/app/views/auth/shared/_progress.html.haml b/app/views/auth/shared/_progress.html.haml new file mode 100644 index 000000000..578f62fa9 --- /dev/null +++ b/app/views/auth/shared/_progress.html.haml @@ -0,0 +1,25 @@ +- progress_index = { rules: 0, details: 1, confirm: 2 }[stage.to_sym] + +%ol.progress-tracker + %li{ class: progress_index.positive? ? 'completed' : 'active' } + .circle + - if progress_index.positive? + = check_icon + .label= t('auth.progress.rules') + %li.separator{ class: progress_index.positive? ? 'completed' : nil } + %li{ class: [progress_index > 1 && 'completed', progress_index == 1 && 'active'] } + .circle + - if progress_index > 1 + = check_icon + .label= t('auth.progress.details') + %li.separator{ class: progress_index > 1 ? 'completed' : nil } + %li{ class: [progress_index > 2 && 'completed', progress_index == 2 && 'active'] } + .circle + - if progress_index > 2 + = check_icon + .label= t('auth.progress.confirm') + - if approved_registrations? + %li.separator{ class: progress_index > 2 ? 'completed' : nil } + %li + .circle + .label= t('auth.progress.review') diff --git a/config/locales/en.yml b/config/locales/en.yml index fa7fb6be0..e5b30d7e3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -125,8 +125,8 @@ en: removed_header_msg: Successfully removed %{username}'s header image resend_confirmation: already_confirmed: This user is already confirmed - send: Resend confirmation email - success: Confirmation email successfully sent! + send: Resend confirmation link + success: Confirmation link successfully sent! reset: Reset reset_password: Reset password resubscribe: Resubscribe @@ -988,7 +988,7 @@ en: prefix_invited_by_user: "@%{name} invites you to join this server of Mastodon!" prefix_sign_up: Sign up on Mastodon today! suffix: With an account, you will be able to follow people, post updates and exchange messages with users from any Mastodon server and more! - didnt_get_confirmation: Didn't receive confirmation instructions? + didnt_get_confirmation: Didn't receive a confirmation link? dont_have_your_security_key: Don't have your security key? forgot_password: Forgot your password? invalid_reset_password_token: Password reset token is invalid or expired. Please request a new one. @@ -1001,12 +1001,17 @@ en: migrate_account_html: If you wish to redirect this account to a different one, you can configure it here. or_log_in_with: Or log in with privacy_policy_agreement_html: I have read and agree to the privacy policy + progress: + confirm: Confirm e-mail + details: Your details + review: Our review + rules: Accept rules providers: cas: CAS saml: SAML register: Sign up registration_closed: "%{instance} is not accepting new members" - resend_confirmation: Resend confirmation instructions + resend_confirmation: Resend confirmation link reset_password: Reset password rules: accept: Accept @@ -1016,13 +1021,16 @@ en: security: Security set_new_password: Set new password setup: - email_below_hint_html: If the below e-mail address is incorrect, you can change it here and receive a new confirmation e-mail. - email_settings_hint_html: The confirmation e-mail was sent to %{email}. If that e-mail address is not correct, you can change it in account settings. - title: Setup + email_below_hint_html: Check your spam folder, or request another one. You can correct your e-mail address if it's wrong. + email_settings_hint_html: Click the link we sent you to verify %{email}. We'll wait right here. + link_not_received: Didn't get a link? + new_confirmation_instructions_sent: You will receive a new e-mail with the confirmation link in a few minutes! + title: Check your inbox sign_in: preamble_html: Sign in with your %{domain} credentials. If your account is hosted on a different server, you will not be able to log in here. title: Sign in to %{domain} sign_up: + manual_review: Sign-ups on %{domain} go through manual review by our moderators. To help us process your registration, write a bit about yourself and why you want an account on %{domain}. preamble: With an account on this Mastodon server, you'll be able to follow any other person on the network, regardless of where their account is hosted. title: Let's get you set up on %{domain}. status: diff --git a/config/locales/simple_form.an.yml b/config/locales/simple_form.an.yml index 10671134d..4d008fa42 100644 --- a/config/locales/simple_form.an.yml +++ b/config/locales/simple_form.an.yml @@ -59,7 +59,6 @@ an: setting_show_application: L'aplicación que utiliza vusté pa publicar publicacions s'amostrará en a vista detallada d'as suyas publicacions setting_use_blurhash: Los gradientes se basan en as colors d'as imachens amagadas pero fendo borrosos los detalles setting_use_pending_items: Amagar nuevos estaus dezaga d'un clic en cuenta de desplazar automaticament lo feed - username: Lo tuyo nombre d'usuario será solo en %{domain} whole_word: Quan la parola clau u frase ye nomás alfanumerica, nomás será aplicau si concuerda con tota la parola domain_allow: domain: Este dominio podrá obtener datos d'este servidor y los datos dentrants serán procesaus y archivados diff --git a/config/locales/simple_form.ar.yml b/config/locales/simple_form.ar.yml index 61befb9e6..449af0f0a 100644 --- a/config/locales/simple_form.ar.yml +++ b/config/locales/simple_form.ar.yml @@ -59,7 +59,6 @@ ar: setting_show_application: سيُعرَض اسم التطبيق الذي تستخدمه عند النشر في العرض المفصّل لمنشوراتك setting_use_blurhash: الألوان التدرّجية مبنية على ألوان المرئيات المخفية ولكنها تحجب كافة التفاصيل setting_use_pending_items: إخفاء تحديثات الخط وراء نقرة بدلًا مِن التمرير التلقائي للتدفق - username: اسم المستخدم الخاص بك سوف يكون فريدا مِن نوعه على %{domain} whole_word: إذا كانت الكلمة أو العبارة مكونة من أرقام وحروف فقط سوف يتم تطبيقها فقط عند مطابقة الكلمة ككل domain_allow: domain: سيكون بإمكان هذا النطاق جلب البيانات من هذا الخادم ومعالجة وتخزين البيانات الواردة منه diff --git a/config/locales/simple_form.ast.yml b/config/locales/simple_form.ast.yml index 9e97d51f2..1b159e2dc 100644 --- a/config/locales/simple_form.ast.yml +++ b/config/locales/simple_form.ast.yml @@ -35,7 +35,6 @@ ast: setting_noindex: Afeuta al perfil públicu ya a les páxines de los artículos setting_show_application: L'aplicación qu'uses pa espublizar apaez na vista detallada de los tos artículos setting_use_blurhash: Los dilíos básense nos colores del conteníu multimedia anubríu mas desenfonca los detalles - username: 'El nome d''usuariu va ser únicu en: %{domain}' featured_tag: name: 'Equí tán dalgunes de les etiquetes qu''usesti apocayá:' form_admin_settings: diff --git a/config/locales/simple_form.be.yml b/config/locales/simple_form.be.yml index 4a2c532ce..f4e46fa0f 100644 --- a/config/locales/simple_form.be.yml +++ b/config/locales/simple_form.be.yml @@ -59,7 +59,6 @@ be: setting_show_application: Праграма, праз якую вы ствараеце допісы, будзе паказвацца ў падрабязнасцях пра допісы setting_use_blurhash: Градыенты заснаваны на колерах схаваных выяў, але размываюць дэталі setting_use_pending_items: Схаваць абнаўленні стужкі за клікам замест аўтаматычнага пракручвання стужкі - username: Ваша імя карыстальніка будзе ўнікальным на %{domain} whole_word: Калі ключавое слова ці фраза складаецца толькі з літар і лічбаў, яно будзе ўжытае толькі калі супадае з усім словам domain_allow: domain: Гэты дамен зможа атрымліваць даныя з гэтага сервера. Даныя з гэтага дамену будуць апрацаваныя ды захаваныя diff --git a/config/locales/simple_form.bg.yml b/config/locales/simple_form.bg.yml index e84edc50d..b611d65fd 100644 --- a/config/locales/simple_form.bg.yml +++ b/config/locales/simple_form.bg.yml @@ -59,7 +59,6 @@ bg: setting_show_application: Приложението, което ползвате за публикуване, ще се показва в подробностите на публикацията ви setting_use_blurhash: Преливането е въз основа на цветовете на скритите визуализации, но се замъгляват подробностите setting_use_pending_items: Да се показват обновявания на часовата ос само след щракване вместо автоматично превъртане на инфоканала - username: Вашето потребителско име ще е неповторимо в %{domain} whole_word: Ако ключовата дума или фраза е само буквеноцифрена, то ще се приложи само, ако съвпадне с цялата дума domain_allow: domain: Домейнът ще може да извлича данни от този сървър и входящите данни от него ще се обработят и съхранят diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml index bcabca034..821600f5d 100644 --- a/config/locales/simple_form.ca.yml +++ b/config/locales/simple_form.ca.yml @@ -59,7 +59,6 @@ ca: setting_show_application: L'aplicació que fas servir per a publicar es mostrarà a la vista detallada dels teus tuts setting_use_blurhash: Els degradats es basen en els colors de les imatges ocultes, però n'enfosqueixen els detalls setting_use_pending_items: Amaga les actualitzacions de la línia de temps després de fer un clic, en lloc de desplaçar-les automàticament - username: El teu nom d'usuari serà únic a %{domain} whole_word: Quan la paraula clau o la frase sigui només alfanumèrica, s'aplicarà si coincideix amb la paraula sencera domain_allow: domain: Aquest domini podrà obtenir dades d’aquest servidor i les dades entrants d’aquests seran processades i emmagatzemades diff --git a/config/locales/simple_form.ckb.yml b/config/locales/simple_form.ckb.yml index 9ce9ac065..e52345020 100644 --- a/config/locales/simple_form.ckb.yml +++ b/config/locales/simple_form.ckb.yml @@ -51,7 +51,6 @@ ckb: setting_show_application: بەرنامەیەک کە بە یارمەتیت توت دەکەیت، لە دیمەنی وردی توتەکان پیشان دەدرێت setting_use_blurhash: سێبەرەکان لە سەر بنەمای ڕەنگەکانی بەکارهاتوو لە وێنە داشاراوەکان دروست دەبن بەڵام وردەزانیاری وێنە تێیدا ڕوون نییە setting_use_pending_items: لەجیاتی ئەوەی بە خۆکارانە کێشان هەبێت لە نووسراوەکان بە کرتەیەک بەڕۆژبوونی پێرستی نووسراوەکان بشارەوە - username: ناوی بەکارهێنەری ئێوە لەسەر %{domain} یەکتا دەبێت whole_word: کاتێک کلیل‌وشە بریتییە لە ژمارە و پیت، تنەها کاتێک پەیدا دەبێت کە لەگەڵ گشتی وشە لە نێو دەقەکە هاوئاهەنگ بێت، نە تەنها لەگەڵ بەشێک لە وشە domain_allow: domain: ئەم دۆمەینە دەتوانێت دراوە لە ئەم ڕاژە وەربگرێت و دراوەی ئەم دۆمەینە لێرە ڕێکدەخرین و پاشکەوت دەکرێن diff --git a/config/locales/simple_form.co.yml b/config/locales/simple_form.co.yml index 79e5837d4..b03ff4a09 100644 --- a/config/locales/simple_form.co.yml +++ b/config/locales/simple_form.co.yml @@ -49,7 +49,6 @@ co: setting_show_application: L'applicazione chì voi utilizate per mandà statuti sarà affissata indè a vista ditagliata di quelli setting_use_blurhash: I digradati blurhash sò basati nant'à i culori di u ritrattu piattatu ma senza i ditagli setting_use_pending_items: Clicchi per messe à ghjornu i statuti invece di fà sfilà a linea autumaticamente - username: U vostru cugnome sarà unicu nant'à %{domain} whole_word: Quandu a parolla o a frasa sana hè alfanumerica, sarà applicata solu s'ella currisponde à a parolla sana domain_allow: domain: Stu duminiu puderà ricuperà i dati di stu servore è i dati ch'affaccanu da quallà saranu trattati è cunservati diff --git a/config/locales/simple_form.cs.yml b/config/locales/simple_form.cs.yml index 7025c6385..f253c82b9 100644 --- a/config/locales/simple_form.cs.yml +++ b/config/locales/simple_form.cs.yml @@ -59,7 +59,6 @@ cs: setting_show_application: Aplikace, kterou používáte k odeslání příspěvků, bude zobrazena jejich detailním zobrazení setting_use_blurhash: Gradienty jsou založeny na barvách skryté grafiky, ale zakrývají jakékoliv detaily setting_use_pending_items: Aktualizovat časovou osu až po kliknutí namísto automatického rolování kanálu - username: Vaše uživatelské jméno bude na serveru %{domain} unikátní whole_word: Je-li klíčové slovo či fráze pouze alfanumerická, bude aplikován pouze, pokud se shoduje s celým slovem domain_allow: domain: Tato doména bude moci stahovat data z tohoto serveru a příchozí data z ní budou zpracována a uložena diff --git a/config/locales/simple_form.cy.yml b/config/locales/simple_form.cy.yml index d965f6b71..f482e6ea9 100644 --- a/config/locales/simple_form.cy.yml +++ b/config/locales/simple_form.cy.yml @@ -59,7 +59,6 @@ cy: setting_show_application: Bydd y cymhwysiad a ddefnyddiwch i bostio yn cael ei arddangos yng ngolwg fanwl eich postiadau setting_use_blurhash: Mae graddiannau wedi'u seilio ar liwiau'r delweddau cudd ond maen nhw'n cuddio unrhyw fanylion setting_use_pending_items: Cuddio diweddariadau llinell amser y tu ôl i glic yn lle sgrolio'n awtomatig - username: Bydd eich enw defnyddiwr yn unigryw ar %{domain} whole_word: Os yw'r allweddair neu'r ymadrodd yn alffaniwmerig yn unig, mi fydd ond yn cael ei osod os yw'n cyfateb a'r gair cyfan domain_allow: domain: Bydd y parth hwn yn gallu nôl data o'r gweinydd hwn a bydd data sy'n dod i mewn ohono yn cael ei brosesu a'i storio diff --git a/config/locales/simple_form.da.yml b/config/locales/simple_form.da.yml index af2942742..501c33b1c 100644 --- a/config/locales/simple_form.da.yml +++ b/config/locales/simple_form.da.yml @@ -59,7 +59,6 @@ da: setting_show_application: Applikationen, hvormed der postes, vil fremgå af detailvisningen af dine indlæg setting_use_blurhash: Gradienter er baseret på de skjulte grafikelementers farver, men slører alle detaljer setting_use_pending_items: Skjul tidslinjeopdateringer bag et klik i stedet for brug af auto-feedrulning - username: Dit brugernavn vil være unikt på %{domain} whole_word: Ved rent alfanumeriske nøgleord/-sætning, forudsætter brugen matchning af hele ordet domain_allow: domain: Dette domæne vil kunne hente data, som efterfølgende behandles og gemmes, fra denne server diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index 4a13cea7c..2b84def3d 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -59,7 +59,6 @@ de: setting_show_application: Die Anwendung die du nutzt wird in der detaillierten Ansicht deiner Beiträge angezeigt setting_use_blurhash: Die Farbverläufe basieren auf den Farben der verborgenen Medien, verschleiern aber jegliche Details setting_use_pending_items: Neue Beiträge hinter einem Klick verstecken, anstatt des automatischen Bildlaufs - username: Dein Profilname wird auf %{domain} einmalig sein whole_word: Wenn das Wort oder die Formulierung nur aus Buchstaben oder Zahlen besteht, tritt der Filter nur dann in Kraft, wenn er exakt dieser Zeichenfolge entspricht domain_allow: domain: Diese Domain kann Daten von diesem Server abrufen, und eingehende Daten werden verarbeitet und gespeichert diff --git a/config/locales/simple_form.el.yml b/config/locales/simple_form.el.yml index 2788fd9b5..88a9840da 100644 --- a/config/locales/simple_form.el.yml +++ b/config/locales/simple_form.el.yml @@ -59,7 +59,6 @@ el: setting_show_application: Η εφαρμογή που χρησιμοποιείς για να στέλνεις τα τουτ σου θα εμφανίζεται στις αναλυτικές λεπτομέρειες τους setting_use_blurhash: Οι χρωματισμοί βασίζονται στα χρώματα του κρυμμένου πολυμέσου αλλά θολώνουν τις λεπτομέρειες setting_use_pending_items: Εμφάνιση ενημερώσεων ροής μετά από κλικ αντί για αυτόματη κύλισή τους - username: Το όνομα χρήστη σου θα είναι μοναδικό στο %{domain} whole_word: Όταν η λέξη ή η φράση κλειδί είναι μόνο αλφαριθμητική, θα εφαρμοστεί μόνο αν ταιριάζει με ολόκληρη τη λέξη domain_allow: domain: Ο τομέας αυτός θα επιτρέπεται να ανακτά δεδομένα από αυτό τον διακομιστή και τα εισερχόμενα δεδομένα θα επεξεργάζονται και θα αποθηκεύονται diff --git a/config/locales/simple_form.en-GB.yml b/config/locales/simple_form.en-GB.yml index 27aa80c42..35b80fd07 100644 --- a/config/locales/simple_form.en-GB.yml +++ b/config/locales/simple_form.en-GB.yml @@ -59,7 +59,6 @@ en-GB: setting_show_application: The application you use to post will be displayed in the detailed view of your posts setting_use_blurhash: Gradients are based on the colors of the hidden visuals but obfuscate any details setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed - username: Your username will be unique on %{domain} whole_word: When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word domain_allow: domain: This domain will be able to fetch data from this server and incoming data from it will be processed and stored diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 96b0131ef..b646a15e2 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -59,7 +59,7 @@ en: setting_show_application: The application you use to post will be displayed in the detailed view of your posts setting_use_blurhash: Gradients are based on the colors of the hidden visuals but obfuscate any details setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed - username: Your username will be unique on %{domain} + username: You can use letters, numbers, and underscores whole_word: When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word domain_allow: domain: This domain will be able to fetch data from this server and incoming data from it will be processed and stored diff --git a/config/locales/simple_form.eo.yml b/config/locales/simple_form.eo.yml index d54248de0..0e0123af7 100644 --- a/config/locales/simple_form.eo.yml +++ b/config/locales/simple_form.eo.yml @@ -59,7 +59,6 @@ eo: setting_show_application: La aplikaĵo, kiun vi uzas por afiŝi, estos montrita en la detala vido de viaj afiŝoj setting_use_blurhash: Transirojn estas bazita sur la koloroj de la kaŝitaj aŭdovidaĵoj sed ne montri iun ajn detalon setting_use_pending_items: Kaŝi tempoliniajn ĝisdatigojn malantaŭ klako anstataŭ aŭtomate rulumi la fluon - username: Via uzantnomo estos unika en %{domain} whole_word: Kiam la vorto aŭ frazo estas nur litera aŭ cifera, ĝi estos uzata nur se ĝi kongruas kun la tuta vorto domain_allow: domain: Ĉi tiu domajno povos akiri datumon de ĉi tiu servilo kaj envenanta datumo estos prilaborita kaj konservita diff --git a/config/locales/simple_form.es-AR.yml b/config/locales/simple_form.es-AR.yml index bcceccfb1..0d29d6612 100644 --- a/config/locales/simple_form.es-AR.yml +++ b/config/locales/simple_form.es-AR.yml @@ -59,7 +59,6 @@ es-AR: setting_show_application: La aplicación que usás para enviar mensajes se mostrará en la vista detallada de tus mensajes setting_use_blurhash: Los gradientes se basan en los colores de las imágenes ocultas pero haciendo borrosos los detalles setting_use_pending_items: Ocultar actualizaciones de la línea temporal detrás de un clic en lugar de desplazar automáticamente el flujo - username: Tu nombre de usuario será único en %{domain} whole_word: Cuando la palabra clave o frase es sólo alfanumérica, sólo será aplicado si coincide con toda la palabra domain_allow: domain: Este dominio podrá recolectar datos de este servidor, y los datos entrantes serán procesados y archivados diff --git a/config/locales/simple_form.es-MX.yml b/config/locales/simple_form.es-MX.yml index 52d8974f2..efc4f0d80 100644 --- a/config/locales/simple_form.es-MX.yml +++ b/config/locales/simple_form.es-MX.yml @@ -59,7 +59,6 @@ es-MX: setting_show_application: La aplicación que utiliza usted para publicar toots se mostrará en la vista detallada de sus toots setting_use_blurhash: Los gradientes se basan en los colores de las imágenes ocultas pero haciendo borrosos los detalles setting_use_pending_items: Ocultar nuevos estados detrás de un clic en lugar de desplazar automáticamente el feed - username: Tu nombre de usuario será único en %{domain} whole_word: Cuando la palabra clave o frase es solo alfanumérica, solo será aplicado si concuerda con toda la palabra domain_allow: domain: Este dominio podrá obtener datos de este servidor y los datos entrantes serán procesados y archivados diff --git a/config/locales/simple_form.es.yml b/config/locales/simple_form.es.yml index 593416aca..a2eafe617 100644 --- a/config/locales/simple_form.es.yml +++ b/config/locales/simple_form.es.yml @@ -59,7 +59,6 @@ es: setting_show_application: La aplicación que utiliza usted para publicar publicaciones se mostrará en la vista detallada de sus publicaciones setting_use_blurhash: Los gradientes se basan en los colores de las imágenes ocultas pero haciendo borrosos los detalles setting_use_pending_items: Ocultar nuevos estados detrás de un clic en lugar de desplazar automáticamente el feed - username: Tu nombre de usuario será único en %{domain} whole_word: Cuando la palabra clave o frase es solo alfanumérica, solo será aplicado si concuerda con toda la palabra domain_allow: domain: Este dominio podrá obtener datos de este servidor y los datos entrantes serán procesados y archivados diff --git a/config/locales/simple_form.et.yml b/config/locales/simple_form.et.yml index 83aa4159d..ce3bc8b96 100644 --- a/config/locales/simple_form.et.yml +++ b/config/locales/simple_form.et.yml @@ -59,7 +59,6 @@ et: setting_show_application: Postitamiseks kasutatud rakenduse infot kuvatakse postituse üksikasjavaates setting_use_blurhash: Värvid põhinevad peidetud visuaalidel, kuid hägustavad igasuguseid detaile setting_use_pending_items: Voo automaatse kerimise asemel peida ajajoone uuendused kliki taha - username: Sinu kasutajanimi on %{domain}-il unikaalne whole_word: Kui võtmesõna või fraas on ainult tähtnumbriline, rakendub see ainult siis, kui see kattub terve sõnaga domain_allow: domain: See domeen saab tõmmata andmeid sellelt serverilt ning sissetulevad andmed sellelt domeenilt töödeldakse ning salvestatakse diff --git a/config/locales/simple_form.eu.yml b/config/locales/simple_form.eu.yml index 175e0c96d..303011447 100644 --- a/config/locales/simple_form.eu.yml +++ b/config/locales/simple_form.eu.yml @@ -59,7 +59,6 @@ eu: setting_show_application: Tootak bidaltzeko erabiltzen duzun aplikazioa zure tooten ikuspegi xehetsuan bistaratuko da setting_use_blurhash: Gradienteak ezkutatutakoaren koloreetan oinarritzen dira, baina xehetasunak ezkutatzen dituzte setting_use_pending_items: Ezkutatu denbora-lerroko eguneraketak klik baten atzean jarioa automatikoki korritu ordez - username: Zure erabiltzaile-izena bakana izango da %{domain} domeinuan whole_word: Hitz eta esaldi gakoa alfanumerikoa denean, hitz osoarekin bat datorrenean besterik ez da aplikatuko domain_allow: domain: Domeinu honek zerbitzari honetatik datuak hartu ahal izango ditu eta bertatik jasotako informazioa prozesatu eta gordeko da diff --git a/config/locales/simple_form.fa.yml b/config/locales/simple_form.fa.yml index 18dd82790..d9db0c697 100644 --- a/config/locales/simple_form.fa.yml +++ b/config/locales/simple_form.fa.yml @@ -59,7 +59,6 @@ fa: setting_show_application: برنامه‌ای که به کمک آن فرسته می‌زنید، در جزئیات فرسته شما نمایش خواهد یافت setting_use_blurhash: سایه‌ها بر اساس رنگ‌های به‌کاررفته در تصویر پنهان‌شده ساخته می‌شوند ولی جزئیات تصویر در آن‌ها آشکار نیست setting_use_pending_items: به جای پیش‌رفتن خودکار در فهرست، به‌روزرسانی فهرست نوشته‌ها را پشت یک کلیک پنهان کن - username: نام کاربری شما روی %{domain} یکتا خواهد بود whole_word: اگر کلیدواژه فقط دارای حروف و اعداد باشد، تنها وقتی پیدا می‌شود که با کل یک واژه در متن منطبق باشد، نه با بخشی از یک واژه domain_allow: domain: این دامین خواهد توانست داده‌ها از این سرور را دریافت کند و داده‌های از این دامین در این‌جا پردازش و ذخیره خواهند شد diff --git a/config/locales/simple_form.fi.yml b/config/locales/simple_form.fi.yml index b96cf0cd7..118054855 100644 --- a/config/locales/simple_form.fi.yml +++ b/config/locales/simple_form.fi.yml @@ -59,7 +59,6 @@ fi: setting_show_application: Viestittelyyn käyttämäsi sovellus näkyy viestiesi yksityiskohtaisessa näkymässä setting_use_blurhash: Liukuvärit perustuvat piilotettujen kuvien väreihin, mutta sumentavat yksityiskohdat setting_use_pending_items: Piilota aikajanan päivitykset napsautuksen taakse sen sijaan, että vierittäisi syötettä automaattisesti - username: Käyttäjänimesi tulee olemaan yksilöllinen %{domain} whole_word: Kun avainsana tai lause on vain aakkosnumeerinen, se otetaan käyttöön, jos se vastaa koko sanaa domain_allow: domain: Tämä verkkotunnus voi noutaa tietoja tältä palvelimelta ja sieltä saapuvat tiedot käsitellään ja tallennetaan diff --git a/config/locales/simple_form.fo.yml b/config/locales/simple_form.fo.yml index 2713d1f89..d22851682 100644 --- a/config/locales/simple_form.fo.yml +++ b/config/locales/simple_form.fo.yml @@ -59,7 +59,6 @@ fo: setting_show_application: Nýtsluskipanin, sum tú brúkar at posta við, verður víst í nágreinligu vísingini av postum tínum setting_use_blurhash: Gradientar eru grundaðir á litirnar av fjaldu myndunum, men grugga allar smálutir setting_use_pending_items: Fjal tíðarlinjudagføringar aftan fyri eitt klikk heldur enn at skrulla tilføringina sjálvvirkandi - username: Brúkaranavnið hjá tær verður eindømi á %{domain} whole_word: Tá lyklaorðið ella frasan einans hevur bókstavir og tøl, so verður hon einans nýtt, um tú samsvarar við alt orðið domain_allow: domain: Økisnavnið kann heinta dátur frá hesum ambætaranum og inngangandi dátur frá honum verða viðgjørdar og goymdar diff --git a/config/locales/simple_form.fr-QC.yml b/config/locales/simple_form.fr-QC.yml index 541bc8be6..6517d3d8d 100644 --- a/config/locales/simple_form.fr-QC.yml +++ b/config/locales/simple_form.fr-QC.yml @@ -59,7 +59,6 @@ fr-QC: setting_show_application: Le nom de l’application que vous utilisez pour publier sera affichée dans la vue détaillée de vos messages setting_use_blurhash: Les dégradés sont basés sur les couleurs des images cachées mais n’en montrent pas les détails setting_use_pending_items: Cacher les mises à jour des fils d’actualités derrière un clic, au lieu de les afficher automatiquement - username: Votre nom d’utilisateur sera unique sur %{domain} whole_word: Si le mot-clé ou la phrase est alphanumérique, alors le filtre ne sera appliqué que s’il correspond au mot entier domain_allow: domain: Ce domaine pourra récupérer des données de ce serveur et les données entrantes seront traitées et stockées diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml index d395b8517..d4697fa1e 100644 --- a/config/locales/simple_form.fr.yml +++ b/config/locales/simple_form.fr.yml @@ -59,7 +59,6 @@ fr: setting_show_application: Le nom de l’application que vous utilisez pour publier sera affichée dans la vue détaillée de vos messages setting_use_blurhash: Les dégradés sont basés sur les couleurs des images cachées mais n’en montrent pas les détails setting_use_pending_items: Cacher les mises à jour des fils d’actualités derrière un clic, au lieu de les afficher automatiquement - username: Votre identifiant sera unique sur %{domain} whole_word: Si le mot-clé ou la phrase est alphanumérique, alors le filtre ne sera appliqué que s’il correspond au mot entier domain_allow: domain: Ce domaine pourra récupérer des données de ce serveur et les données entrantes seront traitées et stockées diff --git a/config/locales/simple_form.fy.yml b/config/locales/simple_form.fy.yml index be950189b..547a2a5ed 100644 --- a/config/locales/simple_form.fy.yml +++ b/config/locales/simple_form.fy.yml @@ -59,7 +59,6 @@ fy: setting_show_application: De tapassing dy’t jo brûke om berjochten te pleatsen, wurdt yn de detaillearre werjefte fan it berjocht toand setting_use_blurhash: Dizige kleuroergongen binne basearre op de kleuren fan de ferstoppe media, wêrmei elk detail ferdwynt setting_use_pending_items: De tiidline wurdt bywurke troch op it oantal nije items te klikken, yn stee fan dat dizze automatysk bywurke wurdt - username: Jo brûkersnamme is unyk op %{domain} whole_word: Wannear it trefwurd of part fan de sin alfanumeryk is, wurdt it allinnich filtere wannear’t it hiele wurd oerienkomt domain_allow: domain: Dit domein is yn steat om gegevens fan dizze server op te heljen, en ynkommende gegevens wurde ferwurke en bewarre diff --git a/config/locales/simple_form.gd.yml b/config/locales/simple_form.gd.yml index c86fc5a0c..a34865ff7 100644 --- a/config/locales/simple_form.gd.yml +++ b/config/locales/simple_form.gd.yml @@ -59,7 +59,6 @@ gd: setting_show_application: Chithear cò an aplacaid a chleachd thu airson post a sgrìobhadh ann an seallaidhean mionaideach nam postaichean agad setting_use_blurhash: Tha caiseadan stèidhichte air dathan nan nithean lèirsinneach a chaidh fhalach ach chan fhaicear am mion-fhiosrachadh setting_use_pending_items: Falaich ùrachaidhean na loidhne-ama air cùlaibh briogaidh seach a bhith a’ sgroladh nam postaichean gu fèin-obrachail - username: Bidh ainm-cleachdaiche àraidh agad air %{domain} whole_word: Mur eil ach litrichean is àireamhan san fhacal-luirg, cha dèid a chur an sàs ach ma bhios e a’ maidseadh an fhacail shlàin domain_allow: domain: "’S urrainn dhan àrainn seo dàta fhaighinn on fhrithealaiche seo agus thèid an dàta a thig a-steach uaithe a phròiseasadh ’s a stòradh" diff --git a/config/locales/simple_form.gl.yml b/config/locales/simple_form.gl.yml index c4a6da566..ec038f9b6 100644 --- a/config/locales/simple_form.gl.yml +++ b/config/locales/simple_form.gl.yml @@ -59,7 +59,6 @@ gl: setting_show_application: A aplicación que estás a utilizar para enviar publicacións mostrarase na vista detallada da publicación setting_use_blurhash: Os gradientes toman as cores da imaxe oculta pero esvaecendo tódolos detalles setting_use_pending_items: Agochar actualizacións da cronoloxía tras un click no lugar de desprazar automáticamente os comentarios - username: O teu nome de usuaria será único en %{domain} whole_word: Se a chave ou frase de paso é só alfanumérica, só se aplicará se concorda a palabra completa domain_allow: domain: Este dominio estará en disposición de obter datos desde este servidor e datos de entrada a el poderán ser procesados e gardados diff --git a/config/locales/simple_form.he.yml b/config/locales/simple_form.he.yml index 8e6631e36..e540c3473 100644 --- a/config/locales/simple_form.he.yml +++ b/config/locales/simple_form.he.yml @@ -59,7 +59,6 @@ he: setting_show_application: היישום בו נעשה שימוש כדי לחצרץ יופיע בתצוגה המפורטת של החצרוץ setting_use_blurhash: הגראדיינטים מבוססים על תוכן התמונה המוסתרת, אבל מסתירים את כל הפרטים setting_use_pending_items: הסתר עדכוני פיד מאחורי קליק במקום לגלול את הפיד אוטומטית - username: שם המשתמש שלך יהיה ייחודי ב- %{domain} whole_word: אם מילת מפתח או ביטוי הם אלפאנומריים בלבד, הם יופעלו רק אם נמצאה התאמה למילה שלמה domain_allow: domain: דומיין זה יוכל לייבא מידע משרת זה והמידע המגיע ממנו יעובד ויאופסן diff --git a/config/locales/simple_form.hu.yml b/config/locales/simple_form.hu.yml index 81d74cebe..ebc07f0d0 100644 --- a/config/locales/simple_form.hu.yml +++ b/config/locales/simple_form.hu.yml @@ -59,7 +59,6 @@ hu: setting_show_application: A bejegyzések részletes nézetében látszani fog, milyen alkalmazást használtál a bejegyzés közzétételéhez setting_use_blurhash: A kihomályosítás az eredeti képből történik, de minden részletet elrejt setting_use_pending_items: Idővonal frissítése csak kattintásra automatikus görgetés helyett - username: A felhasználói neved egyedi lesz a %{domain} domainen whole_word: Ha a kulcsszó alfanumerikus, csak akkor minősül majd találatnak, ha teljes szóra illeszkedik domain_allow: domain: Ez a domain adatokat kérhet le erről a kiszolgálóról, és a bejövő adatok fel lesznek dolgozva és tárolva lesznek diff --git a/config/locales/simple_form.hy.yml b/config/locales/simple_form.hy.yml index b95502155..2bc72ecdb 100644 --- a/config/locales/simple_form.hy.yml +++ b/config/locales/simple_form.hy.yml @@ -49,7 +49,6 @@ hy: setting_show_application: Գրառման մանրամասներում կերեւայ թէ որ ծրագրով ես հրապարակել այն setting_use_blurhash: Կտորները հիմնուում են թաքցուած վիզուալի վրայ՝ խամրեցնելով դետալները setting_use_pending_items: Թաքցնել հոսքի թարմացումները կտտոի ետեւում՝ աւտօմատ թարմացուող հոսքի փոխարէն - username: Քո օգտանունը պէտք է եզակի լինի %{domain}-ում։ whole_word: Եթէ բանալի բառը կամ արտայայտութիւնը պարունակում է միայն այբբենական նիշեր եւ թուեր, ապա այն կիրառուելու է ամբողջ բառի հետ համընկնելու դէպքում միայն domain_allow: domain: Այս տիրոյթը կարող է ստանալ տուեալներ այս սպասարկչից եւ ստացուող տուեալները կարող են օգտագործուել եւ պահուել diff --git a/config/locales/simple_form.id.yml b/config/locales/simple_form.id.yml index 43f278264..2892b1362 100644 --- a/config/locales/simple_form.id.yml +++ b/config/locales/simple_form.id.yml @@ -57,7 +57,6 @@ id: setting_show_application: Aplikasi yang Anda pakai untuk men-toot akan ditampilkan di tampilan detail toot setting_use_blurhash: Gradien didasarkan pada warna visual yang tersembunyi tetapi mengaburkan setiap detail setting_use_pending_items: Sembunyikan pembaruan linimasa di balik klik alih-alih bergulir secara otomatis - username: Nama pengguna Anda unik di %{domain} whole_word: Ketika kata kunci/frasa hanya alfanumerik, maka itu hanya akan diterapkan jika cocok dengan semua kata domain_allow: domain: Domain ini dapat mengambil data dari server ini dan data yang diterima akan diproses dan disimpan diff --git a/config/locales/simple_form.io.yml b/config/locales/simple_form.io.yml index d6aff6afd..d1f4ccf97 100644 --- a/config/locales/simple_form.io.yml +++ b/config/locales/simple_form.io.yml @@ -57,7 +57,6 @@ io: setting_show_application: Softwaro quon vu uzar por postigar montresos che detala vidajo di vua posti setting_use_blurhash: Inklini esas segun kolori di celesis vidaji ma kovras irga detali setting_use_pending_items: Celez tempolineonovi dop kliktar e ne automatike movigar niuzeto - username: Vua uzantonomo esos nura che %{domain} whole_word: Kande klefvorto o fraz esas nur litera e nombra, ol nur aplikesos se ol parigesas la tota vorto domain_allow: domain: Ca domeno povas ganar informi de ca servilo e venanta informo de ol procedagesos e sparesos diff --git a/config/locales/simple_form.is.yml b/config/locales/simple_form.is.yml index ed17c9753..6246099ff 100644 --- a/config/locales/simple_form.is.yml +++ b/config/locales/simple_form.is.yml @@ -59,7 +59,6 @@ is: setting_show_application: Nafnið á forritinu sem þú notar til að senda færslur mun birtast í ítarlegri sýn á færslunum þínum setting_use_blurhash: Litstiglarnir byggja á litunum í földu myndunum, en gera öll smáatriði óskýr setting_use_pending_items: Fela uppfærslur tímalínu þar til smellt er, í stað þess að hún skruni streyminu sjálfvirkt - username: Notandanafnið þitt verður einstakt á %{domain} whole_word: Þegar stikkorð eða setning er einungis tölur og bókstafir, verður það aðeins notað ef það samsvarar heilu orði domain_allow: domain: Þetta lén mun geta sótt gögn af þessum vefþjóni og tekið verður á móti innsendum gögnum frá léninu til vinnslu og geymslu diff --git a/config/locales/simple_form.it.yml b/config/locales/simple_form.it.yml index 189b9bfc5..8e31de783 100644 --- a/config/locales/simple_form.it.yml +++ b/config/locales/simple_form.it.yml @@ -59,7 +59,6 @@ it: setting_show_application: L'applicazione che usi per pubblicare i toot sarà mostrata nella vista di dettaglio dei tuoi toot setting_use_blurhash: I gradienti sono basati sui colori delle immagini nascoste ma offuscano tutti i dettagli setting_use_pending_items: Fare clic per mostrare i nuovi messaggi invece di aggiornare la timeline automaticamente - username: Il tuo nome utente sarà unico su %{domain} whole_word: Quando la parola chiave o la frase è solo alfanumerica, si applica solo se corrisponde alla parola intera domain_allow: domain: Questo dominio potrà recuperare i dati da questo server e i dati in arrivo da esso verranno elaborati e memorizzati diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index f7e2cb954..1e52f77c3 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -59,7 +59,6 @@ ja: setting_show_application: 投稿するのに使用したアプリが投稿の詳細ビューに表示されるようになります setting_use_blurhash: ぼかしはメディアの色を元に生成されますが、細部は見えにくくなっています setting_use_pending_items: 新着があってもタイムラインを自動的にスクロールしないようにします - username: あなたのユーザー名は%{domain}の中で重複していない必要があります whole_word: キーワードまたはフレーズが英数字のみの場合、単語全体と一致する場合のみ適用されるようになります domain_allow: domain: 登録するとこのサーバーからデータを受信したり、このドメインから受信するデータを処理して保存できるようになります diff --git a/config/locales/simple_form.kab.yml b/config/locales/simple_form.kab.yml index 2f8a9261e..b55a3406d 100644 --- a/config/locales/simple_form.kab.yml +++ b/config/locales/simple_form.kab.yml @@ -20,7 +20,6 @@ kab: setting_display_media_hide_all: Ffer yal tikkelt akk taywalt setting_display_media_show_all: Ffer yal tikkelt teywalt yettwacreḍ d tanafrit setting_hide_network: Wid i teṭṭafaṛeḍ d wid i k-yeṭṭafaṛen ur d-ttwaseknen ara deg umaγnu-inek - username: Isem-ik n umseqdac ad yili d ayiwen, ulac am netta deg %{domain} imports: data: Afaylu CSV id yusan seg uqeddac-nniḍen n Maṣṭudun ip_block: diff --git a/config/locales/simple_form.ko.yml b/config/locales/simple_form.ko.yml index 1d63fb631..cb1783880 100644 --- a/config/locales/simple_form.ko.yml +++ b/config/locales/simple_form.ko.yml @@ -59,7 +59,6 @@ ko: setting_show_application: 당신이 게시물을 작성하는데에 사용한 앱이 게시물의 상세정보에 표시 됩니다 setting_use_blurhash: 그라디언트는 숨겨진 내용의 색상을 기반으로 하지만 상세 내용은 보이지 않게 합니다 setting_use_pending_items: 타임라인의 새 게시물을 자동으로 보여 주는 대신, 클릭해서 나타내도록 합니다 - username: 당신의 사용자명은 %{domain} 안에서 유일해야 합니다 whole_word: 키워드가 영문과 숫자로만 이루어 진 경우, 단어 전체에 매칭 되었을 때에만 작동하게 합니다 domain_allow: domain: 이 도메인은 이 서버에서 데이터를 가져갈 수 있고 이 도메인에서 보내진 데이터는 처리되고 저장 됩니다 diff --git a/config/locales/simple_form.ku.yml b/config/locales/simple_form.ku.yml index f7188152d..bd0529772 100644 --- a/config/locales/simple_form.ku.yml +++ b/config/locales/simple_form.ku.yml @@ -59,7 +59,6 @@ ku: setting_show_application: Navê sepana ku tu ji bo şandinê wê bi kar tîne dê di dîtinê berferh ên di şandiyên te de were xuyakirin setting_use_blurhash: Gradyen xwe bi rengên dîtbarîyên veşartî ve radigire, lê belê hûrgilîyan diveşêre setting_use_pending_items: Li şûna ku herkê wek bixweber bizivirînî nûvekirina demnameyê li paş tikandinekî veşêre - username: Navê te yê bikarhênerî li ser %{domain} de bêhempa be whole_word: Dema peyvkilîd an jî hevok bi tenê alfahejmarî çêbe, bi tenê hemû bêjeyê re li hev bike wê pêk bê domain_allow: domain: Ev navê navperê, ji vê rajekarê wê daneyan bistîne û daneyên ku jê bê wê were sazkirin û veşartin diff --git a/config/locales/simple_form.lv.yml b/config/locales/simple_form.lv.yml index ff727235e..22ea7c6a9 100644 --- a/config/locales/simple_form.lv.yml +++ b/config/locales/simple_form.lv.yml @@ -59,7 +59,6 @@ lv: setting_show_application: Lietojumprogramma, ko tu izmanto publicēšanai, tiks parādīta tavu ziņu detalizētajā skatā setting_use_blurhash: Gradientu pamatā ir paslēpto vizuālo attēlu krāsas, bet neskaidras visas detaļas setting_use_pending_items: Paslēpt laika skalas atjauninājumus aiz klikšķa, nevis automātiski ritini plūsmu - username: Tavs lietotājvārds %{domain} būs unikāls whole_word: Ja atslēgvārds vai frāze ir tikai burtciparu, tas tiks lietots tikai tad, ja tas atbilst visam vārdam domain_allow: domain: Šis domēns varēs izgūt datus no šī servera, un no tā ienākošie dati tiks apstrādāti un saglabāti diff --git a/config/locales/simple_form.my.yml b/config/locales/simple_form.my.yml index d3594307a..7beaa2a00 100644 --- a/config/locales/simple_form.my.yml +++ b/config/locales/simple_form.my.yml @@ -59,7 +59,6 @@ my: setting_show_application: ပို့စ်တင်ရန်အတွက် သင်အသုံးပြုသည့် အက်ပလီကေးရှင်းကို သင့်ပို့စ်များ၏ အသေးစိတ်ကြည့်ရှုမှုတွင် ပြသမည်ဖြစ်သည် setting_use_blurhash: Gradients မှာ ဖျောက်ထားသောရုပ်ပုံများ၏ အရောင်များကိုအခြေခံသော်လည်း မည်သည့်အသေးစိတ်အချက်အလက်ကိုမဆို ရှုပ်ထွေးစေနိုင်ပါသည်။ setting_use_pending_items: အပေါ်အောက်လှိမ့်မည့်အစား ကလစ်တစ်ခုနောက်တွင် စာမျက်နှာအပ်ဒိတ်များကို ဖျောက်ထားပါ။ - username: "%{domain} ရှိ သင့်အသုံးပြုသူအမည်မှာ တူညီ၍မရပါ" whole_word: အဓိကစကားလုံး သို့မဟုတ် စကားစုသည် အက္ခရာဂဏန်းများသာဖြစ်ပါကစကားလုံးတစ်ခုလုံးနှင့် ကိုက်ညီမှသာ အသုံးပြုနိုင်မည်ဖြစ်သည် domain_allow: domain: ဤဒိုမိန်းသည် ဤဆာဗာမှ အချက်အလက်များကို ရယူနိုင်မည်ဖြစ်ပြီး ဝင်လာသောအချက်အလက်များကို စီမံဆောင်ရွက်ပေးပြီး သိမ်းဆည်းသွားမည်ဖြစ်သည် diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml index 8592857d4..c2b680ec5 100644 --- a/config/locales/simple_form.nl.yml +++ b/config/locales/simple_form.nl.yml @@ -59,7 +59,6 @@ nl: setting_show_application: De toepassing de je gebruikt om berichten te plaatsen wordt in de gedetailleerde weergave van het bericht getoond setting_use_blurhash: Wazige kleurovergangen zijn gebaseerd op de kleuren van de verborgen media, waarmee elk detail verdwijnt setting_use_pending_items: De tijdlijn wordt bijgewerkt door op het aantal nieuwe items te klikken, in plaats van dat deze automatisch wordt bijgewerkt - username: Jouw gebruikersnaam is uniek op %{domain} whole_word: Wanneer het trefwoord of zinsdeel alfanumeriek is, wordt het alleen gefilterd wanneer het hele woord overeenkomt domain_allow: domain: Dit domein is in staat om gegevens van deze server op te halen, en binnenkomende gegevens worden verwerkt en opgeslagen diff --git a/config/locales/simple_form.nn.yml b/config/locales/simple_form.nn.yml index db4007c4f..e3b8fa3fc 100644 --- a/config/locales/simple_form.nn.yml +++ b/config/locales/simple_form.nn.yml @@ -57,7 +57,6 @@ nn: setting_show_application: Programmet du brukar for å tuta blir vist i den detaljerte visninga av tuta dine setting_use_blurhash: Overgangar er basert på fargane til skjulte grafikkelement, men gjer detaljar utydelege setting_use_pending_items: Gøym tidslineoppdateringar bak eit klikk, i staden for å rulla ned automatisk - username: Brukarnamnet ditt vert unikt på %{domain} whole_word: Når søkjeordet eller setninga berre er alfanumerisk, nyttast det berre om det samsvarar med heile ordet domain_allow: domain: Dette domenet er i stand til å henta data frå denne tenaren og innkomande data vert handsama og lagra diff --git a/config/locales/simple_form.no.yml b/config/locales/simple_form.no.yml index 4cf678b83..efd451ce5 100644 --- a/config/locales/simple_form.no.yml +++ b/config/locales/simple_form.no.yml @@ -57,7 +57,6 @@ setting_show_application: Appen du bruker til å publisere innlegg vil bli vist i den detaljerte visningen til innleggene dine setting_use_blurhash: Gradientene er basert på fargene til de skjulte visualitetene, men gjør alle detaljer uklare setting_use_pending_items: Skjul tidslinjeoppdateringer bak et klikk, i stedet for å automatisk la strømmen skrolle - username: Brukernavnet ditt vil være unikt på %{domain} whole_word: Når søkeordet eller setningen bare er alfanumerisk, aktiveres det bare hvis det samsvarer med hele ordet domain_allow: domain: Dette domenet vil være i stand til å hente data fra denne serveren og dets innkommende data vil bli prosessert og lagret diff --git a/config/locales/simple_form.oc.yml b/config/locales/simple_form.oc.yml index 566f4f100..a3ae020b6 100644 --- a/config/locales/simple_form.oc.yml +++ b/config/locales/simple_form.oc.yml @@ -52,7 +52,6 @@ oc: setting_show_application: Lo nom de l’aplicacion qu’utilizatz per publicar serà mostrat dins la vista detalhada de vòstres tuts setting_use_blurhash: Los degradats venon de las colors de l’imatge rescondut en enfoscar los detalhs setting_use_pending_items: Rescondre las actualizacions del flux d’actualitat aprèp un clic allòc de desfilar lo flux automaticament - username: Vòstre nom d’utilizaire serà unic sus %{domain} whole_word: Quand lo mot-clau o frasa es solament alfranumeric, serà pas qu’aplicat se correspond al mot complèt domain_allow: domain: Aqueste domeni poirà recuperar las donadas d’aqueste servidor estant e las donadas venent d’aqueste domeni seràn tractadas e gardadas diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml index 48f1fb745..43f0990ba 100644 --- a/config/locales/simple_form.pl.yml +++ b/config/locales/simple_form.pl.yml @@ -59,7 +59,6 @@ pl: setting_show_application: W informacjach o wpisie będzie widoczna informacja o aplikacji, z której został wysłany setting_use_blurhash: Gradienty są oparte na kolorach ukrywanej zawartości, ale uniewidaczniają wszystkie szczegóły setting_use_pending_items: Ukryj aktualizacje osi czasu za kliknięciem, zamiast automatycznego przewijania strumienia - username: Twoja nazwa użytkownika będzie niepowtarzalna na %{domain} whole_word: Jeśli słowo lub fraza składa się jedynie z liter lub cyfr, filtr będzie zastosowany tylko do pełnych wystąpień domain_allow: domain: Ta domena będzie mogła pobierać dane z serwera, a dane przychodzące z niej będą przetwarzane i przechowywane diff --git a/config/locales/simple_form.pt-BR.yml b/config/locales/simple_form.pt-BR.yml index bf46305ee..a8fa1fd01 100644 --- a/config/locales/simple_form.pt-BR.yml +++ b/config/locales/simple_form.pt-BR.yml @@ -59,7 +59,6 @@ pt-BR: setting_show_application: O aplicativo que você usar para publicar será exibido na visão detalhada das suas publicações setting_use_blurhash: O blur é baseado nas cores da imagem oculta, ofusca a maioria dos detalhes setting_use_pending_items: Ocultar atualizações da linha do tempo atrás de um clique ao invés de rolar automaticamente - username: Seu nome de usuário será único em %{domain} whole_word: Quando a palavra-chave ou frase é inteiramente alfanumérica, ela será aplicada somente se corresponder a palavra inteira domain_allow: domain: Este domínio poderá obter dados deste servidor e os dados recebidos dele serão processados e armazenados diff --git a/config/locales/simple_form.pt-PT.yml b/config/locales/simple_form.pt-PT.yml index 4e34c5897..de2484d6e 100644 --- a/config/locales/simple_form.pt-PT.yml +++ b/config/locales/simple_form.pt-PT.yml @@ -59,7 +59,6 @@ pt-PT: setting_show_application: A aplicação que usa para publicar será mostrada na vista pormenorizada das suas publicações setting_use_blurhash: Os gradientes são baseados nas cores das imagens escondidas, mas ofuscam quaisquer pormenores setting_use_pending_items: Ocultar atualizações da cronologia por detrás dum clique, em vez de rolar automaticamente o fluxo - username: O teu nome de utilizador será único em %{domain} whole_word: Quando a palavra-chave ou expressão-chave é somente alfanumérica, ela só será aplicada se corresponder à palavra completa domain_allow: domain: Este domínio será capaz de obter dados desta instância e os dados dele recebidos serão processados e armazenados diff --git a/config/locales/simple_form.ro.yml b/config/locales/simple_form.ro.yml index 6ed4905db..af3e000ac 100644 --- a/config/locales/simple_form.ro.yml +++ b/config/locales/simple_form.ro.yml @@ -49,7 +49,6 @@ ro: setting_show_application: Aplicația pe care o utilizați pentru a posta va fi afișată în vizualizarea detaliată a postărilor setting_use_blurhash: Gradienții sunt bazați pe culorile vizualelor ascunse, dar ofuscă orice detalii setting_use_pending_items: Ascunde actualizările cronologice din spatele unui click în loc de a derula automat fluxul - username: Numele tău de utilizator va fi unic pe %{domain} whole_word: Când fraza sau cuvântul este doar alfanumeric, acesta se aplică doar dacă există o potrivire completă domain_allow: domain: Acest domeniu va putea prelua date de pe acest server și datele primite de la el vor fi procesate și stocate diff --git a/config/locales/simple_form.ru.yml b/config/locales/simple_form.ru.yml index 7e23edf7d..908cd645d 100644 --- a/config/locales/simple_form.ru.yml +++ b/config/locales/simple_form.ru.yml @@ -59,7 +59,6 @@ ru: setting_show_application: При просмотре поста будет видно из какого приложения он отправлен. setting_use_blurhash: Градиенты основаны на цветах скрытых медиа, но скрывают любые детали. setting_use_pending_items: Показывать обновления в ленте только после клика вместо автоматической прокрутки. - username: Ваше имя пользователя будет уникальным на %{domain} whole_word: Если слово или фраза состоит только из букв и цифр, сопоставление произойдёт только по полному совпадению domain_allow: domain: Этот домен сможет получать данные с этого сервера и его входящие данные будут обрабатываться и сохранены diff --git a/config/locales/simple_form.sc.yml b/config/locales/simple_form.sc.yml index 56638352a..07110da1d 100644 --- a/config/locales/simple_form.sc.yml +++ b/config/locales/simple_form.sc.yml @@ -53,7 +53,6 @@ sc: setting_show_application: S'aplicatzione chi impreas pro publicare tuts at a èssere ammustrada in sa visualizatzione de detàlliu de is tuts setting_use_blurhash: Is gradientes sunt basados in subra de is colores de is immàgines cuadas ma imbelant totu is detàllios setting_use_pending_items: Cua is atualizatziones in segus de un'incarcu imbetzes de iscùrrere in automàticu su flussu de publicatziones - username: Su nòmine de utente tuo at a èssere ùnicu in %{domain} whole_word: Cando sa crae (faeddu o fràsia) siat isceti alfanumèrica, s'at a aplicare isceti si currispondet a su faeddu intreu domain_allow: domain: Custu domìniu at a pòdere recuperare datos dae custu serbidore e is datos in intrada dae cue ant a èssere protzessados e archiviados diff --git a/config/locales/simple_form.sco.yml b/config/locales/simple_form.sco.yml index 0dc4fdd79..85f075a15 100644 --- a/config/locales/simple_form.sco.yml +++ b/config/locales/simple_form.sco.yml @@ -57,7 +57,6 @@ sco: setting_show_application: The application thit ye uise fir tae post wull be displayed in the detailt view o yer posts setting_use_blurhash: Gradients is based aff o the colors o the image thit's hid, but ye cannae see onie details setting_use_pending_items: Plank timeline updates ahin a chap insteid o automatic scrowin o the feed - username: Yer uisernemm wull be a ane aff on %{domain} whole_word: Whan the keywird or phrase is alphanumeric ainly, it wull ainly get applied if it matches the haill wird domain_allow: domain: This domain wull be able tae get data fae this server an data comin in fae it wull get processed an stowed diff --git a/config/locales/simple_form.si.yml b/config/locales/simple_form.si.yml index e107d82ac..d62c5a0eb 100644 --- a/config/locales/simple_form.si.yml +++ b/config/locales/simple_form.si.yml @@ -57,7 +57,6 @@ si: setting_show_application: ඔබ පළ කිරීමට භාවිතා කරන යෙදුම ඔබගේ පළ කිරීම් වල සවිස්තරාත්මක දර්ශනයේ පෙන්වනු ඇත setting_use_blurhash: අනුක්‍රමණ සැඟවුණු දෘශ්‍යවල වර්ණ මත පදනම් වන නමුත් ඕනෑම විස්තරයක් අපැහැදිලි කරයි setting_use_pending_items: සංග්‍රහය ස්වයංක්‍රීයව අනුචලනය කරනවා වෙනුවට ක්ලික් කිරීමක් පිටුපස කාලරේඛා යාවත්කාලීන සඟවන්න - username: ඔබගේ පරිශීලක නාමය %{domain}හි අද්විතීය වනු ඇත whole_word: මූල පදය හෝ වාක්‍ය ඛණ්ඩය අක්ෂරාංක පමණක් වන විට, එය යෙදෙන්නේ එය සම්පූර්ණ වචනයට ගැලපේ නම් පමණි domain_allow: domain: මෙම වසමට මෙම සේවාදායකයෙන් දත්ත ලබා ගැනීමට හැකි වන අතර එයින් ලැබෙන දත්ත සකස් කර ගබඩා කරනු ලැබේ diff --git a/config/locales/simple_form.sk.yml b/config/locales/simple_form.sk.yml index e8fb86f29..35ebca0bb 100644 --- a/config/locales/simple_form.sk.yml +++ b/config/locales/simple_form.sk.yml @@ -42,7 +42,6 @@ sk: setting_show_application: Aplikácia, ktorú používaš na písanie príspevkov, bude zobrazená v podrobnom náhľade jednotlivých tvojích príspevkov setting_use_blurhash: Prechody sú založené na farbách skrytých vizuálov, ale zahaľujú akékoľvek podrobnosti setting_use_pending_items: Skry aktualizovanie časovej osi tak, aby bola načitávaná iba po kliknutí, namiesto samostatného posúvania - username: Tvoja prezývka bude unikátna pre server %{domain} whole_word: Ak je kľúčové slovo, alebo fráza poskladaná iba s písmen a čísel, bude použité iba ak sa zhoduje s celým výrazom domain_allow: domain: Táto doména bude schopná získavať dáta z tohto servera, a prichádzajúce dáta ním budú spracovávané a uložené diff --git a/config/locales/simple_form.sl.yml b/config/locales/simple_form.sl.yml index ac14735a9..9695ad4de 100644 --- a/config/locales/simple_form.sl.yml +++ b/config/locales/simple_form.sl.yml @@ -59,7 +59,6 @@ sl: setting_show_application: Aplikacija, ki jo uporabljate za objavljanje, bo prikazana v podrobnem pogledu vaših objav setting_use_blurhash: Prelivi temeljijo na barvah skrite vizualne slike, vendar zakrivajo vse podrobnosti setting_use_pending_items: Skrij posodobitev časovnice za klikom namesto samodejnega posodabljanja - username: Vaše uporabniško ime bo edinstveno na %{domain} whole_word: Ko je ključna beseda ali fraza samo alfanumerična, se bo uporabljala le, če se bo ujemala s celotno besedo domain_allow: domain: Ta domena bo lahko prejela podatke s tega strežnika, dohodni podatki z nje pa bodo obdelani in shranjeni diff --git a/config/locales/simple_form.sq.yml b/config/locales/simple_form.sq.yml index a11037fa9..ddbebdf57 100644 --- a/config/locales/simple_form.sq.yml +++ b/config/locales/simple_form.sq.yml @@ -59,7 +59,6 @@ sq: setting_show_application: Aplikacioni që përdorni për mesazhe do të shfaqet te pamja e hollësishme për mesazhet tuaj setting_use_blurhash: Gradientët bazohen në ngjyrat e elementëve pamorë të fshehur, por errësojnë çfarëdo hollësie setting_use_pending_items: Fshihi përditësimet e rrjedhës kohore pas një klikimi, në vend të rrëshqitjes automatike nëpër prurje - username: Emri juaj i përdoruesit do të jetë unik në %{domain} whole_word: Kur fjalëkyçi ose fraza është vetëm numerike, do të aplikohet vetëm nëse përputhet me krejt fjalën domain_allow: domain: Kjo përkatësi do të jetë në gjendje të sjellë të dhëna prej këtij shërbyesi dhe të dhënat ardhëse prej tij do të përpunohen dhe depozitohen diff --git a/config/locales/simple_form.sr-Latn.yml b/config/locales/simple_form.sr-Latn.yml index e987145c1..e268fd20e 100644 --- a/config/locales/simple_form.sr-Latn.yml +++ b/config/locales/simple_form.sr-Latn.yml @@ -59,7 +59,6 @@ sr-Latn: setting_show_application: Aplikacija koju koristite za objavljivanje će biti prikazana u detaljnom prikazu vaših objava setting_use_blurhash: Gradijenti se formiraju na osnovu bojâ skrivenih slika i zamućuju prikaz, prikrivajući detalje setting_use_pending_items: Sakriva ažuriranja vremenske linije iza klika umesto automatskog ažuriranja i pomeranja vremenske linije - username: Vaš nadimak će biti jedinstven na %{domain} whole_word: Kada je ključna reč ili fraza isključivo alfanumerička, biće primenjena samo ako se podudara sa celom rečju domain_allow: domain: Ovaj domen će moći da preuzima podatke sa ovog servera i dolazni podaci sa njega će se obrađivati i čuvati diff --git a/config/locales/simple_form.sr.yml b/config/locales/simple_form.sr.yml index 8ee586377..9c412cd60 100644 --- a/config/locales/simple_form.sr.yml +++ b/config/locales/simple_form.sr.yml @@ -59,7 +59,6 @@ sr: setting_show_application: Апликација коју користите за објављивање ће бити приказана у детаљном приказу ваших објава setting_use_blurhash: Градијенти се формирају на основу бојâ скривених слика и замућују приказ, прикривајући детаље setting_use_pending_items: Сакрива ажурирања временске линије иза клика уместо аутоматског ажурирања и померања временске линије - username: Ваш надимак ће бити јединствен на %{domain} whole_word: Када је кључна реч или фраза искључиво алфанумеричка, биће примењена само ако се подудара са целом речjу domain_allow: domain: Овај домен ће моћи да преузима податке са овог сервера и долазни подаци са њега ће се обрађивати и чувати diff --git a/config/locales/simple_form.sv.yml b/config/locales/simple_form.sv.yml index cf914b058..60847fc94 100644 --- a/config/locales/simple_form.sv.yml +++ b/config/locales/simple_form.sv.yml @@ -59,7 +59,6 @@ sv: setting_show_application: Applikationen du använder för att göra inlägg kommer visas i detaljvyn för dina inlägg setting_use_blurhash: Gradienter är baserade på färgerna av de dolda objekten men fördunklar alla detaljer setting_use_pending_items: Dölj tidslinjeuppdateringar bakom ett klick istället för att automatiskt bläddra i flödet - username: Ditt användarnamn måste vara unikt på %{domain} whole_word: När sökordet eller frasen endast är alfanumerisk, kommer det endast att tillämpas om det matchar hela ordet domain_allow: domain: Denna domän kommer att kunna hämta data från denna server och inkommande data från den kommer att behandlas och lagras diff --git a/config/locales/simple_form.th.yml b/config/locales/simple_form.th.yml index 345f8c183..8fddeaf42 100644 --- a/config/locales/simple_form.th.yml +++ b/config/locales/simple_form.th.yml @@ -59,7 +59,6 @@ th: setting_show_application: จะแสดงแอปพลิเคชันที่คุณใช้ในการโพสต์ในมุมมองโดยละเอียดของโพสต์ของคุณ setting_use_blurhash: การไล่ระดับสีอิงตามสีของภาพที่ซ่อนอยู่แต่ทำให้รายละเอียดใด ๆ คลุมเครือ setting_use_pending_items: ซ่อนการอัปเดตเส้นเวลาไว้หลังการคลิกแทนที่จะเลื่อนฟีดโดยอัตโนมัติ - username: ชื่อผู้ใช้ของคุณจะไม่ซ้ำกันใน %{domain} whole_word: เมื่อคำสำคัญหรือวลีเป็นตัวอักษรและตัวเลขเท่านั้น จะนำไปใช้กับคำสำคัญหรือวลีหากตรงกันทั้งคำเท่านั้น domain_allow: domain: โดเมนนี้จะสามารถดึงข้อมูลจากเซิร์ฟเวอร์นี้และจะประมวลผลและจัดเก็บข้อมูลขาเข้าจากโดเมน diff --git a/config/locales/simple_form.tr.yml b/config/locales/simple_form.tr.yml index a2f42e259..586731466 100644 --- a/config/locales/simple_form.tr.yml +++ b/config/locales/simple_form.tr.yml @@ -59,7 +59,6 @@ tr: setting_show_application: Gönderi gönderimi için kullandığınız uygulama, gönderilerinizin ayrıntılı görünümünde gösterilecektir setting_use_blurhash: Gradyenler gizli görsellerin renklerine dayanır, ancak detayları gizler setting_use_pending_items: Akışı otomatik olarak kaydırmak yerine, zaman çizelgesi güncellemelerini tek bir tıklamayla gizleyin - username: Kullanıcı adınız %{domain} alanında benzersiz olacak whole_word: Anahtar kelime veya kelime öbeği yalnızca alfasayısal olduğunda, yalnızca tüm sözcükle eşleşirse uygulanır domain_allow: domain: Bu alan adı, bu sunucudan veri alabilecek ve ondan gelen veri işlenecek ve saklanacaktır diff --git a/config/locales/simple_form.uk.yml b/config/locales/simple_form.uk.yml index be4d4b764..fa67454d6 100644 --- a/config/locales/simple_form.uk.yml +++ b/config/locales/simple_form.uk.yml @@ -59,7 +59,6 @@ uk: setting_show_application: Застосунок, за допомогою якого ви зробили допис, буде показано серед подробиць допису setting_use_blurhash: Градієнти, що базуються на кольорах прихованих медіа, але роблять нерозрізненними будь-які деталі setting_use_pending_items: Не додавати нові повідомлення до стрічок миттєво, показувати лише після додаткового клацання - username: Ваше ім'я користувача буде унікальним у %{domain} whole_word: Якщо пошукове слово або фраза містить лише літери та цифри, воно має збігатися цілком domain_allow: domain: Цей домен зможе отримувати дані з цього сервера. Вхідні дані будуть оброблені та збережені diff --git a/config/locales/simple_form.vi.yml b/config/locales/simple_form.vi.yml index b5da24dd1..d0e7d165b 100644 --- a/config/locales/simple_form.vi.yml +++ b/config/locales/simple_form.vi.yml @@ -59,7 +59,6 @@ vi: setting_show_application: Tên ứng dụng bạn dùng để đăng tút sẽ hiện trong chi tiết của tút setting_use_blurhash: Lớp phủ mờ dựa trên màu sắc của hình ảnh nhạy cảm setting_use_pending_items: Dồn lại toàn bộ tút mới và chỉ hiển thị khi nhấn vào - username: Tên người dùng của bạn sẽ là duy nhất trên %{domain} whole_word: Khi từ khóa hoặc cụm từ là chữ và số, nó sẽ chỉ hiện ra những từ chính xác như vậy domain_allow: domain: Máy chủ này sẽ tiếp nhận dữ liệu, rồi sau đó xử lý và lưu trữ diff --git a/config/locales/simple_form.zh-CN.yml b/config/locales/simple_form.zh-CN.yml index 8de23085d..2dc84f11a 100644 --- a/config/locales/simple_form.zh-CN.yml +++ b/config/locales/simple_form.zh-CN.yml @@ -59,7 +59,6 @@ zh-CN: setting_show_application: 你用来发表嘟文的应用程序将会在你嘟文的详细内容中显示 setting_use_blurhash: 渐变是基于模糊后的隐藏内容生成的 setting_use_pending_items: 关闭自动滚动更新,时间轴会在点击后更新 - username: 你的用户名在 %{domain} 上是唯一的 whole_word: 如果关键词只包含字母和数字,将只在词语完全匹配时才会应用 domain_allow: domain: 该站点将能够从该服务器上拉取数据,并处理和存储收到的数据。 diff --git a/config/locales/simple_form.zh-HK.yml b/config/locales/simple_form.zh-HK.yml index 02ef592c9..d1ccbbe8a 100644 --- a/config/locales/simple_form.zh-HK.yml +++ b/config/locales/simple_form.zh-HK.yml @@ -59,7 +59,6 @@ zh-HK: setting_show_application: 你用來發表文章的應用程式,將會顯示在你文章的詳細檢視中 setting_use_blurhash: 漸變圖樣會基於隱藏媒體內容產生,但所有細節會變得模糊 setting_use_pending_items: 關閉自動滾動更新,時間軸會在點擊後更新 - username: 你的使用者名稱在 %{domain} 將是獨一無二的 whole_word: 如果關鍵字或詞組僅有字母與數字,則其將只在符合整個單字的時候才會套用 domain_allow: domain: 此網域將能從此站獲取資料,而此站發出的數據也會被處理和存儲。 diff --git a/config/locales/simple_form.zh-TW.yml b/config/locales/simple_form.zh-TW.yml index 882f1d3ff..d2c1213f2 100644 --- a/config/locales/simple_form.zh-TW.yml +++ b/config/locales/simple_form.zh-TW.yml @@ -59,7 +59,6 @@ zh-TW: setting_show_application: 您用來發嘟文的應用程式將會在您嘟文的詳細檢視顯示 setting_use_blurhash: 彩色漸層圖樣是基於隱藏媒體內容顏色產生,所有細節會變得模糊 setting_use_pending_items: 關閉自動捲動更新,時間軸只會在點擊後更新 - username: 您的使用者名稱將於 %{domain} 是獨一無二的 whole_word: 如果關鍵字或詞組僅有字母與數字,則其將只在符合整個單字的時候才會套用 domain_allow: domain: 此網域將能夠攫取本站資料,而自該網域發出的資料也會於本站處理和留存。 diff --git a/config/routes.rb b/config/routes.rb index 099933116..26d5c3526 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -667,9 +667,33 @@ Rails.application.routes.draw do resources :ip_blocks, only: [:index, :show, :update, :create, :destroy] namespace :trends do - resources :tags, only: [:index] - resources :links, only: [:index] - resources :statuses, only: [:index] + resources :tags, only: [:index] do + member do + post :approve + post :reject + end + end + resources :links, only: [:index] do + member do + post :approve + post :reject + end + end + resources :statuses, only: [:index] do + member do + post :approve + post :reject + end + end + + namespace :links do + resources :preview_card_providers, only: [:index], path: :publishers do + member do + post :approve + post :reject + end + end + end end post :measures, to: 'measures#create' diff --git a/jest.config.js b/jest.config.js index 1eb143a59..d6e7bc65f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -13,7 +13,7 @@ const config = { setupFiles: ['raf/polyfill'], setupFilesAfterEnv: ['/app/javascript/mastodon/test_setup.js'], collectCoverageFrom: [ - 'app/javascript/mastodon/**/*.js', + 'app/javascript/mastodon/**/*.{js,jsx,ts,tsx}', '!app/javascript/mastodon/features/emoji/emoji_compressed.js', '!app/javascript/mastodon/locales/locale-data/*.js', '!app/javascript/mastodon/service_worker/entry.js', diff --git a/lib/mastodon/accounts_cli.rb b/lib/mastodon/accounts_cli.rb index a6532541e..5194cd80a 100644 --- a/lib/mastodon/accounts_cli.rb +++ b/lib/mastodon/accounts_cli.rb @@ -57,6 +57,7 @@ module Mastodon option :role option :reattach, type: :boolean option :force, type: :boolean + option :approve, type: :boolean desc 'create USERNAME', 'Create a new user account' long_desc <<-LONG_DESC Create a new user account with a given USERNAME and an @@ -72,6 +73,8 @@ module Mastodon account is still in use by someone else, you can supply the --force option to delete the old record and reattach the username to the new account anyway. + + With the --approve option, the account will be approved. LONG_DESC def create(username) role_id = nil @@ -89,7 +92,7 @@ module Mastodon account = Account.new(username: username) password = SecureRandom.hex - user = User.new(email: options[:email], password: password, agreement: true, approved: true, role_id: role_id, confirmed_at: options[:confirmed] ? Time.now.utc : nil, bypass_invite_request_check: true) + user = User.new(email: options[:email], password: password, agreement: true, role_id: role_id, confirmed_at: options[:confirmed] ? Time.now.utc : nil, bypass_invite_request_check: true) if options[:reattach] account = Account.find_local(username) || Account.new(username: username) @@ -112,6 +115,8 @@ module Mastodon user.confirm! end + user.approve! if options[:approve] + say('OK', :green) say("New password: #{password}") else @@ -184,9 +189,10 @@ module Mastodon user.disabled = true if options[:disable] user.approved = true if options[:approve] user.otp_required_for_login = false if options[:disable_2fa] - user.confirm if options[:confirm] if user.save + user.confirm if options[:confirm] + say('OK', :green) say("New password: #{password}") if options[:reset_password] else diff --git a/package.json b/package.json index 051feb816..6d6025d50 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "fuzzysort": "^2.0.4", "glob": "^10.0.0", "history": "^4.10.1", - "http-link-header": "^1.1.0", + "http-link-header": "^1.1.1", "immutable": "^4.3.0", "imports-loader": "^1.2.0", "intl": "^1.2.5", @@ -86,7 +86,7 @@ "path-complete-extname": "^1.0.0", "pg": "^8.5.0", "pg-connection-string": "^2.5.0", - "postcss": "^8.4.21", + "postcss": "^8.4.22", "postcss-loader": "^4.3.0", "promise.prototype.finally": "^3.1.4", "prop-types": "^15.8.1", @@ -116,7 +116,7 @@ "redux-thunk": "^2.4.2", "regenerator-runtime": "^0.13.11", "requestidlecallback": "^0.3.0", - "reselect": "^4.1.7", + "reselect": "^4.1.8", "rimraf": "^5.0.0", "sass": "^1.61.0", "sass-loader": "^10.2.0", diff --git a/spec/controllers/api/v1/admin/trends/links/preview_card_providers_controller_spec.rb b/spec/controllers/api/v1/admin/trends/links/preview_card_providers_controller_spec.rb new file mode 100644 index 000000000..883a55b7b --- /dev/null +++ b/spec/controllers/api/v1/admin/trends/links/preview_card_providers_controller_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Api::V1::Admin::Trends::Links::PreviewCardProvidersController do + render_views + + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read admin:write' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:account) { Fabricate(:account) } + let(:preview_card_provider) { Fabricate(:preview_card_provider) } + + before do + allow(controller).to receive(:doorkeeper_token) { token } + end + + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + describe 'GET #index' do + it 'returns http success' do + get :index, params: { account_id: account.id, limit: 2 } + + expect(response).to have_http_status(200) + end + end + + describe 'POST #approve' do + before do + post :approve, params: { id: preview_card_provider.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + describe 'POST #reject' do + before do + post :reject, params: { id: preview_card_provider.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end +end diff --git a/spec/controllers/api/v1/admin/trends/links_controller_spec.rb b/spec/controllers/api/v1/admin/trends/links_controller_spec.rb index a64292f06..9c144d3fa 100644 --- a/spec/controllers/api/v1/admin/trends/links_controller_spec.rb +++ b/spec/controllers/api/v1/admin/trends/links_controller_spec.rb @@ -5,14 +5,33 @@ require 'rails_helper' describe Api::V1::Admin::Trends::LinksController do render_views - let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'admin:read') } + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read admin:write' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } let(:account) { Fabricate(:account) } + let(:preview_card) { Fabricate(:preview_card) } before do allow(controller).to receive(:doorkeeper_token) { token } end + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + describe 'GET #index' do it 'returns http success' do get :index, params: { account_id: account.id, limit: 2 } @@ -20,4 +39,30 @@ describe Api::V1::Admin::Trends::LinksController do expect(response).to have_http_status(200) end end + + describe 'POST #approve' do + before do + post :approve, params: { id: preview_card.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + describe 'POST #reject' do + before do + post :reject, params: { id: preview_card.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end end diff --git a/spec/controllers/api/v1/admin/trends/statuses_controller_spec.rb b/spec/controllers/api/v1/admin/trends/statuses_controller_spec.rb index 821cc499f..d25186b37 100644 --- a/spec/controllers/api/v1/admin/trends/statuses_controller_spec.rb +++ b/spec/controllers/api/v1/admin/trends/statuses_controller_spec.rb @@ -5,14 +5,33 @@ require 'rails_helper' describe Api::V1::Admin::Trends::StatusesController do render_views - let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'admin:read') } + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read admin:write' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } let(:account) { Fabricate(:account) } + let(:status) { Fabricate(:status) } before do allow(controller).to receive(:doorkeeper_token) { token } end + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + describe 'GET #index' do it 'returns http success' do get :index, params: { account_id: account.id, limit: 2 } @@ -20,4 +39,30 @@ describe Api::V1::Admin::Trends::StatusesController do expect(response).to have_http_status(200) end end + + describe 'POST #approve' do + before do + post :approve, params: { id: status.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + describe 'POST #reject' do + before do + post :reject, params: { id: status.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end end diff --git a/spec/controllers/api/v1/admin/trends/tags_controller_spec.rb b/spec/controllers/api/v1/admin/trends/tags_controller_spec.rb index 480306ce7..5ee443d57 100644 --- a/spec/controllers/api/v1/admin/trends/tags_controller_spec.rb +++ b/spec/controllers/api/v1/admin/trends/tags_controller_spec.rb @@ -5,14 +5,33 @@ require 'rails_helper' describe Api::V1::Admin::Trends::TagsController do render_views - let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'admin:read') } + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read admin:write' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } let(:account) { Fabricate(:account) } + let(:tag) { Fabricate(:tag) } before do allow(controller).to receive(:doorkeeper_token) { token } end + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + describe 'GET #index' do it 'returns http success' do get :index, params: { account_id: account.id, limit: 2 } @@ -20,4 +39,30 @@ describe Api::V1::Admin::Trends::TagsController do expect(response).to have_http_status(200) end end + + describe 'POST #approve' do + before do + post :approve, params: { id: tag.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + describe 'POST #reject' do + before do + post :reject, params: { id: tag.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end end diff --git a/spec/mailers/notification_mailer_spec.rb b/spec/mailers/notification_mailer_spec.rb index a6db08d85..341fe6f23 100644 --- a/spec/mailers/notification_mailer_spec.rb +++ b/spec/mailers/notification_mailer_spec.rb @@ -29,7 +29,7 @@ RSpec.describe NotificationMailer, type: :mailer do it 'renders the headers' do expect(mail.subject).to eq('You were mentioned by bob') - expect(mail.to).to eq([receiver.email]) + expect(mail[:to].value).to eq("#{receiver.account.username} <#{receiver.email}>") end it 'renders the body' do @@ -46,7 +46,7 @@ RSpec.describe NotificationMailer, type: :mailer do it 'renders the headers' do expect(mail.subject).to eq('bob is now following you') - expect(mail.to).to eq([receiver.email]) + expect(mail[:to].value).to eq("#{receiver.account.username} <#{receiver.email}>") end it 'renders the body' do @@ -62,7 +62,7 @@ RSpec.describe NotificationMailer, type: :mailer do it 'renders the headers' do expect(mail.subject).to eq('bob favourited your post') - expect(mail.to).to eq([receiver.email]) + expect(mail[:to].value).to eq("#{receiver.account.username} <#{receiver.email}>") end it 'renders the body' do @@ -79,7 +79,7 @@ RSpec.describe NotificationMailer, type: :mailer do it 'renders the headers' do expect(mail.subject).to eq('bob boosted your post') - expect(mail.to).to eq([receiver.email]) + expect(mail[:to].value).to eq("#{receiver.account.username} <#{receiver.email}>") end it 'renders the body' do @@ -96,7 +96,7 @@ RSpec.describe NotificationMailer, type: :mailer do it 'renders the headers' do expect(mail.subject).to eq('Pending follower: bob') - expect(mail.to).to eq([receiver.email]) + expect(mail[:to].value).to eq("#{receiver.account.username} <#{receiver.email}>") end it 'renders the body' do diff --git a/spec/models/account_filter_spec.rb b/spec/models/account_filter_spec.rb index 3032260fe..cb00e7609 100644 --- a/spec/models/account_filter_spec.rb +++ b/spec/models/account_filter_spec.rb @@ -44,4 +44,23 @@ describe AccountFilter do expect(filter.results).to match_array [remote_account_one] end end + + describe 'with username' do + let!(:local_account) { Fabricate(:account, domain: nil, username: 'validUserName') } + + it 'works with @ at the beginning of the username' do + filter = described_class.new(username: '@validUserName') + expect(filter.results).to match_array [local_account] + end + + it 'does not work with more than one @ at the beginning of the username' do + filter = described_class.new(username: '@@validUserName') + expect(filter.results).to_not match_array [local_account] + end + + it 'does not work with @ outside the beginning of the username' do + filter = described_class.new(username: 'validUserName@') + expect(filter.results).to_not match_array [local_account] + end + end end diff --git a/spec/services/reblog_service_spec.rb b/spec/services/reblog_service_spec.rb index fdf5ec923..2ad6d30f6 100644 --- a/spec/services/reblog_service_spec.rb +++ b/spec/services/reblog_service_spec.rb @@ -35,13 +35,25 @@ RSpec.describe ReblogService, type: :service do end context 'when the reblogged status is discarded in the meantime' do - let(:status) { Fabricate(:status, account: alice, visibility: :public) } + let(:status) { Fabricate(:status, account: alice, visibility: :public, text: 'discard-status-text') } + # Add a callback to discard the status being reblogged after the + # validations pass but before the database commit is executed. before do - # Update the in-database attribute without reflecting the change in - # the object. This cannot simulate all race conditions, but it is - # pretty close. - Status.where(id: status.id).update_all(deleted_at: Time.now.utc) # rubocop:disable Rails/SkipsModelValidations + Status.class_eval do + before_save :discard_status + def discard_status + Status + .where(id: reblog_of_id) + .where(text: 'discard-status-text') + .update_all(deleted_at: Time.now.utc) # rubocop:disable Rails/SkipsModelValidations + end + end + end + + # Remove race condition simulating `discard_status` callback. + after do + Status._save_callbacks.delete(:discard_status) end it 'raises an exception' do diff --git a/yarn.lock b/yarn.lock index fbd8b5255..f36c6b2c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6047,10 +6047,10 @@ http-errors@~1.6.2: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" -http-link-header@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/http-link-header/-/http-link-header-1.1.0.tgz#a1ca87efdbcb7778d8d0d4525de1e6964ec1f129" - integrity sha512-pj6N1yxOz/ANO8HHsWGg/OoIL1kmRYvQnXQ7PIRpgp+15AnEsRH8fmIJE6D1OdWG2Bov+BJHVla1fFXxg1JbbA== +http-link-header@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/http-link-header/-/http-link-header-1.1.1.tgz#f0e6971b0ed86e858d2077066ecb7ba4f2e50de9" + integrity sha512-mW3N/rTYpCn99s1do0zx6nzFZSwLH9HGfUM4ZqLWJ16ylmYaC2v5eYGqrNTQlByx8AzUgGI+V/32gXPugs1+Sw== http-parser-js@>=0.5.1: version "0.5.3" @@ -8020,10 +8020,10 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== -nanoid@^3.3.4: - version "3.3.4" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" - integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== +nanoid@^3.3.6: + version "3.3.6" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" + integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== nanomatch@^1.2.9: version "1.2.13" @@ -9022,12 +9022,12 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.2.15, postcss@^8.4.21: - version "8.4.21" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" - integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== +postcss@^8.2.15, postcss@^8.4.21, postcss@^8.4.22: + version "8.4.22" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.22.tgz#c29e6776b60ab3af602d4b513d5bd2ff9aa85dc1" + integrity sha512-XseknLAfRHzVWjCEtdviapiBtfLdgyzExD50Rg2ePaucEesyh8Wv4VPdW0nbyDa1ydbrAxV19jvMT4+LFmcNUA== dependencies: - nanoid "^3.3.4" + nanoid "^3.3.6" picocolors "^1.0.0" source-map-js "^1.0.2" @@ -9804,10 +9804,10 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= -reselect@^4.1.7: - version "4.1.7" - resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.7.tgz#56480d9ff3d3188970ee2b76527bd94a95567a42" - integrity sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A== +reselect@^4.1.8: + version "4.1.8" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524" + integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ== resolve-cwd@^2.0.0: version "2.0.0"