diff options
Diffstat (limited to 'app')
27 files changed, 348 insertions, 206 deletions
diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb index f459bab19..5850bd56d 100644 --- a/app/controllers/about_controller.rb +++ b/app/controllers/about_controller.rb @@ -17,7 +17,10 @@ class AboutController < ApplicationController private def new_user - User.new.tap(&:build_account) + User.new.tap do |user| + user.build_account + user.build_invite_request + end end helper_method :new_user diff --git a/app/controllers/admin/pending_accounts_controller.rb b/app/controllers/admin/pending_accounts_controller.rb index 8429d3585..b62a9bc84 100644 --- a/app/controllers/admin/pending_accounts_controller.rb +++ b/app/controllers/admin/pending_accounts_controller.rb @@ -8,29 +8,29 @@ module Admin @form = Form::AccountBatch.new end - def update + def batch @form = Form::AccountBatch.new(form_account_batch_params.merge(current_account: current_account, action: action_from_button)) @form.save rescue ActionController::ParameterMissing - # Do nothing + flash[:alert] = I18n.t('admin.accounts.no_account_selected') ensure redirect_to admin_pending_accounts_path(current_params) end def approve_all - Form::AccountBatch.new(account_ids: User.pending.pluck(:account_id), action: 'approve').save + Form::AccountBatch.new(current_account: current_account, account_ids: User.pending.pluck(:account_id), action: 'approve').save redirect_to admin_pending_accounts_path(current_params) end def reject_all - Form::AccountBatch.new(account_ids: User.pending.pluck(:account_id), action: 'reject').save + Form::AccountBatch.new(current_account: current_account, account_ids: User.pending.pluck(:account_id), action: 'reject').save redirect_to admin_pending_accounts_path(current_params) end private def set_accounts - @accounts = Account.joins(:user).merge(User.pending).page(params[:page]) + @accounts = Account.joins(:user).merge(User.pending.recent).includes(user: :invite_request).page(params[:page]) end def form_account_batch_params diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index 74dd7ff34..84099bd96 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -11,6 +11,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController before_action :set_instance_presenter, only: [:new, :create, :update] before_action :set_body_classes, only: [:new, :create, :edit, :update] + def new + super(&:build_invite_request) + end + def destroy not_found end @@ -25,17 +29,17 @@ class Auth::RegistrationsController < Devise::RegistrationsController def build_resource(hash = nil) super(hash) - resource.locale = I18n.locale - resource.invite_code = params[:invite_code] if resource.invite_code.blank? - resource.agreement = true + resource.locale = I18n.locale + resource.invite_code = params[:invite_code] if resource.invite_code.blank? + resource.agreement = true + resource.current_sign_in_ip = request.remote_ip - resource.current_sign_in_ip = request.remote_ip if resource.current_sign_in_ip.nil? resource.build_account if resource.account.nil? end def configure_sign_up_params devise_parameter_sanitizer.permit(:sign_up) do |u| - u.permit({ account_attributes: [:username] }, :email, :password, :password_confirmation, :invite_code) + u.permit({ account_attributes: [:username], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code) end end diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index 241053261..eb7a0eb4a 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -46,7 +46,7 @@ class Settings::PreferencesController < Settings::BaseController :setting_hide_followers_count, :setting_aggregate_reblogs, :setting_show_application, - notification_emails: %i(follow follow_request reblog favourite mention digest report), + notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account), interactions: %i(must_be_follower must_be_following) ) end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index f70c37522..7ae1e5d0b 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -118,4 +118,9 @@ module ApplicationHelper def storage_host? ENV['S3_ALIAS_HOST'].present? || ENV['S3_CLOUDFRONT_HOST'].present? end + + def quote_wrap(text, line_width: 80, break_sequence: "\n") + text = word_wrap(text, line_width: line_width - 2, break_sequence: break_sequence) + text.split("\n").map { |line| '> ' + line }.join("\n") + end end diff --git a/app/javascript/mastodon/actions/alerts.js b/app/javascript/mastodon/actions/alerts.js index 50cd48a9e..b2c7ab76a 100644 --- a/app/javascript/mastodon/actions/alerts.js +++ b/app/javascript/mastodon/actions/alerts.js @@ -34,6 +34,11 @@ export function showAlertForError(error) { if (error.response) { const { data, status, statusText } = error.response; + if (status === 404 || status === 410) { + // Skip these errors as they are reflected in the UI + return {}; + } + let message = statusText; let title = `${status}`; diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js index 96051818b..73be58d6a 100644 --- a/app/javascript/mastodon/features/account_gallery/index.js +++ b/app/javascript/mastodon/features/account_gallery/index.js @@ -13,8 +13,10 @@ import MediaItem from './components/media_item'; import HeaderContainer from '../account_timeline/containers/header_container'; import { ScrollContainer } from 'react-router-scroll-4'; import LoadMore from '../../components/load_more'; +import MissingIndicator from 'mastodon/components/missing_indicator'; const mapStateToProps = (state, props) => ({ + isAccount: !!state.getIn(['accounts', props.params.accountId]), medias: getAccountGallery(state, props.params.accountId), isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']), hasMore: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'hasMore']), @@ -52,6 +54,7 @@ class AccountGallery extends ImmutablePureComponent { medias: ImmutablePropTypes.list.isRequired, isLoading: PropTypes.bool, hasMore: PropTypes.bool, + isAccount: PropTypes.bool, }; componentDidMount () { @@ -91,7 +94,15 @@ class AccountGallery extends ImmutablePureComponent { } render () { - const { medias, shouldUpdateScroll, isLoading, hasMore } = this.props; + const { medias, shouldUpdateScroll, isLoading, hasMore, isAccount } = this.props; + + if (!isAccount) { + return ( + <Column> + <MissingIndicator /> + </Column> + ); + } let loadOlder = null; diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js index 27dfcc516..844b8a236 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.js +++ b/app/javascript/mastodon/features/account_timeline/components/header.js @@ -2,7 +2,6 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import InnerHeader from '../../account/components/header'; -import MissingIndicator from '../../../components/missing_indicator'; import ImmutablePureComponent from 'react-immutable-pure-component'; import MovedNote from './moved_note'; import { FormattedMessage } from 'react-intl'; @@ -88,7 +87,7 @@ export default class Header extends ImmutablePureComponent { const { account, hideTabs, identity_proofs } = this.props; if (account === null) { - return <MissingIndicator />; + return null; } return ( diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js index a01f1dd9a..27581bfdc 100644 --- a/app/javascript/mastodon/features/account_timeline/index.js +++ b/app/javascript/mastodon/features/account_timeline/index.js @@ -13,6 +13,7 @@ import { List as ImmutableList } from 'immutable'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { FormattedMessage } from 'react-intl'; import { fetchAccountIdentityProofs } from '../../actions/identity_proofs'; +import MissingIndicator from 'mastodon/components/missing_indicator'; const emptyList = ImmutableList(); @@ -20,6 +21,7 @@ const mapStateToProps = (state, { params: { accountId }, withReplies = false }) const path = withReplies ? `${accountId}:with_replies` : accountId; return { + isAccount: !!state.getIn(['accounts', accountId]), statusIds: state.getIn(['timelines', `account:${path}`, 'items'], emptyList), featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], emptyList), isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']), @@ -41,6 +43,7 @@ class AccountTimeline extends ImmutablePureComponent { hasMore: PropTypes.bool, withReplies: PropTypes.bool, blockedBy: PropTypes.bool, + isAccount: PropTypes.bool, }; componentWillMount () { @@ -74,7 +77,15 @@ class AccountTimeline extends ImmutablePureComponent { } render () { - const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy } = this.props; + const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, isAccount } = this.props; + + if (!isAccount) { + return ( + <Column> + <MissingIndicator /> + </Column> + ); + } if (!statusIds && isLoading) { return ( diff --git a/app/javascript/mastodon/features/followers/index.js b/app/javascript/mastodon/features/followers/index.js index ce6357c4c..e3387e1be 100644 --- a/app/javascript/mastodon/features/followers/index.js +++ b/app/javascript/mastodon/features/followers/index.js @@ -16,8 +16,10 @@ import Column from '../ui/components/column'; import HeaderContainer from '../account_timeline/containers/header_container'; import ColumnBackButton from '../../components/column_back_button'; import ScrollableList from '../../components/scrollable_list'; +import MissingIndicator from 'mastodon/components/missing_indicator'; const mapStateToProps = (state, props) => ({ + isAccount: !!state.getIn(['accounts', props.params.accountId]), accountIds: state.getIn(['user_lists', 'followers', props.params.accountId, 'items']), hasMore: !!state.getIn(['user_lists', 'followers', props.params.accountId, 'next']), blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false), @@ -33,6 +35,7 @@ class Followers extends ImmutablePureComponent { accountIds: ImmutablePropTypes.list, hasMore: PropTypes.bool, blockedBy: PropTypes.bool, + isAccount: PropTypes.bool, }; componentWillMount () { @@ -52,7 +55,15 @@ class Followers extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { shouldUpdateScroll, accountIds, hasMore, blockedBy } = this.props; + const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount } = this.props; + + if (!isAccount) { + return ( + <Column> + <MissingIndicator /> + </Column> + ); + } if (!accountIds) { return ( diff --git a/app/javascript/mastodon/features/following/index.js b/app/javascript/mastodon/features/following/index.js index 70e7fde06..3bf89fb2b 100644 --- a/app/javascript/mastodon/features/following/index.js +++ b/app/javascript/mastodon/features/following/index.js @@ -16,8 +16,10 @@ import Column from '../ui/components/column'; import HeaderContainer from '../account_timeline/containers/header_container'; import ColumnBackButton from '../../components/column_back_button'; import ScrollableList from '../../components/scrollable_list'; +import MissingIndicator from 'mastodon/components/missing_indicator'; const mapStateToProps = (state, props) => ({ + isAccount: !!state.getIn(['accounts', props.params.accountId]), accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']), hasMore: !!state.getIn(['user_lists', 'following', props.params.accountId, 'next']), blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false), @@ -33,6 +35,7 @@ class Following extends ImmutablePureComponent { accountIds: ImmutablePropTypes.list, hasMore: PropTypes.bool, blockedBy: PropTypes.bool, + isAccount: PropTypes.bool, }; componentWillMount () { @@ -52,7 +55,15 @@ class Following extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { shouldUpdateScroll, accountIds, hasMore, blockedBy } = this.props; + const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount } = this.props; + + if (!isAccount) { + return ( + <Column> + <MissingIndicator /> + </Column> + ); + } if (!accountIds) { return ( diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 8fc8762a4..c9d896f5b 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -83,7 +83,7 @@ "compose_form.spoiler.unmarked": "Lo tèxte es pas rescondut", "compose_form.spoiler_placeholder": "Escrivètz l’avertiment aquí", "confirmation_modal.cancel": "Anullar", - "confirmations.block.block_and_report": "Block & Report", + "confirmations.block.block_and_report": "Blocar e senhalar", "confirmations.block.confirm": "Blocar", "confirmations.block.message": "Volètz vertadièrament blocar {name} ?", "confirmations.delete.confirm": "Escafar", @@ -117,6 +117,8 @@ "emoji_button.symbols": "Simbòls", "emoji_button.travel": "Viatges & lòcs", "empty_column.account_timeline": "Cap de tuts aquí !", + "empty_column.account_timeline_blocked": "Sètz blocat", + "empty_column.account_unavailable": "Perfil pas disponible", "empty_column.blocks": "Avètz pas blocat degun pel moment.", "empty_column.community": "Lo flux public local es void. Escrivètz quicòm per lo garnir !", "empty_column.direct": "Avètz pas encara cap de messatges. Quand ne mandatz un o que ne recebètz un, serà mostrat aquí.", @@ -358,7 +360,7 @@ "tabs_bar.search": "Recèrcas", "time_remaining.days": "demòra{number, plural, one { # jorn} other {n # jorns}}", "time_remaining.hours": "demòra{number, plural, one { # ora} other {n # oras}}", - "time_remaining.minutes": "demòr{number, plural, one { # minuta} other {nn # minutas}}", + "time_remaining.minutes": "demòra{number, plural, one { # minuta} other {n # minutas}}", "time_remaining.moments": "Moments restants", "time_remaining.seconds": "demòra{number, plural, one { # segonda} other {n # segondas}}", "trends.count_by_accounts": "{count} {rawCount, plural, one {person} ne charra other {people}} ne charran", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index cbe1c5726..ec2d34a26 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -4,7 +4,7 @@ "account.block": "Engelle @{name}", "account.block_domain": "{domain} alanından her şeyi gizle", "account.blocked": "Engellenmiş", - "account.direct": "Direct Message @{name}", + "account.direct": "Mesaj gönder : @{name}", "account.domain_blocked": "Alan adı gizlendi", "account.edit_profile": "Profili düzenle", "account.endorse": "Profildeki özellik", @@ -19,28 +19,28 @@ "account.locked_info": "Bu hesabın gizlilik durumu kilitli olarak ayarlanmış. Sahibi, onu kimin takip edebileceğini elle inceler.", "account.media": "Medya", "account.mention": "@{name} kullanıcısından bahset", - "account.moved_to": "{name} has moved to:", + "account.moved_to": "{name} şuraya taşındı:", "account.mute": "@{name} kullanıcısını sessize al", "account.mute_notifications": "@{name} kullanıcısının bildirimlerini kapat", - "account.muted": "Sessiz", + "account.muted": "Sesi kısık", "account.posts": "Gönderiler", "account.posts_with_replies": "Gönderiler ve yanıtlar", "account.report": "@{name} kullanıcısını bildir", "account.requested": "Onay bekliyor. Takip isteğini iptal etmek için tıklayın", "account.share": "@{name} kullanıcısının profilini paylaş", - "account.show_reblogs": "@{name} kullanıcısından boost'ları göster", + "account.show_reblogs": "@{name} kullanıcısından boostları göster", "account.unblock": "Engeli kaldır @{name}", "account.unblock_domain": "{domain} göster", "account.unendorse": "Profilde özellik yok", "account.unfollow": "Takipten vazgeç", - "account.unmute": "Sesi aç @{name}", + "account.unmute": "Sesi aç : @{name}", "account.unmute_notifications": "@{name} kullanıcısından bildirimleri aç", "alert.unexpected.message": "Beklenmedik bir hata oluştu.", "alert.unexpected.title": "Hay aksi!", "boost_modal.combo": "Bir dahaki sefere {combo} tuşuna basabilirsiniz", "bundle_column_error.body": "Bu bileşen yüklenirken bir şeyler ters gitti.", "bundle_column_error.retry": "Tekrar deneyin", - "bundle_column_error.title": "Network error", + "bundle_column_error.title": "Ağ hatası", "bundle_modal_error.close": "Kapat", "bundle_modal_error.message": "Bu bileşen yüklenirken bir şeyler ters gitti.", "bundle_modal_error.retry": "Tekrar deneyin", @@ -54,7 +54,7 @@ "column.lists": "Listeler", "column.mutes": "Susturulmuş kullanıcılar", "column.notifications": "Bildirimler", - "column.pins": "Pinned toot", + "column.pins": "Sabitlenmiş gönderi", "column.public": "Federe zaman tüneli", "column_back_button.label": "Geri", "column_header.hide_settings": "Ayarları gizle", @@ -66,16 +66,16 @@ "column_subheading.settings": "Ayarlar", "community.column_settings.media_only": "Sadece medya", "compose_form.direct_message_warning": "Bu gönderi sadece belirtilen kullanıcılara gönderilecektir.", - "compose_form.direct_message_warning_learn_more": "Daha fazla bilgi edin", - "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", + "compose_form.direct_message_warning_learn_more": "Daha fazla bilgi edinin", + "compose_form.hashtag_warning": "Bu paylaşım liste dışı olduğu için hiç bir hashtag'de yer almayacak. Sadece herkese açık gönderiler hashtaglerde bulunabilir.", "compose_form.lock_disclaimer": "Hesabınız {locked} değil. Sadece takipçilerle paylaştığınız gönderileri görebilmek için sizi herhangi bir kullanıcı takip edebilir.", "compose_form.lock_disclaimer.lock": "kilitli", "compose_form.placeholder": "Aklınızdan ne geçiyor?", - "compose_form.poll.add_option": "Add a choice", - "compose_form.poll.duration": "Poll duration", - "compose_form.poll.option_placeholder": "Choice {number}", - "compose_form.poll.remove_option": "Remove this choice", - "compose_form.publish": "Toot", + "compose_form.poll.add_option": "Bir seçenek ekleyin", + "compose_form.poll.duration": "Anket süresi", + "compose_form.poll.option_placeholder": "Seçim {number}", + "compose_form.poll.remove_option": "Bu seçimi kaldır", + "compose_form.publish": "Gönder", "compose_form.publish_loud": "{publish}!", "compose_form.sensitive.marked": "Medya hassas olarak işaretlendi", "compose_form.sensitive.unmarked": "Medya hassas olarak işaretlenmemiş", @@ -83,24 +83,24 @@ "compose_form.spoiler.unmarked": "Metin gizli değil", "compose_form.spoiler_placeholder": "İçerik uyarısı", "confirmation_modal.cancel": "İptal", - "confirmations.block.block_and_report": "Block & Report", + "confirmations.block.block_and_report": "Engelle & Bildir", "confirmations.block.confirm": "Engelle", "confirmations.block.message": "{name} kullanıcısını engellemek istiyor musunuz?", "confirmations.delete.confirm": "Sil", "confirmations.delete.message": "Bu gönderiyi silmek istiyor musunuz?", - "confirmations.delete_list.confirm": "Delete", + "confirmations.delete_list.confirm": "Sil", "confirmations.delete_list.message": "Bu listeyi kalıcı olarak silmek istediğinize emin misiniz?", "confirmations.domain_block.confirm": "Alan adının tamamını gizle", - "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.", + "confirmations.domain_block.message": "tüm {domain} alan adını engellemek istediğinizden emin misiniz? Genellikle birkaç hedefli engel ve susturma işi görür ve tercih edilir.", "confirmations.mute.confirm": "Sessize al", "confirmations.mute.message": "{name} kullanıcısını sessize almak istiyor musunuz?", "confirmations.redraft.confirm": "Sil ve yeniden tasarla", - "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.", + "confirmations.redraft.message": "Bu durumu silip tekrar taslaklaştırmak istediğinizden emin misiniz? Tüm cevapları, boostları ve favorileri kaybedeceksiniz.", "confirmations.reply.confirm": "Yanıtla", "confirmations.reply.message": "Şimdi yanıtlarken o an oluşturduğunuz mesajın üzerine yazılır. Devam etmek istediğinize emin misiniz?", "confirmations.unfollow.confirm": "Takibi kaldır", - "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", - "embed.instructions": "Embed this status on your website by copying the code below.", + "confirmations.unfollow.message": "{name}'yi takipten çıkarmak istediğinizden emin misiniz?", + "embed.instructions": "Aşağıdaki kodu kopyalayarak bu durumu sitenize gömün.", "embed.preview": "İşte nasıl görüneceği:", "emoji_button.activity": "Aktivite", "emoji_button.custom": "Özel", @@ -112,7 +112,7 @@ "emoji_button.objects": "Nesneler", "emoji_button.people": "İnsanlar", "emoji_button.recent": "Sık kullanılan", - "emoji_button.search": "Emoji ara...", + "emoji_button.search": "Ara...", "emoji_button.search_results": "Arama sonuçları", "emoji_button.symbols": "Semboller", "emoji_button.travel": "Seyahat ve Yerler", @@ -121,13 +121,13 @@ "empty_column.community": "Yerel zaman çizelgesi boş. Daha fazla eğlence için herkese açık bir gönderi paylaşın!", "empty_column.direct": "Henüz doğrudan mesajınız yok. Bir tane gönderdiğinizde veya aldığınızda burada görünecektir.", "empty_column.domain_blocks": "Henüz hiçbir gizli alan adı yok.", - "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.", - "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.", - "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.", + "empty_column.favourited_statuses": "Hiç favori gönderiminiz yok. Bir tane olursa burada görünecek.", + "empty_column.favourites": "Kimse bu gönderiyi favorilerine eklememiş. Biri eklerse burada görünecek.", + "empty_column.follow_requests": "Hiç takip isteğiniz yok. Bir tane aldığınızda burada görünecek.", "empty_column.hashtag": "Henüz bu hashtag’e sahip hiçbir gönderi yok.", "empty_column.home": "Henüz kimseyi takip etmiyorsunuz. {public} ziyaret edebilir veya arama kısmını kullanarak diğer kullanıcılarla iletişime geçebilirsiniz.", "empty_column.home.public_timeline": "herkese açık zaman tüneli", - "empty_column.list": "There is nothing in this list yet.", + "empty_column.list": "Bu listede henüz hiçbir şey yok.", "empty_column.lists": "Henüz hiç listeniz yok. Bir tane oluşturduğunuzda burada görünecek.", "empty_column.mutes": "Henüz hiçbir kullanıcıyı sessize almadınız.", "empty_column.notifications": "Henüz hiçbir bildiriminiz yok. Diğer insanlarla sobhet edebilmek için etkileşime geçebilirsiniz.", @@ -136,7 +136,7 @@ "follow_request.reject": "Reddet", "getting_started.developers": "Geliştiriciler", "getting_started.directory": "Profil dizini", - "getting_started.documentation": "Documentation", + "getting_started.documentation": "Belgeler", "getting_started.heading": "Başlangıç", "getting_started.invite": "İnsanları davet edin", "getting_started.open_source_notice": "Mastodon açık kaynaklı bir yazılımdır. Github {github}. {apps} üzerinden katkıda bulunabilir, hata raporlayabilirsiniz.", @@ -145,12 +145,12 @@ "hashtag.column_header.tag_mode.all": "ve {additional}", "hashtag.column_header.tag_mode.any": "ya da {additional}", "hashtag.column_header.tag_mode.none": "{additional} olmadan", - "hashtag.column_settings.select.no_options_message": "No suggestions found", - "hashtag.column_settings.select.placeholder": "Enter hashtags…", + "hashtag.column_settings.select.no_options_message": "Hiç öneri bulunamadı", + "hashtag.column_settings.select.placeholder": "Hashtagler girin…", "hashtag.column_settings.tag_mode.all": "Bunların hepsi", "hashtag.column_settings.tag_mode.any": "Bunların hiçbiri", "hashtag.column_settings.tag_mode.none": "Bunların hiçbiri", - "hashtag.column_settings.tag_toggle": "Include additional tags in this column", + "hashtag.column_settings.tag_toggle": "Bu sütundaki ek etiketleri içer", "home.column_settings.basic": "Temel", "home.column_settings.show_reblogs": "Boost edilenleri göster", "home.column_settings.show_replies": "Cevapları göster", @@ -159,122 +159,122 @@ "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}", "introduction.federation.action": "İleri", "introduction.federation.federated.headline": "Birleşik", - "introduction.federation.federated.text": "Diğer dosya sunucularından gelen genel yayınlar, birleşik zaman çizelgesinde görünecektir.", + "introduction.federation.federated.text": "Diğer dosya sunucularından gelen genel gönderiler, birleşik zaman çizelgesinde görünecektir.", "introduction.federation.home.headline": "Ana sayfa", "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!", "introduction.federation.local.headline": "Yerel", - "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.", + "introduction.federation.local.text": "Aynı sunucudaki kişilerin gönderileri yerel zaman tünelinde gözükecektir.", "introduction.interactions.action": "Öğreticiyi bitirin!", "introduction.interactions.favourite.headline": "Favori", - "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.", + "introduction.interactions.favourite.text": "Bir gönderiyi favorilerinize alarak sonrası için saklayabilirsiniz ve yazara gönderiyi beğendiğinizi söyleyebilirsiniz.", "introduction.interactions.reblog.headline": "Boost", - "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.", + "introduction.interactions.reblog.text": "Başkalarının gönderilerini boostlayarak kendi takipçilerinizle paylaşabillirsiniz.", "introduction.interactions.reply.headline": "Yanıt", - "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.", + "introduction.interactions.reply.text": "Başkalarının gönderilerini ve kendi gönderilerinizi yanıtlayabilirsiniz. Bir konuşmada zincirli bir şekilde olacaklardır.", "introduction.welcome.action": "Hadi gidelim!", "introduction.welcome.headline": "İlk adımlar", - "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.", - "keyboard_shortcuts.back": "to navigate back", - "keyboard_shortcuts.blocked": "to open blocked users list", - "keyboard_shortcuts.boost": "to boost", - "keyboard_shortcuts.column": "to focus a status in one of the columns", - "keyboard_shortcuts.compose": "to focus the compose textarea", + "introduction.welcome.text": "Krallığa hoş geldiniz! Az sonra, geniş bir sunucu yelpazesinde mesaj gönderip arkadaşlarınızla konuşabileceksiniz. Ama bu sunucu, {domain}, özel (profilinizi barındırır, bu yüzden adresini hatırlayın).", + "keyboard_shortcuts.back": "geriye gitmek için", + "keyboard_shortcuts.blocked": "engelli kullanıcılar listesini açmak için", + "keyboard_shortcuts.boost": "boostlamak için", + "keyboard_shortcuts.column": "sütunlardan birindeki duruma odaklanmak için", + "keyboard_shortcuts.compose": "yazma alanına odaklanmak için", "keyboard_shortcuts.description": "Açıklama", - "keyboard_shortcuts.direct": "to open direct messages column", - "keyboard_shortcuts.down": "to move down in the list", - "keyboard_shortcuts.enter": "to open status", - "keyboard_shortcuts.favourite": "to favourite", - "keyboard_shortcuts.favourites": "to open favourites list", - "keyboard_shortcuts.federated": "to open federated timeline", + "keyboard_shortcuts.direct": "direkt mesajlar sütununu açmak için", + "keyboard_shortcuts.down": "listede aşağıya inmek için", + "keyboard_shortcuts.enter": "durumu açmak için", + "keyboard_shortcuts.favourite": "favorilere eklemek için", + "keyboard_shortcuts.favourites": "favoriler listesini açmak için", + "keyboard_shortcuts.federated": "federe edilmiş zaman tünelini açmak için", "keyboard_shortcuts.heading": "Klavye kısayolları", - "keyboard_shortcuts.home": "Ana sayfa zaman çizelgesini açmak için", - "keyboard_shortcuts.hotkey": "Hotkey", - "keyboard_shortcuts.legend": "to display this legend", - "keyboard_shortcuts.local": "to open local timeline", - "keyboard_shortcuts.mention": "to mention author", - "keyboard_shortcuts.muted": "to open muted users list", - "keyboard_shortcuts.my_profile": "to open your profile", - "keyboard_shortcuts.notifications": "to open notifications column", - "keyboard_shortcuts.pinned": "to open pinned toots list", - "keyboard_shortcuts.profile": "to open author's profile", - "keyboard_shortcuts.reply": "to reply", - "keyboard_shortcuts.requests": "to open follow requests list", - "keyboard_shortcuts.search": "to focus search", - "keyboard_shortcuts.start": "to open \"get started\" column", - "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", - "keyboard_shortcuts.toot": "to start a brand new toot", - "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", - "keyboard_shortcuts.up": "to move up in the list", + "keyboard_shortcuts.home": "ana sayfa zaman çizelgesini açmak için", + "keyboard_shortcuts.hotkey": "Kısatuş", + "keyboard_shortcuts.legend": "bu efsaneyi görüntülemek için", + "keyboard_shortcuts.local": "yerel zaman tünelini açmak için", + "keyboard_shortcuts.mention": "yazardan bahsetmek için", + "keyboard_shortcuts.muted": "susturulmuş kullanıcı listesini açmak için", + "keyboard_shortcuts.my_profile": "profilinizi açmak için", + "keyboard_shortcuts.notifications": "bildirimler sütununu açmak için", + "keyboard_shortcuts.pinned": "sabitlenmiş gönderiler listesini açmak için", + "keyboard_shortcuts.profile": "yazarın profilini açmak için", + "keyboard_shortcuts.reply": "cevaplamak için", + "keyboard_shortcuts.requests": "takip istekleri listesini açmak için", + "keyboard_shortcuts.search": "aramaya odaklanmak için", + "keyboard_shortcuts.start": "\"başlayın\" sütununu açmak için", + "keyboard_shortcuts.toggle_hidden": "CW'den önceki yazıyı göstermek/gizlemek için", + "keyboard_shortcuts.toot": "yeni bir gönderiye başlamak için", + "keyboard_shortcuts.unfocus": "aramada bir gönderiye odaklanmamak için", + "keyboard_shortcuts.up": "listede yukarıya çıkmak için", "lightbox.close": "Kapat", - "lightbox.next": "Next", - "lightbox.previous": "Previous", - "lists.account.add": "Add to list", - "lists.account.remove": "Remove from list", - "lists.delete": "Delete list", - "lists.edit": "Edit list", - "lists.edit.submit": "Change title", - "lists.new.create": "Add list", - "lists.new.title_placeholder": "New list title", - "lists.search": "Search among people you follow", - "lists.subheading": "Your lists", + "lightbox.next": "Sonraki", + "lightbox.previous": "Önceli", + "lists.account.add": "Listeye ekle", + "lists.account.remove": "Listeden kaldır", + "lists.delete": "Listeyi sil", + "lists.edit": "listeyi düzenle", + "lists.edit.submit": "Başlığı değiştir", + "lists.new.create": "Liste ekle", + "lists.new.title_placeholder": "Yeni liste başlığı", + "lists.search": "Takip ettiğiniz kişiler arasından arayın", + "lists.subheading": "Listeleriniz", "loading_indicator.label": "Yükleniyor...", "media_gallery.toggle_visible": "Görünürlüğü değiştir", "missing_indicator.label": "Bulunamadı", - "missing_indicator.sublabel": "This resource could not be found", - "mute_modal.hide_notifications": "Hide notifications from this user?", - "navigation_bar.apps": "Mobile apps", + "missing_indicator.sublabel": "Bu kaynak bulunamadı", + "mute_modal.hide_notifications": "Bu kullanıcıdan bildirimler gizlensin mı?", + "navigation_bar.apps": "Mobil uygulamalar", "navigation_bar.blocks": "Engellenen kullanıcılar", "navigation_bar.community_timeline": "Yerel zaman tüneli", - "navigation_bar.compose": "Compose new toot", - "navigation_bar.direct": "Direct messages", - "navigation_bar.discover": "Discover", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.compose": "Yeni bir gönderi yazın", + "navigation_bar.direct": "Direkt Mesajlar", + "navigation_bar.discover": "Keşfet", + "navigation_bar.domain_blocks": "Gizli alan adları", "navigation_bar.edit_profile": "Profili düzenle", "navigation_bar.favourites": "Favoriler", - "navigation_bar.filters": "Muted words", + "navigation_bar.filters": "Susturulmuş kelimeler", "navigation_bar.follow_requests": "Takip istekleri", "navigation_bar.info": "Genişletilmiş bilgi", - "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts", - "navigation_bar.lists": "Lists", + "navigation_bar.keyboard_shortcuts": "Klavye kısayolları", + "navigation_bar.lists": "Listeler", "navigation_bar.logout": "Çıkış", "navigation_bar.mutes": "Sessize alınmış kullanıcılar", - "navigation_bar.personal": "Personal", - "navigation_bar.pins": "Pinned toots", + "navigation_bar.personal": "Kişisel", + "navigation_bar.pins": "Sabitlenmiş gönderiler", "navigation_bar.preferences": "Tercihler", "navigation_bar.public_timeline": "Federe zaman tüneli", - "navigation_bar.security": "Security", + "navigation_bar.security": "Güvenlik", "notification.favourite": "{name} senin durumunu favorilere ekledi", "notification.follow": "{name} seni takip ediyor", "notification.mention": "{name} mentioned you", - "notification.poll": "A poll you have voted in has ended", + "notification.poll": "Oy verdiğiniz bir anket bitti", "notification.reblog": "{name} senin durumunu boost etti", "notifications.clear": "Bildirimleri temizle", "notifications.clear_confirmation": "Tüm bildirimlerinizi kalıcı olarak temizlemek ister misiniz?", "notifications.column_settings.alert": "Masaüstü bildirimleri", "notifications.column_settings.favourite": "Favoriler:", - "notifications.column_settings.filter_bar.advanced": "Display all categories", - "notifications.column_settings.filter_bar.category": "Quick filter bar", - "notifications.column_settings.filter_bar.show": "Show", + "notifications.column_settings.filter_bar.advanced": "Tüm kategorileri göster", + "notifications.column_settings.filter_bar.category": "Hızlı filtre çubuğu", + "notifications.column_settings.filter_bar.show": "Göster", "notifications.column_settings.follow": "Yeni takipçiler:", "notifications.column_settings.mention": "Bahsedilenler:", - "notifications.column_settings.poll": "Poll results:", - "notifications.column_settings.push": "Push notifications", - "notifications.column_settings.reblog": "Boost’lar:", + "notifications.column_settings.poll": "Anket sonuçları:", + "notifications.column_settings.push": "Push bildirimleri", + "notifications.column_settings.reblog": "Boostlar:", "notifications.column_settings.show": "Bildirimlerde göster", "notifications.column_settings.sound": "Ses çal", - "notifications.filter.all": "All", - "notifications.filter.boosts": "Boosts", - "notifications.filter.favourites": "Favourites", - "notifications.filter.follows": "Follows", - "notifications.filter.mentions": "Mentions", - "notifications.filter.polls": "Poll results", - "notifications.group": "{count} notifications", - "poll.closed": "Closed", - "poll.refresh": "Refresh", + "notifications.filter.all": "Tümü", + "notifications.filter.boosts": "Boostlar", + "notifications.filter.favourites": "Favoriler", + "notifications.filter.follows": "Takip edilenler", + "notifications.filter.mentions": "Bahsetmeler", + "notifications.filter.polls": "Anket sonuçları", + "notifications.group": "{count} bildirim", + "poll.closed": "Kapandı", + "poll.refresh": "Yenile", "poll.total_votes": "{count, plural, one {# vote} other {# votes}}", - "poll.vote": "Vote", - "poll_button.add_poll": "Add a poll", - "poll_button.remove_poll": "Remove poll", + "poll.vote": "Oy ver", + "poll_button.add_poll": "Bir anket ekleyin", + "poll_button.remove_poll": "Anket kaldır", "privacy.change": "Gönderi gizliliğini ayarla", "privacy.direct.long": "Sadece bahsedilen kişilere gönder", "privacy.direct.short": "Direkt", @@ -284,100 +284,100 @@ "privacy.public.short": "Herkese açık", "privacy.unlisted.long": "Herkese açık zaman tüneline gönderme", "privacy.unlisted.short": "Listelenmemiş", - "regeneration_indicator.label": "Loading…", - "regeneration_indicator.sublabel": "Your home feed is being prepared!", - "relative_time.days": "{number}d", - "relative_time.hours": "{number}h", - "relative_time.just_now": "now", - "relative_time.minutes": "{number}m", - "relative_time.seconds": "{number}s", + "regeneration_indicator.label": "Yükleniyor…", + "regeneration_indicator.sublabel": "Ev akışınız hazırlanıyor!", + "relative_time.days": "{number}g", + "relative_time.hours": "{number}s", + "relative_time.just_now": "şimdi", + "relative_time.minutes": "{number}dk", + "relative_time.seconds": "{number}sn", "reply_indicator.cancel": "İptal", - "report.forward": "Forward to {target}", - "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", - "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", + "report.forward": "Şu kişiye ilet : {target}", + "report.forward_hint": "Bu hesap başka bir sunucudan. Anonimleştirilmiş bir rapor oraya da gönderilsin mi?", + "report.hint": "Bu rapor sunucu moderatörlerine gönderilecek. Bu hesabı neden bildirdiğiniz hakkında bilgi verebirsiniz:", "report.placeholder": "Ek yorumlar", "report.submit": "Gönder", "report.target": "Raporlama", "search.placeholder": "Ara", - "search_popout.search_format": "Advanced search format", + "search_popout.search_format": "Gelişmiş arama formatı", "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.", "search_popout.tips.hashtag": "hashtag", - "search_popout.tips.status": "status", + "search_popout.tips.status": "durum", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", - "search_popout.tips.user": "user", - "search_results.accounts": "People", - "search_results.hashtags": "Hashtags", - "search_results.statuses": "Toots", + "search_popout.tips.user": "kullanıcı", + "search_results.accounts": "İnsanlar", + "search_results.hashtags": "Hashtagler", + "search_results.statuses": "Gönderiler", "search_results.total": "{count, number} {count, plural, one {sonuç} other {sonuçlar}}", "status.admin_account": "@{name} için denetim arayüzünü açın", "status.admin_status": "Denetim arayüzünde bu durumu açın", - "status.block": "Block @{name}", - "status.cancel_reblog_private": "Unboost", + "status.block": "Engelle : @{name}", + "status.cancel_reblog_private": "Boost'u geri al", "status.cannot_reblog": "Bu gönderi boost edilemez", "status.copy": "Bağlantı durumunu kopyala", "status.delete": "Sil", - "status.detailed_status": "Detailed conversation view", - "status.direct": "Direct message @{name}", - "status.embed": "Embed", + "status.detailed_status": "Detaylı yazışma dökümü", + "status.direct": "@{name}'e gönder", + "status.embed": "Gömülü", "status.favourite": "Favorilere ekle", - "status.filtered": "Filtered", + "status.filtered": "Filtrelenmiş", "status.load_more": "Daha fazla", "status.media_hidden": "Gizli görsel", - "status.mention": "Bahset @{name}", - "status.more": "More", - "status.mute": "Mute @{name}", - "status.mute_conversation": "Mute conversation", + "status.mention": "Bahset : @{name}", + "status.more": "Daha fazla", + "status.mute": "Sustur : @{name}", + "status.mute_conversation": "Yazışmayı sustur", "status.open": "Bu gönderiyi genişlet", - "status.pin": "Pin on profile", - "status.pinned": "Pinned toot", - "status.read_more": "Read more", - "status.reblog": "Boost'la", + "status.pin": "Profile sabitle", + "status.pinned": "Sabitlenmiş gönderi", + "status.read_more": "Daha dazla oku", + "status.reblog": "Boostla", "status.reblog_private": "Boost to original audience", "status.reblogged_by": "{name} boost etti", - "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.", - "status.redraft": "Delete & re-draft", + "status.reblogs.empty": "Kimse bu gönderiyi boostlamadı. Biri yaptığında burada gözükecek.", + "status.redraft": "Sil & tekrar taslakla", "status.reply": "Cevapla", "status.replyAll": "Konuşmayı cevapla", "status.report": "@{name}'i raporla", "status.sensitive_toggle": "Görmek için tıklayınız", "status.sensitive_warning": "Hassas içerik", - "status.share": "Share", - "status.show_less": "Daha azı", - "status.show_less_all": "Show less for all", - "status.show_more": "Daha fazlası", - "status.show_more_all": "Show more for all", - "status.show_thread": "Show thread", + "status.share": "Paylaş", + "status.show_less": "Daha az göster", + "status.show_less_all": "Hepsi için daha az göster", + "status.show_more": "Daha fazla göster", + "status.show_more_all": "Hepsi için daha fazla göster", + "status.show_thread": "Başlığı göster", "status.unmute_conversation": "Unmute conversation", - "status.unpin": "Unpin from profile", - "suggestions.dismiss": "Dismiss suggestion", - "suggestions.header": "You might be interested in…", + "status.unpin": "Profilden sabitlemeyi kaldır", + "suggestions.dismiss": "Öneriyi görmezden gel", + "suggestions.header": "Şuna ilgi duyuyor olabilirsiniz…", "tabs_bar.federated_timeline": "Federe", "tabs_bar.home": "Ana sayfa", "tabs_bar.local_timeline": "Yerel", "tabs_bar.notifications": "Bildirimler", - "tabs_bar.search": "Search", + "tabs_bar.search": "Ara", "time_remaining.days": "{number, plural, one {# day} other {# days}} left", "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left", "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left", "time_remaining.moments": "Moments remaining", "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left", "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking", - "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", - "upload_area.title": "Upload için sürükle bırak yapınız", + "ui.beforeunload": "Mastodon'dan ayrılırsanız taslağınız kaybolacak.", + "upload_area.title": "Karşıya yükleme için sürükle bırak yapınız", "upload_button.label": "Görsel ekle", "upload_error.limit": "Dosya yükleme sınırı aşıldı.", - "upload_error.poll": "File upload not allowed with polls.", + "upload_error.poll": "Anketlerde dosya yüklemesine izin verilmez.", "upload_form.description": "Describe for the visually impaired", - "upload_form.focus": "Crop", + "upload_form.focus": "Kırp", "upload_form.undo": "Geri al", "upload_progress.label": "Yükleniyor...", - "video.close": "Close video", - "video.exit_fullscreen": "Exit full screen", - "video.expand": "Expand video", - "video.fullscreen": "Full screen", - "video.hide": "Hide video", - "video.mute": "Mute sound", - "video.pause": "Pause", - "video.play": "Play", - "video.unmute": "Unmute sound" + "video.close": "Videoyu kapat", + "video.exit_fullscreen": "Tam ekrandan çık", + "video.expand": "Videoyu genişlet", + "video.fullscreen": "Tam ekran", + "video.hide": "Videoyu gizle", + "video.mute": "Sesi kıs", + "video.pause": "Duraklat", + "video.play": "Oynat", + "video.unmute": "Sesi aç" } diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss index f4f458cf4..a790251f4 100644 --- a/app/javascript/styles/mastodon/accounts.scss +++ b/app/javascript/styles/mastodon/accounts.scss @@ -292,3 +292,29 @@ .directory__tag .trends__item__current { width: auto; } + +.pending-account { + &__header { + color: $darker-text-color; + + a { + color: $ui-secondary-color; + text-decoration: none; + + &:hover, + &:active, + &:focus { + text-decoration: underline; + } + } + + strong { + color: $primary-text-color; + font-weight: 700; + } + } + + &__body { + margin-top: 10px; + } +} diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss index 307e509d5..e736d7a7e 100644 --- a/app/javascript/styles/mastodon/widgets.scss +++ b/app/javascript/styles/mastodon/widgets.scss @@ -377,6 +377,10 @@ border: 0; } + strong { + font-weight: 700; + } + thead th { text-align: center; text-transform: uppercase; @@ -414,6 +418,11 @@ } } + &__comment { + width: 50%; + vertical-align: initial !important; + } + @media screen and (max-width: $no-gap-breakpoint) { tbody td.optional { display: none; diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 5b71dfad5..83d303c33 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -57,7 +57,8 @@ class Form::AdminSettings attr_accessor(*KEYS) - validates :site_short_description, :site_description, :site_extended_description, :site_terms, :closed_registrations_message, html: true + validates :site_short_description, :site_description, html: { wrap_with: :p } + validates :site_extended_description, :site_terms, :closed_registrations_message, html: true validates :registrations_mode, inclusion: { in: %w(open approved none) } validates :min_invite_role, inclusion: { in: %w(disabled user moderator admin) } validates :site_contact_email, :site_contact_username, presence: true diff --git a/app/models/user.rb b/app/models/user.rb index 66c1543ff..b2fb820af 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -74,6 +74,9 @@ class User < ApplicationRecord has_many :applications, class_name: 'Doorkeeper::Application', as: :owner has_many :backups, inverse_of: :user + has_one :invite_request, class_name: 'UserInviteRequest', inverse_of: :user, dependent: :destroy + accepts_nested_attributes_for :invite_request, reject_if: ->(attributes) { attributes['text'].blank? } + validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale? validates_with BlacklistedEmailValidator, if: :email_changed? validates_with EmailMxValidator, if: :validate_email_dns? @@ -188,6 +191,10 @@ class User < ApplicationRecord settings.notification_emails['report'] end + def allows_pending_account_emails? + settings.notification_emails['pending_account'] + end + def hides_network? @hides_network ||= settings.hide_network end @@ -292,7 +299,7 @@ class User < ApplicationRecord def notify_staff_about_pending_account! User.staff.includes(:account).each do |u| - next unless u.allows_report_emails? + next unless u.allows_pending_account_emails? AdminMailer.new_pending_account(u.account, self).deliver_later end end diff --git a/app/models/user_invite_request.rb b/app/models/user_invite_request.rb new file mode 100644 index 000000000..2b76c88b9 --- /dev/null +++ b/app/models/user_invite_request.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: user_invite_requests +# +# id :bigint(8) not null, primary key +# user_id :bigint(8) +# text :text +# created_at :datetime not null +# updated_at :datetime not null +# + +class UserInviteRequest < ApplicationRecord + belongs_to :user, inverse_of: :invite_request + validates :text, presence: true, length: { maximum: 420 } +end diff --git a/app/validators/html_validator.rb b/app/validators/html_validator.rb index b7caee5a9..1c9cd303c 100644 --- a/app/validators/html_validator.rb +++ b/app/validators/html_validator.rb @@ -1,18 +1,20 @@ # frozen_string_literal: true class HtmlValidator < ActiveModel::EachValidator + ERROR_RE = /Opening and ending tag mismatch|Unexpected end tag/ + def validate_each(record, attribute, value) return if value.blank? + errors = html_errors(value) - unless errors.empty? - record.errors.add(attribute, I18n.t('html_validator.invalid_markup', error: errors.first.to_s)) - end + + record.errors.add(attribute, I18n.t('html_validator.invalid_markup', error: errors.first.to_s)) unless errors.empty? end private def html_errors(str) - fragment = Nokogiri::HTML.fragment(str) - fragment.errors + fragment = Nokogiri::HTML.fragment(options[:wrap_with] ? "<#{options[:wrap_with]}>#{str}</#{options[:wrap_with]}>" : str) + fragment.errors.select { |error| ERROR_RE =~ error.message } end end diff --git a/app/validators/poll_validator.rb b/app/validators/poll_validator.rb index fd497c8d0..9d7321cad 100644 --- a/app/validators/poll_validator.rb +++ b/app/validators/poll_validator.rb @@ -14,6 +14,6 @@ class PollValidator < ActiveModel::Validator poll.errors.add(:options, I18n.t('polls.errors.over_character_limit', max: MAX_OPTION_CHARS)) if poll.options.any? { |option| option.mb_chars.grapheme_length > MAX_OPTION_CHARS } poll.errors.add(:options, I18n.t('polls.errors.duplicate_options')) unless poll.options.uniq.size == poll.options.size poll.errors.add(:expires_at, I18n.t('polls.errors.duration_too_long')) if poll.expires_at.nil? || poll.expires_at - current_time > MAX_EXPIRATION - poll.errors.add(:expires_at, I18n.t('polls.errors.duration_too_short')) if poll.expires_at.present? && poll.expires_at - current_time < MIN_EXPIRATION + poll.errors.add(:expires_at, I18n.t('polls.errors.duration_too_short')) if poll.expires_at.present? && (poll.expires_at - current_time).ceil < MIN_EXPIRATION end end diff --git a/app/views/about/_registration.html.haml b/app/views/about/_registration.html.haml index 09cbe2e28..ff32ec8c4 100644 --- a/app/views/about/_registration.html.haml +++ b/app/views/about/_registration.html.haml @@ -10,6 +10,11 @@ = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations? = 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 => 'off' }, hint: false, disabled: closed_registrations? + - if approved_registrations? + .fields-group + = f.simple_fields_for :invite_request do |invite_request_fields| + = invite_request_fields.input :text, as: :text, wrapper: :with_block_label, required: false + .fields-group = f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path), disabled: closed_registrations? diff --git a/app/views/admin/pending_accounts/_account.html.haml b/app/views/admin/pending_accounts/_account.html.haml index c520dc065..1ed5dafdd 100644 --- a/app/views/admin/pending_accounts/_account.html.haml +++ b/app/views/admin/pending_accounts/_account.html.haml @@ -1,14 +1,14 @@ .batch-table__row %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox = f.check_box :account_ids, { multiple: true, include_hidden: false }, account.id - .batch-table__row__content.batch-table__row__content--unpadded - %table.accounts-table - %tbody - %tr - %td - = account.user_email - = "(@#{account.username})" - %br/ - = account.user_current_sign_in_ip - %td.accounts-table__count - = table_link_to 'pencil', t('admin.accounts.edit'), admin_account_path(account.id) + .batch-table__row__content.pending-account + .pending-account__header + = link_to admin_account_path(account.id) do + %strong= account.user_email + = "(@#{account.username})" + %br/ + = account.user_current_sign_in_ip + + - if account.user&.invite_request&.text&.present? + .pending-account__body + %p= account.user&.invite_request&.text diff --git a/app/views/admin/pending_accounts/index.html.haml b/app/views/admin/pending_accounts/index.html.haml index 1bfd3824f..171976e33 100644 --- a/app/views/admin/pending_accounts/index.html.haml +++ b/app/views/admin/pending_accounts/index.html.haml @@ -1,7 +1,7 @@ - content_for :page_title do = t('admin.pending_accounts.title', count: User.pending.count) -= form_for(@form, url: admin_pending_accounts_path, method: :patch) do |f| += form_for(@form, url: batch_admin_pending_accounts_path) do |f| = hidden_field_tag :page, params[:page] || 1 .batch-table diff --git a/app/views/admin_mailer/new_pending_account.text.erb b/app/views/admin_mailer/new_pending_account.text.erb index ed31ae2eb..a466ee2de 100644 --- a/app/views/admin_mailer/new_pending_account.text.erb +++ b/app/views/admin_mailer/new_pending_account.text.erb @@ -2,7 +2,11 @@ <%= raw t('admin_mailer.new_pending_account.body') %> -<%= raw t('admin.accounts.email') %>: <%= @account.user_email %> -<%= raw t('admin.accounts.most_recent_ip') %>: <%= @account.user_current_sign_in_ip %> +<%= @account.user_email %> (@<%= @account.username %>) +<%= @account.user_current_sign_in_ip %> +<% if @account.user&.invite_request&.text.present? %> -<%= raw t('application_mailer.view')%> <%= admin_account_url(@account.id) %> +<%= quote_wrap(@account.user&.invite_request&.text) %> +<% end %> + +<%= raw t('application_mailer.view')%> <%= admin_pending_accounts_url %> diff --git a/app/views/auth/registrations/new.html.haml b/app/views/auth/registrations/new.html.haml index 1caf2b401..bd6e3a13f 100644 --- a/app/views/auth/registrations/new.html.haml +++ b/app/views/auth/registrations/new.html.haml @@ -21,12 +21,19 @@ .fields-group = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' } + .fields-group = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' } + - if approved_registrations? && !@invite.present? + .fields-group + = f.simple_fields_for :invite_request do |invite_request_fields| + = invite_request_fields.input :text, as: :text, wrapper: :with_block_label, required: false + = f.input :invite_code, as: :hidden - %p.hint= t('auth.agreement_html', rules_path: about_more_path, terms_path: terms_path) + .fields-group + = f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path) .actions = f.button :button, sign_up_message, type: :submit diff --git a/app/views/settings/notifications/show.html.haml b/app/views/settings/notifications/show.html.haml index 8aaac043b..6ec57b502 100644 --- a/app/views/settings/notifications/show.html.haml +++ b/app/views/settings/notifications/show.html.haml @@ -14,6 +14,7 @@ - if current_user.staff? = ff.input :report, as: :boolean, wrapper: :with_label + = ff.input :pending_account, as: :boolean, wrapper: :with_label .fields-group = f.simple_fields_for :notification_emails, hash_to_object(current_user.settings.notification_emails) do |ff| diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml index c666bafb5..a50f33517 100644 --- a/app/views/settings/preferences/show.html.haml +++ b/app/views/settings/preferences/show.html.haml @@ -6,6 +6,7 @@ %li= link_to t('preferences.publishing'), '#settings_publishing' %li= link_to t('preferences.other'), '#settings_other' %li= link_to t('preferences.web'), '#settings_web' + %li= link_to t('settings.notifications'), settings_notifications_path = simple_form_for current_user, url: settings_preferences_path, html: { method: :put } do |f| = render 'shared/error_messages', object: current_user |