mirror of
https://framagit.org/tykayn/mastodon.git
synced 2023-08-25 08:33:12 +02:00
Merge remote-tracking branch 'tootsuite/master' into glitchsoc/master
This commit is contained in:
commit
447d7e6127
95
.env.nanobox
95
.env.nanobox
@ -13,11 +13,29 @@ DB_PORT=5432
|
||||
|
||||
DATABASE_URL=postgresql://$DATA_DB_USER:$DATA_DB_PASS@$DATA_DB_HOST/gonano
|
||||
|
||||
# Optional ElasticSearch configuration
|
||||
# ES_ENABLED=true
|
||||
# ES_HOST=localhost
|
||||
# ES_PORT=9200
|
||||
|
||||
# Optimizations
|
||||
LD_PRELOAD=/data/lib/libjemalloc.so
|
||||
|
||||
# ImageMagick optimizations
|
||||
MAGICK_TEMPORARY_PATH=/app/tmp
|
||||
MAGICK_MEMORY_LIMIT=128MiB
|
||||
MAGICK_MAP_LIMIT=64MiB
|
||||
MAGICK_TIME_LIMIT=15
|
||||
MAGICK_AREA_LIMIT=16MP
|
||||
MAGICK_WIDTH_LIMIT=8KP
|
||||
MAGICK_HEIGHT_LIMIT=8KP
|
||||
|
||||
# Federation
|
||||
# Note: Changing LOCAL_DOMAIN or LOCAL_HTTPS at a later time will cause unwanted side effects.
|
||||
# Note: Changing LOCAL_DOMAIN at a later time will cause unwanted side effects, including breaking all existing federation.
|
||||
# LOCAL_DOMAIN should *NOT* contain the protocol part of the domain e.g https://example.com.
|
||||
LOCAL_DOMAIN=${APP_NAME}.nanoapp.io
|
||||
LOCAL_HTTPS=false
|
||||
|
||||
# Changing LOCAL_HTTPS in production is no longer supported. (Mastodon will always serve https:// links)
|
||||
|
||||
# Use this only if you need to run mastodon on a different domain than the one used for federation.
|
||||
# You can read more about this option on https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Serving_a_different_domain.md
|
||||
@ -31,7 +49,6 @@ LOCAL_HTTPS=false
|
||||
|
||||
# Application secrets
|
||||
# Generate each with the `rake secret` task (`nanobox run bundle exec rake secret`)
|
||||
PAPERCLIP_SECRET=$PAPERCLIP_SECRET
|
||||
SECRET_KEY_BASE=$SECRET_KEY_BASE
|
||||
OTP_SECRET=$OTP_SECRET
|
||||
|
||||
@ -131,9 +148,79 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
||||
|
||||
# Cluster number setting for streaming API server.
|
||||
# If you comment out following line, cluster number will be `numOfCpuCores - 1`.
|
||||
STREAMING_CLUSTER_NUM=1
|
||||
# STREAMING_CLUSTER_NUM=1
|
||||
|
||||
# Docker mastodon user
|
||||
# If you use Docker, you may want to assign UID/GID manually.
|
||||
# UID=1000
|
||||
# GID=1000
|
||||
|
||||
# LDAP authentication (optional)
|
||||
# LDAP_ENABLED=true
|
||||
# LDAP_HOST=localhost
|
||||
# LDAP_PORT=389
|
||||
# LDAP_METHOD=simple_tls
|
||||
# LDAP_BASE=
|
||||
# LDAP_BIND_DN=
|
||||
# LDAP_PASSWORD=
|
||||
# LDAP_UID=cn
|
||||
|
||||
# PAM authentication (optional)
|
||||
# PAM authentication uses for the email generation the "email" pam variable
|
||||
# and optional as fallback PAM_DEFAULT_SUFFIX
|
||||
# The pam environment variable "email" is provided by:
|
||||
# https://github.com/devkral/pam_email_extractor
|
||||
# PAM_ENABLED=true
|
||||
# Fallback Suffix for email address generation (nil by default)
|
||||
# PAM_DEFAULT_SUFFIX=pam
|
||||
# Name of the pam service (pam "auth" section is evaluated)
|
||||
# PAM_DEFAULT_SERVICE=rpam
|
||||
# Name of the pam service used for checking if an user can register (pam "account" section is evaluated) (nil (disabled) by default)
|
||||
# PAM_CONTROLLED_SERVICE=rpam
|
||||
|
||||
# Global OAuth settings (optional) :
|
||||
# If you have only one strategy, you may want to enable this
|
||||
# OAUTH_REDIRECT_AT_SIGN_IN=true
|
||||
|
||||
# Optional CAS authentication (cf. omniauth-cas) :
|
||||
# CAS_ENABLED=true
|
||||
# CAS_URL=https://sso.myserver.com/
|
||||
# CAS_HOST=sso.myserver.com/
|
||||
# CAS_PORT=443
|
||||
# CAS_SSL=true
|
||||
# CAS_VALIDATE_URL=
|
||||
# CAS_CALLBACK_URL=
|
||||
# CAS_LOGOUT_URL=
|
||||
# CAS_LOGIN_URL=
|
||||
# CAS_UID_FIELD='user'
|
||||
# CAS_CA_PATH=
|
||||
# CAS_DISABLE_SSL_VERIFICATION=false
|
||||
# CAS_UID_KEY='user'
|
||||
# CAS_NAME_KEY='name'
|
||||
# CAS_EMAIL_KEY='email'
|
||||
# CAS_NICKNAME_KEY='nickname'
|
||||
# CAS_FIRST_NAME_KEY='firstname'
|
||||
# CAS_LAST_NAME_KEY='lastname'
|
||||
# CAS_LOCATION_KEY='location'
|
||||
# CAS_IMAGE_KEY='image'
|
||||
# CAS_PHONE_KEY='phone'
|
||||
|
||||
# Optional SAML authentication (cf. omniauth-saml)
|
||||
# SAML_ENABLED=true
|
||||
# SAML_ACS_URL=
|
||||
# SAML_ISSUER=http://localhost:3000/auth/auth/saml/callback
|
||||
# SAML_IDP_SSO_TARGET_URL=https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO
|
||||
# SAML_IDP_CERT=
|
||||
# SAML_IDP_CERT_FINGERPRINT=
|
||||
# SAML_NAME_IDENTIFIER_FORMAT=
|
||||
# SAML_CERT=
|
||||
# SAML_PRIVATE_KEY=
|
||||
# SAML_SECURITY_WANT_ASSERTION_SIGNED=true
|
||||
# SAML_SECURITY_WANT_ASSERTION_ENCRYPTED=true
|
||||
# SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
|
||||
# SAML_ATTRIBUTES_STATEMENTS_UID="urn:oid:0.9.2342.19200300.100.1.1"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_EMAIL="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.5.4.42"
|
||||
# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
|
||||
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=
|
||||
|
@ -207,7 +207,9 @@ STREAMING_CLUSTER_NUM=1
|
||||
# SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
|
||||
# SAML_ATTRIBUTES_STATEMENTS_UID="urn:oid:0.9.2342.19200300.100.1.1"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_EMAIL="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.5.4.42"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.16.840.1.113730.3.1.241"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_FIRST_NAME="urn:oid:2.5.4.42"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_LAST_NAME="urn:oid:2.5.4.4"
|
||||
# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
|
||||
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=
|
||||
|
@ -1,4 +1,3 @@
|
||||
# Federation
|
||||
LOCAL_DOMAIN=cb6e6126.ngrok.io
|
||||
LOCAL_HTTPS=true
|
||||
OTP_SECRET=100c7faeef00caa29242f6b04156742bf76065771fd4117990c4282b8748ff3d99f8fdae97c982ab5bd2e6756a159121377cce4421f4a8ecd2d67bd7749a3fb4
|
||||
|
15
Dockerfile
15
Dockerfile
@ -1,7 +1,7 @@
|
||||
FROM ruby:2.5.0-alpine3.7
|
||||
|
||||
LABEL maintainer="https://github.com/tootsuite/mastodon" \
|
||||
description="A GNU Social-compatible microblogging server"
|
||||
description="Your self-hosted, globally interconnected microblogging community"
|
||||
|
||||
ARG UID=991
|
||||
ARG GID=991
|
||||
@ -9,8 +9,8 @@ ARG GID=991
|
||||
ENV RAILS_SERVE_STATIC_FILES=true \
|
||||
RAILS_ENV=production NODE_ENV=production
|
||||
|
||||
ARG YARN_VERSION=1.3.2
|
||||
ARG YARN_DOWNLOAD_SHA256=6cfe82e530ef0837212f13e45c1565ba53f5199eec2527b85ecbcd88bf26821d
|
||||
ARG YARN_VERSION=1.5.1
|
||||
ARG YARN_DOWNLOAD_SHA256=cd31657232cf48d57fdbff55f38bfa058d2fb4950450bd34af72dac796af4de1
|
||||
ARG LIBICONV_VERSION=1.15
|
||||
ARG LIBICONV_DOWNLOAD_SHA256=ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178
|
||||
|
||||
@ -38,7 +38,6 @@ RUN apk -U upgrade \
|
||||
libidn \
|
||||
libpq \
|
||||
nodejs \
|
||||
nodejs-npm \
|
||||
protobuf \
|
||||
su-exec \
|
||||
tini \
|
||||
@ -73,9 +72,13 @@ RUN bundle config build.nokogiri --with-iconv-lib=/usr/local/lib --with-iconv-in
|
||||
&& yarn --pure-lockfile \
|
||||
&& yarn cache clean
|
||||
|
||||
RUN addgroup -g ${GID} mastodon && adduser -h /mastodon -s /bin/sh -D -G mastodon -u ${UID} mastodon
|
||||
RUN addgroup -g ${GID} mastodon && adduser -h /mastodon -s /bin/sh -D -G mastodon -u ${UID} mastodon \
|
||||
&& mkdir -p /mastodon/public/system /mastodon/public/assets /mastodon/public/packs \
|
||||
&& chown -R mastodon:mastodon /mastodon/public
|
||||
|
||||
COPY --chown=mastodon:mastodon . /mastodon
|
||||
COPY . /mastodon
|
||||
|
||||
RUN chown -R mastodon:mastodon /mastodon
|
||||
|
||||
VOLUME /mastodon/public/system /mastodon/public/assets /mastodon/public/packs
|
||||
|
||||
|
8
Gemfile
8
Gemfile
@ -28,15 +28,15 @@ gem 'bootsnap'
|
||||
gem 'browser'
|
||||
gem 'charlock_holmes', '~> 0.7.5'
|
||||
gem 'iso-639'
|
||||
gem 'chewy', '~> 0.10', git: 'https://github.com/toptal/chewy.git'
|
||||
gem 'chewy', '~> 5.0'
|
||||
gem 'cld3', '~> 3.2.0'
|
||||
gem 'devise', '~> 4.4'
|
||||
gem 'devise-two-factor', '~> 3.0'
|
||||
|
||||
gem 'devise_pam_authenticatable2', '~> 8.0', install_if: -> { ENV['PAM_ENABLED'] == 'true' }
|
||||
gem 'net-ldap', '~> 0.10', install_if: -> { ENV['LDAP_ENABLED'] == 'true' }
|
||||
gem 'omniauth-cas', '~> 1.1', install_if: -> { ENV['CAS_ENABLED'] == 'true' }
|
||||
gem 'omniauth-saml', '~> 1.10', install_if: -> { ENV['SAML_ENABLED'] == 'true' }
|
||||
gem 'net-ldap', '~> 0.10'
|
||||
gem 'omniauth-cas', '~> 1.1'
|
||||
gem 'omniauth-saml', '~> 1.10'
|
||||
gem 'omniauth', '~> 1.2'
|
||||
|
||||
gem 'doorkeeper', '~> 4.2'
|
||||
|
15
Gemfile.lock
15
Gemfile.lock
@ -1,12 +1,3 @@
|
||||
GIT
|
||||
remote: https://github.com/toptal/chewy.git
|
||||
revision: a7d21eb4b0bd7415533ef134bb6d31b2df309701
|
||||
specs:
|
||||
chewy (0.10.1)
|
||||
activesupport (>= 4.0)
|
||||
elasticsearch (>= 2.0.0)
|
||||
elasticsearch-dsl
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
@ -118,6 +109,10 @@ GEM
|
||||
case_transform (0.2)
|
||||
activesupport
|
||||
charlock_holmes (0.7.5)
|
||||
chewy (5.0.0)
|
||||
activesupport (>= 4.0)
|
||||
elasticsearch (>= 2.0.0)
|
||||
elasticsearch-dsl
|
||||
chunky_png (1.3.8)
|
||||
cld3 (3.2.2)
|
||||
ffi (>= 1.1.0, < 1.10.0)
|
||||
@ -634,7 +629,7 @@ DEPENDENCIES
|
||||
capistrano-yarn (~> 2.0)
|
||||
capybara (~> 2.15)
|
||||
charlock_holmes (~> 0.7.5)
|
||||
chewy (~> 0.10)!
|
||||
chewy (~> 5.0)
|
||||
cld3 (~> 3.2.0)
|
||||
climate_control (~> 0.2)
|
||||
devise (~> 4.4)
|
||||
|
57
app/controllers/activitypub/collections_controller.rb
Normal file
57
app/controllers/activitypub/collections_controller.rb
Normal file
@ -0,0 +1,57 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::CollectionsController < Api::BaseController
|
||||
include SignatureVerification
|
||||
|
||||
before_action :set_account
|
||||
before_action :set_size
|
||||
before_action :set_statuses
|
||||
|
||||
def show
|
||||
render json: collection_presenter,
|
||||
serializer: ActivityPub::CollectionSerializer,
|
||||
adapter: ActivityPub::Adapter,
|
||||
content_type: 'application/activity+json',
|
||||
skip_activities: true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find_local!(params[:account_username])
|
||||
end
|
||||
|
||||
def set_statuses
|
||||
@statuses = scope_for_collection.paginate_by_max_id(20, params[:max_id], params[:since_id])
|
||||
@statuses = cache_collection(@statuses, Status)
|
||||
end
|
||||
|
||||
def set_size
|
||||
case params[:id]
|
||||
when 'featured'
|
||||
@account.pinned_statuses.count
|
||||
else
|
||||
raise ActiveRecord::NotFound
|
||||
end
|
||||
end
|
||||
|
||||
def scope_for_collection
|
||||
case params[:id]
|
||||
when 'featured'
|
||||
@account.statuses.permitted_for(@account, signed_request_account).tap do |scope|
|
||||
scope.merge!(@account.pinned_statuses)
|
||||
end
|
||||
else
|
||||
raise ActiveRecord::NotFound
|
||||
end
|
||||
end
|
||||
|
||||
def collection_presenter
|
||||
ActivityPub::CollectionPresenter.new(
|
||||
id: account_collection_url(@account, params[:id]),
|
||||
type: :ordered,
|
||||
size: @size,
|
||||
items: @statuses
|
||||
)
|
||||
end
|
||||
end
|
@ -9,7 +9,7 @@ class ActivityPub::OutboxesController < Api::BaseController
|
||||
@statuses = @account.statuses.permitted_for(@account, signed_request_account).paginate_by_max_id(20, params[:max_id], params[:since_id])
|
||||
@statuses = cache_collection(@statuses, Status)
|
||||
|
||||
render json: outbox_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
|
||||
render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -11,12 +11,18 @@ class Api::V1::Statuses::PinsController < Api::BaseController
|
||||
|
||||
def create
|
||||
StatusPin.create!(account: current_account, status: @status)
|
||||
distribute_add_activity!
|
||||
render json: @status, serializer: REST::StatusSerializer
|
||||
end
|
||||
|
||||
def destroy
|
||||
pin = StatusPin.find_by(account: current_account, status: @status)
|
||||
pin&.destroy!
|
||||
|
||||
if pin
|
||||
pin.destroy!
|
||||
distribute_remove_activity!
|
||||
end
|
||||
|
||||
render json: @status, serializer: REST::StatusSerializer
|
||||
end
|
||||
|
||||
@ -25,4 +31,24 @@ class Api::V1::Statuses::PinsController < Api::BaseController
|
||||
def set_status
|
||||
@status = Status.find(params[:status_id])
|
||||
end
|
||||
|
||||
def distribute_add_activity!
|
||||
json = ActiveModelSerializers::SerializableResource.new(
|
||||
@status,
|
||||
serializer: ActivityPub::AddSerializer,
|
||||
adapter: ActivityPub::Adapter
|
||||
).as_json
|
||||
|
||||
ActivityPub::RawDistributionWorker.perform_async(Oj.dump(json), current_account)
|
||||
end
|
||||
|
||||
def distribute_remove_activity!
|
||||
json = ActiveModelSerializers::SerializableResource.new(
|
||||
@status,
|
||||
serializer: ActivityPub::RemoveSerializer,
|
||||
adapter: ActivityPub::Adapter
|
||||
).as_json
|
||||
|
||||
ActivityPub::RawDistributionWorker.perform_async(Oj.dump(json), current_account)
|
||||
end
|
||||
end
|
||||
|
@ -13,10 +13,9 @@ class Auth::SessionsController < Devise::SessionsController
|
||||
|
||||
def new
|
||||
Devise.omniauth_configs.each do |provider, config|
|
||||
if config.strategy.redirect_at_sign_in
|
||||
return redirect_to(omniauth_authorize_path(resource_name, provider))
|
||||
end
|
||||
return redirect_to(omniauth_authorize_path(resource_name, provider)) if config.strategy.redirect_at_sign_in
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
@ -60,6 +59,14 @@ class Auth::SessionsController < Devise::SessionsController
|
||||
end
|
||||
end
|
||||
|
||||
def after_sign_out_path_for(_resource_or_scope)
|
||||
Devise.omniauth_configs.each_value do |config|
|
||||
return root_path if config.strategy.redirect_at_sign_in
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def two_factor_enabled?
|
||||
find_user.try(:otp_required_for_login?)
|
||||
end
|
||||
|
@ -17,11 +17,7 @@ module Localized
|
||||
end
|
||||
|
||||
def default_locale
|
||||
request_locale || env_locale || I18n.default_locale
|
||||
end
|
||||
|
||||
def env_locale
|
||||
ENV['DEFAULT_LOCALE']
|
||||
request_locale || I18n.default_locale
|
||||
end
|
||||
|
||||
def request_locale
|
||||
@ -29,12 +25,10 @@ module Localized
|
||||
end
|
||||
|
||||
def preferred_locale
|
||||
http_accept_language.preferred_language_from([env_locale]) ||
|
||||
http_accept_language.preferred_language_from(I18n.available_locales)
|
||||
end
|
||||
|
||||
def compatible_locale
|
||||
http_accept_language.compatible_language_from([env_locale]) ||
|
||||
http_accept_language.compatible_language_from(I18n.available_locales)
|
||||
end
|
||||
end
|
||||
|
@ -35,7 +35,8 @@ class HomeController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
redirect_to(default_redirect_path)
|
||||
matches = request.path.match(%r{\A/web/timelines/tag/(?<tag>.+)\z})
|
||||
redirect_to(matches ? tag_path(CGI.unescape(matches[:tag])) : default_redirect_path)
|
||||
end
|
||||
|
||||
def set_pack
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
module InstanceHelper
|
||||
def site_title
|
||||
Setting.site_title.presence || site_hostname
|
||||
Setting.site_title
|
||||
end
|
||||
|
||||
def site_hostname
|
||||
|
@ -8,6 +8,27 @@ module StreamEntriesHelper
|
||||
account.display_name.presence || account.username
|
||||
end
|
||||
|
||||
def account_description(account)
|
||||
prepend_str = [
|
||||
[
|
||||
number_to_human(account.statuses_count, strip_insignificant_zeros: true),
|
||||
t('accounts.posts'),
|
||||
].join(' '),
|
||||
|
||||
[
|
||||
number_to_human(account.following_count, strip_insignificant_zeros: true),
|
||||
t('accounts.following'),
|
||||
].join(' '),
|
||||
|
||||
[
|
||||
number_to_human(account.followers_count, strip_insignificant_zeros: true),
|
||||
t('accounts.followers'),
|
||||
].join(' '),
|
||||
].join(', ')
|
||||
|
||||
[prepend_str, account.note].join(' · ')
|
||||
end
|
||||
|
||||
def stream_link_target
|
||||
embedded_view? ? '_blank' : nil
|
||||
end
|
||||
|
@ -1,6 +1,7 @@
|
||||
import api from '../api';
|
||||
import { throttle } from 'lodash';
|
||||
import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
|
||||
import { tagHistory } from '../settings';
|
||||
import { useEmoji } from './emojis';
|
||||
|
||||
import {
|
||||
@ -27,6 +28,9 @@ export const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO';
|
||||
export const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR';
|
||||
export const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY';
|
||||
export const COMPOSE_SUGGESTION_SELECT = 'COMPOSE_SUGGESTION_SELECT';
|
||||
export const COMPOSE_SUGGESTION_TAGS_UPDATE = 'COMPOSE_SUGGESTION_TAGS_UPDATE';
|
||||
|
||||
export const COMPOSE_TAG_HISTORY_UPDATE = 'COMPOSE_TAG_HISTORY_UPDATE';
|
||||
|
||||
export const COMPOSE_MOUNT = 'COMPOSE_MOUNT';
|
||||
export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT';
|
||||
@ -92,8 +96,9 @@ export function mentionCompose(account, router) {
|
||||
export function submitCompose() {
|
||||
return function (dispatch, getState) {
|
||||
const status = getState().getIn(['compose', 'text'], '');
|
||||
const media = getState().getIn(['compose', 'media_attachments']);
|
||||
|
||||
if (!status || !status.length) {
|
||||
if ((!status || !status.length) && media.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -102,7 +107,7 @@ export function submitCompose() {
|
||||
api(getState).post('/api/v1/statuses', {
|
||||
status,
|
||||
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
|
||||
media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')),
|
||||
media_ids: media.map(item => item.get('id')),
|
||||
sensitive: getState().getIn(['compose', 'sensitive']),
|
||||
spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''),
|
||||
visibility: getState().getIn(['compose', 'privacy']),
|
||||
@ -111,6 +116,7 @@ export function submitCompose() {
|
||||
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
|
||||
},
|
||||
}).then(function (response) {
|
||||
dispatch(insertIntoTagHistory(response.data.tags));
|
||||
dispatch(submitComposeSuccess({ ...response.data }));
|
||||
|
||||
// To make the app more responsive, immediately get the status into the columns
|
||||
@ -273,12 +279,22 @@ const fetchComposeSuggestionsEmojis = (dispatch, getState, token) => {
|
||||
dispatch(readyComposeSuggestionsEmojis(token, results));
|
||||
};
|
||||
|
||||
const fetchComposeSuggestionsTags = (dispatch, getState, token) => {
|
||||
dispatch(updateSuggestionTags(token));
|
||||
};
|
||||
|
||||
export function fetchComposeSuggestions(token) {
|
||||
return (dispatch, getState) => {
|
||||
if (token[0] === ':') {
|
||||
switch (token[0]) {
|
||||
case ':':
|
||||
fetchComposeSuggestionsEmojis(dispatch, getState, token);
|
||||
} else {
|
||||
break;
|
||||
case '#':
|
||||
fetchComposeSuggestionsTags(dispatch, getState, token);
|
||||
break;
|
||||
default:
|
||||
fetchComposeSuggestionsAccounts(dispatch, getState, token);
|
||||
break;
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -308,6 +324,9 @@ export function selectComposeSuggestion(position, token, suggestion) {
|
||||
startPosition = position - 1;
|
||||
|
||||
dispatch(useEmoji(suggestion));
|
||||
} else if (suggestion[0] === '#') {
|
||||
completion = suggestion;
|
||||
startPosition = position - 1;
|
||||
} else {
|
||||
completion = getState().getIn(['accounts', suggestion, 'acct']);
|
||||
startPosition = position;
|
||||
@ -322,6 +341,48 @@ export function selectComposeSuggestion(position, token, suggestion) {
|
||||
};
|
||||
};
|
||||
|
||||
export function updateSuggestionTags(token) {
|
||||
return {
|
||||
type: COMPOSE_SUGGESTION_TAGS_UPDATE,
|
||||
token,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateTagHistory(tags) {
|
||||
return {
|
||||
type: COMPOSE_TAG_HISTORY_UPDATE,
|
||||
tags,
|
||||
};
|
||||
}
|
||||
|
||||
export function hydrateCompose() {
|
||||
return (dispatch, getState) => {
|
||||
const me = getState().getIn(['meta', 'me']);
|
||||
const history = tagHistory.get(me);
|
||||
|
||||
if (history !== null) {
|
||||
dispatch(updateTagHistory(history));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function insertIntoTagHistory(tags) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const oldHistory = state.getIn(['compose', 'tagHistory']);
|
||||
const me = state.getIn(['meta', 'me']);
|
||||
const names = tags.map(({ name }) => name);
|
||||
const intersectedOldHistory = oldHistory.filter(name => !names.includes(name));
|
||||
|
||||
names.push(...intersectedOldHistory.toJS());
|
||||
|
||||
const newHistory = names.slice(0, 1000);
|
||||
|
||||
tagHistory.set(me, newHistory);
|
||||
dispatch(updateTagHistory(newHistory));
|
||||
};
|
||||
}
|
||||
|
||||
export function mountCompose() {
|
||||
return {
|
||||
type: COMPOSE_MOUNT,
|
||||
|
10
app/javascript/mastodon/actions/dropdown_menu.js
Normal file
10
app/javascript/mastodon/actions/dropdown_menu.js
Normal file
@ -0,0 +1,10 @@
|
||||
export const DROPDOWN_MENU_OPEN = 'DROPDOWN_MENU_OPEN';
|
||||
export const DROPDOWN_MENU_CLOSE = 'DROPDOWN_MENU_CLOSE';
|
||||
|
||||
export function openDropdownMenu(id, placement) {
|
||||
return { type: DROPDOWN_MENU_OPEN, id, placement };
|
||||
}
|
||||
|
||||
export function closeDropdownMenu(id) {
|
||||
return { type: DROPDOWN_MENU_CLOSE, id };
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import { Iterable, fromJS } from 'immutable';
|
||||
import { hydrateCompose } from './compose';
|
||||
|
||||
export const STORE_HYDRATE = 'STORE_HYDRATE';
|
||||
export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY';
|
||||
@ -8,10 +9,14 @@ const convertState = rawState =>
|
||||
Iterable.isIndexed(v) ? v.toList() : v.toMap());
|
||||
|
||||
export function hydrateStore(rawState) {
|
||||
return dispatch => {
|
||||
const state = convertState(rawState);
|
||||
|
||||
return {
|
||||
dispatch({
|
||||
type: STORE_HYDRATE,
|
||||
state,
|
||||
});
|
||||
|
||||
dispatch(hydrateCompose());
|
||||
};
|
||||
};
|
||||
|
@ -121,6 +121,7 @@ export const refreshHomeTimeline = () => refreshTimeline('home', '/api/v
|
||||
export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public');
|
||||
export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true });
|
||||
export const refreshAccountTimeline = (accountId, withReplies) => refreshTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies });
|
||||
export const refreshAccountFeaturedTimeline = accountId => refreshTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
|
||||
export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
|
||||
export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
|
||||
export const refreshListTimeline = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`);
|
||||
|
@ -3,6 +3,7 @@ import 'intl/locale-data/jsonp/en';
|
||||
import 'es6-symbol/implement';
|
||||
import includes from 'array-includes';
|
||||
import assign from 'object-assign';
|
||||
import values from 'object.values';
|
||||
import isNaN from 'is-nan';
|
||||
|
||||
if (!Array.prototype.includes) {
|
||||
@ -13,6 +14,10 @@ if (!Object.assign) {
|
||||
Object.assign = assign;
|
||||
}
|
||||
|
||||
if (!Object.values) {
|
||||
values.shim();
|
||||
}
|
||||
|
||||
if (!Number.isNaN) {
|
||||
Number.isNaN = isNaN;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
|
||||
@ -8,10 +9,29 @@ export default class AttachmentList extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
media: ImmutablePropTypes.list.isRequired,
|
||||
compact: PropTypes.bool,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { media } = this.props;
|
||||
const { media, compact } = this.props;
|
||||
|
||||
if (compact) {
|
||||
return (
|
||||
<div className='attachment-list compact'>
|
||||
<ul className='attachment-list__list'>
|
||||
{media.map(attachment => {
|
||||
const displayUrl = attachment.get('remote_url') || attachment.get('url');
|
||||
|
||||
return (
|
||||
<li key={attachment.get('id')}>
|
||||
<a href={displayUrl} target='_blank' rel='noopener'><i className='fa fa-link' /> {filename(displayUrl)}</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='attachment-list'>
|
||||
@ -20,11 +40,15 @@ export default class AttachmentList extends ImmutablePureComponent {
|
||||
</div>
|
||||
|
||||
<ul className='attachment-list__list'>
|
||||
{media.map(attachment => (
|
||||
{media.map(attachment => {
|
||||
const displayUrl = attachment.get('remote_url') || attachment.get('url');
|
||||
|
||||
return (
|
||||
<li key={attachment.get('id')}>
|
||||
<a href={attachment.get('remote_url')} target='_blank' rel='noopener'>{filename(attachment.get('remote_url'))}</a>
|
||||
<a href={displayUrl} target='_blank' rel='noopener'>{filename(displayUrl)}</a>
|
||||
</li>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
@ -20,7 +20,7 @@ const textAtCursorMatchesToken = (str, caretPosition) => {
|
||||
word = str.slice(left, right + caretPosition);
|
||||
}
|
||||
|
||||
if (!word || word.trim().length < 3 || ['@', ':'].indexOf(word[0]) === -1) {
|
||||
if (!word || word.trim().length < 3 || ['@', ':', '#'].indexOf(word[0]) === -1) {
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
@ -170,6 +170,9 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
|
||||
if (typeof suggestion === 'object') {
|
||||
inner = <AutosuggestEmoji emoji={suggestion} />;
|
||||
key = suggestion.id;
|
||||
} else if (suggestion[0] === '#') {
|
||||
inner = suggestion;
|
||||
key = suggestion;
|
||||
} else {
|
||||
inner = <AutosuggestAccountContainer id={suggestion} />;
|
||||
key = suggestion;
|
||||
|
@ -8,6 +8,7 @@ import spring from 'react-motion/lib/spring';
|
||||
import detectPassiveEvents from 'detect-passive-events';
|
||||
|
||||
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false;
|
||||
let id = 0;
|
||||
|
||||
class DropdownMenu extends React.PureComponent {
|
||||
|
||||
@ -29,6 +30,10 @@ class DropdownMenu extends React.PureComponent {
|
||||
placement: 'bottom',
|
||||
};
|
||||
|
||||
state = {
|
||||
mounted: false,
|
||||
};
|
||||
|
||||
handleDocumentClick = e => {
|
||||
if (this.node && !this.node.contains(e.target)) {
|
||||
this.props.onClose();
|
||||
@ -38,6 +43,7 @@ class DropdownMenu extends React.PureComponent {
|
||||
componentDidMount () {
|
||||
document.addEventListener('click', this.handleDocumentClick, false);
|
||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
this.setState({ mounted: true });
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
@ -82,11 +88,15 @@ class DropdownMenu extends React.PureComponent {
|
||||
|
||||
render () {
|
||||
const { items, style, placement, arrowOffsetLeft, arrowOffsetTop } = this.props;
|
||||
const { mounted } = this.state;
|
||||
|
||||
return (
|
||||
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
|
||||
{({ opacity, scaleX, scaleY }) => (
|
||||
<div className='dropdown-menu' style={{ ...style, opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }} ref={this.setRef}>
|
||||
// It should not be transformed when mounting because the resulting
|
||||
// size will be used to determine the coordinate of the menu by
|
||||
// react-overlays
|
||||
<div className='dropdown-menu' style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
|
||||
<div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} />
|
||||
|
||||
<ul>
|
||||
@ -115,8 +125,10 @@ export default class Dropdown extends React.PureComponent {
|
||||
status: ImmutablePropTypes.map,
|
||||
isUserTouching: PropTypes.func,
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
onModalOpen: PropTypes.func,
|
||||
onModalClose: PropTypes.func,
|
||||
onOpen: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
dropdownPlacement: PropTypes.string,
|
||||
openDropdownId: PropTypes.number,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@ -124,37 +136,28 @@ export default class Dropdown extends React.PureComponent {
|
||||
};
|
||||
|
||||
state = {
|
||||
expanded: false,
|
||||
id: id++,
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
if (!this.state.expanded && this.props.isUserTouching() && this.props.onModalOpen) {
|
||||
const { status, items } = this.props;
|
||||
handleClick = ({ target }) => {
|
||||
if (this.state.id === this.props.openDropdownId) {
|
||||
this.handleClose();
|
||||
} else {
|
||||
const { top } = target.getBoundingClientRect();
|
||||
const placement = top * 2 < innerHeight ? 'bottom' : 'top';
|
||||
|
||||
this.props.onModalOpen({
|
||||
status,
|
||||
actions: items,
|
||||
onClick: this.handleItemClick,
|
||||
});
|
||||
|
||||
return;
|
||||
this.props.onOpen(this.state.id, this.handleItemClick, placement);
|
||||
}
|
||||
|
||||
this.setState({ expanded: !this.state.expanded });
|
||||
}
|
||||
|
||||
handleClose = () => {
|
||||
if (this.props.onModalClose) {
|
||||
this.props.onModalClose();
|
||||
}
|
||||
|
||||
this.setState({ expanded: false });
|
||||
this.props.onClose(this.state.id);
|
||||
}
|
||||
|
||||
handleKeyDown = e => {
|
||||
switch(e.key) {
|
||||
case 'Enter':
|
||||
this.handleClick();
|
||||
this.handleClick(e);
|
||||
break;
|
||||
case 'Escape':
|
||||
this.handleClose();
|
||||
@ -186,22 +189,22 @@ export default class Dropdown extends React.PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { icon, items, size, title, disabled } = this.props;
|
||||
const { expanded } = this.state;
|
||||
const { icon, items, size, title, disabled, dropdownPlacement, openDropdownId } = this.props;
|
||||
const open = this.state.id === openDropdownId;
|
||||
|
||||
return (
|
||||
<div onKeyDown={this.handleKeyDown}>
|
||||
<IconButton
|
||||
icon={icon}
|
||||
title={title}
|
||||
active={expanded}
|
||||
active={open}
|
||||
disabled={disabled}
|
||||
size={size}
|
||||
ref={this.setTargetRef}
|
||||
onClick={this.handleClick}
|
||||
/>
|
||||
|
||||
<Overlay show={expanded} placement='bottom' target={this.findTarget}>
|
||||
<Overlay show={open} placement={dropdownPlacement} target={this.findTarget}>
|
||||
<DropdownMenu items={items} onClose={this.handleClose} />
|
||||
</Overlay>
|
||||
</div>
|
||||
|
@ -11,6 +11,7 @@ export default class ExtendedVideoPlayer extends React.PureComponent {
|
||||
time: PropTypes.number,
|
||||
controls: PropTypes.bool.isRequired,
|
||||
muted: PropTypes.bool.isRequired,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
handleLoadedData = () => {
|
||||
@ -31,6 +32,12 @@ export default class ExtendedVideoPlayer extends React.PureComponent {
|
||||
this.video = c;
|
||||
}
|
||||
|
||||
handleClick = e => {
|
||||
e.stopPropagation();
|
||||
const handler = this.props.onClick;
|
||||
if (handler) handler();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { src, muted, controls, alt } = this.props;
|
||||
|
||||
@ -46,6 +53,7 @@ export default class ExtendedVideoPlayer extends React.PureComponent {
|
||||
muted={muted}
|
||||
controls={controls}
|
||||
loop={!controls}
|
||||
onClick={this.handleClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -167,6 +167,14 @@ class Item extends React.PureComponent {
|
||||
vShift = shiftToPoint(widthRatio, (containerHeight * (height / 100)), originalHeight, focusY, true);
|
||||
}
|
||||
|
||||
if (originalWidth > originalHeight) {
|
||||
imageStyle.height = '100%';
|
||||
imageStyle.width = 'auto';
|
||||
} else {
|
||||
imageStyle.height = 'auto';
|
||||
imageStyle.width = '100%';
|
||||
}
|
||||
|
||||
imageStyle.top = vShift;
|
||||
imageStyle.left = hShift;
|
||||
} else {
|
||||
|
@ -12,9 +12,15 @@ export default class Permalink extends React.PureComponent {
|
||||
href: PropTypes.string.isRequired,
|
||||
to: PropTypes.string.isRequired,
|
||||
children: PropTypes.node,
|
||||
onInterceptClick: PropTypes.func,
|
||||
};
|
||||
|
||||
handleClick = (e) => {
|
||||
handleClick = e => {
|
||||
if (this.props.onInterceptClick && this.props.onInterceptClick()) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(this.props.to);
|
||||
@ -22,7 +28,7 @@ export default class Permalink extends React.PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { href, children, className, ...other } = this.props;
|
||||
const { href, children, className, onInterceptClick, ...other } = this.props;
|
||||
|
||||
return (
|
||||
<a target='_blank' href={href} onClick={this.handleClick} {...other} className={`permalink${className ? ' ' + className : ''}`}>
|
||||
|
@ -17,7 +17,7 @@ export default class ScrollableList extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
scrollKey: PropTypes.string.isRequired,
|
||||
onScrollToBottom: PropTypes.func,
|
||||
onLoadMore: PropTypes.func.isRequired,
|
||||
onScrollToTop: PropTypes.func,
|
||||
onScroll: PropTypes.func,
|
||||
trackScroll: PropTypes.bool,
|
||||
@ -45,9 +45,11 @@ export default class ScrollableList extends PureComponent {
|
||||
const offset = scrollHeight - scrollTop - clientHeight;
|
||||
this._oldScrollPosition = scrollHeight - scrollTop;
|
||||
|
||||
if (400 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
|
||||
this.props.onScrollToBottom();
|
||||
} else if (scrollTop < 100 && this.props.onScrollToTop) {
|
||||
if (400 > offset && this.props.onLoadMore && !this.props.isLoading) {
|
||||
this.props.onLoadMore();
|
||||
}
|
||||
|
||||
if (scrollTop < 100 && this.props.onScrollToTop) {
|
||||
this.props.onScrollToTop();
|
||||
} else if (this.props.onScroll) {
|
||||
this.props.onScroll();
|
||||
@ -138,7 +140,7 @@ export default class ScrollableList extends PureComponent {
|
||||
|
||||
handleLoadMore = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.onScrollToBottom();
|
||||
this.props.onLoadMore();
|
||||
}
|
||||
|
||||
_recentlyMoved () {
|
||||
|
@ -7,6 +7,7 @@ import RelativeTimestamp from './relative_timestamp';
|
||||
import DisplayName from './display_name';
|
||||
import StatusContent from './status_content';
|
||||
import StatusActionBar from './status_action_bar';
|
||||
import AttachmentList from './attachment_list';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { MediaGallery, Video } from '../features/ui/util/async-components';
|
||||
@ -138,7 +139,7 @@ export default class Status extends ImmutablePureComponent {
|
||||
let media = null;
|
||||
let statusAvatar, prepend;
|
||||
|
||||
const { hidden } = this.props;
|
||||
const { hidden, featured } = this.props;
|
||||
const { isExpanded } = this.state;
|
||||
|
||||
let { status, account, ...other } = this.props;
|
||||
@ -156,7 +157,14 @@ export default class Status extends ImmutablePureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||
if (featured) {
|
||||
prepend = (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-thumb-tack status__prepend-icon' /></div>
|
||||
<FormattedMessage id='status.pinned' defaultMessage='Pinned toot' />
|
||||
</div>
|
||||
);
|
||||
} else if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||
const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
|
||||
|
||||
prepend = (
|
||||
@ -170,9 +178,14 @@ export default class Status extends ImmutablePureComponent {
|
||||
status = status.get('reblog');
|
||||
}
|
||||
|
||||
if (status.get('media_attachments').size > 0 && !this.props.muted) {
|
||||
if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
|
||||
|
||||
if (status.get('media_attachments').size > 0) {
|
||||
if (this.props.muted || status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
|
||||
media = (
|
||||
<AttachmentList
|
||||
compact
|
||||
media={status.get('media_attachments')}
|
||||
/>
|
||||
);
|
||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
const video = status.getIn(['media_attachments', 0]);
|
||||
|
||||
|
@ -25,6 +25,11 @@ export default class StatusContent extends React.PureComponent {
|
||||
|
||||
_updateStatusLinks () {
|
||||
const node = this.node;
|
||||
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
const links = node.querySelectorAll('a');
|
||||
|
||||
for (var i = 0; i < links.length; ++i) {
|
||||
@ -115,6 +120,10 @@ export default class StatusContent extends React.PureComponent {
|
||||
render () {
|
||||
const { status } = this.props;
|
||||
|
||||
if (status.get('content').length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
|
||||
|
||||
const content = { __html: status.get('contentHtml') };
|
||||
|
@ -11,7 +11,8 @@ export default class StatusList extends ImmutablePureComponent {
|
||||
static propTypes = {
|
||||
scrollKey: PropTypes.string.isRequired,
|
||||
statusIds: ImmutablePropTypes.list.isRequired,
|
||||
onScrollToBottom: PropTypes.func,
|
||||
featuredStatusIds: ImmutablePropTypes.list,
|
||||
onLoadMore: PropTypes.func,
|
||||
onScrollToTop: PropTypes.func,
|
||||
onScroll: PropTypes.func,
|
||||
trackScroll: PropTypes.bool,
|
||||
@ -50,7 +51,7 @@ export default class StatusList extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { statusIds, ...other } = this.props;
|
||||
const { statusIds, featuredStatusIds, ...other } = this.props;
|
||||
const { isLoading, isPartial } = other;
|
||||
|
||||
if (isPartial) {
|
||||
@ -68,8 +69,8 @@ export default class StatusList extends ImmutablePureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
const scrollableContent = (isLoading || statusIds.size > 0) ? (
|
||||
statusIds.map((statusId) => (
|
||||
let scrollableContent = (isLoading || statusIds.size > 0) ? (
|
||||
statusIds.map(statusId => (
|
||||
<StatusContainer
|
||||
key={statusId}
|
||||
id={statusId}
|
||||
@ -79,6 +80,18 @@ export default class StatusList extends ImmutablePureComponent {
|
||||
))
|
||||
) : null;
|
||||
|
||||
if (scrollableContent && featuredStatusIds) {
|
||||
scrollableContent = featuredStatusIds.map(statusId => (
|
||||
<StatusContainer
|
||||
key={`f-${statusId}`}
|
||||
id={statusId}
|
||||
featured
|
||||
onMoveUp={this.handleMoveUp}
|
||||
onMoveDown={this.handleMoveDown}
|
||||
/>
|
||||
)).concat(scrollableContent);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollableList {...other} ref={this.setRef}>
|
||||
{scrollableContent}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { openDropdownMenu, closeDropdownMenu } from '../actions/dropdown_menu';
|
||||
import { openModal, closeModal } from '../actions/modal';
|
||||
import { connect } from 'react-redux';
|
||||
import DropdownMenu from '../components/dropdown_menu';
|
||||
@ -5,12 +6,22 @@ import { isUserTouching } from '../is_mobile';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isModalOpen: state.get('modal').modalType === 'ACTIONS',
|
||||
dropdownPlacement: state.getIn(['dropdown_menu', 'placement']),
|
||||
openDropdownId: state.getIn(['dropdown_menu', 'openId']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
isUserTouching,
|
||||
onModalOpen: props => dispatch(openModal('ACTIONS', props)),
|
||||
onModalClose: () => dispatch(closeModal()),
|
||||
const mapDispatchToProps = (dispatch, { status, items }) => ({
|
||||
onOpen(id, onItemClick, dropdownPlacement) {
|
||||
dispatch(isUserTouching() ? openModal('ACTIONS', {
|
||||
status,
|
||||
actions: items,
|
||||
onClick: onItemClick,
|
||||
}) : openDropdownMenu(id, dropdownPlacement));
|
||||
},
|
||||
onClose(id) {
|
||||
dispatch(closeModal());
|
||||
dispatch(closeDropdownMenu(id));
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DropdownMenu);
|
||||
|
@ -13,6 +13,7 @@ const messages = defineMessages({
|
||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
|
||||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
||||
});
|
||||
|
||||
class Avatar extends ImmutablePureComponent {
|
||||
@ -69,6 +70,7 @@ export default class Header extends ImmutablePureComponent {
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
onFollow: PropTypes.func.isRequired,
|
||||
onBlock: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
@ -80,11 +82,20 @@ export default class Header extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
let info = '';
|
||||
let mutingInfo = '';
|
||||
let actionBtn = '';
|
||||
let lockedIcon = '';
|
||||
|
||||
if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
|
||||
info = <span className='account--follows-info'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>;
|
||||
} else if (me !== account.get('id') && account.getIn(['relationship', 'blocking'])) {
|
||||
info = <span className='account--follows-info'><FormattedMessage id='account.blocked' defaultMessage='Blocked' /></span>;
|
||||
}
|
||||
|
||||
if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) {
|
||||
mutingInfo = <span className='account--muting-info'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>;
|
||||
} else if (me !== account.get('id') && account.getIn(['relationship', 'domain_blocking'])) {
|
||||
mutingInfo = <span className='account--muting-info'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain hidden' /></span>;
|
||||
}
|
||||
|
||||
if (me !== account.get('id')) {
|
||||
@ -100,6 +111,12 @@ export default class Header extends ImmutablePureComponent {
|
||||
<IconButton size={26} icon={account.getIn(['relationship', 'following']) ? 'user-times' : 'user-plus'} active={account.getIn(['relationship', 'following'])} title={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />
|
||||
</div>
|
||||
);
|
||||
} else if (account.getIn(['relationship', 'blocking'])) {
|
||||
actionBtn = (
|
||||
<div className='account--action-button'>
|
||||
<IconButton size={26} icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,6 +141,7 @@ export default class Header extends ImmutablePureComponent {
|
||||
<div className='account__header__content' dangerouslySetInnerHTML={content} />
|
||||
|
||||
{info}
|
||||
{mutingInfo}
|
||||
{actionBtn}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Permalink from '../../../components/permalink';
|
||||
import { displaySensitiveMedia } from '../../../initial_state';
|
||||
|
||||
export default class MediaItem extends ImmutablePureComponent {
|
||||
|
||||
@ -9,8 +10,22 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||
media: ImmutablePropTypes.map.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
visible: !this.props.media.getIn(['status', 'sensitive']) || displaySensitiveMedia,
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
if (!this.state.visible) {
|
||||
this.setState({ visible: true });
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { media } = this.props;
|
||||
const { visible } = this.state;
|
||||
const status = media.get('status');
|
||||
const focusX = media.getIn(['meta', 'focus', 'x']);
|
||||
const focusY = media.getIn(['meta', 'focus', 'y']);
|
||||
@ -18,21 +33,28 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||
const y = ((focusY / -2) + .5) * 100;
|
||||
const style = {};
|
||||
|
||||
let content;
|
||||
let label, icon;
|
||||
|
||||
if (media.get('type') === 'gifv') {
|
||||
content = <span className='media-gallery__gifv__label'>GIF</span>;
|
||||
label = <span className='media-gallery__gifv__label'>GIF</span>;
|
||||
}
|
||||
|
||||
if (!status.get('sensitive')) {
|
||||
if (visible) {
|
||||
style.backgroundImage = `url(${media.get('preview_url')})`;
|
||||
style.backgroundPosition = `${x}% ${y}%`;
|
||||
} else {
|
||||
icon = (
|
||||
<span className='account-gallery__item__icons'>
|
||||
<i className='fa fa-eye-slash' />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='account-gallery__item'>
|
||||
<Permalink to={`/statuses/${status.get('id')}`} href={status.get('url')} style={style}>
|
||||
{content}
|
||||
<Permalink to={`/statuses/${status.get('id')}`} href={status.get('url')} style={style} onInterceptClick={this.handleClick}>
|
||||
{icon}
|
||||
{label}
|
||||
</Permalink>
|
||||
</div>
|
||||
);
|
||||
|
@ -21,6 +21,7 @@ export default class Header extends ImmutablePureComponent {
|
||||
onMute: PropTypes.func.isRequired,
|
||||
onBlockDomain: PropTypes.func.isRequired,
|
||||
onUnblockDomain: PropTypes.func.isRequired,
|
||||
hideTabs: PropTypes.bool,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
@ -68,7 +69,7 @@ export default class Header extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account } = this.props;
|
||||
const { account, hideTabs } = this.props;
|
||||
|
||||
if (account === null) {
|
||||
return <MissingIndicator />;
|
||||
@ -81,6 +82,7 @@ export default class Header extends ImmutablePureComponent {
|
||||
<InnerHeader
|
||||
account={account}
|
||||
onFollow={this.handleFollow}
|
||||
onBlock={this.handleBlock}
|
||||
/>
|
||||
|
||||
<ActionBar
|
||||
@ -94,11 +96,13 @@ export default class Header extends ImmutablePureComponent {
|
||||
onUnblockDomain={this.handleUnblockDomain}
|
||||
/>
|
||||
|
||||
{!hideTabs && (
|
||||
<div className='account__section-headline'>
|
||||
<NavLink exact to={`/accounts/${account.get('id')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink>
|
||||
<NavLink exact to={`/accounts/${account.get('id')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots with replies' /></NavLink>
|
||||
<NavLink exact to={`/accounts/${account.get('id')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { connect } from 'react-redux';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { fetchAccount } from '../../actions/accounts';
|
||||
import { refreshAccountTimeline, expandAccountTimeline } from '../../actions/timelines';
|
||||
import { refreshAccountTimeline, refreshAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines';
|
||||
import StatusList from '../../components/status_list';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import Column from '../ui/components/column';
|
||||
@ -17,6 +17,7 @@ const mapStateToProps = (state, { params: { accountId }, withReplies = false })
|
||||
|
||||
return {
|
||||
statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()),
|
||||
featuredStatusIds: state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], ImmutableList()),
|
||||
isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
|
||||
hasMore: !!state.getIn(['timelines', `account:${path}`, 'next']),
|
||||
};
|
||||
@ -29,31 +30,36 @@ export default class AccountTimeline extends ImmutablePureComponent {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
statusIds: ImmutablePropTypes.list,
|
||||
featuredStatusIds: ImmutablePropTypes.list,
|
||||
isLoading: PropTypes.bool,
|
||||
hasMore: PropTypes.bool,
|
||||
withReplies: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
this.props.dispatch(fetchAccount(this.props.params.accountId));
|
||||
this.props.dispatch(refreshAccountTimeline(this.props.params.accountId, this.props.withReplies));
|
||||
const { params: { accountId }, withReplies } = this.props;
|
||||
|
||||
this.props.dispatch(fetchAccount(accountId));
|
||||
this.props.dispatch(refreshAccountFeaturedTimeline(accountId));
|
||||
this.props.dispatch(refreshAccountTimeline(accountId, withReplies));
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) {
|
||||
this.props.dispatch(fetchAccount(nextProps.params.accountId));
|
||||
this.props.dispatch(refreshAccountFeaturedTimeline(nextProps.params.accountId));
|
||||
this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId, nextProps.params.withReplies));
|
||||
}
|
||||
}
|
||||
|
||||
handleScrollToBottom = () => {
|
||||
handleLoadMore = () => {
|
||||
if (!this.props.isLoading && this.props.hasMore) {
|
||||
this.props.dispatch(expandAccountTimeline(this.props.params.accountId, this.props.withReplies));
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { statusIds, isLoading, hasMore } = this.props;
|
||||
const { statusIds, featuredStatusIds, isLoading, hasMore } = this.props;
|
||||
|
||||
if (!statusIds && isLoading) {
|
||||
return (
|
||||
@ -71,9 +77,10 @@ export default class AccountTimeline extends ImmutablePureComponent {
|
||||
prepend={<HeaderContainer accountId={this.props.params.accountId} />}
|
||||
scrollKey='account_timeline'
|
||||
statusIds={statusIds}
|
||||
featuredStatusIds={featuredStatusIds}
|
||||
isLoading={isLoading}
|
||||
hasMore={hasMore}
|
||||
onScrollToBottom={this.handleScrollToBottom}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
@ -50,6 +50,7 @@ export default class ComposeForm extends ImmutablePureComponent {
|
||||
onPaste: PropTypes.func.isRequired,
|
||||
onPickEmoji: PropTypes.func.isRequired,
|
||||
showSearch: PropTypes.bool,
|
||||
anyMedia: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@ -142,10 +143,10 @@ export default class ComposeForm extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, onPaste, showSearch } = this.props;
|
||||
const { intl, onPaste, showSearch, anyMedia } = this.props;
|
||||
const disabled = this.props.is_submitting;
|
||||
const text = [this.props.spoiler_text, countableText(this.props.text)].join('');
|
||||
|
||||
const disabledButton = disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
|
||||
let publishText = '';
|
||||
|
||||
if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
|
||||
@ -203,7 +204,7 @@ export default class ComposeForm extends ImmutablePureComponent {
|
||||
</div>
|
||||
|
||||
<div className='compose-form__publish'>
|
||||
<div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0)} block /></div>
|
||||
<div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabledButton} block /></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -23,6 +23,7 @@ const mapStateToProps = state => ({
|
||||
is_submitting: state.getIn(['compose', 'is_submitting']),
|
||||
is_uploading: state.getIn(['compose', 'is_uploading']),
|
||||
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
|
||||
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
@ -62,7 +62,7 @@ export default class Favourites extends ImmutablePureComponent {
|
||||
this.column = c;
|
||||
}
|
||||
|
||||
handleScrollToBottom = debounce(() => {
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandFavouritedStatuses());
|
||||
}, 300, { leading: true })
|
||||
|
||||
@ -89,7 +89,7 @@ export default class Favourites extends ImmutablePureComponent {
|
||||
scrollKey={`favourited_statuses-${columnId}`}
|
||||
hasMore={hasMore}
|
||||
isLoading={isLoading}
|
||||
onScrollToBottom={this.handleScrollToBottom}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
@ -80,7 +80,7 @@ export default class Followers extends ImmutablePureComponent {
|
||||
<ScrollContainer scrollKey='followers'>
|
||||
<div className='scrollable' onScroll={this.handleScroll}>
|
||||
<div className='followers'>
|
||||
<HeaderContainer accountId={this.props.params.accountId} />
|
||||
<HeaderContainer accountId={this.props.params.accountId} hideTabs />
|
||||
{accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
|
||||
{loadMore}
|
||||
</div>
|
||||
|
@ -80,7 +80,7 @@ export default class Following extends ImmutablePureComponent {
|
||||
<ScrollContainer scrollKey='following'>
|
||||
<div className='scrollable' onScroll={this.handleScroll}>
|
||||
<div className='following'>
|
||||
<HeaderContainer accountId={this.props.params.accountId} />
|
||||
<HeaderContainer accountId={this.props.params.accountId} hideTabs />
|
||||
{accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
|
||||
{loadMore}
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export default class ClearColumnButton extends React.Component {
|
||||
export default class ClearColumnButton extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
|
@ -50,8 +50,14 @@ export default class Notifications extends React.PureComponent {
|
||||
trackScroll: true,
|
||||
};
|
||||
|
||||
handleScrollToBottom = debounce(() => {
|
||||
componentWillUnmount () {
|
||||
this.handleLoadMore.cancel();
|
||||
this.handleScrollToTop.cancel();
|
||||
this.handleScroll.cancel();
|
||||
this.props.dispatch(scrollTopNotifications(false));
|
||||
}
|
||||
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandNotifications());
|
||||
}, 300, { leading: true });
|
||||
|
||||
@ -136,7 +142,7 @@ export default class Notifications extends React.PureComponent {
|
||||
isLoading={isLoading}
|
||||
hasMore={hasMore}
|
||||
emptyMessage={emptyMessage}
|
||||
onScrollToBottom={this.handleScrollToBottom}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
onScrollToTop={this.handleScrollToTop}
|
||||
onScroll={this.handleScroll}
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
|
@ -2,6 +2,10 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Toggle from 'react-toggle';
|
||||
import noop from 'lodash/noop';
|
||||
import StatusContent from '../../../components/status_content';
|
||||
import { MediaGallery, Video } from '../../ui/util/async-components';
|
||||
import Bundle from '../../ui/components/bundle';
|
||||
|
||||
export default class StatusCheckBox extends React.PureComponent {
|
||||
|
||||
@ -14,18 +18,48 @@ export default class StatusCheckBox extends React.PureComponent {
|
||||
|
||||
render () {
|
||||
const { status, checked, onToggle, disabled } = this.props;
|
||||
const content = { __html: status.get('contentHtml') };
|
||||
let media = null;
|
||||
|
||||
if (status.get('reblog')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (status.get('media_attachments').size > 0) {
|
||||
if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
|
||||
|
||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
const video = status.getIn(['media_attachments', 0]);
|
||||
|
||||
media = (
|
||||
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
|
||||
{Component => (
|
||||
<Component
|
||||
preview={video.get('preview_url')}
|
||||
src={video.get('url')}
|
||||
width={239}
|
||||
height={110}
|
||||
inline
|
||||
sensitive={status.get('sensitive')}
|
||||
onOpenVideo={noop}
|
||||
/>
|
||||
)}
|
||||
</Bundle>
|
||||
);
|
||||
} else {
|
||||
media = (
|
||||
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
|
||||
{Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={noop} />}
|
||||
</Bundle>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='status-check-box'>
|
||||
<div
|
||||
className='status__content'
|
||||
dangerouslySetInnerHTML={content}
|
||||
/>
|
||||
<div className='status-check-box__status'>
|
||||
<StatusContent status={status} />
|
||||
{media}
|
||||
</div>
|
||||
|
||||
<div className='status-check-box-toggle'>
|
||||
<Toggle checked={checked} onChange={onToggle} disabled={disabled} />
|
||||
|
@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
||||
const emptyComponent = () => null;
|
||||
const noop = () => { };
|
||||
|
||||
class Bundle extends React.Component {
|
||||
class Bundle extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
fetchComponent: PropTypes.func.isRequired,
|
||||
|
@ -13,7 +13,7 @@ const messages = defineMessages({
|
||||
retry: { id: 'bundle_column_error.retry', defaultMessage: 'Try again' },
|
||||
});
|
||||
|
||||
class BundleColumnError extends React.Component {
|
||||
class BundleColumnError extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
onRetry: PropTypes.func.isRequired,
|
||||
|
@ -10,7 +10,7 @@ const messages = defineMessages({
|
||||
close: { id: 'bundle_modal_error.close', defaultMessage: 'Close' },
|
||||
});
|
||||
|
||||
class BundleModalError extends React.Component {
|
||||
class BundleModalError extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
onRetry: PropTypes.func.isRequired,
|
||||
|
@ -103,8 +103,8 @@ export default class FocalPointModal extends ImmutablePureComponent {
|
||||
const height = media.getIn(['meta', 'original', 'height']) || null;
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal media-modal'>
|
||||
<div className={classNames('media-modal__content focal-point', { dragging })} ref={this.setRef}>
|
||||
<div className='modal-root__modal video-modal'>
|
||||
<div className={classNames('focal-point', { dragging })} ref={this.setRef}>
|
||||
<ImageLoader
|
||||
previewSrc={media.get('preview_url')}
|
||||
src={media.get('url')}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import ZoomableImage from './zoomable_image';
|
||||
|
||||
export default class ImageLoader extends React.PureComponent {
|
||||
|
||||
@ -10,6 +11,7 @@ export default class ImageLoader extends React.PureComponent {
|
||||
previewSrc: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
onClick: PropTypes.func,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
@ -24,6 +26,7 @@ export default class ImageLoader extends React.PureComponent {
|
||||
}
|
||||
|
||||
removers = [];
|
||||
canvas = null;
|
||||
|
||||
get canvasContext() {
|
||||
if (!this.canvas) {
|
||||
@ -43,6 +46,10 @@ export default class ImageLoader extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.removeEventListeners();
|
||||
}
|
||||
|
||||
loadImage (props) {
|
||||
this.removeEventListeners();
|
||||
this.setState({ loading: true, error: false });
|
||||
@ -118,7 +125,7 @@ export default class ImageLoader extends React.PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { alt, src, width, height } = this.props;
|
||||
const { alt, src, width, height, onClick } = this.props;
|
||||
const { loading } = this.state;
|
||||
|
||||
const className = classNames('image-loader', {
|
||||
@ -128,22 +135,19 @@ export default class ImageLoader extends React.PureComponent {
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{loading ? (
|
||||
<canvas
|
||||
className='image-loader__preview-canvas'
|
||||
width={width}
|
||||
height={height}
|
||||
ref={this.setCanvasRef}
|
||||
style={{ opacity: loading ? 1 : 0 }}
|
||||
/>
|
||||
|
||||
{!loading && (
|
||||
<img
|
||||
alt={alt}
|
||||
className='image-loader__img'
|
||||
src={src}
|
||||
width={width}
|
||||
height={height}
|
||||
/>
|
||||
) : (
|
||||
<ZoomableImage
|
||||
alt={alt}
|
||||
src={src}
|
||||
onClick={onClick}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@ -3,6 +3,7 @@ import ReactSwipeableViews from 'react-swipeable-views';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import ExtendedVideoPlayer from '../../../components/extended_video_player';
|
||||
import classNames from 'classnames';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
@ -26,6 +27,7 @@ export default class MediaModal extends ImmutablePureComponent {
|
||||
|
||||
state = {
|
||||
index: null,
|
||||
navigationHidden: false,
|
||||
};
|
||||
|
||||
handleSwipe = (index) => {
|
||||
@ -68,14 +70,21 @@ export default class MediaModal extends ImmutablePureComponent {
|
||||
return this.state.index !== null ? this.state.index : this.props.index;
|
||||
}
|
||||
|
||||
toggleNavigation = () => {
|
||||
this.setState(prevState => ({
|
||||
navigationHidden: !prevState.navigationHidden,
|
||||
}));
|
||||
};
|
||||
|
||||
render () {
|
||||
const { media, intl, onClose } = this.props;
|
||||
const { navigationHidden } = this.state;
|
||||
|
||||
const index = this.getIndex();
|
||||
let pagination = [];
|
||||
|
||||
const leftNav = media.size > 1 && <button tabIndex='0' className='modal-container__nav modal-container__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><i className='fa fa-fw fa-chevron-left' /></button>;
|
||||
const rightNav = media.size > 1 && <button tabIndex='0' className='modal-container__nav modal-container__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><i className='fa fa-fw fa-chevron-right' /></button>;
|
||||
const leftNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><i className='fa fa-fw fa-chevron-left' /></button>;
|
||||
const rightNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><i className='fa fa-fw fa-chevron-right' /></button>;
|
||||
|
||||
if (media.size > 1) {
|
||||
pagination = media.map((item, i) => {
|
||||
@ -92,9 +101,30 @@ export default class MediaModal extends ImmutablePureComponent {
|
||||
const height = image.getIn(['meta', 'original', 'height']) || null;
|
||||
|
||||
if (image.get('type') === 'image') {
|
||||
return <ImageLoader previewSrc={image.get('preview_url')} src={image.get('url')} width={width} height={height} alt={image.get('description')} key={image.get('url')} />;
|
||||
return (
|
||||
<ImageLoader
|
||||
previewSrc={image.get('preview_url')}
|
||||
src={image.get('url')}
|
||||
width={width}
|
||||
height={height}
|
||||
alt={image.get('description')}
|
||||
key={image.get('url')}
|
||||
onClick={this.toggleNavigation}
|
||||
/>
|
||||
);
|
||||
} else if (image.get('type') === 'gifv') {
|
||||
return <ExtendedVideoPlayer src={image.get('url')} muted controls={false} width={width} height={height} key={image.get('preview_url')} alt={image.get('description')} />;
|
||||
return (
|
||||
<ExtendedVideoPlayer
|
||||
src={image.get('url')}
|
||||
muted
|
||||
controls={false}
|
||||
width={width}
|
||||
height={height}
|
||||
key={image.get('preview_url')}
|
||||
alt={image.get('description')}
|
||||
onClick={this.toggleNavigation}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -104,21 +134,43 @@ export default class MediaModal extends ImmutablePureComponent {
|
||||
alignItems: 'center', // center vertically
|
||||
};
|
||||
|
||||
const navigationClassName = classNames('media-modal__navigation', {
|
||||
'media-modal__navigation--hidden': navigationHidden,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal media-modal'>
|
||||
{leftNav}
|
||||
|
||||
<div
|
||||
className='media-modal__closer'
|
||||
role='presentation'
|
||||
onClick={onClose}
|
||||
>
|
||||
<div className='media-modal__content'>
|
||||
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
|
||||
<ReactSwipeableViews containerStyle={containerStyle} onChangeIndex={this.handleSwipe} index={index}>
|
||||
<ReactSwipeableViews
|
||||
style={{
|
||||
// you can't use 100vh, because the viewport height is taller
|
||||
// than the visible part of the document in some mobile
|
||||
// browsers when it's address bar is visible.
|
||||
// https://developers.google.com/web/updates/2016/12/url-bar-resizing
|
||||
height: `${document.body.clientHeight}px`,
|
||||
}}
|
||||
containerStyle={containerStyle}
|
||||
onChangeIndex={this.handleSwipe}
|
||||
onSwitching={this.handleSwitching}
|
||||
index={index}
|
||||
>
|
||||
{content}
|
||||
</ReactSwipeableViews>
|
||||
</div>
|
||||
</div>
|
||||
<div className={navigationClassName}>
|
||||
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={40} />
|
||||
{leftNav}
|
||||
{rightNav}
|
||||
<ul className='media-modal__pagination'>
|
||||
{pagination}
|
||||
</ul>
|
||||
|
||||
{rightNav}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { NavLink, withRouter } from 'react-router-dom';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { debounce } from 'lodash';
|
||||
import { isUserTouching } from '../../../is_mobile';
|
||||
@ -24,14 +24,12 @@ export function getLink (index) {
|
||||
}
|
||||
|
||||
@injectIntl
|
||||
export default class TabsBar extends React.Component {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object.isRequired,
|
||||
}
|
||||
@withRouter
|
||||
export default class TabsBar extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
setRef = ref => {
|
||||
@ -59,7 +57,7 @@ export default class TabsBar extends React.Component {
|
||||
|
||||
const listener = debounce(() => {
|
||||
nextTab.removeEventListener('transitionend', listener);
|
||||
this.context.router.history.push(to);
|
||||
this.props.history.push(to);
|
||||
}, 50);
|
||||
|
||||
nextTab.addEventListener('transitionend', listener);
|
||||
|
@ -16,7 +16,7 @@ export default class VideoModal extends ImmutablePureComponent {
|
||||
const { media, time, onClose } = this.props;
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal media-modal'>
|
||||
<div className='modal-root__modal video-modal'>
|
||||
<div>
|
||||
<Video
|
||||
preview={media.get('preview_url')}
|
||||
|
151
app/javascript/mastodon/features/ui/components/zoomable_image.js
Normal file
151
app/javascript/mastodon/features/ui/components/zoomable_image.js
Normal file
@ -0,0 +1,151 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const MIN_SCALE = 1;
|
||||
const MAX_SCALE = 4;
|
||||
|
||||
const getMidpoint = (p1, p2) => ({
|
||||
x: (p1.clientX + p2.clientX) / 2,
|
||||
y: (p1.clientY + p2.clientY) / 2,
|
||||
});
|
||||
|
||||
const getDistance = (p1, p2) =>
|
||||
Math.sqrt(Math.pow(p1.clientX - p2.clientX, 2) + Math.pow(p1.clientY - p2.clientY, 2));
|
||||
|
||||
const clamp = (min, max, value) => Math.min(max, Math.max(min, value));
|
||||
|
||||
export default class ZoomableImage extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
alt: PropTypes.string,
|
||||
src: PropTypes.string.isRequired,
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
onClick: PropTypes.func,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
alt: '',
|
||||
width: null,
|
||||
height: null,
|
||||
};
|
||||
|
||||
state = {
|
||||
scale: MIN_SCALE,
|
||||
}
|
||||
|
||||
removers = [];
|
||||
container = null;
|
||||
image = null;
|
||||
lastTouchEndTime = 0;
|
||||
lastDistance = 0;
|
||||
|
||||
componentDidMount () {
|
||||
let handler = this.handleTouchStart;
|
||||
this.container.addEventListener('touchstart', handler);
|
||||
this.removers.push(() => this.container.removeEventListener('touchstart', handler));
|
||||
handler = this.handleTouchMove;
|
||||
// on Chrome 56+, touch event listeners will default to passive
|
||||
// https://www.chromestatus.com/features/5093566007214080
|
||||
this.container.addEventListener('touchmove', handler, { passive: false });
|
||||
this.removers.push(() => this.container.removeEventListener('touchend', handler));
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.removeEventListeners();
|
||||
}
|
||||
|
||||
removeEventListeners () {
|
||||
this.removers.forEach(listeners => listeners());
|
||||
this.removers = [];
|
||||
}
|
||||
|
||||
handleTouchStart = e => {
|
||||
if (e.touches.length !== 2) return;
|
||||
|
||||
this.lastDistance = getDistance(...e.touches);
|
||||
}
|
||||
|
||||
handleTouchMove = e => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = this.container;
|
||||
if (e.touches.length === 1 && scrollTop !== scrollHeight - clientHeight) {
|
||||
// prevent propagating event to MediaModal
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
if (e.touches.length !== 2) return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const distance = getDistance(...e.touches);
|
||||
const midpoint = getMidpoint(...e.touches);
|
||||
const scale = clamp(MIN_SCALE, MAX_SCALE, this.state.scale * distance / this.lastDistance);
|
||||
|
||||
this.zoom(scale, midpoint);
|
||||
|
||||
this.lastMidpoint = midpoint;
|
||||
this.lastDistance = distance;
|
||||
}
|
||||
|
||||
zoom(nextScale, midpoint) {
|
||||
const { scale } = this.state;
|
||||
const { scrollLeft, scrollTop } = this.container;
|
||||
|
||||
// math memo:
|
||||
// x = (scrollLeft + midpoint.x) / scrollWidth
|
||||
// x' = (nextScrollLeft + midpoint.x) / nextScrollWidth
|
||||
// scrollWidth = clientWidth * scale
|
||||
// scrollWidth' = clientWidth * nextScale
|
||||
// Solve x = x' for nextScrollLeft
|
||||
const nextScrollLeft = (scrollLeft + midpoint.x) * nextScale / scale - midpoint.x;
|
||||
const nextScrollTop = (scrollTop + midpoint.y) * nextScale / scale - midpoint.y;
|
||||
|
||||
this.setState({ scale: nextScale }, () => {
|
||||
this.container.scrollLeft = nextScrollLeft;
|
||||
this.container.scrollTop = nextScrollTop;
|
||||
});
|
||||
}
|
||||
|
||||
handleClick = e => {
|
||||
// don't propagate event to MediaModal
|
||||
e.stopPropagation();
|
||||
const handler = this.props.onClick;
|
||||
if (handler) handler();
|
||||
}
|
||||
|
||||
setContainerRef = c => {
|
||||
this.container = c;
|
||||
}
|
||||
|
||||
setImageRef = c => {
|
||||
this.image = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { alt, src } = this.props;
|
||||
const { scale } = this.state;
|
||||
const overflow = scale === 1 ? 'hidden' : 'scroll';
|
||||
|
||||
return (
|
||||
<div
|
||||
className='zoomable-image'
|
||||
ref={this.setContainerRef}
|
||||
style={{ overflow }}
|
||||
>
|
||||
<img
|
||||
role='presentation'
|
||||
ref={this.setImageRef}
|
||||
alt={alt}
|
||||
src={src}
|
||||
style={{
|
||||
transform: `scale(${scale})`,
|
||||
transformOrigin: '0 0',
|
||||
}}
|
||||
onClick={this.handleClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -56,10 +56,7 @@ const makeMapStateToProps = () => {
|
||||
|
||||
const mapDispatchToProps = (dispatch, { timelineId, loadMore }) => ({
|
||||
|
||||
onScrollToBottom: debounce(() => {
|
||||
dispatch(scrollTopTimeline(timelineId, false));
|
||||
loadMore();
|
||||
}, 300, { leading: true }),
|
||||
onLoadMore: debounce(loadMore, 300, { leading: true }),
|
||||
|
||||
onScrollToTop: debounce(() => {
|
||||
dispatch(scrollTopTimeline(timelineId, true));
|
||||
|
@ -1,3 +1,4 @@
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import NotificationsContainer from './containers/notifications_container';
|
||||
import PropTypes from 'prop-types';
|
||||
@ -55,6 +56,7 @@ const messages = defineMessages({
|
||||
const mapStateToProps = state => ({
|
||||
isComposing: state.getIn(['compose', 'is_composing']),
|
||||
hasComposingText: state.getIn(['compose', 'text']) !== '',
|
||||
dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
|
||||
});
|
||||
|
||||
const keyMap = {
|
||||
@ -84,10 +86,93 @@ const keyMap = {
|
||||
goToMuted: 'g m',
|
||||
};
|
||||
|
||||
class SwitchingColumnsArea extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
location: PropTypes.object,
|
||||
onLayoutChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
mobile: isMobile(window.innerWidth),
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
window.addEventListener('resize', this.handleResize, { passive: true });
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
|
||||
this.node.handleChildrenContentChange();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
}
|
||||
|
||||
handleResize = debounce(() => {
|
||||
// The cached heights are no longer accurate, invalidate
|
||||
this.props.onLayoutChange();
|
||||
|
||||
this.setState({ mobile: isMobile(window.innerWidth) });
|
||||
}, 500, {
|
||||
trailing: true,
|
||||
});
|
||||
|
||||
setRef = c => {
|
||||
this.node = c.getWrappedInstance().getWrappedInstance();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children } = this.props;
|
||||
const { mobile } = this.state;
|
||||
|
||||
return (
|
||||
<ColumnsAreaContainer ref={this.setRef} singleColumn={mobile}>
|
||||
<WrappedSwitch>
|
||||
<Redirect from='/' to='/getting-started' exact />
|
||||
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
||||
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
||||
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
|
||||
|
||||
<WrappedRoute path='/notifications' component={Notifications} content={children} />
|
||||
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
|
||||
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
|
||||
|
||||
<WrappedRoute path='/statuses/new' component={Compose} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
|
||||
|
||||
<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
|
||||
<WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
|
||||
<WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
|
||||
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
|
||||
<WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />
|
||||
|
||||
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
|
||||
<WrappedRoute path='/blocks' component={Blocks} content={children} />
|
||||
<WrappedRoute path='/mutes' component={Mutes} content={children} />
|
||||
<WrappedRoute path='/lists' component={Lists} content={children} />
|
||||
|
||||
<WrappedRoute component={GenericNotFound} content={children} />
|
||||
</WrappedSwitch>
|
||||
</ColumnsAreaContainer>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@connect(mapStateToProps)
|
||||
@injectIntl
|
||||
@withRouter
|
||||
export default class UI extends React.Component {
|
||||
export default class UI extends React.PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object.isRequired,
|
||||
@ -100,10 +185,10 @@ export default class UI extends React.Component {
|
||||
hasComposingText: PropTypes.bool,
|
||||
location: PropTypes.object,
|
||||
intl: PropTypes.object.isRequired,
|
||||
dropdownMenuIsOpen: PropTypes.bool,
|
||||
};
|
||||
|
||||
state = {
|
||||
width: window.innerWidth,
|
||||
draggingOver: false,
|
||||
};
|
||||
|
||||
@ -118,14 +203,10 @@ export default class UI extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleResize = debounce(() => {
|
||||
handleLayoutChange = () => {
|
||||
// The cached heights are no longer accurate, invalidate
|
||||
this.props.dispatch(clearHeight());
|
||||
|
||||
this.setState({ width: window.innerWidth });
|
||||
}, 500, {
|
||||
trailing: true,
|
||||
});
|
||||
}
|
||||
|
||||
handleDragEnter = (e) => {
|
||||
e.preventDefault();
|
||||
@ -193,7 +274,6 @@ export default class UI extends React.Component {
|
||||
|
||||
componentWillMount () {
|
||||
window.addEventListener('beforeunload', this.handleBeforeUnload, false);
|
||||
window.addEventListener('resize', this.handleResize, { passive: true });
|
||||
document.addEventListener('dragenter', this.handleDragEnter, false);
|
||||
document.addEventListener('dragover', this.handleDragOver, false);
|
||||
document.addEventListener('drop', this.handleDrop, false);
|
||||
@ -214,28 +294,8 @@ export default class UI extends React.Component {
|
||||
};
|
||||
}
|
||||
|
||||
shouldComponentUpdate (nextProps) {
|
||||
if (nextProps.isComposing !== this.props.isComposing) {
|
||||
// Avoid expensive update just to toggle a class
|
||||
this.node.classList.toggle('is-composing', nextProps.isComposing);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Why isn't this working?!?
|
||||
// return super.shouldComponentUpdate(nextProps, nextState);
|
||||
return true;
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
|
||||
this.columnsAreaNode.handleChildrenContentChange();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('beforeunload', this.handleBeforeUnload);
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
document.removeEventListener('dragenter', this.handleDragEnter);
|
||||
document.removeEventListener('dragover', this.handleDragOver);
|
||||
document.removeEventListener('drop', this.handleDrop);
|
||||
@ -247,10 +307,6 @@ export default class UI extends React.Component {
|
||||
this.node = c;
|
||||
}
|
||||
|
||||
setColumnsAreaRef = c => {
|
||||
this.columnsAreaNode = c.getWrappedInstance().getWrappedInstance();
|
||||
}
|
||||
|
||||
handleHotkeyNew = e => {
|
||||
e.preventDefault();
|
||||
|
||||
@ -350,8 +406,8 @@ export default class UI extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { width, draggingOver } = this.state;
|
||||
const { children } = this.props;
|
||||
const { draggingOver } = this.state;
|
||||
const { children, isComposing, location, dropdownMenuIsOpen } = this.props;
|
||||
|
||||
const handlers = {
|
||||
help: this.handleHotkeyToggleHelp,
|
||||
@ -374,43 +430,12 @@ export default class UI extends React.Component {
|
||||
|
||||
return (
|
||||
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef}>
|
||||
<div className='ui' ref={this.setRef}>
|
||||
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef} style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}>
|
||||
<TabsBar />
|
||||
|
||||
<ColumnsAreaContainer ref={this.setColumnsAreaRef} singleColumn={isMobile(width)}>
|
||||
<WrappedSwitch>
|
||||
<Redirect from='/' to='/getting-started' exact />
|
||||
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
||||
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
||||
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
|
||||
|
||||
<WrappedRoute path='/notifications' component={Notifications} content={children} />
|
||||
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
|
||||
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
|
||||
|
||||
<WrappedRoute path='/statuses/new' component={Compose} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
|
||||
|
||||
<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
|
||||
<WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
|
||||
<WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
|
||||
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
|
||||
<WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />
|
||||
|
||||
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
|
||||
<WrappedRoute path='/blocks' component={Blocks} content={children} />
|
||||
<WrappedRoute path='/mutes' component={Mutes} content={children} />
|
||||
<WrappedRoute path='/lists' component={Lists} content={children} />
|
||||
|
||||
<WrappedRoute component={GenericNotFound} content={children} />
|
||||
</WrappedSwitch>
|
||||
</ColumnsAreaContainer>
|
||||
<SwitchingColumnsArea location={location} onLayoutChange={this.handleLayoutChange}>
|
||||
{children}
|
||||
</SwitchingColumnsArea>
|
||||
|
||||
<NotificationsContainer />
|
||||
<LoadingBarContainer className='loading-bar' />
|
||||
|
@ -14,6 +14,7 @@ function loadPolyfills() {
|
||||
const needsBasePolyfills = !(
|
||||
window.Intl &&
|
||||
Object.assign &&
|
||||
Object.values &&
|
||||
Number.isNaN &&
|
||||
window.Symbol &&
|
||||
Array.prototype.includes
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "حظر @{name}",
|
||||
"account.block_domain": "إخفاء كل شيئ قادم من إسم النطاق {domain}",
|
||||
"account.blocked": "محظور",
|
||||
"account.disclaimer_full": "قد لا تعكس المعلومات أدناه الملف الشخصي الكامل للمستخدم.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "تعديل الملف الشخصي",
|
||||
"account.follow": "تابِع",
|
||||
"account.followers": "المتابعون",
|
||||
@ -13,8 +15,9 @@
|
||||
"account.moved_to": "{name} إنتقل إلى :",
|
||||
"account.mute": "أكتم @{name}",
|
||||
"account.mute_notifications": "كتم إخطارات @{name}",
|
||||
"account.muted": "مكتوم",
|
||||
"account.posts": "التبويقات",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.posts_with_replies": "تبويقات تحتوي على رُدود",
|
||||
"account.report": "أبلغ عن @{name}",
|
||||
"account.requested": "في انتظار الموافقة",
|
||||
"account.share": "مشاركة @{name}'s profile",
|
||||
@ -208,21 +211,22 @@
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "إلغاء",
|
||||
"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.forward": "التحويل إلى {target}",
|
||||
"report.forward_hint": "هذا الحساب ينتمي إلى خادوم آخَر. هل تودّ إرسال نسخة مجهولة مِن التقرير إلى هنالك أيضًا ؟",
|
||||
"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.placeholder": "تعليقات إضافية",
|
||||
"report.submit": "إرسال",
|
||||
"report.target": "إبلاغ",
|
||||
"search.placeholder": "ابحث",
|
||||
"search_popout.search_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": "وسم",
|
||||
"search_popout.tips.status": "حالة",
|
||||
"search_popout.tips.text": "جملة قصيرة تُمكّنُك من عرض أسماء و حسابات و كلمات رمزية",
|
||||
"search_popout.tips.user": "مستخدِم",
|
||||
"search_results.accounts": "People",
|
||||
"search_results.hashtags": "Hashtags",
|
||||
"search_results.statuses": "Toots",
|
||||
"search_results.accounts": "أشخاص",
|
||||
"search_results.hashtags": "الوُسوم",
|
||||
"search_results.statuses": "التبويقات",
|
||||
"search_results.total": "{count, number} {count, plural, one {result} و {results}}",
|
||||
"standalone.public_title": "نظرة على ...",
|
||||
"status.block": "Block @{name}",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "كتم المحادثة",
|
||||
"status.open": "وسع هذه المشاركة",
|
||||
"status.pin": "تدبيس على الملف الشخصي",
|
||||
"status.pinned": "تبويق مثبَّت",
|
||||
"status.reblog": "رَقِّي",
|
||||
"status.reblogged_by": "{name} رقى",
|
||||
"status.reply": "ردّ",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "أظهر المزيد",
|
||||
"status.unmute_conversation": "فك الكتم عن المحادثة",
|
||||
"status.unpin": "فك التدبيس من الملف الشخصي",
|
||||
"tabs_bar.compose": "تحرير",
|
||||
"tabs_bar.federated_timeline": "الموحَّد",
|
||||
"tabs_bar.home": "الرئيسية",
|
||||
"tabs_bar.local_timeline": "المحلي",
|
||||
@ -259,7 +263,7 @@
|
||||
"upload_area.title": "إسحب ثم أفلت للرفع",
|
||||
"upload_button.label": "إضافة وسائط",
|
||||
"upload_form.description": "وصف للمعاقين بصريا",
|
||||
"upload_form.focus": "Crop",
|
||||
"upload_form.focus": "قص",
|
||||
"upload_form.undo": "إلغاء",
|
||||
"upload_progress.label": "يرفع...",
|
||||
"video.close": "إغلاق الفيديو",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Блокирай",
|
||||
"account.block_domain": "Hide everything from {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Редактирай профила си",
|
||||
"account.follow": "Последвай",
|
||||
"account.followers": "Последователи",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} has moved to:",
|
||||
"account.mute": "Mute @{name}",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Публикации",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Report @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Reporting",
|
||||
"search.placeholder": "Търсене",
|
||||
"search_popout.search_format": "Advanced search 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.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Expand this status",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Споделяне",
|
||||
"status.reblogged_by": "{name} сподели",
|
||||
"status.reply": "Отговор",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Show more",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"tabs_bar.compose": "Съставяне",
|
||||
"tabs_bar.federated_timeline": "Federated",
|
||||
"tabs_bar.home": "Начало",
|
||||
"tabs_bar.local_timeline": "Local",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Bloca @{name}",
|
||||
"account.block_domain": "Amaga-ho tot de {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "La informació següent pot reflectir incompleta el perfil de l'usuari.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Edita el perfil",
|
||||
"account.follow": "Segueix",
|
||||
"account.followers": "Seguidors",
|
||||
@ -13,8 +15,9 @@
|
||||
"account.moved_to": "{name} s'ha mogut a:",
|
||||
"account.mute": "Silencia @{name}",
|
||||
"account.mute_notifications": "Notificacions desactivades de @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Toots",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.posts_with_replies": "Toots amb respostes",
|
||||
"account.report": "Informe @{name}",
|
||||
"account.requested": "Esperant aprovació. Clic per a cancel·lar la petició de seguiment",
|
||||
"account.share": "Comparteix el perfil de @{name}",
|
||||
@ -208,20 +211,21 @@
|
||||
"relative_time.minutes": "fa {number} minuts",
|
||||
"relative_time.seconds": "fa {number} segons",
|
||||
"reply_indicator.cancel": "Cancel·lar",
|
||||
"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": "Reenvia a {target}",
|
||||
"report.forward_hint": "Aquest compte és d'un altre servidor. Enviar-hi també una copia anònima del informe?",
|
||||
"report.hint": "El informe s'enviarà als moderadors de la teva instància. Pots explicar perquè vols informar d'aquest compte aquí:",
|
||||
"report.placeholder": "Comentaris addicionals",
|
||||
"report.submit": "Enviar",
|
||||
"report.target": "Informes",
|
||||
"search.placeholder": "Cercar",
|
||||
"search_popout.search_format": "Format de cerca avançada",
|
||||
"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": "etiqueta",
|
||||
"search_popout.tips.status": "status",
|
||||
"search_popout.tips.text": "El text simple retorna coincidències amb els noms de visualització, els noms d'usuari i els hashtags",
|
||||
"search_popout.tips.user": "usuari",
|
||||
"search_results.accounts": "People",
|
||||
"search_results.hashtags": "Hashtags",
|
||||
"search_results.accounts": "Gent",
|
||||
"search_results.hashtags": "Etiquetes",
|
||||
"search_results.statuses": "Toots",
|
||||
"search_results.total": "{count, number} {count, plural, un {result} altres {results}}",
|
||||
"standalone.public_title": "Una mirada a l'interior ...",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Silenciar conversació",
|
||||
"status.open": "Ampliar aquest estat",
|
||||
"status.pin": "Fixat en el perfil",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Impuls",
|
||||
"status.reblogged_by": "{name} ha retootejat",
|
||||
"status.reply": "Respondre",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Mostra més",
|
||||
"status.unmute_conversation": "Activar conversació",
|
||||
"status.unpin": "Deslliga del perfil",
|
||||
"tabs_bar.compose": "Compondre",
|
||||
"tabs_bar.federated_timeline": "Federada",
|
||||
"tabs_bar.home": "Inici",
|
||||
"tabs_bar.local_timeline": "Local",
|
||||
@ -259,7 +263,7 @@
|
||||
"upload_area.title": "Arrossega i deixa anar per carregar",
|
||||
"upload_button.label": "Afegir multimèdia",
|
||||
"upload_form.description": "Descriure els problemes visuals",
|
||||
"upload_form.focus": "Crop",
|
||||
"upload_form.focus": "Retallar",
|
||||
"upload_form.undo": "Desfer",
|
||||
"upload_progress.label": "Pujant...",
|
||||
"video.close": "Tancar el vídeo",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "@{name} blocken",
|
||||
"account.block_domain": "Alles von {domain} verstecken",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Das Profil wird möglicherweise unvollständig wiedergegeben.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Profil bearbeiten",
|
||||
"account.follow": "Folgen",
|
||||
"account.followers": "Folgende",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} ist umgezogen auf:",
|
||||
"account.mute": "@{name} stummschalten",
|
||||
"account.mute_notifications": "Benachrichtigungen von @{name} verbergen",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Beiträge",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "@{name} melden",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "{target} melden",
|
||||
"search.placeholder": "Suche",
|
||||
"search_popout.search_format": "Advanced search 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.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Thread stummschalten",
|
||||
"status.open": "Diesen Beitrag öffnen",
|
||||
"status.pin": "Im Profil anheften",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Teilen",
|
||||
"status.reblogged_by": "{name} teilte",
|
||||
"status.reply": "Antworten",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Mehr anzeigen",
|
||||
"status.unmute_conversation": "Stummschaltung von Thread aufheben",
|
||||
"status.unpin": "Vom Profil lösen",
|
||||
"tabs_bar.compose": "Schreiben",
|
||||
"tabs_bar.federated_timeline": "Föderation",
|
||||
"tabs_bar.home": "Startseite",
|
||||
"tabs_bar.local_timeline": "Lokal",
|
||||
|
@ -274,6 +274,10 @@
|
||||
},
|
||||
{
|
||||
"descriptors": [
|
||||
{
|
||||
"defaultMessage": "Pinned toot",
|
||||
"id": "status.pinned"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "{name} boosted",
|
||||
"id": "status.reblogged_by"
|
||||
@ -469,9 +473,25 @@
|
||||
"defaultMessage": "Awaiting approval. Click to cancel follow request",
|
||||
"id": "account.requested"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Unblock @{name}",
|
||||
"id": "account.unblock"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Follows you",
|
||||
"id": "account.follows_you"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Blocked",
|
||||
"id": "account.blocked"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Muted",
|
||||
"id": "account.muted"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Domain hidden",
|
||||
"id": "account.domain_blocked"
|
||||
}
|
||||
],
|
||||
"path": "app/javascript/mastodon/features/account/components/header.json"
|
||||
@ -683,6 +703,14 @@
|
||||
"defaultMessage": "Search",
|
||||
"id": "search.placeholder"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
|
||||
"id": "search_popout.tips.full_text"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Simple text returns matching display names, usernames and hashtags",
|
||||
"id": "search_popout.tips.text"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Advanced search format",
|
||||
"id": "search_popout.search_format"
|
||||
@ -698,10 +726,6 @@
|
||||
{
|
||||
"defaultMessage": "status",
|
||||
"id": "search_popout.tips.status"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Simple text returns matching display names, usernames and hashtags",
|
||||
"id": "search_popout.tips.text"
|
||||
}
|
||||
],
|
||||
"path": "app/javascript/mastodon/features/compose/components/search.json"
|
||||
@ -1589,6 +1613,10 @@
|
||||
},
|
||||
{
|
||||
"descriptors": [
|
||||
{
|
||||
"defaultMessage": "Close",
|
||||
"id": "lightbox.close"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Additional comments",
|
||||
"id": "report.placeholder"
|
||||
@ -1618,10 +1646,6 @@
|
||||
},
|
||||
{
|
||||
"descriptors": [
|
||||
{
|
||||
"defaultMessage": "Compose",
|
||||
"id": "tabs_bar.compose"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Home",
|
||||
"id": "tabs_bar.home"
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Block @{name}",
|
||||
"account.block_domain": "Hide everything from {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Edit profile",
|
||||
"account.follow": "Follow",
|
||||
"account.followers": "Followers",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} has moved to:",
|
||||
"account.mute": "Mute @{name}",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Toots",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Report @{name}",
|
||||
@ -223,6 +226,7 @@
|
||||
"report.target": "Reporting {target}",
|
||||
"search.placeholder": "Search",
|
||||
"search_popout.search_format": "Advanced search 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.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
@ -245,6 +249,7 @@
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Expand this status",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Boost",
|
||||
"status.reblogged_by": "{name} boosted",
|
||||
"status.reply": "Reply",
|
||||
@ -257,7 +262,6 @@
|
||||
"status.show_more": "Show more",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"tabs_bar.compose": "Compose",
|
||||
"tabs_bar.federated_timeline": "Federated",
|
||||
"tabs_bar.home": "Home",
|
||||
"tabs_bar.local_timeline": "Local",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Bloki @{name}",
|
||||
"account.block_domain": "Kaŝi ĉion de {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Subaj informoj povas reflekti la profilon de la uzanto nekomplete.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Redakti profilon",
|
||||
"account.follow": "Sekvi",
|
||||
"account.followers": "Sekvantoj",
|
||||
@ -13,8 +15,9 @@
|
||||
"account.moved_to": "{name} moviĝis al:",
|
||||
"account.mute": "Silentigi @{name}",
|
||||
"account.mute_notifications": "Silentigi sciigojn el @{name}",
|
||||
"account.posts": "Hupoj",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Mesaĝoj",
|
||||
"account.posts_with_replies": "Mesaĝoj kun respondoj",
|
||||
"account.report": "Signali @{name}",
|
||||
"account.requested": "Atendo de aprobo. Alklaku por nuligi peton de sekvado",
|
||||
"account.share": "Diskonigi la profilon de @{name}",
|
||||
@ -147,7 +150,7 @@
|
||||
"navigation_bar.edit_profile": "Redakti profilon",
|
||||
"navigation_bar.favourites": "Stelumoj",
|
||||
"navigation_bar.follow_requests": "Petoj de sekvado",
|
||||
"navigation_bar.info": "Pri ĉiu tiu nodo",
|
||||
"navigation_bar.info": "Pri ĉi tiu nodo",
|
||||
"navigation_bar.keyboard_shortcuts": "Klavaraj mallongigoj",
|
||||
"navigation_bar.lists": "Listoj",
|
||||
"navigation_bar.logout": "Elsaluti",
|
||||
@ -208,21 +211,22 @@
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "Nuligi",
|
||||
"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": "Plusendi al {target}",
|
||||
"report.forward_hint": "La konto estas en alia servilo. Ĉu sendi sennomigitan kopion de la signalo ankaŭ tien?",
|
||||
"report.hint": "La signalo estos sendita al la kontrolantoj de via nodo. Vi povas doni klarigon pri kial vi signalas ĉi tiun konton sube:",
|
||||
"report.placeholder": "Pliaj komentoj",
|
||||
"report.submit": "Sendi",
|
||||
"report.target": "Signali {target}",
|
||||
"search.placeholder": "Serĉi",
|
||||
"search_popout.search_format": "Detala serĉo",
|
||||
"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": "kradvorto",
|
||||
"search_popout.tips.status": "mesaĝoj",
|
||||
"search_popout.tips.text": "Simpla teksto montras la kongruajn afiŝitajn nomojn, uzantnomojn kaj kradvortojn",
|
||||
"search_popout.tips.user": "uzanto",
|
||||
"search_results.accounts": "People",
|
||||
"search_results.hashtags": "Hashtags",
|
||||
"search_results.statuses": "Toots",
|
||||
"search_results.accounts": "Homoj",
|
||||
"search_results.hashtags": "Kradvortoj",
|
||||
"search_results.statuses": "Mesaĝoj",
|
||||
"search_results.total": "{count, number} {count, plural, one {rezulto} other {rezultoj}}",
|
||||
"standalone.public_title": "Enrigardo…",
|
||||
"status.block": "Bloki @{name}",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Silentigi konversacion",
|
||||
"status.open": "Grandigi ĉi tiun mesaĝon",
|
||||
"status.pin": "Alpingli en la profilo",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Diskonigi",
|
||||
"status.reblogged_by": "{name} diskonigis",
|
||||
"status.reply": "Respondi",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Grandigi",
|
||||
"status.unmute_conversation": "Malsilentigi konversacion",
|
||||
"status.unpin": "Depingli de profilo",
|
||||
"tabs_bar.compose": "Ekskribi",
|
||||
"tabs_bar.federated_timeline": "Fratara tempolinio",
|
||||
"tabs_bar.home": "Hejmo",
|
||||
"tabs_bar.local_timeline": "Loka tempolinio",
|
||||
@ -259,7 +263,7 @@
|
||||
"upload_area.title": "Altreni kaj lasi por alŝuti",
|
||||
"upload_button.label": "Aldoni aŭdovidaĵon",
|
||||
"upload_form.description": "Priskribi por misvidantaj homoj",
|
||||
"upload_form.focus": "Crop",
|
||||
"upload_form.focus": "Stuci",
|
||||
"upload_form.undo": "Malfari",
|
||||
"upload_progress.label": "Alŝutado…",
|
||||
"video.close": "Fermi videon",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Bloquear",
|
||||
"account.block_domain": "Ocultar todo de {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "La siguiente información del usuario puede estar incompleta.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Editar perfil",
|
||||
"account.follow": "Seguir",
|
||||
"account.followers": "Seguidores",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} se ha mudado a:",
|
||||
"account.mute": "Silenciar a @{name}",
|
||||
"account.mute_notifications": "Silenciar notificaciones de @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Publicaciones",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Reportar a @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Reportando",
|
||||
"search.placeholder": "Buscar",
|
||||
"search_popout.search_format": "Formato de búsqueda avanzada",
|
||||
"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": "etiqueta",
|
||||
"search_popout.tips.status": "status",
|
||||
"search_popout.tips.text": "El texto simple devuelve correspondencias de nombre, usuario y hashtag",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Silenciar conversación",
|
||||
"status.open": "Expandir estado",
|
||||
"status.pin": "Fijar",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Retootear",
|
||||
"status.reblogged_by": "Retooteado por {name}",
|
||||
"status.reply": "Responder",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Mostrar más",
|
||||
"status.unmute_conversation": "Dejar de silenciar conversación",
|
||||
"status.unpin": "Dejar de fijar",
|
||||
"tabs_bar.compose": "Redactar",
|
||||
"tabs_bar.federated_timeline": "Federado",
|
||||
"tabs_bar.home": "Inicio",
|
||||
"tabs_bar.local_timeline": "Local",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "مسدودسازی @{name}",
|
||||
"account.block_domain": "پنهانسازی همه چیز از سرور {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "اطلاعات زیر ممکن است نمایهٔ این کاربر را به تمامی نشان ندهد.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "ویرایش نمایه",
|
||||
"account.follow": "پی بگیرید",
|
||||
"account.followers": "پیگیران",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} منتقل شده است به:",
|
||||
"account.mute": "بیصدا کردن @{name}",
|
||||
"account.mute_notifications": "بیصداکردن اعلانها از طرف @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "نوشتهها",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "گزارش @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "گزارشدادن",
|
||||
"search.placeholder": "جستجو",
|
||||
"search_popout.search_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": "هشتگ",
|
||||
"search_popout.tips.status": "نوشته",
|
||||
"search_popout.tips.text": "جستجوی متنی ساده برای نامها، نامهای کاربری، و هشتگها",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "بیصداکردن گفتگو",
|
||||
"status.open": "این نوشته را باز کن",
|
||||
"status.pin": "نوشتهٔ ثابت نمایه",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "بازبوقیدن",
|
||||
"status.reblogged_by": "{name} بازبوقید",
|
||||
"status.reply": "پاسخ",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "نمایش",
|
||||
"status.unmute_conversation": "باصداکردن گفتگو",
|
||||
"status.unpin": "برداشتن نوشتهٔ ثابت نمایه",
|
||||
"tabs_bar.compose": "بنویسید",
|
||||
"tabs_bar.federated_timeline": "همگانی",
|
||||
"tabs_bar.home": "خانه",
|
||||
"tabs_bar.local_timeline": "محلی",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Estä @{name}",
|
||||
"account.block_domain": "Piilota kaikki sisältö verkkotunnuksesta {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Alla olevat käyttäjän profiilitiedot saattavat olla epätäydellisiä.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Muokkaa",
|
||||
"account.follow": "Seuraa",
|
||||
"account.followers": "Seuraajia",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} on muuttanut instanssiin:",
|
||||
"account.mute": "Mykistä @{name}",
|
||||
"account.mute_notifications": "Mykistä ilmoitukset käyttäjältä @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Töötit",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Report @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Reporting",
|
||||
"search.placeholder": "Hae",
|
||||
"search_popout.search_format": "Tarkennettu haku",
|
||||
"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": "hashtagi",
|
||||
"search_popout.tips.status": "status",
|
||||
"search_popout.tips.text": "Pelkkä tekstihaku palauttaa hakua vastaavat nimimerkit, käyttäjänimet ja hastagit",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Mykistä keskustelu",
|
||||
"status.open": "Laajenna statuspäivitys",
|
||||
"status.pin": "Kiinnitä profiiliin",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Buustaa",
|
||||
"status.reblogged_by": "{name} buustasi",
|
||||
"status.reply": "Vastaa",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Näytä lisää",
|
||||
"status.unmute_conversation": "Poista mykistys keskustelulta",
|
||||
"status.unpin": "Irrota profiilista",
|
||||
"tabs_bar.compose": "Luo",
|
||||
"tabs_bar.federated_timeline": "Federated",
|
||||
"tabs_bar.home": "Koti",
|
||||
"tabs_bar.local_timeline": "Paikallinen",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Bloquer @{name}",
|
||||
"account.block_domain": "Tout masquer venant de {domain}",
|
||||
"account.blocked": "Bloqué",
|
||||
"account.disclaimer_full": "Les données ci-dessous peuvent ne pas refléter ce profil dans sa totalité.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Modifier le profil",
|
||||
"account.follow": "Suivre",
|
||||
"account.followers": "Abonné⋅e⋅s",
|
||||
@ -13,8 +15,9 @@
|
||||
"account.moved_to": "{name} a déménagé vers :",
|
||||
"account.mute": "Masquer @{name}",
|
||||
"account.mute_notifications": "Ignorer les notifications de @{name}",
|
||||
"account.posts": "Statuts",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.muted": "Silencé",
|
||||
"account.posts": "Pouets",
|
||||
"account.posts_with_replies": "Pouets avec réponses",
|
||||
"account.report": "Signaler",
|
||||
"account.requested": "Invitation envoyée",
|
||||
"account.share": "Partager le profil de @{name}",
|
||||
@ -208,21 +211,22 @@
|
||||
"relative_time.minutes": "{number} min",
|
||||
"relative_time.seconds": "{number} s",
|
||||
"reply_indicator.cancel": "Annuler",
|
||||
"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": "Transférer à {target}",
|
||||
"report.forward_hint": "Le compte provient d'un autre serveur. Envoyez également une copie anonyme du rapport ?",
|
||||
"report.hint": "Le rapport sera envoyé aux modérateurs de votre instance. Vous pouvez expliquer pourquoi vous signalez ce compte ci-dessous :",
|
||||
"report.placeholder": "Commentaires additionnels",
|
||||
"report.submit": "Envoyer",
|
||||
"report.target": "Signalement",
|
||||
"search.placeholder": "Rechercher",
|
||||
"search_popout.search_format": "Recherche avancée",
|
||||
"search_popout.tips.full_text": "Les textes simples retournent les pouets que vous avez écris, mis en favori, épinglés, ou ayant été mentionnés, ainsi que les noms d'utilisateurs, les noms affichés, et les hashtags correspondant.",
|
||||
"search_popout.tips.hashtag": "hashtag",
|
||||
"search_popout.tips.status": "statuts",
|
||||
"search_popout.tips.text": "Un texte simple renvoie les noms affichés, les noms d’utilisateur⋅ice et les hashtags correspondants",
|
||||
"search_popout.tips.user": "utilisateur⋅ice",
|
||||
"search_results.accounts": "People",
|
||||
"search_results.accounts": "Personnes",
|
||||
"search_results.hashtags": "Hashtags",
|
||||
"search_results.statuses": "Toots",
|
||||
"search_results.statuses": "Pouets",
|
||||
"search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}",
|
||||
"standalone.public_title": "Jeter un coup d’œil…",
|
||||
"status.block": "Block @{name}",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Masquer la conversation",
|
||||
"status.open": "Déplier ce statut",
|
||||
"status.pin": "Épingler sur le profil",
|
||||
"status.pinned": "Pouet épinglé",
|
||||
"status.reblog": "Partager",
|
||||
"status.reblogged_by": "{name} a partagé :",
|
||||
"status.reply": "Répondre",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Déplier",
|
||||
"status.unmute_conversation": "Ne plus masquer la conversation",
|
||||
"status.unpin": "Retirer du profil",
|
||||
"tabs_bar.compose": "Composer",
|
||||
"tabs_bar.federated_timeline": "Fil public global",
|
||||
"tabs_bar.home": "Accueil",
|
||||
"tabs_bar.local_timeline": "Fil public local",
|
||||
@ -259,7 +263,7 @@
|
||||
"upload_area.title": "Glissez et déposez pour envoyer",
|
||||
"upload_button.label": "Joindre un média",
|
||||
"upload_form.description": "Décrire pour les malvoyants",
|
||||
"upload_form.focus": "Crop",
|
||||
"upload_form.focus": "Recadrer",
|
||||
"upload_form.undo": "Annuler",
|
||||
"upload_progress.label": "Envoi en cours…",
|
||||
"video.close": "Fermer la vidéo",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Bloquear @{name}",
|
||||
"account.block_domain": "Ocultar calquer contido de {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "A información inferior podería mostrar un perfil incompleto da usuaria.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Editar perfil",
|
||||
"account.follow": "Seguir",
|
||||
"account.followers": "Seguidoras",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} marchou a:",
|
||||
"account.mute": "Acalar @{name}",
|
||||
"account.mute_notifications": "Acalar as notificacións de @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Toots",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Informar sobre @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Informar {target}",
|
||||
"search.placeholder": "Buscar",
|
||||
"search_popout.search_format": "Formato de busca avanzada",
|
||||
"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": "etiqueta",
|
||||
"search_popout.tips.status": "estado",
|
||||
"search_popout.tips.text": "Texto simple devolve coincidencias con nomes públicos, nomes de usuaria e etiquetas",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Acalar conversa",
|
||||
"status.open": "Expandir este estado",
|
||||
"status.pin": "Fixar no perfil",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Promover",
|
||||
"status.reblogged_by": "{name} promoveu",
|
||||
"status.reply": "Resposta",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Mostrar máis",
|
||||
"status.unmute_conversation": "Non acalar a conversa",
|
||||
"status.unpin": "Despegar do perfil",
|
||||
"tabs_bar.compose": "Compoñer",
|
||||
"tabs_bar.federated_timeline": "Federado",
|
||||
"tabs_bar.home": "Inicio",
|
||||
"tabs_bar.local_timeline": "Local",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "חסימת @{name}",
|
||||
"account.block_domain": "להסתיר הכל מהקהילה {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "המידע להלן עשוי להיות לא עדכני או לא שלם.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "עריכת פרופיל",
|
||||
"account.follow": "מעקב",
|
||||
"account.followers": "עוקבים",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "החשבון {name} הועבר אל:",
|
||||
"account.mute": "להשתיק את @{name}",
|
||||
"account.mute_notifications": "להסתיר התראות מאת @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "הודעות",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "לדווח על @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "דיווח",
|
||||
"search.placeholder": "חיפוש",
|
||||
"search_popout.search_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": "האשתג",
|
||||
"search_popout.tips.status": "status",
|
||||
"search_popout.tips.text": "טקסט פשוט מחזיר כינויים, שמות משתמש והאשתגים",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "השתקת שיחה",
|
||||
"status.open": "הרחבת הודעה",
|
||||
"status.pin": "לקבע באודות",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "הדהוד",
|
||||
"status.reblogged_by": "הודהד על ידי {name}",
|
||||
"status.reply": "תגובה",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "הראה יותר",
|
||||
"status.unmute_conversation": "הסרת השתקת שיחה",
|
||||
"status.unpin": "לשחרר מקיבוע באודות",
|
||||
"tabs_bar.compose": "חיבור",
|
||||
"tabs_bar.federated_timeline": "ציר זמן בין-קהילתי",
|
||||
"tabs_bar.home": "בבית",
|
||||
"tabs_bar.local_timeline": "ציר זמן מקומי",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Blokiraj @{name}",
|
||||
"account.block_domain": "Sakrij sve sa {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Ovaj korisnik je sa druge instance. Ovaj broj bi mogao biti veći.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Uredi profil",
|
||||
"account.follow": "Slijedi",
|
||||
"account.followers": "Sljedbenici",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} has moved to:",
|
||||
"account.mute": "Utišaj @{name}",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Postovi",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Prijavi @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Prijavljivanje",
|
||||
"search.placeholder": "Traži",
|
||||
"search_popout.search_format": "Advanced search 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.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Utišaj razgovor",
|
||||
"status.open": "Proširi ovaj status",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Podigni",
|
||||
"status.reblogged_by": "{name} je podigao",
|
||||
"status.reply": "Odgovori",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Pokaži više",
|
||||
"status.unmute_conversation": "Poništi utišavanje razgovora",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"tabs_bar.compose": "Sastavi",
|
||||
"tabs_bar.federated_timeline": "Federalni",
|
||||
"tabs_bar.home": "Dom",
|
||||
"tabs_bar.local_timeline": "Lokalno",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "@{name} letiltása",
|
||||
"account.block_domain": "Minden elrejtése innen: {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Az alul található információk hiányosan mutathatják be a felhasználót.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Profil szerkesztése",
|
||||
"account.follow": "Követés",
|
||||
"account.followers": "Követők",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} átköltözött:",
|
||||
"account.mute": "@{name} némítása",
|
||||
"account.mute_notifications": "@{name} értesítések némítása",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Státuszok",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "@{name} jelentése",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Reporting",
|
||||
"search.placeholder": "Keresés",
|
||||
"search_popout.search_format": "Fejlett keresés",
|
||||
"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.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Beszélgetés némítása",
|
||||
"status.open": "Státusz kinagyítása",
|
||||
"status.pin": "Kitűzés a profilra",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Reblog",
|
||||
"status.reblogged_by": "{name} reblogolta",
|
||||
"status.reply": "Válasz",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Többet",
|
||||
"status.unmute_conversation": "Beszélgetés némításának elvonása",
|
||||
"status.unpin": "Kitűzés eltávolítása a profilról",
|
||||
"tabs_bar.compose": "Összeállítás",
|
||||
"tabs_bar.federated_timeline": "Federált",
|
||||
"tabs_bar.home": "Kezdőlap",
|
||||
"tabs_bar.local_timeline": "Local",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Արգելափակել @{name}֊ին",
|
||||
"account.block_domain": "Թաքցնել ամենը հետեւյալ տիրույթից՝ {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Ներքոհիշյալը կարող է ոչ ամբողջությամբ արտացոլել օգտատիրոջ էջի տվյալները։",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Խմբագրել անձնական էջը",
|
||||
"account.follow": "Հետեւել",
|
||||
"account.followers": "Հետեւվողներ",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name}֊ը տեղափոխվել է՝",
|
||||
"account.mute": "Լռեցնել @{name}֊ին",
|
||||
"account.mute_notifications": "Անջատել ծանուցումները @{name}֊ից",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Գրառումներ",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Բողոքել @{name}֊ից",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Բողոքել {target}֊ի մասին",
|
||||
"search.placeholder": "Փնտրել",
|
||||
"search_popout.search_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": "պիտակ",
|
||||
"search_popout.tips.status": "թութ",
|
||||
"search_popout.tips.text": "Հասարակ տեքստը կվերադարձնի համընկնող անուններ, օգտանուններ ու պիտակներ",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Լռեցնել խոսակցությունը",
|
||||
"status.open": "Ընդարձակել այս թութը",
|
||||
"status.pin": "Ամրացնել անձնական էջում",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Տարածել",
|
||||
"status.reblogged_by": "{name} տարածել է",
|
||||
"status.reply": "Պատասխանել",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Ավելին",
|
||||
"status.unmute_conversation": "Ապալռեցնել խոսակցությունը",
|
||||
"status.unpin": "Հանել անձնական էջից",
|
||||
"tabs_bar.compose": "Շարադրել",
|
||||
"tabs_bar.federated_timeline": "Դաշնային",
|
||||
"tabs_bar.home": "Հիմնական",
|
||||
"tabs_bar.local_timeline": "Տեղական",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Blokir @{name}",
|
||||
"account.block_domain": "Hide everything from {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Ubah profil",
|
||||
"account.follow": "Ikuti",
|
||||
"account.followers": "Pengikut",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} has moved to:",
|
||||
"account.mute": "Bisukan @{name}",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Postingan",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Laporkan @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Melaporkan",
|
||||
"search.placeholder": "Pencarian",
|
||||
"search_popout.search_format": "Advanced search 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.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Tampilkan status ini",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Boost",
|
||||
"status.reblogged_by": "di-boost {name}",
|
||||
"status.reply": "Balas",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Tampilkan semua",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"tabs_bar.compose": "Tulis",
|
||||
"tabs_bar.federated_timeline": "Gabungan",
|
||||
"tabs_bar.home": "Beranda",
|
||||
"tabs_bar.local_timeline": "Lokal",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Blokusar @{name}",
|
||||
"account.block_domain": "Hide everything from {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Modifikar profilo",
|
||||
"account.follow": "Sequar",
|
||||
"account.followers": "Sequanti",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} has moved to:",
|
||||
"account.mute": "Celar @{name}",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Mesaji",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Denuncar @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Denuncante",
|
||||
"search.placeholder": "Serchez",
|
||||
"search_popout.search_format": "Advanced search 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.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Detaligar ca mesajo",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Repetar",
|
||||
"status.reblogged_by": "{name} repetita",
|
||||
"status.reply": "Respondar",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Montrar plue",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"tabs_bar.compose": "Kompozar",
|
||||
"tabs_bar.federated_timeline": "Federata",
|
||||
"tabs_bar.home": "Hemo",
|
||||
"tabs_bar.local_timeline": "Lokala",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Blocca @{name}",
|
||||
"account.block_domain": "Hide everything from {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Modifica profilo",
|
||||
"account.follow": "Segui",
|
||||
"account.followers": "Seguaci",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} has moved to:",
|
||||
"account.mute": "Silenzia @{name}",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Posts",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Segnala @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Invio la segnalazione",
|
||||
"search.placeholder": "Cerca",
|
||||
"search_popout.search_format": "Advanced search 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.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Espandi questo post",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Condividi",
|
||||
"status.reblogged_by": "{name} ha condiviso",
|
||||
"status.reply": "Rispondi",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Mostra di più",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"tabs_bar.compose": "Scrivi",
|
||||
"tabs_bar.federated_timeline": "Federazione",
|
||||
"tabs_bar.home": "Home",
|
||||
"tabs_bar.local_timeline": "Locale",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "@{name}さんをブロック",
|
||||
"account.block_domain": "{domain}全体を非表示",
|
||||
"account.blocked": "ブロック済み",
|
||||
"account.disclaimer_full": "以下の情報は不正確な可能性があります。",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "プロフィールを編集",
|
||||
"account.follow": "フォロー",
|
||||
"account.followers": "フォロワー",
|
||||
@ -13,8 +15,9 @@
|
||||
"account.moved_to": "{name}さんは引っ越しました:",
|
||||
"account.mute": "@{name}さんをミュート",
|
||||
"account.mute_notifications": "@{name}さんからの通知を受け取らない",
|
||||
"account.muted": "ミュート済み",
|
||||
"account.posts": "投稿",
|
||||
"account.posts_with_replies": "トゥートと返信",
|
||||
"account.posts_with_replies": "投稿と返信",
|
||||
"account.report": "@{name}さんを通報",
|
||||
"account.requested": "フォロー承認待ちです。クリックしてキャンセル",
|
||||
"account.share": "@{name}さんのプロフィールを共有する",
|
||||
@ -223,6 +226,7 @@
|
||||
"report.target": "{target}さんを通報する",
|
||||
"search.placeholder": "検索",
|
||||
"search_popout.search_format": "高度な検索フォーマット",
|
||||
"search_popout.tips.full_text": "表示名やユーザー名、ハッシュタグのほか、あなたのトゥートやお気に入り、ブーストしたトゥート、返信に一致する単純なテキスト。",
|
||||
"search_popout.tips.hashtag": "ハッシュタグ",
|
||||
"search_popout.tips.status": "トゥート",
|
||||
"search_popout.tips.text": "表示名やユーザー名、ハッシュタグに一致する単純なテキスト",
|
||||
@ -245,8 +249,9 @@
|
||||
"status.mute_conversation": "会話をミュート",
|
||||
"status.open": "詳細を表示",
|
||||
"status.pin": "プロフィールに固定表示",
|
||||
"status.pinned": "固定されたトゥート",
|
||||
"status.reblog": "ブースト",
|
||||
"status.reblogged_by": "{name}さんにブーストされました",
|
||||
"status.reblogged_by": "{name}さんがブースト",
|
||||
"status.reply": "返信",
|
||||
"status.replyAll": "全員に返信",
|
||||
"status.report": "@{name}さんを通報",
|
||||
@ -257,12 +262,11 @@
|
||||
"status.show_more": "もっと見る",
|
||||
"status.unmute_conversation": "会話のミュートを解除",
|
||||
"status.unpin": "プロフィールの固定表示を解除",
|
||||
"tabs_bar.compose": "投稿",
|
||||
"tabs_bar.federated_timeline": "連合",
|
||||
"tabs_bar.home": "ホーム",
|
||||
"tabs_bar.local_timeline": "ローカル",
|
||||
"tabs_bar.notifications": "通知",
|
||||
"ui.beforeunload": "Mastodonから離れるとあなたのドラフトは失われます。",
|
||||
"ui.beforeunload": "Mastodonから離れると送信前の投稿は失われます。",
|
||||
"upload_area.title": "ドラッグ&ドロップでアップロード",
|
||||
"upload_button.label": "メディアを追加",
|
||||
"upload_form.description": "視覚障害者のための説明",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "@{name}을 차단",
|
||||
"account.block_domain": "{domain} 전체를 숨김",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "여기 있는 정보는 유저의 프로파일을 정확히 반영하지 못 할 수도 있습니다.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "프로필 편집",
|
||||
"account.follow": "팔로우",
|
||||
"account.followers": "팔로워",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name}는 계정을 이동했습니다:",
|
||||
"account.mute": "@{name} 뮤트",
|
||||
"account.mute_notifications": "@{name}의 알림을 뮤트",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "게시물",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "@{name} 신고",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "문제가 된 사용자",
|
||||
"search.placeholder": "검색",
|
||||
"search_popout.search_format": "고급 검색 방법",
|
||||
"search_popout.tips.full_text": "단순한 텍스트 검색은 당신이 작성했거나, 관심글로 지정했거나, 부스트했거나, 멘션을 받은 게시글, 그리고 유저네임, 디스플레이네임, 해시태그를 반환합니다.",
|
||||
"search_popout.tips.hashtag": "해시태그",
|
||||
"search_popout.tips.status": "툿",
|
||||
"search_popout.tips.text": "단순한 텍스트 검색은 관계된 프로필 이름, 유저 이름 그리고 해시태그를 표시합니다",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "이 대화를 뮤트",
|
||||
"status.open": "상세 정보 표시",
|
||||
"status.pin": "고정",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "부스트",
|
||||
"status.reblogged_by": "{name}님이 부스트 했습니다",
|
||||
"status.reply": "답장",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "더 보기",
|
||||
"status.unmute_conversation": "이 대화의 뮤트 해제하기",
|
||||
"status.unpin": "고정 해제",
|
||||
"tabs_bar.compose": "포스트",
|
||||
"tabs_bar.federated_timeline": "연합",
|
||||
"tabs_bar.home": "홈",
|
||||
"tabs_bar.local_timeline": "로컬",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Blokkeer @{name}",
|
||||
"account.block_domain": "Negeer alles van {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "De informatie hieronder kan mogelijk een incompleet beeld geven van dit gebruikersprofiel.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Profiel bewerken",
|
||||
"account.follow": "Volgen",
|
||||
"account.followers": "Volgers",
|
||||
@ -13,8 +15,9 @@
|
||||
"account.moved_to": "{name} is verhuisd naar:",
|
||||
"account.mute": "Negeer @{name}",
|
||||
"account.mute_notifications": "Negeer meldingen van @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Toots",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.posts_with_replies": "Toots met reacties",
|
||||
"account.report": "Rapporteer @{name}",
|
||||
"account.requested": "Wacht op goedkeuring. Klik om het volgverzoek te annuleren",
|
||||
"account.share": "Profiel van @{name} delen",
|
||||
@ -208,19 +211,20 @@
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "Annuleren",
|
||||
"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": "Doorsturen naar {target}",
|
||||
"report.forward_hint": "Het account bevindt zich op een andere server. Stuur daar eveneens een geanonimiseerde kopie van de gerapporteerde toot(s) naartoe?",
|
||||
"report.hint": "De gerapporteerde toot(s) worden naar de moderatoren van jouw server gestuurd. Je kunt hieronder een uitleg geven waarom je dit account rapporteert:",
|
||||
"report.placeholder": "Extra opmerkingen",
|
||||
"report.submit": "Verzenden",
|
||||
"report.target": "Rapporteer {target}",
|
||||
"search.placeholder": "Zoeken",
|
||||
"search_popout.search_format": "Geavanceerd zoeken",
|
||||
"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": "toot",
|
||||
"search_popout.tips.text": "Gebruik gewone tekst om te zoeken op weergavenamen, gebruikersnamen en hashtags",
|
||||
"search_popout.tips.user": "gebruiker",
|
||||
"search_results.accounts": "People",
|
||||
"search_results.accounts": "Gebruikers",
|
||||
"search_results.hashtags": "Hashtags",
|
||||
"search_results.statuses": "Toots",
|
||||
"search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Negeer conversatie",
|
||||
"status.open": "Toot volledig tonen",
|
||||
"status.pin": "Aan profielpagina vastmaken",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Boost",
|
||||
"status.reblogged_by": "{name} boostte",
|
||||
"status.reply": "Reageren",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Meer tonen",
|
||||
"status.unmute_conversation": "Conversatie niet meer negeren",
|
||||
"status.unpin": "Van profielpagina losmaken",
|
||||
"tabs_bar.compose": "Schrijven",
|
||||
"tabs_bar.federated_timeline": "Globaal",
|
||||
"tabs_bar.home": "Start",
|
||||
"tabs_bar.local_timeline": "Lokaal",
|
||||
@ -259,7 +263,7 @@
|
||||
"upload_area.title": "Hierin slepen om te uploaden",
|
||||
"upload_button.label": "Media toevoegen",
|
||||
"upload_form.description": "Omschrijf dit voor mensen met een visuele beperking",
|
||||
"upload_form.focus": "Crop",
|
||||
"upload_form.focus": "Bijsnijden",
|
||||
"upload_form.undo": "Ongedaan maken",
|
||||
"upload_progress.label": "Uploaden...",
|
||||
"video.close": "Video sluiten",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Blokkér @{name}",
|
||||
"account.block_domain": "Skjul alt fra {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Informasjonen nedenfor kan gi et ufullstendig bilde av brukerens profil.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Rediger profil",
|
||||
"account.follow": "Følg",
|
||||
"account.followers": "Følgere",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} har flyttet til:",
|
||||
"account.mute": "Demp @{name}",
|
||||
"account.mute_notifications": "Ignorer varsler fra @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Innlegg",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Rapportér @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Rapporterer",
|
||||
"search.placeholder": "Søk",
|
||||
"search_popout.search_format": "Avansert søkeformat",
|
||||
"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": "emneknagg",
|
||||
"search_popout.tips.status": "status",
|
||||
"search_popout.tips.text": "Enkel tekst returnerer matchende visningsnavn, brukernavn og emneknagger",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Demp samtale",
|
||||
"status.open": "Utvid denne statusen",
|
||||
"status.pin": "Fest på profilen",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Fremhev",
|
||||
"status.reblogged_by": "Fremhevd av {name}",
|
||||
"status.reply": "Svar",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Vis mer",
|
||||
"status.unmute_conversation": "Ikke demp samtale",
|
||||
"status.unpin": "Angre festing på profilen",
|
||||
"tabs_bar.compose": "Komponer",
|
||||
"tabs_bar.federated_timeline": "Felles",
|
||||
"tabs_bar.home": "Hjem",
|
||||
"tabs_bar.local_timeline": "Lokal",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Blocar @{name}",
|
||||
"account.block_domain": "Tot amagar del domeni {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Aquelas informacions de perfil pòdon èsser incomplètas.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Modificar lo perfil",
|
||||
"account.follow": "Sègre",
|
||||
"account.followers": "Seguidors",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} a mudat los catons a :",
|
||||
"account.mute": "Rescondre @{name}",
|
||||
"account.mute_notifications": "Rescondre las notificacions de @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Estatuts",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Senhalar @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Senhalar {target}",
|
||||
"search.placeholder": "Recercar",
|
||||
"search_popout.search_format": "Format recèrca avançada",
|
||||
"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": "etiqueta",
|
||||
"search_popout.tips.status": "estatut",
|
||||
"search_popout.tips.text": "Lo tèxt brut tòrna escais, noms d’utilizaire e etiquetas correspondents",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Rescondre la conversacion",
|
||||
"status.open": "Desplegar aqueste estatut",
|
||||
"status.pin": "Penjar al perfil",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Partejar",
|
||||
"status.reblogged_by": "{name} a partejat",
|
||||
"status.reply": "Respondre",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Desplegar",
|
||||
"status.unmute_conversation": "Tornar mostrar la conversacion",
|
||||
"status.unpin": "Tirar del perfil",
|
||||
"tabs_bar.compose": "Compausar",
|
||||
"tabs_bar.federated_timeline": "Flux public global",
|
||||
"tabs_bar.home": "Acuèlh",
|
||||
"tabs_bar.local_timeline": "Flux public local",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Blokuj @{name}",
|
||||
"account.block_domain": "Blokuj wszystko z {domain}",
|
||||
"account.blocked": "Zablokowany",
|
||||
"account.disclaimer_full": "Poniższe informacje mogą nie odwzorowywać bezbłędnie profilu użytkownika.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Edytuj profil",
|
||||
"account.follow": "Śledź",
|
||||
"account.followers": "Śledzący",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} przeniósł się do:",
|
||||
"account.mute": "Wycisz @{name}",
|
||||
"account.mute_notifications": "Wycisz powiadomienia o @{name}",
|
||||
"account.muted": "Wyciszony",
|
||||
"account.posts": "Wpisy",
|
||||
"account.posts_with_replies": "Wpisy z odpowiedziami",
|
||||
"account.report": "Zgłoś @{name}",
|
||||
@ -223,6 +226,7 @@
|
||||
"report.target": "Zgłaszanie {target}",
|
||||
"search.placeholder": "Szukaj",
|
||||
"search_popout.search_format": "Zaawansowane wyszukiwanie",
|
||||
"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": "wpis",
|
||||
"search_popout.tips.text": "Proste wyszukiwanie pasujących pseudonimów, nazw użytkowników i hashtagów",
|
||||
@ -245,6 +249,7 @@
|
||||
"status.mute_conversation": "Wycisz konwersację",
|
||||
"status.open": "Rozszerz ten wpis",
|
||||
"status.pin": "Przypnij do profilu",
|
||||
"status.pinned": "Przypięty wpis",
|
||||
"status.reblog": "Podbij",
|
||||
"status.reblogged_by": "{name} podbił",
|
||||
"status.reply": "Odpowiedz",
|
||||
@ -257,7 +262,6 @@
|
||||
"status.show_more": "Pokaż więcej",
|
||||
"status.unmute_conversation": "Cofnij wyciszenie konwersacji",
|
||||
"status.unpin": "Odepnij z profilu",
|
||||
"tabs_bar.compose": "Napisz",
|
||||
"tabs_bar.federated_timeline": "Globalne",
|
||||
"tabs_bar.home": "Strona główna",
|
||||
"tabs_bar.local_timeline": "Lokalne",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Bloquear @{name}",
|
||||
"account.block_domain": "Esconder tudo de {domain}",
|
||||
"account.blocked": "Bloqueado",
|
||||
"account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de maneira incompleta.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Editar perfil",
|
||||
"account.follow": "Seguir",
|
||||
"account.followers": "Seguidores",
|
||||
@ -13,8 +15,9 @@
|
||||
"account.moved_to": "{name} se mudou para:",
|
||||
"account.mute": "Silenciar @{name}",
|
||||
"account.mute_notifications": "Silenciar notificações de @{name}",
|
||||
"account.posts": "Posts",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.muted": "Silenciado",
|
||||
"account.posts": "Toots",
|
||||
"account.posts_with_replies": "Toots e respostas",
|
||||
"account.report": "Denunciar @{name}",
|
||||
"account.requested": "Aguardando aprovação. Clique para cancelar a solicitação",
|
||||
"account.share": "Compartilhar perfil de @{name}",
|
||||
@ -208,19 +211,20 @@
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "Cancelar",
|
||||
"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": "Encaminhar para {target}",
|
||||
"report.forward_hint": "Essa conta pertence à um outro servidor. Encaminhar uma cópia da denúncia com seus dados tornados anônimos para esse servidor?",
|
||||
"report.hint": "A sua denúncia será enviada aos moderadores da instância. Você pode adicionar uma explicação de porque você está denunciando essa conta abaixo:",
|
||||
"report.placeholder": "Comentários adicionais",
|
||||
"report.submit": "Enviar",
|
||||
"report.target": "Denunciar",
|
||||
"search.placeholder": "Pesquisar",
|
||||
"search_popout.search_format": "Formato de busca avançado",
|
||||
"search_popout.tips.full_text": "Texto simples retorna status que você escreveu, favoritou, compartilhou ou em que tenha sido mencionado; também retorna nomes de exibição, usuários e hashtags correspondentes.",
|
||||
"search_popout.tips.hashtag": "hashtag",
|
||||
"search_popout.tips.status": "status",
|
||||
"search_popout.tips.text": "Texto simples retorna nomes de exibição, usuários e hashtags correspondentes",
|
||||
"search_popout.tips.user": "usuário",
|
||||
"search_results.accounts": "People",
|
||||
"search_results.accounts": "Pessoas",
|
||||
"search_results.hashtags": "Hashtags",
|
||||
"search_results.statuses": "Toots",
|
||||
"search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Silenciar conversa",
|
||||
"status.open": "Expandir",
|
||||
"status.pin": "Fixar no perfil",
|
||||
"status.pinned": "Toot fixado",
|
||||
"status.reblog": "Compartilhar",
|
||||
"status.reblogged_by": "{name} compartilhou",
|
||||
"status.reply": "Responder",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Mostrar mais",
|
||||
"status.unmute_conversation": "Desativar silêncio desta conversa",
|
||||
"status.unpin": "Desafixar do perfil",
|
||||
"tabs_bar.compose": "Criar",
|
||||
"tabs_bar.federated_timeline": "Global",
|
||||
"tabs_bar.home": "Página inicial",
|
||||
"tabs_bar.local_timeline": "Local",
|
||||
@ -259,7 +263,7 @@
|
||||
"upload_area.title": "Arraste e solte para enviar",
|
||||
"upload_button.label": "Adicionar mídia",
|
||||
"upload_form.description": "Descreva a imagem para deficientes visuais",
|
||||
"upload_form.focus": "Crop",
|
||||
"upload_form.focus": "Recortar",
|
||||
"upload_form.undo": "Desfazer",
|
||||
"upload_progress.label": "Salvando...",
|
||||
"video.close": "Fechar vídeo",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Bloquear @{name}",
|
||||
"account.block_domain": "Esconder tudo do domínio {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de forma incompleta.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Editar perfil",
|
||||
"account.follow": "Seguir",
|
||||
"account.followers": "Seguidores",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} mudou a sua conta para:",
|
||||
"account.mute": "Silenciar @{name}",
|
||||
"account.mute_notifications": "Silenciar notificações de @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Posts",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Denunciar @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Denunciar",
|
||||
"search.placeholder": "Pesquisar",
|
||||
"search_popout.search_format": "Formato avançado de pesquisa",
|
||||
"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.text": "O texto simples retorna a correspondência de nomes, utilizadores e hashtags",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Silenciar conversa",
|
||||
"status.open": "Expandir",
|
||||
"status.pin": "Fixar no perfil",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Partilhar",
|
||||
"status.reblogged_by": "{name} partilhou",
|
||||
"status.reply": "Responder",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Mostrar mais",
|
||||
"status.unmute_conversation": "Deixar de silenciar esta conversa",
|
||||
"status.unpin": "Não fixar no perfil",
|
||||
"tabs_bar.compose": "Criar",
|
||||
"tabs_bar.federated_timeline": "Global",
|
||||
"tabs_bar.home": "Home",
|
||||
"tabs_bar.local_timeline": "Local",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Блокировать",
|
||||
"account.block_domain": "Блокировать все с {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Нижеуказанная информация может не полностью отражать профиль пользователя.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Изменить профиль",
|
||||
"account.follow": "Подписаться",
|
||||
"account.followers": "Подписаны",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "Ищите {name} здесь:",
|
||||
"account.mute": "Заглушить",
|
||||
"account.mute_notifications": "Скрыть уведомления от @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Посты",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Пожаловаться",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Жалуемся на",
|
||||
"search.placeholder": "Поиск",
|
||||
"search_popout.search_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": "хэштег",
|
||||
"search_popout.tips.status": "статус",
|
||||
"search_popout.tips.text": "Простой ввод текста покажет совпадающие имена пользователей, отображаемые имена и хэштеги",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Заглушить тред",
|
||||
"status.open": "Развернуть статус",
|
||||
"status.pin": "Закрепить в профиле",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Продвинуть",
|
||||
"status.reblogged_by": "{name} продвинул(а)",
|
||||
"status.reply": "Ответить",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Развернуть",
|
||||
"status.unmute_conversation": "Снять глушение с треда",
|
||||
"status.unpin": "Открепить от профиля",
|
||||
"tabs_bar.compose": "Написать",
|
||||
"tabs_bar.federated_timeline": "Глобальная",
|
||||
"tabs_bar.home": "Главная",
|
||||
"tabs_bar.local_timeline": "Локальная",
|
||||
|
@ -1,20 +1,23 @@
|
||||
{
|
||||
"account.block": "Blokovať @{name}",
|
||||
"account.block_domain": "Ukryť všetko z {domain}",
|
||||
"account.blocked": "Blokovaný/á",
|
||||
"account.disclaimer_full": "Inofrmácie nižšie nemusia byť úplným odrazom uživateľovho účtu.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Upraviť profil",
|
||||
"account.follow": "Následovať",
|
||||
"account.followers": "Sledujúci",
|
||||
"account.follows": "Sledujete",
|
||||
"account.follows_you": "Následuje vás",
|
||||
"account.follows_you": "Následuje ťa",
|
||||
"account.hide_reblogs": "Skryť povýšenia od @{name}",
|
||||
"account.media": "Médiá",
|
||||
"account.mention": "Spomeňte @{name}",
|
||||
"account.moved_to": "{name} sa presunul/a na:",
|
||||
"account.mute": "Ignorovať @{name}",
|
||||
"account.mute_notifications": "Stĺmiť notifikácie od @{name}",
|
||||
"account.muted": "Utíšený/á",
|
||||
"account.posts": "Hlášky",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.posts_with_replies": "Príspevky s odpoveďami",
|
||||
"account.report": "Nahlásiť @{name}",
|
||||
"account.requested": "Čaká na schválenie. Kliknite pre zrušenie žiadosti",
|
||||
"account.share": "Zdieľať @{name} profil",
|
||||
@ -156,8 +159,8 @@
|
||||
"navigation_bar.preferences": "Možnosti",
|
||||
"navigation_bar.public_timeline": "Federovaná časová os",
|
||||
"notification.favourite": "{name} sa páči tvoj status",
|
||||
"notification.follow": "{name} vás začal(a) sledovať",
|
||||
"notification.mention": "{name} vás spomenul",
|
||||
"notification.follow": "{name} ťa začal/a následovať",
|
||||
"notification.mention": "{name} ťa spomenul/a",
|
||||
"notification.reblog": "{name} re-tootol tvoj status",
|
||||
"notifications.clear": "Vyčistiť zoznam notifikácii",
|
||||
"notifications.clear_confirmation": "Naozaj chcete nenávratne prečistiť všetky vaše notifikácie?",
|
||||
@ -208,21 +211,22 @@
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "Zrušiť",
|
||||
"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": "Posuň ku {target}",
|
||||
"report.forward_hint": "Tento účet je z iného serveru. Chceš poslať anonymnú kópiu reportu aj tam?",
|
||||
"report.hint": "Toto nahlásenie bude zaslané správcom servera. Môžeš napísať odvôvodnenie prečo si nahlásil/a tento účet:",
|
||||
"report.placeholder": "Ďalšie komentáre",
|
||||
"report.submit": "Poslať",
|
||||
"report.target": "Nahlásenie {target}",
|
||||
"search.placeholder": "Hľadať",
|
||||
"search_popout.search_format": "Pokročilý formát vyhľadávania",
|
||||
"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": "haštag",
|
||||
"search_popout.tips.status": "status",
|
||||
"search_popout.tips.text": "Jednoduchý text vráti zhodujúce sa mená, prezývky a hashtagy",
|
||||
"search_popout.tips.user": "používateľ",
|
||||
"search_results.accounts": "People",
|
||||
"search_results.hashtags": "Hashtags",
|
||||
"search_results.statuses": "Toots",
|
||||
"search_results.accounts": "Ľudia",
|
||||
"search_results.hashtags": "Haštagy",
|
||||
"search_results.statuses": "Príspevky",
|
||||
"search_results.total": "{count, number} {count, plural, one {result} ostatné {results}}",
|
||||
"standalone.public_title": "Pohľad dovnútra...",
|
||||
"status.block": "Blokovať @{name}",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Ignorovať konverzáciu",
|
||||
"status.open": "Otvoriť tento status",
|
||||
"status.pin": "Pripnúť na profil",
|
||||
"status.pinned": "Pripnutý príspevok",
|
||||
"status.reblog": "Povýšiť",
|
||||
"status.reblogged_by": "{name} povýšil",
|
||||
"status.reply": "Odpovedať",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Zobraz viac",
|
||||
"status.unmute_conversation": "Prestať ignorovať konverzáciu",
|
||||
"status.unpin": "Odopnúť z profilu",
|
||||
"tabs_bar.compose": "Napísať",
|
||||
"tabs_bar.federated_timeline": "Federovaná",
|
||||
"tabs_bar.home": "Domov",
|
||||
"tabs_bar.local_timeline": "Lokálna",
|
||||
@ -259,7 +263,7 @@
|
||||
"upload_area.title": "Ťahaj a pusti pre nahratie",
|
||||
"upload_button.label": "Pridať médiá",
|
||||
"upload_form.description": "Opis pre slabo vidiacich",
|
||||
"upload_form.focus": "Crop",
|
||||
"upload_form.focus": "Vystrihni",
|
||||
"upload_form.undo": "Navrátiť",
|
||||
"upload_progress.label": "Nahráva sa...",
|
||||
"video.close": "Zavrieť video",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Blokiraj korisnika @{name}",
|
||||
"account.block_domain": "Sakrij sve sa domena {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Navedene informacije možda ne odslikavaju korisnički profil u potpunosti.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Izmeni profil",
|
||||
"account.follow": "Zaprati",
|
||||
"account.followers": "Pratioca",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} se pomerio na:",
|
||||
"account.mute": "Ućutkaj korisnika @{name}",
|
||||
"account.mute_notifications": "Isključi obaveštenja od korisnika @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Statusa",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Prijavi @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Prijavljujem {target}",
|
||||
"search.placeholder": "Pretraga",
|
||||
"search_popout.search_format": "Napredni format pretrage",
|
||||
"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": "hešteg",
|
||||
"search_popout.tips.status": "status",
|
||||
"search_popout.tips.text": "Traženjem običnog teksta ćete dobiti sva pronađena imena, sva korisnička imena i sve nađene heštegove",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Ućutkaj prepisku",
|
||||
"status.open": "Proširi ovaj status",
|
||||
"status.pin": "Prikači na profil",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Podrži",
|
||||
"status.reblogged_by": "{name} podržao(la)",
|
||||
"status.reply": "Odgovori",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Prikaži više",
|
||||
"status.unmute_conversation": "Uključi prepisku",
|
||||
"status.unpin": "Otkači sa profila",
|
||||
"tabs_bar.compose": "Napiši",
|
||||
"tabs_bar.federated_timeline": "Federisano",
|
||||
"tabs_bar.home": "Početna",
|
||||
"tabs_bar.local_timeline": "Lokalno",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Блокирај корисника @{name}",
|
||||
"account.block_domain": "Сакриј све са домена {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Наведене информације можда не одсликавају кориснички профил у потпуности.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Измени профил",
|
||||
"account.follow": "Запрати",
|
||||
"account.followers": "Пратиоца",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} се померио на:",
|
||||
"account.mute": "Ућуткај корисника @{name}",
|
||||
"account.mute_notifications": "Искључи обавештења од корисника @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Статуса",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Пријави @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Пријављујем {target}",
|
||||
"search.placeholder": "Претрага",
|
||||
"search_popout.search_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": "хештег",
|
||||
"search_popout.tips.status": "статус",
|
||||
"search_popout.tips.text": "Тражењем обичног текста ћете добити сва пронађена имена, сва корисничка имена и све нађене хештегове",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Ућуткај преписку",
|
||||
"status.open": "Прошири овај статус",
|
||||
"status.pin": "Прикачи на профил",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Подржи",
|
||||
"status.reblogged_by": "{name} подржао(ла)",
|
||||
"status.reply": "Одговори",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Прикажи више",
|
||||
"status.unmute_conversation": "Укључи преписку",
|
||||
"status.unpin": "Откачи са профила",
|
||||
"tabs_bar.compose": "Напиши",
|
||||
"tabs_bar.federated_timeline": "Федерисано",
|
||||
"tabs_bar.home": "Почетна",
|
||||
"tabs_bar.local_timeline": "Локално",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Blockera @{name}",
|
||||
"account.block_domain": "Dölj allt från {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Informationen nedan kan spegla användarens profil ofullständigt.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Redigera profil",
|
||||
"account.follow": "Följ",
|
||||
"account.followers": "Följare",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} har flyttat till:",
|
||||
"account.mute": "Tysta @{name}",
|
||||
"account.mute_notifications": "Stäng av notifieringar från @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Inlägg",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Rapportera @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Rapporterar {target}",
|
||||
"search.placeholder": "Sök",
|
||||
"search_popout.search_format": "Avancerat sökformat",
|
||||
"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.text": "Enkel text returnerar matchande visningsnamn, användarnamn och hashtags",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Tysta konversation",
|
||||
"status.open": "Utvidga denna status",
|
||||
"status.pin": "Fäst i profil",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Knuff",
|
||||
"status.reblogged_by": "{name} knuffade",
|
||||
"status.reply": "Svara",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Visa mer",
|
||||
"status.unmute_conversation": "Öppna konversation",
|
||||
"status.unpin": "Ångra fäst i profil",
|
||||
"tabs_bar.compose": "Skriv",
|
||||
"tabs_bar.federated_timeline": "Förenad",
|
||||
"tabs_bar.home": "Hem",
|
||||
"tabs_bar.local_timeline": "Lokal",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Block @{name}",
|
||||
"account.block_domain": "Hide everything from {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Edit profile",
|
||||
"account.follow": "Follow",
|
||||
"account.followers": "Followers",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} has moved to:",
|
||||
"account.mute": "Mute @{name}",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Posts",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Report @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Reporting",
|
||||
"search.placeholder": "Search",
|
||||
"search_popout.search_format": "Advanced search 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.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Expand this status",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Boost",
|
||||
"status.reblogged_by": "{name} boosted",
|
||||
"status.reply": "Reply",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Show more",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"tabs_bar.compose": "Compose",
|
||||
"tabs_bar.federated_timeline": "Federated",
|
||||
"tabs_bar.home": "Home",
|
||||
"tabs_bar.local_timeline": "Local",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Engelle @{name}",
|
||||
"account.block_domain": "Hide everything from {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Profili düzenle",
|
||||
"account.follow": "Takip et",
|
||||
"account.followers": "Takipçiler",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} has moved to:",
|
||||
"account.mute": "Sustur @{name}",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Gönderiler",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Rapor et @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Raporlama",
|
||||
"search.placeholder": "Ara",
|
||||
"search_popout.search_format": "Advanced search 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.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Bu gönderiyi genişlet",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Boost'la",
|
||||
"status.reblogged_by": "{name} boost etti",
|
||||
"status.reply": "Cevapla",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Daha fazlası",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"tabs_bar.compose": "Oluştur",
|
||||
"tabs_bar.federated_timeline": "Federe",
|
||||
"tabs_bar.home": "Ana sayfa",
|
||||
"tabs_bar.local_timeline": "Yerel",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "Заблокувати",
|
||||
"account.block_domain": "Заглушити {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Налаштування профілю",
|
||||
"account.follow": "Підписатися",
|
||||
"account.followers": "Підписники",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} has moved to:",
|
||||
"account.mute": "Заглушити",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Пости",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "Поскаржитися",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "Скаржимося на",
|
||||
"search.placeholder": "Пошук",
|
||||
"search_popout.search_format": "Advanced search 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.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "Заглушити діалог",
|
||||
"status.open": "Розгорнути допис",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "Передмухнути",
|
||||
"status.reblogged_by": "{name} передмухнув(-ла)",
|
||||
"status.reply": "Відповісти",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "Розгорнути",
|
||||
"status.unmute_conversation": "Зняти глушення з діалогу",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"tabs_bar.compose": "Написати",
|
||||
"tabs_bar.federated_timeline": "Глобальна",
|
||||
"tabs_bar.home": "Головна",
|
||||
"tabs_bar.local_timeline": "Локальна",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "屏蔽 @{name}",
|
||||
"account.block_domain": "隐藏来自 {domain} 的内容",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "此处显示的信息可能不是全部内容。",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "修改个人资料",
|
||||
"account.follow": "关注",
|
||||
"account.followers": "关注者",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} 已经迁移到:",
|
||||
"account.mute": "隐藏 @{name}",
|
||||
"account.mute_notifications": "隐藏来自 @{name} 的通知",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "嘟文",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "举报 @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "举报 {target}",
|
||||
"search.placeholder": "搜索",
|
||||
"search_popout.search_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": "话题标签",
|
||||
"search_popout.tips.status": "嘟文",
|
||||
"search_popout.tips.text": "使用普通字符进行搜索将会返回昵称、用户名和话题标签",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "隐藏此对话",
|
||||
"status.open": "展开嘟文",
|
||||
"status.pin": "在个人资料页面置顶",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "转嘟",
|
||||
"status.reblogged_by": "{name} 转嘟了",
|
||||
"status.reply": "回复",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "显示内容",
|
||||
"status.unmute_conversation": "不再隐藏此对话",
|
||||
"status.unpin": "在个人资料页面取消置顶",
|
||||
"tabs_bar.compose": "撰写",
|
||||
"tabs_bar.federated_timeline": "跨站",
|
||||
"tabs_bar.home": "主页",
|
||||
"tabs_bar.local_timeline": "本站",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "封鎖 @{name}",
|
||||
"account.block_domain": "隱藏來自 {domain} 的一切文章",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "下列資料不一定完整。",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "修改個人資料",
|
||||
"account.follow": "關注",
|
||||
"account.followers": "關注的人",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} has moved to:",
|
||||
"account.mute": "將 @{name} 靜音",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "文章",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "舉報 @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "舉報",
|
||||
"search.placeholder": "搜尋",
|
||||
"search_popout.search_format": "Advanced search 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.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "靜音對話",
|
||||
"status.open": "展開文章",
|
||||
"status.pin": "置頂到資料頁",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "轉推",
|
||||
"status.reblogged_by": "{name} 轉推",
|
||||
"status.reply": "回應",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "顯示更多",
|
||||
"status.unmute_conversation": "解禁對話",
|
||||
"status.unpin": "解除置頂",
|
||||
"tabs_bar.compose": "撰寫",
|
||||
"tabs_bar.federated_timeline": "跨站",
|
||||
"tabs_bar.home": "主頁",
|
||||
"tabs_bar.local_timeline": "本站",
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"account.block": "封鎖 @{name}",
|
||||
"account.block_domain": "隱藏來自 {domain} 的一切貼文",
|
||||
"account.blocked": "Blocked",
|
||||
"account.disclaimer_full": "下列資料不一定完整。",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "編輯用者資訊",
|
||||
"account.follow": "關注",
|
||||
"account.followers": "專注者",
|
||||
@ -13,6 +15,7 @@
|
||||
"account.moved_to": "{name} has moved to:",
|
||||
"account.mute": "消音 @{name}",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "貼文",
|
||||
"account.posts_with_replies": "Toots with replies",
|
||||
"account.report": "檢舉 @{name}",
|
||||
@ -216,6 +219,7 @@
|
||||
"report.target": "通報中",
|
||||
"search.placeholder": "搜尋",
|
||||
"search_popout.search_format": "Advanced search 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.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
@ -238,6 +242,7 @@
|
||||
"status.mute_conversation": "消音對話",
|
||||
"status.open": "展開這個狀態",
|
||||
"status.pin": "置頂到個人資訊頁",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.reblog": "轉推",
|
||||
"status.reblogged_by": "{name} 轉推了",
|
||||
"status.reply": "回應",
|
||||
@ -250,7 +255,6 @@
|
||||
"status.show_more": "看更多",
|
||||
"status.unmute_conversation": "不消音對話",
|
||||
"status.unpin": "解除置頂",
|
||||
"tabs_bar.compose": "編輯",
|
||||
"tabs_bar.federated_timeline": "聯盟",
|
||||
"tabs_bar.home": "家",
|
||||
"tabs_bar.local_timeline": "本地",
|
||||
|
@ -102,7 +102,7 @@ const initialState = ImmutableMap();
|
||||
export default function accounts(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case STORE_HYDRATE:
|
||||
return state.merge(action.state.get('accounts'));
|
||||
return normalizeAccounts(state, Object.values(action.state.get('accounts').toJS()));
|
||||
case ACCOUNT_FETCH_SUCCESS:
|
||||
case NOTIFICATIONS_UPDATE:
|
||||
return normalizeAccount(state, action.account);
|
||||
|
@ -16,6 +16,8 @@ import {
|
||||
COMPOSE_SUGGESTIONS_CLEAR,
|
||||
COMPOSE_SUGGESTIONS_READY,
|
||||
COMPOSE_SUGGESTION_SELECT,
|
||||
COMPOSE_SUGGESTION_TAGS_UPDATE,
|
||||
COMPOSE_TAG_HISTORY_UPDATE,
|
||||
COMPOSE_SENSITIVITY_CHANGE,
|
||||
COMPOSE_SPOILERNESS_CHANGE,
|
||||
COMPOSE_SPOILER_TEXT_CHANGE,
|
||||
@ -54,6 +56,7 @@ const initialState = ImmutableMap({
|
||||
default_sensitive: false,
|
||||
resetFileKey: Math.floor((Math.random() * 0x10000)),
|
||||
idempotencyKey: null,
|
||||
tagHistory: ImmutableList(),
|
||||
});
|
||||
|
||||
function statusToTextMentions(state, status) {
|
||||
@ -87,7 +90,6 @@ function appendMedia(state, media) {
|
||||
map.update('media_attachments', list => list.push(media));
|
||||
map.set('is_uploading', false);
|
||||
map.set('resetFileKey', Math.floor((Math.random() * 0x10000)));
|
||||
map.update('text', oldText => `${oldText.trim()} ${media.get('text_url')}`);
|
||||
map.set('focusDate', new Date());
|
||||
map.set('idempotencyKey', uuid());
|
||||
|
||||
@ -98,12 +100,10 @@ function appendMedia(state, media) {
|
||||
};
|
||||
|
||||
function removeMedia(state, mediaId) {
|
||||
const media = state.get('media_attachments').find(item => item.get('id') === mediaId);
|
||||
const prevSize = state.get('media_attachments').size;
|
||||
|
||||
return state.withMutations(map => {
|
||||
map.update('media_attachments', list => list.filterNot(item => item.get('id') === mediaId));
|
||||
map.update('text', text => text.replace(media.get('text_url'), '').trim());
|
||||
map.set('idempotencyKey', uuid());
|
||||
|
||||
if (prevSize === 1) {
|
||||
@ -122,6 +122,18 @@ const insertSuggestion = (state, position, token, completion) => {
|
||||
});
|
||||
};
|
||||
|
||||
const updateSuggestionTags = (state, token) => {
|
||||
const prefix = token.slice(1);
|
||||
|
||||
return state.merge({
|
||||
suggestions: state.get('tagHistory')
|
||||
.filter(tag => tag.startsWith(prefix))
|
||||
.slice(0, 4)
|
||||
.map(tag => '#' + tag),
|
||||
suggestion_token: token,
|
||||
});
|
||||
};
|
||||
|
||||
const insertEmoji = (state, position, emojiData) => {
|
||||
const emoji = emojiData.native;
|
||||
|
||||
@ -252,6 +264,10 @@ export default function compose(state = initialState, action) {
|
||||
return state.set('suggestions', ImmutableList(action.accounts ? action.accounts.map(item => item.id) : action.emojis)).set('suggestion_token', action.token);
|
||||
case COMPOSE_SUGGESTION_SELECT:
|
||||
return insertSuggestion(state, action.position, action.token, action.completion);
|
||||
case COMPOSE_SUGGESTION_TAGS_UPDATE:
|
||||
return updateSuggestionTags(state, action.token);
|
||||
case COMPOSE_TAG_HISTORY_UPDATE:
|
||||
return state.set('tagHistory', fromJS(action.tags));
|
||||
case TIMELINE_DELETE:
|
||||
if (action.id === state.get('in_reply_to')) {
|
||||
return state.set('in_reply_to', null);
|
||||
|
18
app/javascript/mastodon/reducers/dropdown_menu.js
Normal file
18
app/javascript/mastodon/reducers/dropdown_menu.js
Normal file
@ -0,0 +1,18 @@
|
||||
import Immutable from 'immutable';
|
||||
import {
|
||||
DROPDOWN_MENU_OPEN,
|
||||
DROPDOWN_MENU_CLOSE,
|
||||
} from '../actions/dropdown_menu';
|
||||
|
||||
const initialState = Immutable.Map({ openId: null, placement: null });
|
||||
|
||||
export default function dropdownMenu(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case DROPDOWN_MENU_OPEN:
|
||||
return state.merge({ openId: action.id, placement: action.placement });
|
||||
case DROPDOWN_MENU_CLOSE:
|
||||
return state.get('openId') === action.id ? state.set('openId', null) : state;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import { combineReducers } from 'redux-immutable';
|
||||
import dropdown_menu from './dropdown_menu';
|
||||
import timelines from './timelines';
|
||||
import meta from './meta';
|
||||
import alerts from './alerts';
|
||||
@ -26,6 +27,7 @@ import lists from './lists';
|
||||
import listEditor from './list_editor';
|
||||
|
||||
const reducers = {
|
||||
dropdown_menu,
|
||||
timelines,
|
||||
meta,
|
||||
alerts,
|
||||
|
@ -44,3 +44,4 @@ export default class Settings {
|
||||
}
|
||||
|
||||
export const pushNotificationsSetting = new Settings('mastodon_push_notification_data');
|
||||
export const tagHistory = new Settings('mastodon_tag_history');
|
||||
|
@ -194,6 +194,28 @@ $small-breakpoint: 960px;
|
||||
}
|
||||
}
|
||||
|
||||
.closed-registrations-message {
|
||||
margin-top: 20px;
|
||||
|
||||
&,
|
||||
p {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: $ui-primary-color;
|
||||
margin-bottom: 0;
|
||||
|
||||
a {
|
||||
color: $ui-highlight-color;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
em {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
@ -832,8 +854,13 @@ $small-breakpoint: 960px;
|
||||
}
|
||||
|
||||
&__features {
|
||||
& > p {
|
||||
padding-right: 60px;
|
||||
}
|
||||
|
||||
.features-list {
|
||||
margin: 40px 0 !important;
|
||||
margin: 40px 0;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
&__action {
|
||||
@ -842,17 +869,11 @@ $small-breakpoint: 960px;
|
||||
}
|
||||
|
||||
.features-list {
|
||||
margin-top: 20px;
|
||||
|
||||
.features-list__row {
|
||||
display: flex;
|
||||
padding: 10px 0;
|
||||
justify-content: space-between;
|
||||
|
||||
&:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.visual {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
@ -878,6 +899,14 @@ $small-breakpoint: 960px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: $small-breakpoint) {
|
||||
display: grid;
|
||||
grid-gap: 30px;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-auto-columns: 50%;
|
||||
grid-auto-rows: max-content;
|
||||
}
|
||||
}
|
||||
|
||||
.extended-description {
|
||||
|
@ -105,6 +105,16 @@
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
text-transform: uppercase;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: $ui-primary-color;
|
||||
padding-bottom: 8px;
|
||||
margin-bottom: 8px;
|
||||
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 16px;
|
||||
color: $ui-secondary-color;
|
||||
|
@ -862,12 +862,27 @@
|
||||
border-bottom: 1px solid $ui-secondary-color;
|
||||
display: flex;
|
||||
|
||||
.status-check-box__status {
|
||||
margin: 10px 0 10px 10px;
|
||||
flex: 1;
|
||||
|
||||
.media-gallery {
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.status__content {
|
||||
flex: 1 1 auto;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding: 0;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.video-player {
|
||||
margin-top: 8px;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.media-gallery__item-thumbnail {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1418,36 +1433,29 @@
|
||||
|
||||
.image-loader {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&.image-loader--loading {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
|
||||
.image-loader__preview-canvas {
|
||||
filter: blur(2px);
|
||||
}
|
||||
}
|
||||
|
||||
.image-loader__img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
&.image-loader--amorphous {
|
||||
position: static;
|
||||
|
||||
.image-loader__preview-canvas {
|
||||
&.image-loader--amorphous .image-loader__preview-canvas {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.image-loader__img {
|
||||
position: static;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
.zoomable-image {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.navigation-bar {
|
||||
@ -2784,29 +2792,6 @@ a.status-card {
|
||||
}
|
||||
}
|
||||
|
||||
.modal-container__nav {
|
||||
align-items: center;
|
||||
background: rgba($base-overlay-background, 0.5);
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
color: $primary-text-color;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 24px;
|
||||
height: 100%;
|
||||
padding: 30px 15px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.modal-container__nav--left {
|
||||
left: -61px;
|
||||
}
|
||||
|
||||
.modal-container__nav--right {
|
||||
right: -61px;
|
||||
}
|
||||
|
||||
.account--follows-info {
|
||||
color: $primary-text-color;
|
||||
position: absolute;
|
||||
@ -2823,6 +2808,22 @@ a.status-card {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.account--muting-info {
|
||||
color: $primary-text-color;
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 10px;
|
||||
opacity: 0.7;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
background-color: rgba($base-overlay-background, 0.4);
|
||||
text-transform: uppercase;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.account--action-button {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
@ -3403,29 +3404,27 @@ a.status-card {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.video-modal {
|
||||
max-width: 100vw;
|
||||
max-height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.media-modal {
|
||||
max-width: 80vw;
|
||||
max-height: 80vh;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
.extended-video-player,
|
||||
img,
|
||||
canvas,
|
||||
video {
|
||||
max-width: 80vw;
|
||||
max-height: 80vh;
|
||||
max-width: 100vw;
|
||||
max-height: 100vh;
|
||||
width: auto;
|
||||
height: auto;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.extended-video-player,
|
||||
video {
|
||||
display: flex;
|
||||
width: 80vw;
|
||||
height: 80vh;
|
||||
}
|
||||
|
||||
img,
|
||||
canvas {
|
||||
display: block;
|
||||
@ -3434,12 +3433,65 @@ a.status-card {
|
||||
}
|
||||
|
||||
.react-swipeable-view-container {
|
||||
max-width: 80vw;
|
||||
width: 100vw;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.media-modal__content {
|
||||
background: $base-overlay-background;
|
||||
.media-modal__closer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.media-modal__navigation {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s linear;
|
||||
will-change: opacity;
|
||||
|
||||
* {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
&.media-modal__navigation--hidden {
|
||||
opacity: 0;
|
||||
|
||||
* {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-modal__nav {
|
||||
background: rgba($base-overlay-background, 0.5);
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
color: $primary-text-color;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 24px;
|
||||
height: 20vmax;
|
||||
margin: auto 0;
|
||||
padding: 30px 15px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.media-modal__nav--left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.media-modal__nav--right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.media-modal__pagination {
|
||||
@ -3447,7 +3499,8 @@ a.status-card {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -40px;
|
||||
bottom: 20px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.media-modal__page-dot {
|
||||
@ -3471,8 +3524,8 @@ a.status-card {
|
||||
|
||||
.media-modal__close {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 4px;
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
@ -4169,9 +4222,8 @@ a.status-card {
|
||||
border-radius: 4px;
|
||||
margin-top: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.attachment-list__icon {
|
||||
&__icon {
|
||||
flex: 0 0 auto;
|
||||
color: $ui-base-lighter-color;
|
||||
padding: 8px 18px;
|
||||
@ -4186,9 +4238,9 @@ a.status-card {
|
||||
.fa {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-list__list {
|
||||
&__list {
|
||||
list-style: none;
|
||||
padding: 4px 0;
|
||||
padding-left: 8px;
|
||||
@ -4210,6 +4262,21 @@ a.status-card {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.compact {
|
||||
border: 0;
|
||||
margin-top: 4px;
|
||||
|
||||
.attachment-list__list {
|
||||
padding: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.fa {
|
||||
color: $ui-base-lighter-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Media Gallery */
|
||||
@ -4613,7 +4680,7 @@ a.status-card {
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
position: absolute;
|
||||
color: inherit;
|
||||
color: $ui-primary-color;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
|
||||
@ -4621,6 +4688,7 @@ a.status-card {
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: 0;
|
||||
color: $ui-secondary-color;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
@ -4632,6 +4700,14 @@ a.status-card {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__icons {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.account__section-headline {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user