Compare commits
98 Commits
5c5d81cf26
...
11900689bf
Author | SHA1 | Date |
---|---|---|
Essem | 11900689bf | |
Essem | 9b54b27bf7 | |
Essem | 40ae5aed66 | |
Essem | 13c9fa62fa | |
Essem | ba37843ec2 | |
Essem | b07f5f89d4 | |
Essem | aae6e1b1fd | |
Essem | a12b2ad57a | |
Essem | d54affc107 | |
Essem | 0d68ecf75d | |
Essem | 4311fff076 | |
Essem | a2ab3f541c | |
Essem | 11bebd28a2 | |
Essem | 09b64d761a | |
Essem | 0838432237 | |
Essem | a27b838741 | |
Essem | 4d832522b9 | |
Essem | 03ea7618ad | |
Essem | 22fc82dfee | |
Essem | 938175d5e8 | |
Essem | 28ecb2a4be | |
Essem | 14c0e46ef4 | |
Essem | 227a8d71b3 | |
Claire | c8e5e13c89 | |
Eugen Rochko | 91531e9586 | |
Eugen Rochko | fc533cfad3 | |
Eugen Rochko | b55bbfa2b3 | |
Eugen Rochko | 1ae08ae257 | |
Claire | 0e76b919b5 | |
Claire | a844a6a577 | |
Claire | c3a128f31e | |
Claire | afaad0755f | |
Claire | 1d1c3a808a | |
Claire | f635cde756 | |
Claire | 0f8b33238f | |
Renaud Chaput | 576c085ea0 | |
Claire | 777984faeb | |
Claire | f14b6f3d99 | |
Claire | 903dc53522 | |
Eugen Rochko | 375af259a2 | |
Claire | 67842ffb22 | |
Claire | 0f966209ca | |
Claire | f2b23aa5f3 | |
Eugen Rochko | 0cea7a623b | |
Eugen Rochko | 29f9dc742e | |
Eugen Rochko | dd061291b1 | |
renovate[bot] | 766c1fea20 | |
renovate[bot] | 55e2c827bd | |
renovate[bot] | 45f8364cd1 | |
renovate[bot] | bbf36836b6 | |
github-actions[bot] | 799e3be9bd | |
Eugen Rochko | 8e7e86ee35 | |
Renaud Chaput | 6c381f20b1 | |
Claire | 81a04ac25c | |
Claire | 37ca59815c | |
renovate[bot] | 119c7aa0df | |
Claire | 58376eedda | |
Claire | d71d26a3c9 | |
Claire | de6c9e0fcd | |
Claire | 387c78ddf9 | |
Claire | dfa43707eb | |
Matt Jankowski | 34f293475e | |
github-actions[bot] | 5db5fa879b | |
Matt Jankowski | 8c1d29df7e | |
Renaud Chaput | ec1e770fea | |
Claire | 05eda8d193 | |
Claire | 70a8fcf07d | |
Matt Jankowski | 142c018cfa | |
renovate[bot] | ec6d016da1 | |
renovate[bot] | 7f5e930bd2 | |
renovate[bot] | f5444c8fe4 | |
renovate[bot] | 05abefe989 | |
Claire | 814a48517f | |
Matt Jankowski | a59f5694fe | |
Claire | 75f34b80a8 | |
renovate[bot] | 1df00d4e76 | |
renovate[bot] | 2ec3fcaffe | |
renovate[bot] | a506b09de0 | |
renovate[bot] | 1feb228275 | |
Nick Schonning | d13cdced1e | |
Claire | 885d0faf83 | |
github-actions[bot] | c007dd5dd2 | |
Matt Jankowski | 77897cd24c | |
Matt Jankowski | 718ee72c80 | |
Matt Jankowski | cdd168f5d3 | |
renovate[bot] | 01464074c9 | |
renovate[bot] | 3f363c61bc | |
Claire | 7434c9c276 | |
Matt Jankowski | 39bac24cb7 | |
Matt Jankowski | 62722238c9 | |
Eugen Rochko | be52633ee4 | |
Claire | f4d753aedf | |
Claire | 98a2bb8be2 | |
Claire | 954b470fbc | |
Claire | d4449cc682 | |
renovate[bot] | 99c9db5f67 | |
renovate[bot] | 27a6fa7b0e | |
Claire | 44bf7b8128 |
|
@ -274,6 +274,9 @@ MAX_POLL_OPTIONS=5
|
|||
# Maximum allowed poll option characters
|
||||
MAX_POLL_OPTION_CHARS=100
|
||||
|
||||
# Maximum number of emoji reactions per toot and user (minimum 1)
|
||||
MAX_REACTIONS=1
|
||||
|
||||
# Maximum image and video/audio upload sizes
|
||||
# Units are in bytes
|
||||
# 1048576 bytes equals 1 megabyte
|
||||
|
|
|
@ -125,6 +125,22 @@
|
|||
],
|
||||
groupName: null, // We dont want them to belong to any group
|
||||
},
|
||||
{
|
||||
// Group all RuboCop packages with `rubocop` in the same PR
|
||||
matchManagers: ['bundler'],
|
||||
matchPackageNames: ['rubocop'],
|
||||
matchPackagePrefixes: ['rubocop-'],
|
||||
matchUpdateTypes: ['patch', 'minor'],
|
||||
groupName: 'RuboCop (non-major)',
|
||||
},
|
||||
{
|
||||
// Group all RSpec packages with `rspec` in the same PR
|
||||
matchManagers: ['bundler'],
|
||||
matchPackageNames: ['rspec'],
|
||||
matchPackagePrefixes: ['rspec-'],
|
||||
matchUpdateTypes: ['patch', 'minor'],
|
||||
groupName: 'RSpec (non-major)',
|
||||
},
|
||||
// Add labels depending on package manager
|
||||
{ matchManagers: ['npm', 'nvm'], addLabels: ['javascript'] },
|
||||
{ matchManagers: ['bundler', 'ruby-version'], addLabels: ['ruby'] },
|
||||
|
|
|
@ -114,6 +114,7 @@ jobs:
|
|||
- '3.0'
|
||||
- '3.1'
|
||||
- '.ruby-version'
|
||||
- '3.3'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
@ -189,6 +190,7 @@ jobs:
|
|||
- '3.0'
|
||||
- '3.1'
|
||||
- '.ruby-version'
|
||||
- '3.3'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
@ -288,6 +290,7 @@ jobs:
|
|||
- '3.0'
|
||||
- '3.1'
|
||||
- '.ruby-version'
|
||||
- '3.3'
|
||||
search-image:
|
||||
- docker.elastic.co/elasticsearch/elasticsearch:7.17.13
|
||||
include:
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
inherits_from: .haml-lint_todo.yml
|
||||
|
||||
exclude:
|
||||
- 'vendor/**/*'
|
||||
- lib/templates/haml/scaffold/_form.html.haml
|
||||
|
|
3
Gemfile
3
Gemfile
|
@ -58,6 +58,7 @@ gem 'htmlentities', '~> 4.3'
|
|||
gem 'http', '~> 5.1'
|
||||
gem 'http_accept_language', '~> 2.1'
|
||||
gem 'httplog', '~> 1.6.2'
|
||||
gem 'i18n', '1.14.1' # TODO: Remove version when resolved: https://github.com/glebm/i18n-tasks/issues/552 / https://github.com/ruby-i18n/i18n/pull/688
|
||||
gem 'idn-ruby', require: 'idn'
|
||||
gem 'inline_svg'
|
||||
gem 'kaminari', '~> 1.2'
|
||||
|
@ -89,7 +90,7 @@ gem 'sidekiq-bulk', '~> 0.2.0'
|
|||
gem 'simple-navigation', '~> 4.4'
|
||||
gem 'simple_form', '~> 5.2'
|
||||
gem 'stoplight', '~> 3.0.1'
|
||||
gem 'strong_migrations', '1.7.0'
|
||||
gem 'strong_migrations', '1.8.0'
|
||||
gem 'tty-prompt', '~> 0.23', require: false
|
||||
gem 'twitter-text', '~> 3.1.0'
|
||||
gem 'tzinfo-data', '~> 1.2023'
|
||||
|
|
50
Gemfile.lock
50
Gemfile.lock
|
@ -139,7 +139,7 @@ GEM
|
|||
erubi (~> 1.4)
|
||||
parser (>= 2.4)
|
||||
smart_properties
|
||||
bigdecimal (3.1.6)
|
||||
bigdecimal (3.1.7)
|
||||
bindata (2.4.15)
|
||||
binding_of_caller (1.0.0)
|
||||
debug_inspector (>= 0.0.1)
|
||||
|
@ -213,7 +213,7 @@ GEM
|
|||
devise_pam_authenticatable2 (9.2.0)
|
||||
devise (>= 4.0.0)
|
||||
rpam2 (~> 4.0)
|
||||
diff-lcs (1.5.0)
|
||||
diff-lcs (1.5.1)
|
||||
discard (1.3.0)
|
||||
activerecord (>= 4.2, < 8)
|
||||
docile (1.4.0)
|
||||
|
@ -225,8 +225,7 @@ GEM
|
|||
dotenv-rails (2.8.1)
|
||||
dotenv (= 2.8.1)
|
||||
railties (>= 3.2)
|
||||
drb (2.2.0)
|
||||
ruby2_keywords
|
||||
drb (2.2.1)
|
||||
ed25519 (1.3.0)
|
||||
elasticsearch (7.13.3)
|
||||
elasticsearch-api (= 7.13.3)
|
||||
|
@ -438,7 +437,7 @@ GEM
|
|||
mime-types-data (3.2023.1205)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.5)
|
||||
minitest (5.21.2)
|
||||
minitest (5.22.3)
|
||||
msgpack (1.7.2)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.3.0)
|
||||
|
@ -500,7 +499,7 @@ GEM
|
|||
openssl-signature_algorithm (1.3.0)
|
||||
openssl (> 2.0)
|
||||
orm_adapter (0.5.0)
|
||||
ox (2.14.17)
|
||||
ox (2.14.18)
|
||||
parallel (1.24.0)
|
||||
parser (3.3.0.5)
|
||||
ast (~> 2.4.1)
|
||||
|
@ -535,7 +534,7 @@ GEM
|
|||
activesupport (>= 3.0.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.7.3)
|
||||
rack (2.2.8.1)
|
||||
rack (2.2.9)
|
||||
rack-attack (6.7.0)
|
||||
rack (>= 1.0, < 4)
|
||||
rack-cors (2.0.2)
|
||||
|
@ -583,7 +582,7 @@ GEM
|
|||
rails-html-sanitizer (1.6.0)
|
||||
loofah (~> 2.21)
|
||||
nokogiri (~> 1.14)
|
||||
rails-i18n (7.0.8)
|
||||
rails-i18n (7.0.9)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 6.0.0, < 8)
|
||||
railties (7.1.3.2)
|
||||
|
@ -601,7 +600,7 @@ GEM
|
|||
link_header (~> 0.0, >= 0.0.8)
|
||||
rdf-normalize (0.7.0)
|
||||
rdf (~> 3.3)
|
||||
rdoc (6.6.2)
|
||||
rdoc (6.6.3.1)
|
||||
psych (>= 4.0.0)
|
||||
redcarpet (3.6.0)
|
||||
redis (4.8.1)
|
||||
|
@ -635,21 +634,21 @@ GEM
|
|||
rspec-mocks (3.13.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-rails (6.1.1)
|
||||
rspec-rails (6.1.2)
|
||||
actionpack (>= 6.1)
|
||||
activesupport (>= 6.1)
|
||||
railties (>= 6.1)
|
||||
rspec-core (~> 3.12)
|
||||
rspec-expectations (~> 3.12)
|
||||
rspec-mocks (~> 3.12)
|
||||
rspec-support (~> 3.12)
|
||||
rspec-core (~> 3.13)
|
||||
rspec-expectations (~> 3.13)
|
||||
rspec-mocks (~> 3.13)
|
||||
rspec-support (~> 3.13)
|
||||
rspec-sidekiq (4.1.0)
|
||||
rspec-core (~> 3.0)
|
||||
rspec-expectations (~> 3.0)
|
||||
rspec-mocks (~> 3.0)
|
||||
sidekiq (>= 5, < 8)
|
||||
rspec-support (3.13.1)
|
||||
rubocop (1.60.2)
|
||||
rubocop (1.62.1)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
parallel (~> 1.10)
|
||||
|
@ -657,24 +656,24 @@ GEM
|
|||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.30.0, < 2.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.30.0)
|
||||
parser (>= 3.2.1.0)
|
||||
rubocop-ast (1.31.2)
|
||||
parser (>= 3.3.0.4)
|
||||
rubocop-capybara (2.20.0)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-factory_bot (2.25.0)
|
||||
rubocop (~> 1.33)
|
||||
rubocop-factory_bot (2.25.1)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-performance (1.20.2)
|
||||
rubocop (>= 1.48.1, < 2.0)
|
||||
rubocop-ast (>= 1.30.0, < 2.0)
|
||||
rubocop-rails (2.23.1)
|
||||
rubocop-rails (2.24.0)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
rubocop-ast (>= 1.30.0, < 2.0)
|
||||
rubocop-rspec (2.26.1)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-rspec (2.27.1)
|
||||
rubocop (~> 1.40)
|
||||
rubocop-capybara (~> 2.17)
|
||||
rubocop-factory_bot (~> 2.22)
|
||||
|
@ -735,7 +734,7 @@ GEM
|
|||
stoplight (3.0.2)
|
||||
redlock (~> 1.0)
|
||||
stringio (3.1.0)
|
||||
strong_migrations (1.7.0)
|
||||
strong_migrations (1.8.0)
|
||||
activerecord (>= 5.2)
|
||||
swd (1.3.0)
|
||||
activesupport (>= 3)
|
||||
|
@ -866,6 +865,7 @@ DEPENDENCIES
|
|||
http (~> 5.1)
|
||||
http_accept_language (~> 2.1)
|
||||
httplog (~> 1.6.2)
|
||||
i18n (= 1.14.1)
|
||||
i18n-tasks (~> 1.0)
|
||||
idn-ruby
|
||||
inline_svg
|
||||
|
@ -940,7 +940,7 @@ DEPENDENCIES
|
|||
simplecov-lcov (~> 0.8)
|
||||
stackprof
|
||||
stoplight (~> 3.0.1)
|
||||
strong_migrations (= 1.7.0)
|
||||
strong_migrations (= 1.8.0)
|
||||
test-prof
|
||||
thor (~> 1.2)
|
||||
tty-prompt (~> 0.23)
|
||||
|
|
|
@ -12,6 +12,10 @@ class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController
|
|||
private
|
||||
|
||||
def set_recently_used_tags
|
||||
@recently_used_tags = Tag.recently_used(current_account).where.not(id: current_account.featured_tags).limit(10)
|
||||
@recently_used_tags = Tag.recently_used(current_account).where.not(id: featured_tag_ids).limit(10)
|
||||
end
|
||||
|
||||
def featured_tag_ids
|
||||
current_account.featured_tags.pluck(:tag_id)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::ReactionsController < Api::V1::Statuses::BaseController
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:favourites' }
|
||||
before_action :require_user!
|
||||
|
||||
def create
|
||||
ReactService.new.call(current_account, @status, params[:id])
|
||||
render json: @status, serializer: REST::StatusSerializer
|
||||
end
|
||||
|
||||
def destroy
|
||||
UnreactWorker.perform_async(current_account.id, @status.id, params[:id])
|
||||
|
||||
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, reactions_map: { @status.id => false })
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
end
|
|
@ -0,0 +1,61 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class SeveredRelationshipsController < ApplicationController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :set_body_classes
|
||||
before_action :set_cache_headers
|
||||
|
||||
before_action :set_event, only: [:following, :followers]
|
||||
|
||||
def index
|
||||
@events = AccountRelationshipSeveranceEvent.where(account: current_account)
|
||||
end
|
||||
|
||||
def following
|
||||
respond_to do |format|
|
||||
format.csv { send_data following_data, filename: "following-#{@event.target_name}-#{@event.created_at.to_date.iso8601}.csv" }
|
||||
end
|
||||
end
|
||||
|
||||
def followers
|
||||
respond_to do |format|
|
||||
format.csv { send_data followers_data, filename: "followers-#{@event.target_name}-#{@event.created_at.to_date.iso8601}.csv" }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_event
|
||||
@event = AccountRelationshipSeveranceEvent.find(params[:id])
|
||||
end
|
||||
|
||||
def following_data
|
||||
CSV.generate(headers: ['Account address', 'Show boosts', 'Notify on new posts', 'Languages'], write_headers: true) do |csv|
|
||||
@event.severed_relationships.active.about_local_account(current_account).includes(:remote_account).reorder(id: :desc).each do |follow|
|
||||
csv << [acct(follow.target_account), follow.show_reblogs, follow.notify, follow.languages&.join(', ')]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def followers_data
|
||||
CSV.generate(headers: ['Account address'], write_headers: true) do |csv|
|
||||
@event.severed_relationships.passive.about_local_account(current_account).includes(:remote_account).reorder(id: :desc).each do |follow|
|
||||
csv << [acct(follow.account)]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def acct(account)
|
||||
account.local? ? account.local_username_and_domain : account.acct
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'admin'
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.cache_control.replace(private: true, no_store: true)
|
||||
end
|
||||
end
|
|
@ -81,6 +81,7 @@ export const INIT_MEDIA_EDIT_MODAL = 'INIT_MEDIA_EDIT_MODAL';
|
|||
|
||||
export const COMPOSE_CHANGE_MEDIA_DESCRIPTION = 'COMPOSE_CHANGE_MEDIA_DESCRIPTION';
|
||||
export const COMPOSE_CHANGE_MEDIA_FOCUS = 'COMPOSE_CHANGE_MEDIA_FOCUS';
|
||||
export const COMPOSE_CHANGE_MEDIA_ORDER = 'COMPOSE_CHANGE_MEDIA_ORDER';
|
||||
|
||||
export const COMPOSE_SET_STATUS = 'COMPOSE_SET_STATUS';
|
||||
export const COMPOSE_FOCUS = 'COMPOSE_FOCUS';
|
||||
|
@ -845,3 +846,9 @@ export function changePollSettings(expiresIn, isMultiple) {
|
|||
isMultiple,
|
||||
};
|
||||
}
|
||||
|
||||
export const changeMediaOrder = (a, b) => ({
|
||||
type: COMPOSE_CHANGE_MEDIA_ORDER,
|
||||
a,
|
||||
b,
|
||||
});
|
||||
|
|
|
@ -51,6 +51,16 @@ export const UNBOOKMARK_REQUEST = 'UNBOOKMARKED_REQUEST';
|
|||
export const UNBOOKMARK_SUCCESS = 'UNBOOKMARKED_SUCCESS';
|
||||
export const UNBOOKMARK_FAIL = 'UNBOOKMARKED_FAIL';
|
||||
|
||||
export const REACTION_UPDATE = 'REACTION_UPDATE';
|
||||
|
||||
export const REACTION_ADD_REQUEST = 'REACTION_ADD_REQUEST';
|
||||
export const REACTION_ADD_SUCCESS = 'REACTION_ADD_SUCCESS';
|
||||
export const REACTION_ADD_FAIL = 'REACTION_ADD_FAIL';
|
||||
|
||||
export const REACTION_REMOVE_REQUEST = 'REACTION_REMOVE_REQUEST';
|
||||
export const REACTION_REMOVE_SUCCESS = 'REACTION_REMOVE_SUCCESS';
|
||||
export const REACTION_REMOVE_FAIL = 'REACTION_REMOVE_FAIL';
|
||||
|
||||
export function reblog(status, visibility) {
|
||||
return function (dispatch, getState) {
|
||||
dispatch(reblogRequest(status));
|
||||
|
@ -516,3 +526,75 @@ export function unpinFail(status, error) {
|
|||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export const addReaction = (statusId, name, url) => (dispatch, getState) => {
|
||||
const status = getState().get('statuses').get(statusId);
|
||||
let alreadyAdded = false;
|
||||
if (status) {
|
||||
const reaction = status.get('reactions').find(x => x.get('name') === name);
|
||||
if (reaction && reaction.get('me')) {
|
||||
alreadyAdded = true;
|
||||
}
|
||||
}
|
||||
if (!alreadyAdded) {
|
||||
dispatch(addReactionRequest(statusId, name, url));
|
||||
}
|
||||
|
||||
// encodeURIComponent is required for the Keycap Number Sign emoji, see:
|
||||
// <https://github.com/glitch-soc/mastodon/pull/1980#issuecomment-1345538932>
|
||||
api(getState).post(`/api/v1/statuses/${statusId}/react/${encodeURIComponent(name)}`).then(() => {
|
||||
dispatch(addReactionSuccess(statusId, name));
|
||||
}).catch(err => {
|
||||
if (!alreadyAdded) {
|
||||
dispatch(addReactionFail(statusId, name, err));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const addReactionRequest = (statusId, name, url) => ({
|
||||
type: REACTION_ADD_REQUEST,
|
||||
id: statusId,
|
||||
name,
|
||||
url,
|
||||
});
|
||||
|
||||
export const addReactionSuccess = (statusId, name) => ({
|
||||
type: REACTION_ADD_SUCCESS,
|
||||
id: statusId,
|
||||
name,
|
||||
});
|
||||
|
||||
export const addReactionFail = (statusId, name, error) => ({
|
||||
type: REACTION_ADD_FAIL,
|
||||
id: statusId,
|
||||
name,
|
||||
error,
|
||||
});
|
||||
|
||||
export const removeReaction = (statusId, name) => (dispatch, getState) => {
|
||||
dispatch(removeReactionRequest(statusId, name));
|
||||
|
||||
api(getState).post(`/api/v1/statuses/${statusId}/unreact/${encodeURIComponent(name)}`).then(() => {
|
||||
dispatch(removeReactionSuccess(statusId, name));
|
||||
}).catch(err => {
|
||||
dispatch(removeReactionFail(statusId, name, err));
|
||||
});
|
||||
};
|
||||
|
||||
export const removeReactionRequest = (statusId, name) => ({
|
||||
type: REACTION_REMOVE_REQUEST,
|
||||
id: statusId,
|
||||
name,
|
||||
});
|
||||
|
||||
export const removeReactionSuccess = (statusId, name) => ({
|
||||
type: REACTION_REMOVE_SUCCESS,
|
||||
id: statusId,
|
||||
name,
|
||||
});
|
||||
|
||||
export const removeReactionFail = (statusId, name) => ({
|
||||
type: REACTION_REMOVE_FAIL,
|
||||
id: statusId,
|
||||
name,
|
||||
});
|
||||
|
|
|
@ -174,6 +174,7 @@ const excludeTypesFromFilter = filter => {
|
|||
'follow',
|
||||
'follow_request',
|
||||
'favourite',
|
||||
'reaction',
|
||||
'reblog',
|
||||
'mention',
|
||||
'poll',
|
||||
|
|
|
@ -7,8 +7,8 @@ import PersonIcon from '@/material-icons/400-24px/person.svg?react';
|
|||
import SmartToyIcon from '@/material-icons/400-24px/smart_toy.svg?react';
|
||||
|
||||
|
||||
export const Badge = ({ icon, label, domain }) => (
|
||||
<div className='account-role'>
|
||||
export const Badge = ({ icon, label, domain, roleId }) => (
|
||||
<div className='account-role' data-account-role-id={roleId}>
|
||||
{icon}
|
||||
{label}
|
||||
{domain && <span className='account-role__domain'>{domain}</span>}
|
||||
|
@ -19,6 +19,7 @@ Badge.propTypes = {
|
|||
icon: PropTypes.node,
|
||||
label: PropTypes.node,
|
||||
domain: PropTypes.node,
|
||||
roleId: PropTypes.string
|
||||
};
|
||||
|
||||
Badge.defaultProps = {
|
||||
|
|
|
@ -13,7 +13,7 @@ import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'
|
|||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import { ButtonInTabsBar, useColumnsContext } from 'flavours/glitch/features/ui/util/columns_context';
|
||||
import { ButtonInTabsBar } from 'flavours/glitch/features/ui/util/columns_context';
|
||||
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
|
||||
|
||||
import { useAppHistory } from './router';
|
||||
|
@ -26,10 +26,9 @@ const messages = defineMessages({
|
|||
back: { id: 'column_back_button.label', defaultMessage: 'Back' },
|
||||
});
|
||||
|
||||
const BackButton = ({ pinned, show, onlyIcon }) => {
|
||||
const BackButton = ({ onlyIcon }) => {
|
||||
const history = useAppHistory();
|
||||
const intl = useIntl();
|
||||
const { multiColumn } = useColumnsContext();
|
||||
|
||||
const handleBackClick = useCallback(() => {
|
||||
if (history.location?.state?.fromMastodon) {
|
||||
|
@ -39,10 +38,6 @@ const BackButton = ({ pinned, show, onlyIcon }) => {
|
|||
}
|
||||
}, [history]);
|
||||
|
||||
const showButton = history && !pinned && ((multiColumn && history.location?.state?.fromMastodon) || show);
|
||||
|
||||
if (!showButton) return null;
|
||||
|
||||
return (
|
||||
<button onClick={handleBackClick} className={classNames('column-header__back-button', { 'compact': onlyIcon })} aria-label={intl.formatMessage(messages.back)}>
|
||||
<Icon id='chevron-left' icon={ArrowBackIcon} className='column-back-button__icon' />
|
||||
|
@ -52,8 +47,6 @@ const BackButton = ({ pinned, show, onlyIcon }) => {
|
|||
};
|
||||
|
||||
BackButton.propTypes = {
|
||||
pinned: PropTypes.bool,
|
||||
show: PropTypes.bool,
|
||||
onlyIcon: PropTypes.bool,
|
||||
};
|
||||
|
||||
|
@ -118,7 +111,7 @@ class ColumnHeader extends PureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { title, icon, iconComponent, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props;
|
||||
const { title, icon, iconComponent, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues, history } = this.props;
|
||||
const { collapsed, animating } = this.state;
|
||||
|
||||
const wrapperClassName = classNames('column-header__wrapper', {
|
||||
|
@ -161,7 +154,9 @@ class ColumnHeader extends PureComponent {
|
|||
pinButton = <button className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' icon={AddIcon} /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
|
||||
}
|
||||
|
||||
backButton = <BackButton pinned={pinned} show={showBackButton} onlyIcon={!!title} />;
|
||||
if (history && !pinned && ((multiColumn && history.location?.state?.fromMastodon) || showBackButton)) {
|
||||
backButton = <BackButton onlyIcon={!!title} />;
|
||||
}
|
||||
|
||||
const collapsedContent = [
|
||||
extraContent,
|
||||
|
@ -199,16 +194,16 @@ class ColumnHeader extends PureComponent {
|
|||
<h1 className={buttonClassName}>
|
||||
{hasTitle && (
|
||||
<>
|
||||
{showBackButton && backButton}
|
||||
{backButton}
|
||||
|
||||
<button onClick={this.handleTitleClick} className='column-header__title'>
|
||||
{!showBackButton && <Icon id={icon} icon={iconComponent} className='column-header__icon' />}
|
||||
{!backButton && <Icon id={icon} icon={iconComponent} className='column-header__icon' />}
|
||||
{title}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!hasTitle && showBackButton && backButton}
|
||||
{!hasTitle && backButton}
|
||||
|
||||
<div className='column-header__buttons'>
|
||||
{extraButton}
|
||||
|
|
|
@ -20,8 +20,9 @@ import Card from '../features/status/components/card';
|
|||
// to use the progress bar to show download progress
|
||||
import Bundle from '../features/ui/components/bundle';
|
||||
import { MediaGallery, Video, Audio } from '../features/ui/util/async-components';
|
||||
import { IdentityConsumer } from '../features/ui/util/identity_consumer';
|
||||
import { SensitiveMediaContext } from '../features/ui/util/sensitive_media_context';
|
||||
import { displayMedia } from '../initial_state';
|
||||
import { displayMedia, visibleReactions } from '../initial_state';
|
||||
|
||||
import AttachmentList from './attachment_list';
|
||||
import { CollapseButton } from './collapse_button';
|
||||
|
@ -31,6 +32,7 @@ import StatusContent from './status_content';
|
|||
import StatusHeader from './status_header';
|
||||
import StatusIcons from './status_icons';
|
||||
import StatusPrepend from './status_prepend';
|
||||
import StatusReactions from './status_reactions';
|
||||
|
||||
const domParser = new DOMParser();
|
||||
|
||||
|
@ -91,6 +93,8 @@ class Status extends ImmutablePureComponent {
|
|||
onDelete: PropTypes.func,
|
||||
onDirect: PropTypes.func,
|
||||
onMention: PropTypes.func,
|
||||
onReactionAdd: PropTypes.func,
|
||||
onReactionRemove: PropTypes.func,
|
||||
onPin: PropTypes.func,
|
||||
onOpenMedia: PropTypes.func,
|
||||
onOpenVideo: PropTypes.func,
|
||||
|
@ -758,6 +762,7 @@ class Status extends ImmutablePureComponent {
|
|||
if (this.props.prepend && account) {
|
||||
const notifKind = {
|
||||
favourite: 'favourited',
|
||||
reaction: 'reacted',
|
||||
reblog: 'boosted',
|
||||
reblogged_by: 'boosted',
|
||||
status: 'posted',
|
||||
|
@ -841,6 +846,19 @@ class Status extends ImmutablePureComponent {
|
|||
{...statusContentProps}
|
||||
/>
|
||||
|
||||
<IdentityConsumer>
|
||||
{identity => (
|
||||
<StatusReactions
|
||||
statusId={status.get('id')}
|
||||
reactions={status.get('reactions')}
|
||||
numVisible={visibleReactions}
|
||||
addReaction={this.props.onReactionAdd}
|
||||
removeReaction={this.props.onReactionRemove}
|
||||
canReact={identity.signedIn}
|
||||
/>
|
||||
)}
|
||||
</IdentityConsumer>
|
||||
|
||||
{(!isCollapsed || !(muted || !settings.getIn(['collapsed', 'show_action_bar']))) && (
|
||||
<StatusActionBar
|
||||
status={status}
|
||||
|
|
|
@ -8,6 +8,7 @@ import { withRouter } from 'react-router-dom';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import AddReactionIcon from '@/material-icons/400-24px/add_reaction.svg?react';
|
||||
import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react';
|
||||
import BookmarkBorderIcon from '@/material-icons/400-24px/bookmark.svg?react';
|
||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||
|
@ -26,7 +27,8 @@ import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend
|
|||
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
|
||||
|
||||
import DropdownMenuContainer from '../containers/dropdown_menu_container';
|
||||
import { me } from '../initial_state';
|
||||
import EmojiPickerDropdown from '../features/compose/containers/emoji_picker_dropdown_container';
|
||||
import { me, maxReactions } from '../initial_state';
|
||||
|
||||
import { IconButton } from './icon_button';
|
||||
import { RelativeTimestamp } from './relative_timestamp';
|
||||
|
@ -48,6 +50,7 @@ const messages = defineMessages({
|
|||
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
|
||||
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favorite' },
|
||||
react: { id: 'status.react', defaultMessage: 'React' },
|
||||
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
|
||||
open: { id: 'status.open', defaultMessage: 'Expand this status' },
|
||||
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
|
||||
|
@ -76,6 +79,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
status: ImmutablePropTypes.map.isRequired,
|
||||
onReply: PropTypes.func,
|
||||
onFavourite: PropTypes.func,
|
||||
onReactionAdd: PropTypes.func,
|
||||
onReblog: PropTypes.func,
|
||||
onDelete: PropTypes.func,
|
||||
onDirect: PropTypes.func,
|
||||
|
@ -133,6 +137,10 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleEmojiPick = data => {
|
||||
this.props.onReactionAdd(this.props.status.get('id'), data.native.replace(/:/g, ''), data.imageUrl);
|
||||
};
|
||||
|
||||
handleReblogClick = e => {
|
||||
const { signedIn } = this.context.identity;
|
||||
|
||||
|
@ -321,6 +329,8 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
<IconButton className='status__action-bar-button' title={intl.formatMessage(messages.hide)} icon='eye' iconComponent={VisibilityIcon} onClick={this.handleHideClick} />
|
||||
);
|
||||
|
||||
const canReact = permissions && status.get('reactions').filter(r => r.get('count') > 0 && r.get('me')).size < maxReactions;
|
||||
|
||||
return (
|
||||
<div className='status__action-bar'>
|
||||
<IconButton
|
||||
|
@ -334,6 +344,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
/>
|
||||
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon={reblogIcon} iconComponent={reblogIconComponent} onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} />
|
||||
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
|
||||
<EmojiPickerDropdown className='status__action-bar-button' onPickEmoji={this.handleEmojiPick} title={intl.formatMessage(messages.react)} icon={AddReactionIcon} disabled={!canReact} />
|
||||
<IconButton className='status__action-bar-button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} />
|
||||
|
||||
{filterButton}
|
||||
|
|
|
@ -9,6 +9,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
|
||||
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
|
||||
import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react';
|
||||
import MoodIcon from '@/material-icons/400-24px/mood.svg?react';
|
||||
import PushPinIcon from '@/material-icons/400-24px/push_pin.svg?react';
|
||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
||||
|
@ -69,6 +70,14 @@ export default class StatusPrepend extends PureComponent {
|
|||
values={{ name : link }}
|
||||
/>
|
||||
);
|
||||
case 'reaction':
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='notification.reaction'
|
||||
defaultMessage='{name} reacted to your status'
|
||||
values={{ name: link }}
|
||||
/>
|
||||
);
|
||||
case 'reblog':
|
||||
return (
|
||||
<FormattedMessage
|
||||
|
@ -124,6 +133,10 @@ export default class StatusPrepend extends PureComponent {
|
|||
iconId = 'star';
|
||||
iconComponent = StarIcon;
|
||||
break;
|
||||
case 'reaction':
|
||||
iconId = 'mood';
|
||||
iconComponent = MoodIcon;
|
||||
break;
|
||||
case 'featured':
|
||||
iconId = 'thumb-tack';
|
||||
iconComponent = PushPinIcon;
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import TransitionMotion from 'react-motion/lib/TransitionMotion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
|
||||
import { unicodeMapping } from '../features/emoji/emoji_unicode_mapping_light';
|
||||
import { autoPlayGif, reduceMotion } from '../initial_state';
|
||||
import { assetHost } from '../utils/config';
|
||||
|
||||
import { AnimatedNumber } from './animated_number';
|
||||
|
||||
export default class StatusReactions extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
statusId: PropTypes.string.isRequired,
|
||||
reactions: ImmutablePropTypes.list.isRequired,
|
||||
numVisible: PropTypes.number,
|
||||
addReaction: PropTypes.func.isRequired,
|
||||
canReact: PropTypes.bool.isRequired,
|
||||
removeReaction: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
willEnter() {
|
||||
return { scale: reduceMotion ? 1 : 0 };
|
||||
}
|
||||
|
||||
willLeave() {
|
||||
return { scale: reduceMotion ? 0 : spring(0, { stiffness: 170, damping: 26 }) };
|
||||
}
|
||||
|
||||
render() {
|
||||
const { reactions, numVisible } = this.props;
|
||||
let visibleReactions = reactions
|
||||
.filter(x => x.get('count') > 0)
|
||||
.sort((a, b) => b.get('count') - a.get('count'));
|
||||
|
||||
if (numVisible >= 0) {
|
||||
visibleReactions = visibleReactions.filter((_, i) => i < numVisible);
|
||||
}
|
||||
|
||||
const styles = visibleReactions.map(reaction => ({
|
||||
key: reaction.get('name'),
|
||||
data: reaction,
|
||||
style: { scale: reduceMotion ? 1 : spring(1, { stiffness: 150, damping: 13 }) },
|
||||
})).toArray();
|
||||
|
||||
return (
|
||||
<TransitionMotion styles={styles} willEnter={this.willEnter} willLeave={this.willLeave}>
|
||||
{items => (
|
||||
<div className={classNames('reactions-bar', { 'reactions-bar--empty': visibleReactions.isEmpty() })}>
|
||||
{items.map(({ key, data, style }) => (
|
||||
<Reaction
|
||||
key={key}
|
||||
statusId={this.props.statusId}
|
||||
reaction={data}
|
||||
style={{ transform: `scale(${style.scale})`, position: style.scale < 0.5 ? 'absolute' : 'static' }}
|
||||
addReaction={this.props.addReaction}
|
||||
removeReaction={this.props.removeReaction}
|
||||
canReact={this.props.canReact}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</TransitionMotion>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Reaction extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
statusId: PropTypes.string,
|
||||
reaction: ImmutablePropTypes.map.isRequired,
|
||||
addReaction: PropTypes.func.isRequired,
|
||||
removeReaction: PropTypes.func.isRequired,
|
||||
canReact: PropTypes.bool.isRequired,
|
||||
style: PropTypes.object,
|
||||
};
|
||||
|
||||
state = {
|
||||
hovered: false,
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
const { reaction, statusId, addReaction, removeReaction } = this.props;
|
||||
|
||||
if (reaction.get('me')) {
|
||||
removeReaction(statusId, reaction.get('name'));
|
||||
} else {
|
||||
addReaction(statusId, reaction.get('name'));
|
||||
}
|
||||
};
|
||||
|
||||
handleMouseEnter = () => this.setState({ hovered: true });
|
||||
|
||||
handleMouseLeave = () => this.setState({ hovered: false });
|
||||
|
||||
render() {
|
||||
const { reaction } = this.props;
|
||||
|
||||
return (
|
||||
<button
|
||||
className={classNames('reactions-bar__item', { active: reaction.get('me') })}
|
||||
onClick={this.handleClick}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
disabled={!this.props.canReact}
|
||||
style={this.props.style}
|
||||
>
|
||||
<span className='reactions-bar__item__emoji'>
|
||||
<Emoji
|
||||
hovered={this.state.hovered}
|
||||
emoji={reaction.get('name')}
|
||||
url={reaction.get('url')}
|
||||
staticUrl={reaction.get('static_url')}
|
||||
/>
|
||||
</span>
|
||||
<span className='reactions-bar__item__count'>
|
||||
<AnimatedNumber value={reaction.get('count')} />
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Emoji extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
emoji: PropTypes.string.isRequired,
|
||||
hovered: PropTypes.bool.isRequired,
|
||||
url: PropTypes.string,
|
||||
staticUrl: PropTypes.string,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { emoji, hovered, url, staticUrl } = this.props;
|
||||
|
||||
if (unicodeMapping[emoji]) {
|
||||
const { filename, shortCode } = unicodeMapping[this.props.emoji];
|
||||
const title = shortCode ? `:${shortCode}:` : '';
|
||||
|
||||
return (
|
||||
<img
|
||||
draggable='false'
|
||||
className='emojione'
|
||||
alt={emoji}
|
||||
title={title}
|
||||
src={`${assetHost}/emoji/${filename}.svg`}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
const filename = (autoPlayGif || hovered) ? url : staticUrl;
|
||||
const shortCode = `:${emoji}:`;
|
||||
|
||||
return (
|
||||
<img
|
||||
draggable='false'
|
||||
className='emojione custom-emoji'
|
||||
alt={shortCode}
|
||||
title={shortCode}
|
||||
src={filename}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -13,7 +13,6 @@ import {
|
|||
import { openModal } from '../actions/modal';
|
||||
import { initMuteModal } from '../actions/mutes';
|
||||
import Account from '../components/account';
|
||||
import { unfollowModal } from '../initial_state';
|
||||
import { makeGetAccount } from '../selectors';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -34,7 +33,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
|
||||
onFollow (account) {
|
||||
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
|
||||
if (unfollowModal) {
|
||||
dispatch(openModal({
|
||||
modalType: 'CONFIRM',
|
||||
modalProps: {
|
||||
|
@ -43,9 +41,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
|
||||
},
|
||||
}));
|
||||
} else {
|
||||
dispatch(unfollowAccount(account.get('id')));
|
||||
}
|
||||
} else {
|
||||
dispatch(followAccount(account.get('id')));
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ import {
|
|||
unbookmark,
|
||||
pin,
|
||||
unpin,
|
||||
addReaction,
|
||||
removeReaction,
|
||||
} from 'flavours/glitch/actions/interactions';
|
||||
import { changeLocalSetting } from 'flavours/glitch/actions/local_settings';
|
||||
import { openModal } from 'flavours/glitch/actions/modal';
|
||||
|
@ -173,6 +175,14 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
|
|||
}
|
||||
},
|
||||
|
||||
onReactionAdd (statusId, name, url) {
|
||||
dispatch(addReaction(statusId, name, url));
|
||||
},
|
||||
|
||||
onReactionRemove (statusId, name) {
|
||||
dispatch(removeReaction(statusId, name));
|
||||
},
|
||||
|
||||
onEmbed (status) {
|
||||
dispatch(openModal({
|
||||
modalType: 'EMBED',
|
||||
|
|
|
@ -329,7 +329,7 @@ class Header extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
account.get('roles', []).forEach((role) => {
|
||||
badges.push(<Badge key={`role-badge-${role.get('id')}`} label={<span>{role.get('name')}</span>} domain={domain} />);
|
||||
badges.push(<Badge key={`role-badge-${role.get('id')}`} label={<span>{role.get('name')}</span>} domain={domain} roleId={role.get('id')} />);
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -19,7 +19,6 @@ import { initDomainBlockModal, unblockDomain } from '../../../actions/domain_blo
|
|||
import { openModal } from '../../../actions/modal';
|
||||
import { initMuteModal } from '../../../actions/mutes';
|
||||
import { initReport } from '../../../actions/reports';
|
||||
import { unfollowModal } from '../../../initial_state';
|
||||
import { makeGetAccount, getAccountHidden } from '../../../selectors';
|
||||
import Header from '../components/header';
|
||||
|
||||
|
@ -45,7 +44,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
|
||||
onFollow (account) {
|
||||
if (account.getIn(['relationship', 'following'])) {
|
||||
if (unfollowModal) {
|
||||
dispatch(openModal({
|
||||
modalType: 'CONFIRM',
|
||||
modalProps: {
|
||||
|
@ -54,11 +52,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
|
||||
},
|
||||
}));
|
||||
} else {
|
||||
dispatch(unfollowAccount(account.get('id')));
|
||||
}
|
||||
} else if (account.getIn(['relationship', 'requested'])) {
|
||||
if (unfollowModal) {
|
||||
dispatch(openModal({
|
||||
modalType: 'CONFIRM',
|
||||
modalProps: {
|
||||
|
@ -67,9 +61,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
|
||||
},
|
||||
}));
|
||||
} else {
|
||||
dispatch(unfollowAccount(account.get('id')));
|
||||
}
|
||||
} else {
|
||||
dispatch(followAccount(account.get('id')));
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import PollButtonContainer from '../containers/poll_button_container';
|
|||
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
|
||||
import SpoilerButtonContainer from '../containers/spoiler_button_container';
|
||||
import UploadButtonContainer from '../containers/upload_button_container';
|
||||
import UploadFormContainer from '../containers/upload_form_container';
|
||||
import WarningContainer from '../containers/warning_container';
|
||||
import { countableText } from '../util/counter';
|
||||
|
||||
|
@ -34,6 +33,7 @@ import { PollForm } from "./poll_form";
|
|||
import { ReplyIndicator } from './reply_indicator';
|
||||
import { SecondaryPrivacyButton } from './secondary_privacy_button';
|
||||
import { ThreadModeButton } from './thread_mode_button';
|
||||
import { UploadForm } from './upload_form';
|
||||
|
||||
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
|
||||
|
||||
|
@ -308,7 +308,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<UploadFormContainer />
|
||||
<UploadForm />
|
||||
<PollForm />
|
||||
|
||||
<div className='compose-form__footer'>
|
||||
|
|
|
@ -327,6 +327,9 @@ class EmojiPickerDropdown extends PureComponent {
|
|||
onPickEmoji: PropTypes.func.isRequired,
|
||||
onSkinTone: PropTypes.func.isRequired,
|
||||
skinTone: PropTypes.number.isRequired,
|
||||
title: PropTypes.string,
|
||||
icon: PropTypes.node,
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -361,7 +364,7 @@ class EmojiPickerDropdown extends PureComponent {
|
|||
};
|
||||
|
||||
onToggle = (e) => {
|
||||
if (!this.state.loading && (!e.key || e.key === 'Enter')) {
|
||||
if (!this.state.disabled && !this.state.loading && (!e.key || e.key === 'Enter')) {
|
||||
if (this.state.active) {
|
||||
this.onHideDropdown();
|
||||
} else {
|
||||
|
@ -389,19 +392,18 @@ class EmojiPickerDropdown extends PureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis } = this.props;
|
||||
const title = intl.formatMessage(messages.emoji);
|
||||
const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis, title, icon, disabled } = this.props;
|
||||
const { active, loading, placement } = this.state;
|
||||
|
||||
return (
|
||||
<div className='emoji-picker-dropdown' onKeyDown={this.handleKeyDown} ref={this.setTargetRef}>
|
||||
<IconButton
|
||||
title={title}
|
||||
title={title || intl.formatMessage(messages.emoji)}
|
||||
aria-expanded={active}
|
||||
active={active}
|
||||
iconComponent={MoodIcon}
|
||||
disabled={disabled}
|
||||
iconComponent={icon || MoodIcon}
|
||||
onClick={this.onToggle}
|
||||
inverted
|
||||
/>
|
||||
|
||||
<Overlay show={active} placement={placement} flip target={this.findTarget} popperConfig={{ strategy: 'fixed', onFirstUpdate: this.handleOverlayEnter }}>
|
||||
|
|
|
@ -1,43 +1,42 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import spring from 'react-motion/lib/spring';
|
||||
|
||||
import CloseIcon from '@/material-icons/400-20px/close.svg?react';
|
||||
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
|
||||
import WarningIcon from '@/material-icons/400-24px/warning.svg?react';
|
||||
import { undoUploadCompose, initMediaEditModal } from 'flavours/glitch/actions/compose';
|
||||
import { Blurhash } from 'flavours/glitch/components/blurhash';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import Motion from 'flavours/glitch/features/ui/util/optional_motion';
|
||||
|
||||
import Motion from '../../ui/util/optional_motion';
|
||||
export const Upload = ({ id, onDragStart, onDragEnter, onDragEnd }) => {
|
||||
const dispatch = useDispatch();
|
||||
const media = useSelector(state => state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id));
|
||||
const sensitive = useSelector(state => state.getIn(['compose', 'sensitive']));
|
||||
|
||||
export default class Upload extends ImmutablePureComponent {
|
||||
const handleUndoClick = useCallback(() => {
|
||||
dispatch(undoUploadCompose(id));
|
||||
}, [dispatch, id]);
|
||||
|
||||
static propTypes = {
|
||||
media: ImmutablePropTypes.map.isRequired,
|
||||
sensitive: PropTypes.bool,
|
||||
onUndo: PropTypes.func.isRequired,
|
||||
onOpenFocalPoint: PropTypes.func.isRequired,
|
||||
};
|
||||
const handleFocalPointClick = useCallback(() => {
|
||||
dispatch(initMediaEditModal(id));
|
||||
}, [dispatch, id]);
|
||||
|
||||
handleUndoClick = e => {
|
||||
e.stopPropagation();
|
||||
this.props.onUndo(this.props.media.get('id'));
|
||||
};
|
||||
const handleDragStart = useCallback(() => {
|
||||
onDragStart(id);
|
||||
}, [onDragStart, id]);
|
||||
|
||||
handleFocalPointClick = e => {
|
||||
e.stopPropagation();
|
||||
this.props.onOpenFocalPoint(this.props.media.get('id'));
|
||||
};
|
||||
|
||||
render () {
|
||||
const { media, sensitive } = this.props;
|
||||
const handleDragEnter = useCallback(() => {
|
||||
onDragEnter(id);
|
||||
}, [onDragEnter, id]);
|
||||
|
||||
if (!media) {
|
||||
return null;
|
||||
|
@ -50,7 +49,7 @@ export default class Upload extends ImmutablePureComponent {
|
|||
const missingDescription = (media.get('description') || '').length === 0;
|
||||
|
||||
return (
|
||||
<div className='compose-form__upload'>
|
||||
<div className='compose-form__upload' draggable onDragStart={handleDragStart} onDragEnter={handleDragEnter} onDragEnd={onDragEnd}>
|
||||
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
|
||||
{({ scale }) => (
|
||||
<div className='compose-form__upload__thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: !sensitive ? `url(${media.get('preview_url')})` : null, backgroundPosition: `${x}% ${y}%` }}>
|
||||
|
@ -60,18 +59,23 @@ export default class Upload extends ImmutablePureComponent {
|
|||
/>}
|
||||
|
||||
<div className='compose-form__upload__actions'>
|
||||
<button type='button' className='icon-button compose-form__upload__delete' onClick={this.handleUndoClick}><Icon icon={CloseIcon} /></button>
|
||||
<button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon icon={EditIcon} /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>
|
||||
<button type='button' className='icon-button compose-form__upload__delete' onClick={handleUndoClick}><Icon icon={CloseIcon} /></button>
|
||||
<button type='button' className='icon-button' onClick={handleFocalPointClick}><Icon icon={EditIcon} /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>
|
||||
</div>
|
||||
|
||||
<div className='compose-form__upload__warning'>
|
||||
<button type='button' className={classNames('icon-button', { active: missingDescription })} onClick={this.handleFocalPointClick}>{missingDescription && <Icon icon={WarningIcon} />} ALT</button>
|
||||
<button type='button' className={classNames('icon-button', { active: missingDescription })} onClick={handleFocalPointClick}>{missingDescription && <Icon icon={WarningIcon} />} ALT</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Motion>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
Upload.propTypes = {
|
||||
id: PropTypes.string,
|
||||
onDragEnter: PropTypes.func,
|
||||
onDragStart: PropTypes.func,
|
||||
onDragEnd: PropTypes.func,
|
||||
};
|
||||
|
|
|
@ -1,28 +1,51 @@
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { useRef, useCallback } from 'react';
|
||||
|
||||
import UploadContainer from '../containers/upload_container';
|
||||
import UploadProgressContainer from '../containers/upload_progress_container';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
import { changeMediaOrder } from 'flavours/glitch/actions/compose';
|
||||
|
||||
import { SensitiveButton } from './sensitive_button';
|
||||
import { Upload } from './upload';
|
||||
import { UploadProgress } from './upload_progress';
|
||||
|
||||
export default class UploadForm extends ImmutablePureComponent {
|
||||
export const UploadForm = () => {
|
||||
const dispatch = useDispatch();
|
||||
const mediaIds = useSelector(state => state.getIn(['compose', 'media_attachments']).map(item => item.get('id')));
|
||||
const active = useSelector(state => state.getIn(['compose', 'is_uploading']));
|
||||
const progress = useSelector(state => state.getIn(['compose', 'progress']));
|
||||
const isProcessing = useSelector(state => state.getIn(['compose', 'is_processing']));
|
||||
|
||||
static propTypes = {
|
||||
mediaIds: ImmutablePropTypes.list.isRequired,
|
||||
};
|
||||
const dragItem = useRef();
|
||||
const dragOverItem = useRef();
|
||||
|
||||
render () {
|
||||
const { mediaIds } = this.props;
|
||||
const handleDragStart = useCallback(id => {
|
||||
dragItem.current = id;
|
||||
}, [dragItem]);
|
||||
|
||||
const handleDragEnter = useCallback(id => {
|
||||
dragOverItem.current = id;
|
||||
}, [dragOverItem]);
|
||||
|
||||
const handleDragEnd = useCallback(() => {
|
||||
dispatch(changeMediaOrder(dragItem.current, dragOverItem.current));
|
||||
dragItem.current = null;
|
||||
dragOverItem.current = null;
|
||||
}, [dispatch, dragItem, dragOverItem]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<UploadProgressContainer />
|
||||
<UploadProgress active={active} progress={progress} isProcessing={isProcessing} />
|
||||
|
||||
{mediaIds.size > 0 && (
|
||||
<div className='compose-form__uploads'>
|
||||
{mediaIds.map(id => (
|
||||
<UploadContainer id={id} key={id} />
|
||||
<Upload
|
||||
key={id}
|
||||
id={id}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragEnd={handleDragEnd}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
@ -30,6 +53,4 @@ export default class UploadForm extends ImmutablePureComponent {
|
|||
{!mediaIds.isEmpty() && <SensitiveButton />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
|
@ -10,17 +9,7 @@ import { Icon } from 'flavours/glitch/components/icon';
|
|||
|
||||
import Motion from '../../ui/util/optional_motion';
|
||||
|
||||
export default class UploadProgress extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
active: PropTypes.bool,
|
||||
progress: PropTypes.number,
|
||||
isProcessing: PropTypes.bool,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { active, progress, isProcessing } = this.props;
|
||||
|
||||
export const UploadProgress = ({ active, progress, isProcessing }) => {
|
||||
if (!active) {
|
||||
return null;
|
||||
}
|
||||
|
@ -50,6 +39,10 @@ export default class UploadProgress extends PureComponent {
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
UploadProgress.propTypes = {
|
||||
active: PropTypes.bool,
|
||||
progress: PropTypes.number,
|
||||
isProcessing: PropTypes.bool,
|
||||
};
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import { undoUploadCompose, initMediaEditModal, submitCompose } from '../../../actions/compose';
|
||||
import Upload from '../components/upload';
|
||||
|
||||
const mapStateToProps = (state, { id }) => ({
|
||||
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
|
||||
sensitive: state.getIn(['compose', 'sensitive']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onUndo: id => {
|
||||
dispatch(undoUploadCompose(id));
|
||||
},
|
||||
|
||||
onOpenFocalPoint: id => {
|
||||
dispatch(initMediaEditModal(id));
|
||||
},
|
||||
|
||||
onSubmit (router) {
|
||||
dispatch(submitCompose(router));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Upload);
|
|
@ -1,9 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import UploadForm from '../components/upload_form';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
mediaIds: state.getIn(['compose', 'media_attachments']).map(item => item.get('id')),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(UploadForm);
|
|
@ -1,11 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import UploadProgress from '../components/upload_progress';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
active: state.getIn(['compose', 'is_uploading']),
|
||||
progress: state.getIn(['compose', 'progress']),
|
||||
isProcessing: state.getIn(['compose', 'is_processing']),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(UploadProgress);
|
|
@ -21,7 +21,7 @@ import { DisplayName } from 'flavours/glitch/components/display_name';
|
|||
import { IconButton } from 'flavours/glitch/components/icon_button';
|
||||
import { Permalink } from 'flavours/glitch/components/permalink';
|
||||
import { ShortNumber } from 'flavours/glitch/components/short_number';
|
||||
import { autoPlayGif, me, unfollowModal } from 'flavours/glitch/initial_state';
|
||||
import { autoPlayGif, me } from 'flavours/glitch/initial_state';
|
||||
import { makeGetAccount } from 'flavours/glitch/selectors';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -50,7 +50,6 @@ const makeMapStateToProps = () => {
|
|||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
onFollow(account) {
|
||||
if (account.getIn(['relationship', 'following'])) {
|
||||
if (unfollowModal) {
|
||||
dispatch(
|
||||
openModal({
|
||||
modalType: 'CONFIRM',
|
||||
|
@ -66,11 +65,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
|
||||
} }),
|
||||
);
|
||||
} else {
|
||||
dispatch(unfollowAccount(account.get('id')));
|
||||
}
|
||||
} else if (account.getIn(['relationship', 'requested'])) {
|
||||
if (unfollowModal) {
|
||||
dispatch(openModal({
|
||||
modalType: 'CONFIRM',
|
||||
modalProps: {
|
||||
|
@ -79,9 +74,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
|
||||
},
|
||||
}));
|
||||
} else {
|
||||
dispatch(unfollowAccount(account.get('id')));
|
||||
}
|
||||
} else {
|
||||
dispatch(followAccount(account.get('id')));
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ class FollowRequests extends ImmutablePureComponent {
|
|||
);
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} icon='user-plus' iconComponent={PersonAddIcon} heading={intl.formatMessage(messages.heading)} alwaysShowBackButton>
|
||||
<Column bindToDocument={!multiColumn} icon='user-plus' iconComponent={PersonAddIcon} heading={intl.formatMessage(messages.heading)}>
|
||||
<ScrollableList
|
||||
scrollKey='follow_requests'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
|
|
|
@ -161,6 +161,17 @@ export default class ColumnSettings extends PureComponent {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<section role='group' aria-labelledby='notifications-reaction'>
|
||||
<h3 id='notifications-reaction'><FormattedMessage id='notifications.column_settings.reaction' defaultMessage='Reactions:' /></h3>
|
||||
|
||||
<div className='column-settings__pillbar'>
|
||||
<PillBarButton disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'reaction']} onChange={onChange} label={alertStr} />
|
||||
{showPushSettings && <PillBarButton prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'reaction']} onChange={this.onPushChange} label={pushStr} />}
|
||||
<PillBarButton prefix='notifications' settings={settings} settingPath={['shows', 'reaction']} onChange={onChange} label={showStr} />
|
||||
<PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'reaction']} onChange={onChange} label={soundStr} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section role='group' aria-labelledby='notifications-mention'>
|
||||
<h3 id='notifications-mention'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></h3>
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|||
|
||||
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
|
||||
import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react';
|
||||
import MoodIcon from '@/material-icons/400-24px/mood.svg?react';
|
||||
import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
|
||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
||||
|
@ -14,6 +15,7 @@ import { Icon } from 'flavours/glitch/components/icon';
|
|||
const tooltips = defineMessages({
|
||||
mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
|
||||
favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favorites' },
|
||||
reactions: { id: 'notifications.filter.reactions', defaultMessage: 'Reactions' },
|
||||
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' },
|
||||
polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
|
||||
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
|
||||
|
@ -81,6 +83,13 @@ class FilterBar extends PureComponent {
|
|||
>
|
||||
<Icon id='star' icon={StarIcon} />
|
||||
</button>
|
||||
<button
|
||||
className={selectedFilter === 'reaction' ? 'active' : ''}
|
||||
onClick={this.onClick('reaction')}
|
||||
title={intl.formatMessage(tooltips.reactions)}
|
||||
>
|
||||
<Icon id='mood' icon={MoodIcon} />
|
||||
</button>
|
||||
<button
|
||||
className={selectedFilter === 'reblog' ? 'active' : ''}
|
||||
onClick={this.onClick('reblog')}
|
||||
|
|
|
@ -41,7 +41,8 @@ export const FilteredNotificationsBanner = () => {
|
|||
</div>
|
||||
|
||||
<div className='filtered-notifications-banner__badge'>
|
||||
{toCappedNumber(policy.getIn(['summary', 'pending_notifications_count']))}
|
||||
<div className='filtered-notifications-banner__badge__badge'>{toCappedNumber(policy.getIn(['summary', 'pending_notifications_count']))}</div>
|
||||
<FormattedMessage id='filtered_notifications_banner.private_mentions' defaultMessage='{count, plural, one {private mention} other {private mentions}}' values={{ count: policy.getIn(['summary', 'pending_notifications_count']) }} />
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
|
|
|
@ -22,12 +22,14 @@ import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
|
|||
import FollowRequestContainer from '../containers/follow_request_container';
|
||||
import NotificationOverlayContainer from '../containers/overlay_container';
|
||||
|
||||
import { RelationshipsSeveranceEvent } from './relationships_severance_event';
|
||||
import Report from './report';
|
||||
|
||||
const messages = defineMessages({
|
||||
follow: { id: 'notification.follow', defaultMessage: '{name} followed you' },
|
||||
adminSignUp: { id: 'notification.admin.sign_up', defaultMessage: '{name} signed up' },
|
||||
adminReport: { id: 'notification.admin.report', defaultMessage: '{name} reported {target}' },
|
||||
relationshipsSevered: { id: 'notification.relationships_severance_event', defaultMessage: 'Lost connections with {name}' },
|
||||
});
|
||||
|
||||
const notificationForScreenReader = (intl, message, timestamp) => {
|
||||
|
@ -203,6 +205,31 @@ class Notification extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
renderReaction (notification) {
|
||||
return (
|
||||
<StatusContainer
|
||||
containerId={notification.get('id')}
|
||||
hidden={!!this.props.hidden}
|
||||
id={notification.get('status')}
|
||||
account={notification.get('account')}
|
||||
prepend='reaction'
|
||||
muted
|
||||
withDismiss
|
||||
notification={notification}
|
||||
onMoveDown={this.handleMoveDown}
|
||||
onMoveUp={this.handleMoveUp}
|
||||
onMention={this.props.onMention}
|
||||
contextType='notifications'
|
||||
getScrollPosition={this.props.getScrollPosition}
|
||||
updateScrollBottom={this.props.updateScrollBottom}
|
||||
cachedMediaWidth={this.props.cachedMediaWidth}
|
||||
cacheMediaWidth={this.props.cacheMediaWidth}
|
||||
onUnmount={this.props.onUnmount}
|
||||
unread={this.props.unread}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderReblog (notification) {
|
||||
return (
|
||||
<StatusContainer
|
||||
|
@ -303,6 +330,29 @@ class Notification extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
renderRelationshipsSevered (notification) {
|
||||
const { intl, unread, hidden } = this.props;
|
||||
const event = notification.get('event');
|
||||
|
||||
if (!event) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<div className={classNames('notification notification-severed-relationships focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.relationshipsSevered, { name: notification.getIn(['event', 'target_name']) }), notification.get('created_at'))}>
|
||||
<RelationshipsSeveranceEvent
|
||||
type={event.get('type')}
|
||||
target={event.get('target_name')}
|
||||
followersCount={event.get('followers_count')}
|
||||
followingCount={event.get('following_count')}
|
||||
hidden={hidden}
|
||||
/>
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
renderAdminSignUp (notification, account, link) {
|
||||
const { intl, unread } = this.props;
|
||||
|
||||
|
@ -388,6 +438,8 @@ class Notification extends ImmutablePureComponent {
|
|||
return this.renderMention(notification);
|
||||
case 'favourite':
|
||||
return this.renderFavourite(notification);
|
||||
case 'reaction':
|
||||
return this.renderReaction(notification);
|
||||
case 'reblog':
|
||||
return this.renderReblog(notification);
|
||||
case 'status':
|
||||
|
@ -396,6 +448,8 @@ class Notification extends ImmutablePureComponent {
|
|||
return this.renderUpdate(notification);
|
||||
case 'poll':
|
||||
return this.renderPoll(notification);
|
||||
case 'severed_relationships':
|
||||
return this.renderRelationshipsSevered(notification);
|
||||
case 'admin.sign_up':
|
||||
return this.renderAdminSignUp(notification, account, link);
|
||||
case 'admin.report':
|
||||
|
|
|
@ -7,8 +7,8 @@ import { Link } from 'react-router-dom';
|
|||
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
|
||||
import DoneIcon from '@/material-icons/400-24px/done.svg?react';
|
||||
import VolumeOffIcon from '@/material-icons/400-24px/volume_off.svg?react';
|
||||
import { acceptNotificationRequest, dismissNotificationRequest } from 'flavours/glitch/actions/notifications';
|
||||
import { Avatar } from 'flavours/glitch/components/avatar';
|
||||
import { IconButton } from 'flavours/glitch/components/icon_button';
|
||||
|
@ -51,7 +51,7 @@ export const NotificationRequest = ({ id, accountId, notificationsCount }) => {
|
|||
</Link>
|
||||
|
||||
<div className='notification-request__actions'>
|
||||
<IconButton iconComponent={VolumeOffIcon} onClick={handleDismiss} title={intl.formatMessage(messages.dismiss)} />
|
||||
<IconButton iconComponent={DeleteIcon} onClick={handleDismiss} title={intl.formatMessage(messages.dismiss)} />
|
||||
<IconButton iconComponent={DoneIcon} onClick={handleAccept} title={intl.formatMessage(messages.accept)} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import HeartBrokenIcon from '@/material-icons/400-24px/heart_broken-fill.svg?react';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import { domain } from 'flavours/glitch/initial_state';
|
||||
|
||||
// This needs to be kept in sync with app/models/relationships_severance_event.rb
|
||||
const messages = defineMessages({
|
||||
account_suspension: { id: 'notification.relationships_severance_event.account_suspension', defaultMessage: 'An admin from {from} has suspended {target}, which means you can no longer receive updates from them or interact with them.' },
|
||||
domain_block: { id: 'notification.relationships_severance_event.domain_block', defaultMessage: 'An admin from {from} has blocked {target}, including {followersCount} of your followers and {followingCount, plural, one {# account} other {# accounts}} you follow.' },
|
||||
user_domain_block: { id: 'notification.relationships_severance_event.user_domain_block', defaultMessage: 'You have blocked {target}, removing {followersCount} of your followers and {followingCount, plural, one {# account} other {# accounts}} you follow.' },
|
||||
});
|
||||
|
||||
export const RelationshipsSeveranceEvent = ({ type, target, followingCount, followersCount, hidden }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
if (hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<a href='/severed_relationships' target='_blank' rel='noopener noreferrer' className='notification__relationships-severance-event'>
|
||||
<Icon id='heart_broken' icon={HeartBrokenIcon} />
|
||||
|
||||
<div className='notification__relationships-severance-event__content'>
|
||||
<p>{intl.formatMessage(messages[type], { from: <strong>{domain}</strong>, target: <strong>{target}</strong>, followingCount, followersCount })}</p>
|
||||
<span className='link-button'><FormattedMessage id='notification.relationships_severance_event.learn_more' defaultMessage='Learn more' /></span>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
RelationshipsSeveranceEvent.propTypes = {
|
||||
type: PropTypes.oneOf([
|
||||
'account_suspension',
|
||||
'domain_block',
|
||||
'user_domain_block',
|
||||
]).isRequired,
|
||||
target: PropTypes.string.isRequired,
|
||||
followersCount: PropTypes.number.isRequired,
|
||||
followingCount: PropTypes.number.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
};
|
|
@ -8,6 +8,7 @@ import { withRouter } from 'react-router-dom';
|
|||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
||||
import AddReactionIcon from '@/material-icons/400-24px/add_reaction.svg?react';
|
||||
import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react';
|
||||
import BookmarkBorderIcon from '@/material-icons/400-24px/bookmark.svg?react';
|
||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||
|
@ -26,7 +27,8 @@ import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
|
|||
|
||||
import { IconButton } from '../../../components/icon_button';
|
||||
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
|
||||
import { me } from '../../../initial_state';
|
||||
import { me, maxReactions } from '../../../initial_state';
|
||||
import EmojiPickerDropdown from '../../compose/containers/emoji_picker_dropdown_container';
|
||||
|
||||
const messages = defineMessages({
|
||||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||
|
@ -40,6 +42,7 @@ const messages = defineMessages({
|
|||
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
|
||||
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favorite' },
|
||||
react: { id: 'status.react', defaultMessage: 'React' },
|
||||
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
|
||||
more: { id: 'status.more', defaultMessage: 'More' },
|
||||
mute: { id: 'status.mute', defaultMessage: 'Mute @{name}' },
|
||||
|
@ -69,6 +72,7 @@ class ActionBar extends PureComponent {
|
|||
onReply: PropTypes.func.isRequired,
|
||||
onReblog: PropTypes.func.isRequired,
|
||||
onFavourite: PropTypes.func.isRequired,
|
||||
onReactionAdd: PropTypes.func.isRequired,
|
||||
onBookmark: PropTypes.func.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
onEdit: PropTypes.func.isRequired,
|
||||
|
@ -96,6 +100,10 @@ class ActionBar extends PureComponent {
|
|||
this.props.onFavourite(this.props.status, e);
|
||||
};
|
||||
|
||||
handleEmojiPick = data => {
|
||||
this.props.onReactionAdd(this.props.status.get('id'), data.native.replace(/:/g, ''), data.imageUrl);
|
||||
};
|
||||
|
||||
handleBookmarkClick = (e) => {
|
||||
this.props.onBookmark(this.props.status, e);
|
||||
};
|
||||
|
@ -231,6 +239,8 @@ class ActionBar extends PureComponent {
|
|||
replyIconComponent = ReplyAllIcon;
|
||||
}
|
||||
|
||||
const canReact = signedIn && status.get('reactions').filter(r => r.get('count') > 0 && r.get('me')).size < maxReactions;
|
||||
|
||||
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
|
||||
|
||||
let reblogTitle, reblogIconComponent;
|
||||
|
@ -254,6 +264,7 @@ class ActionBar extends PureComponent {
|
|||
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={replyIcon} iconComponent={replyIconComponent} onClick={this.handleReplyClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className={classNames({ reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={reblogIconComponent} onClick={this.handleReblogClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} /></div>
|
||||
<div className='detailed-status__button'><EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} title={intl.formatMessage(messages.react)} icon={AddReactionIcon} disabled={!canReact} /></div>
|
||||
<div className='detailed-status__button'><IconButton className='bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} /></div>
|
||||
|
||||
<div className='detailed-status__action-bar-dropdown'>
|
||||
|
|
|
@ -21,6 +21,7 @@ import { Avatar } from '../../../components/avatar';
|
|||
import { DisplayName } from '../../../components/display_name';
|
||||
import MediaGallery from '../../../components/media_gallery';
|
||||
import StatusContent from '../../../components/status_content';
|
||||
import StatusReactions from '../../../components/status_reactions';
|
||||
import Audio from '../../audio';
|
||||
import scheduleIdleTask from '../../ui/util/schedule_idle_task';
|
||||
import Video from '../../video';
|
||||
|
@ -29,6 +30,10 @@ import Card from './card';
|
|||
|
||||
class DetailedStatus extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map,
|
||||
settings: ImmutablePropTypes.map.isRequired,
|
||||
|
@ -47,6 +52,8 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
available: PropTypes.bool,
|
||||
}),
|
||||
onToggleMediaVisibility: PropTypes.func,
|
||||
onReactionAdd: PropTypes.func.isRequired,
|
||||
onReactionRemove: PropTypes.func.isRequired,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
|
@ -307,6 +314,14 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
{...statusContentProps}
|
||||
/>
|
||||
|
||||
<StatusReactions
|
||||
statusId={status.get('id')}
|
||||
reactions={status.get('reactions')}
|
||||
addReaction={this.props.onReactionAdd}
|
||||
removeReaction={this.props.onReactionRemove}
|
||||
canReact={this.context.identity.signedIn}
|
||||
/>
|
||||
|
||||
<div className='detailed-status__meta'>
|
||||
<div className='detailed-status__meta__line'>
|
||||
<a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener noreferrer'>
|
||||
|
|
|
@ -40,6 +40,8 @@ import {
|
|||
unreblog,
|
||||
pin,
|
||||
unpin,
|
||||
addReaction,
|
||||
removeReaction,
|
||||
} from '../../actions/interactions';
|
||||
import { changeLocalSetting } from '../../actions/local_settings';
|
||||
import { openModal } from '../../actions/modal';
|
||||
|
@ -308,6 +310,19 @@ class Status extends ImmutablePureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleReactionAdd = (statusId, name, url) => {
|
||||
const { dispatch } = this.props;
|
||||
const { signedIn } = this.context.identity;
|
||||
|
||||
if (signedIn) {
|
||||
dispatch(addReaction(statusId, name, url));
|
||||
}
|
||||
};
|
||||
|
||||
handleReactionRemove = (statusId, name) => {
|
||||
this.props.dispatch(removeReaction(statusId, name));
|
||||
};
|
||||
|
||||
handlePin = (status) => {
|
||||
if (status.get('pinned')) {
|
||||
this.props.dispatch(unpin(status));
|
||||
|
@ -748,6 +763,8 @@ class Status extends ImmutablePureComponent {
|
|||
settings={settings}
|
||||
onOpenVideo={this.handleOpenVideo}
|
||||
onOpenMedia={this.handleOpenMedia}
|
||||
onReactionAdd={this.handleReactionAdd}
|
||||
onReactionRemove={this.handleReactionRemove}
|
||||
expanded={isExpanded}
|
||||
onToggleHidden={this.handleToggleHidden}
|
||||
onTranslate={this.handleTranslate}
|
||||
|
@ -762,6 +779,7 @@ class Status extends ImmutablePureComponent {
|
|||
status={status}
|
||||
onReply={this.handleReplyClick}
|
||||
onFavourite={this.handleFavouriteClick}
|
||||
onReactionAdd={this.handleReactionAdd}
|
||||
onReblog={this.handleReblogClick}
|
||||
onBookmark={this.handleBookmarkClick}
|
||||
onDelete={this.handleDeleteClick}
|
||||
|
|
|
@ -22,7 +22,7 @@ import { GIFV } from 'flavours/glitch/components/gifv';
|
|||
import { IconButton } from 'flavours/glitch/components/icon_button';
|
||||
import Audio from 'flavours/glitch/features/audio';
|
||||
import { CharacterCounter } from 'flavours/glitch/features/compose/components/character_counter';
|
||||
import UploadProgress from 'flavours/glitch/features/compose/components/upload_progress';
|
||||
import { UploadProgress } from 'flavours/glitch/features/compose/components/upload_progress';
|
||||
import { Tesseract as fetchTesseract } from 'flavours/glitch/features/ui/util/async-components';
|
||||
import { me } from 'flavours/glitch/initial_state';
|
||||
import { assetHost } from 'flavours/glitch/utils/config';
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
export class IdentityConsumer extends PureComponent {
|
||||
static contextTypes = {
|
||||
identity: PropTypes.object
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
children: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
return this.props.children(this.context.identity);
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@
|
|||
* @property {boolean} limited_federation_mode
|
||||
* @property {string} locale
|
||||
* @property {string | null} mascot
|
||||
* @property {number} max_reactions
|
||||
* @property {string=} me
|
||||
* @property {string=} moved_to_account_id
|
||||
* @property {string=} owner
|
||||
|
@ -44,6 +45,7 @@
|
|||
* @property {boolean} use_blurhash
|
||||
* @property {boolean=} use_pending_items
|
||||
* @property {string} version
|
||||
* @property {number} visible_reactions
|
||||
* @property {string} sso_redirect
|
||||
* @property {string} status_page_url
|
||||
* @property {boolean} system_emoji_font
|
||||
|
@ -59,6 +61,7 @@
|
|||
* @property {object} local_settings
|
||||
* @property {number} max_feed_hashtags
|
||||
* @property {number} poll_limits
|
||||
* @property {number} max_reactions
|
||||
*/
|
||||
|
||||
const element = document.getElementById('initial-state');
|
||||
|
@ -103,6 +106,7 @@ export const expandSpoilers = getMeta('expand_spoilers');
|
|||
export const forceSingleColumn = !getMeta('advanced_layout');
|
||||
export const limitedFederationMode = getMeta('limited_federation_mode');
|
||||
export const mascot = getMeta('mascot');
|
||||
export const maxReactions = (initialState && initialState.max_reactions) || 1;
|
||||
export const me = getMeta('me');
|
||||
export const movedToAccountId = getMeta('moved_to_account_id');
|
||||
export const owner = getMeta('owner');
|
||||
|
@ -118,10 +122,10 @@ export const source_url = getMeta('source_url');
|
|||
export const timelinePreview = getMeta('timeline_preview');
|
||||
export const title = getMeta('title');
|
||||
export const trendsAsLanding = getMeta('trends_as_landing_page');
|
||||
export const unfollowModal = getMeta('unfollow_modal');
|
||||
export const useBlurhash = getMeta('use_blurhash');
|
||||
export const usePendingItems = getMeta('use_pending_items');
|
||||
export const version = getMeta('version');
|
||||
export const visibleReactions = getMeta('visible_reactions');
|
||||
export const languages = initialState?.languages;
|
||||
export const criticalUpdatesPending = initialState?.critical_updates_pending;
|
||||
export const statusPageUrl = getMeta('status_page_url');
|
||||
|
|
|
@ -59,14 +59,15 @@
|
|||
"navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
|
||||
"navigation_bar.misc": "Misc",
|
||||
"notification.markForDeletion": "Mark for deletion",
|
||||
"notification.reaction": "{name} reacted to your post",
|
||||
"notification_purge.btn_all": "Select\nall",
|
||||
"notification_purge.btn_apply": "Clear\nselected",
|
||||
"notification_purge.btn_invert": "Invert\nselection",
|
||||
"notification_purge.btn_none": "Select\nnone",
|
||||
"notification_purge.start": "Enter notification cleaning mode",
|
||||
"notifications.column_settings.filter_bar.advanced": "Display all categories",
|
||||
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
||||
"notifications.column_settings.filter_bar.show_bar": "Show filter bar",
|
||||
"notifications.column_settings.reaction": "Reactions:",
|
||||
"notifications.filter.reactions": "Reactions",
|
||||
"notifications.marked_clear": "Clear selected notifications",
|
||||
"notifications.marked_clear_confirmation": "Are you sure you want to permanently clear all selected notifications?",
|
||||
"settings.always_show_spoilers_field": "Always enable the Content Warning field",
|
||||
|
@ -157,6 +158,7 @@
|
|||
"status.in_reply_to": "This toot is a reply",
|
||||
"status.is_poll": "This toot is a poll",
|
||||
"status.local_only": "Only visible from your instance",
|
||||
"status.react": "React",
|
||||
"status.uncollapse": "Uncollapse",
|
||||
"suggestions.dismiss": "Dismiss suggestion"
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ import {
|
|||
INIT_MEDIA_EDIT_MODAL,
|
||||
COMPOSE_CHANGE_MEDIA_DESCRIPTION,
|
||||
COMPOSE_CHANGE_MEDIA_FOCUS,
|
||||
COMPOSE_CHANGE_MEDIA_ORDER,
|
||||
COMPOSE_SET_STATUS,
|
||||
COMPOSE_FOCUS,
|
||||
} from '../actions/compose';
|
||||
|
@ -656,6 +657,14 @@ export default function compose(state = initialState, action) {
|
|||
return state.set('language', action.language);
|
||||
case COMPOSE_FOCUS:
|
||||
return state.set('focusDate', new Date()).update('text', text => text.length > 0 ? text : action.defaultText);
|
||||
case COMPOSE_CHANGE_MEDIA_ORDER:
|
||||
return state.update('media_attachments', list => {
|
||||
const indexA = list.findIndex(x => x.get('id') === action.a);
|
||||
const moveItem = list.get(indexA);
|
||||
const indexB = list.findIndex(x => x.get('id') === action.b);
|
||||
|
||||
return list.splice(indexA, 1).splice(indexB, 0, moveItem);
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ export const notificationToMap = (notification, markForDelete = false) => Immuta
|
|||
markedForDelete: markForDelete,
|
||||
status: notification.status ? notification.status.id : null,
|
||||
report: notification.report ? fromJS(notification.report) : null,
|
||||
event: notification.event ? fromJS(notification.event) : null,
|
||||
});
|
||||
|
||||
const normalizeNotification = (state, notification, usePendingItems) => {
|
||||
|
|
|
@ -35,6 +35,7 @@ const initialState = ImmutableMap({
|
|||
follow: false,
|
||||
follow_request: false,
|
||||
favourite: false,
|
||||
reaction: false,
|
||||
reblog: false,
|
||||
mention: false,
|
||||
poll: false,
|
||||
|
@ -57,6 +58,7 @@ const initialState = ImmutableMap({
|
|||
follow: true,
|
||||
follow_request: false,
|
||||
favourite: true,
|
||||
reaction: true,
|
||||
reblog: true,
|
||||
mention: true,
|
||||
poll: true,
|
||||
|
@ -70,6 +72,7 @@ const initialState = ImmutableMap({
|
|||
follow: true,
|
||||
follow_request: false,
|
||||
favourite: true,
|
||||
reaction: true,
|
||||
reblog: true,
|
||||
mention: true,
|
||||
poll: true,
|
||||
|
|
|
@ -15,6 +15,11 @@ import {
|
|||
BOOKMARK_FAIL,
|
||||
UNBOOKMARK_REQUEST,
|
||||
UNBOOKMARK_FAIL,
|
||||
REACTION_UPDATE,
|
||||
REACTION_ADD_FAIL,
|
||||
REACTION_REMOVE_FAIL,
|
||||
REACTION_ADD_REQUEST,
|
||||
REACTION_REMOVE_REQUEST,
|
||||
} from '../actions/interactions';
|
||||
import {
|
||||
STATUS_MUTE_SUCCESS,
|
||||
|
@ -42,6 +47,43 @@ const deleteStatus = (state, id, references) => {
|
|||
return state.delete(id);
|
||||
};
|
||||
|
||||
const updateReaction = (state, id, name, updater) => state.update(
|
||||
id,
|
||||
status => status.update(
|
||||
'reactions',
|
||||
reactions => {
|
||||
const index = reactions.findIndex(reaction => reaction.get('name') === name);
|
||||
if (index > -1) {
|
||||
return reactions.update(index, reaction => updater(reaction));
|
||||
} else {
|
||||
return reactions.push(updater(fromJS({ name, count: 0 })));
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const updateReactionCount = (state, reaction) => updateReaction(state, reaction.status_id, reaction.name, x => x.set('count', reaction.count));
|
||||
|
||||
// The url parameter is only used when adding a new custom emoji reaction
|
||||
// (one that wasn't in the reactions list before) because we don't have its
|
||||
// URL yet. In all other cases, it's undefined.
|
||||
const addReaction = (state, id, name, url) => updateReaction(
|
||||
state,
|
||||
id,
|
||||
name,
|
||||
x => x.set('me', true)
|
||||
.update('count', n => n + 1)
|
||||
.update('url', old => old ? old : url)
|
||||
.update('static_url', old => old ? old : url),
|
||||
);
|
||||
|
||||
const removeReaction = (state, id, name) => updateReaction(
|
||||
state,
|
||||
id,
|
||||
name,
|
||||
x => x.set('me', false).update('count', n => n - 1),
|
||||
);
|
||||
|
||||
const statusTranslateSuccess = (state, id, translation) => {
|
||||
return state.withMutations(map => {
|
||||
map.setIn([id, 'translation'], fromJS(normalizeStatusTranslation(translation, map.get(id))));
|
||||
|
@ -95,6 +137,14 @@ export default function statuses(state = initialState, action) {
|
|||
return state.setIn([action.status.get('id'), 'reblogged'], true);
|
||||
case REBLOG_FAIL:
|
||||
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'reblogged'], false);
|
||||
case REACTION_UPDATE:
|
||||
return updateReactionCount(state, action.reaction);
|
||||
case REACTION_ADD_REQUEST:
|
||||
case REACTION_REMOVE_FAIL:
|
||||
return addReaction(state, action.id, action.name, action.url);
|
||||
case REACTION_REMOVE_REQUEST:
|
||||
case REACTION_ADD_FAIL:
|
||||
return removeReaction(state, action.id, action.name);
|
||||
case UNREBLOG_REQUEST:
|
||||
return state.setIn([action.status.get('id'), 'reblogged'], false);
|
||||
case UNREBLOG_FAIL:
|
||||
|
|
|
@ -1469,7 +1469,8 @@ body > [data-popper-placement] {
|
|||
border-bottom: 0;
|
||||
|
||||
.status__content,
|
||||
.status__action-bar {
|
||||
.status__action-bar,
|
||||
.reactions-bar {
|
||||
margin-inline-start: $thread-margin;
|
||||
width: calc(100% - ($thread-margin));
|
||||
}
|
||||
|
@ -1598,6 +1599,10 @@ body > [data-popper-placement] {
|
|||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.reactions-bar--empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.status__relative-time {
|
||||
|
@ -1812,6 +1817,14 @@ body > [data-popper-placement] {
|
|||
&-spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
& > .emoji-picker-dropdown {
|
||||
height: 24px;
|
||||
|
||||
> .emoji-button {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.detailed-status__action-bar-dropdown {
|
||||
|
@ -2379,6 +2392,44 @@ a.account__display-name {
|
|||
}
|
||||
}
|
||||
|
||||
.notification__relationships-severance-event {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
color: $secondary-text-color;
|
||||
text-decoration: none;
|
||||
align-items: flex-start;
|
||||
padding: 16px 32px;
|
||||
border-bottom: 1px solid var(--background-border-color);
|
||||
|
||||
&:hover {
|
||||
color: $primary-text-color;
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding: 2px;
|
||||
color: $highlight-text-color;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
flex-grow: 1;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.link-button {
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notification__message {
|
||||
padding: 8px 14px; // glitch: reduced padding
|
||||
padding-bottom: 0;
|
||||
|
@ -4924,6 +4975,10 @@ a.status-card {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.detailed-status__button .emoji-button {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.column-settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -9035,6 +9090,8 @@ noscript {
|
|||
}
|
||||
|
||||
&--empty {
|
||||
margin-top: 0;
|
||||
|
||||
.emoji-button {
|
||||
padding: 0;
|
||||
}
|
||||
|
@ -9411,8 +9468,9 @@ noscript {
|
|||
|
||||
.safety-action-modal,
|
||||
.interaction-modal {
|
||||
max-width: 90vw;
|
||||
max-width: 100vw;
|
||||
width: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.interaction-modal {
|
||||
|
@ -10321,18 +10379,24 @@ noscript {
|
|||
margin-top: 16px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
gap: 6px;
|
||||
color: $darker-text-color;
|
||||
|
||||
a {
|
||||
display: inline-flex;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
padding: 4px 12px;
|
||||
background: $ui-base-color;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover span {
|
||||
text-decoration: underline;
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
background: lighten($ui-base-color, 4%);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10548,10 +10612,10 @@ noscript {
|
|||
.filtered-notifications-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: $ui-base-color;
|
||||
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
||||
padding: 15px;
|
||||
gap: 15px;
|
||||
border: 1px solid var(--background-border-color);
|
||||
border-top: 0;
|
||||
padding: 24px 32px;
|
||||
gap: 16px;
|
||||
color: $darker-text-color;
|
||||
text-decoration: none;
|
||||
|
||||
|
@ -10559,15 +10623,12 @@ noscript {
|
|||
&:active,
|
||||
&:focus {
|
||||
color: $secondary-text-color;
|
||||
|
||||
.filtered-notifications-banner__badge {
|
||||
background: $secondary-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
&__text {
|
||||
|
@ -10583,13 +10644,24 @@ noscript {
|
|||
}
|
||||
|
||||
&__badge {
|
||||
background: $darker-text-color;
|
||||
color: $ui-base-color;
|
||||
border-radius: 100px;
|
||||
padding: 2px 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 999px;
|
||||
background: var(--background-border-color);
|
||||
color: $darker-text-color;
|
||||
padding: 4px;
|
||||
padding-inline-end: 8px;
|
||||
gap: 6px;
|
||||
font-weight: 500;
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
|
||||
&__badge {
|
||||
background: $ui-button-background-color;
|
||||
color: $white;
|
||||
border-radius: 100px;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10598,7 +10670,7 @@ noscript {
|
|||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
||||
border-bottom: 1px solid var(--background-border-color);
|
||||
|
||||
&__link {
|
||||
display: flex;
|
||||
|
@ -10646,7 +10718,7 @@ noscript {
|
|||
|
||||
.icon-button {
|
||||
border-radius: 4px;
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
border: 1px solid var(--background-border-color);
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
|
@ -75,6 +75,7 @@ export const INIT_MEDIA_EDIT_MODAL = 'INIT_MEDIA_EDIT_MODAL';
|
|||
|
||||
export const COMPOSE_CHANGE_MEDIA_DESCRIPTION = 'COMPOSE_CHANGE_MEDIA_DESCRIPTION';
|
||||
export const COMPOSE_CHANGE_MEDIA_FOCUS = 'COMPOSE_CHANGE_MEDIA_FOCUS';
|
||||
export const COMPOSE_CHANGE_MEDIA_ORDER = 'COMPOSE_CHANGE_MEDIA_ORDER';
|
||||
|
||||
export const COMPOSE_SET_STATUS = 'COMPOSE_SET_STATUS';
|
||||
export const COMPOSE_FOCUS = 'COMPOSE_FOCUS';
|
||||
|
@ -811,3 +812,9 @@ export function changePollSettings(expiresIn, isMultiple) {
|
|||
isMultiple,
|
||||
};
|
||||
}
|
||||
|
||||
export const changeMediaOrder = (a, b) => ({
|
||||
type: COMPOSE_CHANGE_MEDIA_ORDER,
|
||||
a,
|
||||
b,
|
||||
});
|
||||
|
|
|
@ -7,8 +7,8 @@ import PersonIcon from '@/material-icons/400-24px/person.svg?react';
|
|||
import SmartToyIcon from '@/material-icons/400-24px/smart_toy.svg?react';
|
||||
|
||||
|
||||
export const Badge = ({ icon, label, domain }) => (
|
||||
<div className='account-role'>
|
||||
export const Badge = ({ icon, label, domain, roleId }) => (
|
||||
<div className='account-role' data-account-role-id={roleId}>
|
||||
{icon}
|
||||
{label}
|
||||
{domain && <span className='account-role__domain'>{domain}</span>}
|
||||
|
@ -19,6 +19,7 @@ Badge.propTypes = {
|
|||
icon: PropTypes.node,
|
||||
label: PropTypes.node,
|
||||
domain: PropTypes.node,
|
||||
roleId: PropTypes.string
|
||||
};
|
||||
|
||||
Badge.defaultProps = {
|
||||
|
|
|
@ -13,7 +13,7 @@ import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'
|
|||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { ButtonInTabsBar, useColumnsContext } from 'mastodon/features/ui/util/columns_context';
|
||||
import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
import { useAppHistory } from './router';
|
||||
|
@ -26,10 +26,9 @@ const messages = defineMessages({
|
|||
back: { id: 'column_back_button.label', defaultMessage: 'Back' },
|
||||
});
|
||||
|
||||
const BackButton = ({ pinned, show, onlyIcon }) => {
|
||||
const BackButton = ({ onlyIcon }) => {
|
||||
const history = useAppHistory();
|
||||
const intl = useIntl();
|
||||
const { multiColumn } = useColumnsContext();
|
||||
|
||||
const handleBackClick = useCallback(() => {
|
||||
if (history.location?.state?.fromMastodon) {
|
||||
|
@ -39,10 +38,6 @@ const BackButton = ({ pinned, show, onlyIcon }) => {
|
|||
}
|
||||
}, [history]);
|
||||
|
||||
const showButton = history && !pinned && ((multiColumn && history.location?.state?.fromMastodon) || show);
|
||||
|
||||
if (!showButton) return null;
|
||||
|
||||
return (
|
||||
<button onClick={handleBackClick} className={classNames('column-header__back-button', { 'compact': onlyIcon })} aria-label={intl.formatMessage(messages.back)}>
|
||||
<Icon id='chevron-left' icon={ArrowBackIcon} className='column-back-button__icon' />
|
||||
|
@ -52,8 +47,6 @@ const BackButton = ({ pinned, show, onlyIcon }) => {
|
|||
};
|
||||
|
||||
BackButton.propTypes = {
|
||||
pinned: PropTypes.bool,
|
||||
show: PropTypes.bool,
|
||||
onlyIcon: PropTypes.bool,
|
||||
};
|
||||
|
||||
|
@ -118,7 +111,7 @@ class ColumnHeader extends PureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { title, icon, iconComponent, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props;
|
||||
const { title, icon, iconComponent, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues, history } = this.props;
|
||||
const { collapsed, animating } = this.state;
|
||||
|
||||
const wrapperClassName = classNames('column-header__wrapper', {
|
||||
|
@ -161,7 +154,9 @@ class ColumnHeader extends PureComponent {
|
|||
pinButton = <button className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' icon={AddIcon} /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
|
||||
}
|
||||
|
||||
backButton = <BackButton pinned={pinned} show={showBackButton} onlyIcon={!!title} />;
|
||||
if (history && !pinned && ((multiColumn && history.location?.state?.fromMastodon) || showBackButton)) {
|
||||
backButton = <BackButton onlyIcon={!!title} />;
|
||||
}
|
||||
|
||||
const collapsedContent = [
|
||||
extraContent,
|
||||
|
@ -199,16 +194,16 @@ class ColumnHeader extends PureComponent {
|
|||
<h1 className={buttonClassName}>
|
||||
{hasTitle && (
|
||||
<>
|
||||
{showBackButton && backButton}
|
||||
{backButton}
|
||||
|
||||
<button onClick={this.handleTitleClick} className='column-header__title'>
|
||||
{!showBackButton && <Icon id={icon} icon={iconComponent} className='column-header__icon' />}
|
||||
{!backButton && <Icon id={icon} icon={iconComponent} className='column-header__icon' />}
|
||||
{title}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!hasTitle && showBackButton && backButton}
|
||||
{!hasTitle && backButton}
|
||||
|
||||
<div className='column-header__buttons'>
|
||||
{extraButton}
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
import { openModal } from '../actions/modal';
|
||||
import { initMuteModal } from '../actions/mutes';
|
||||
import Account from '../components/account';
|
||||
import { unfollowModal } from '../initial_state';
|
||||
import { makeGetAccount } from '../selectors';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -34,7 +33,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
|
||||
onFollow (account) {
|
||||
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
|
||||
if (unfollowModal) {
|
||||
dispatch(openModal({
|
||||
modalType: 'CONFIRM',
|
||||
modalProps: {
|
||||
|
@ -43,9 +41,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
|
||||
},
|
||||
}));
|
||||
} else {
|
||||
dispatch(unfollowAccount(account.get('id')));
|
||||
}
|
||||
} else {
|
||||
dispatch(followAccount(account.get('id')));
|
||||
}
|
||||
|
|
|
@ -408,7 +408,7 @@ class Header extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
account.get('roles', []).forEach((role) => {
|
||||
badges.push(<Badge key={`role-badge-${role.get('id')}`} label={<span>{role.get('name')}</span>} domain={domain} />);
|
||||
badges.push(<Badge key={`role-badge-${role.get('id')}`} label={<span>{role.get('name')}</span>} domain={domain} roleId={role.get('id')} />);
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -21,7 +21,6 @@ import { initDomainBlockModal, unblockDomain } from '../../../actions/domain_blo
|
|||
import { openModal } from '../../../actions/modal';
|
||||
import { initMuteModal } from '../../../actions/mutes';
|
||||
import { initReport } from '../../../actions/reports';
|
||||
import { unfollowModal } from '../../../initial_state';
|
||||
import { makeGetAccount, getAccountHidden } from '../../../selectors';
|
||||
import Header from '../components/header';
|
||||
|
||||
|
@ -47,7 +46,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
|
||||
onFollow (account) {
|
||||
if (account.getIn(['relationship', 'following'])) {
|
||||
if (unfollowModal) {
|
||||
dispatch(openModal({
|
||||
modalType: 'CONFIRM',
|
||||
modalProps: {
|
||||
|
@ -56,11 +54,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
|
||||
},
|
||||
}));
|
||||
} else {
|
||||
dispatch(unfollowAccount(account.get('id')));
|
||||
}
|
||||
} else if (account.getIn(['relationship', 'requested'])) {
|
||||
if (unfollowModal) {
|
||||
dispatch(openModal({
|
||||
modalType: 'CONFIRM',
|
||||
modalProps: {
|
||||
|
@ -69,9 +63,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
|
||||
},
|
||||
}));
|
||||
} else {
|
||||
dispatch(unfollowAccount(account.get('id')));
|
||||
}
|
||||
} else {
|
||||
dispatch(followAccount(account.get('id')));
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import PollButtonContainer from '../containers/poll_button_container';
|
|||
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
|
||||
import SpoilerButtonContainer from '../containers/spoiler_button_container';
|
||||
import UploadButtonContainer from '../containers/upload_button_container';
|
||||
import UploadFormContainer from '../containers/upload_form_container';
|
||||
import WarningContainer from '../containers/warning_container';
|
||||
import { countableText } from '../util/counter';
|
||||
|
||||
|
@ -30,6 +29,7 @@ import { EditIndicator } from './edit_indicator';
|
|||
import { NavigationBar } from './navigation_bar';
|
||||
import { PollForm } from "./poll_form";
|
||||
import { ReplyIndicator } from './reply_indicator';
|
||||
import { UploadForm } from './upload_form';
|
||||
|
||||
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
|
||||
|
||||
|
@ -283,7 +283,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<UploadFormContainer />
|
||||
<UploadForm />
|
||||
<PollForm />
|
||||
|
||||
<div className='compose-form__footer'>
|
||||
|
|
|
@ -1,43 +1,42 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import spring from 'react-motion/lib/spring';
|
||||
|
||||
import CloseIcon from '@/material-icons/400-20px/close.svg?react';
|
||||
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
|
||||
import WarningIcon from '@/material-icons/400-24px/warning.svg?react';
|
||||
import { undoUploadCompose, initMediaEditModal } from 'mastodon/actions/compose';
|
||||
import { Blurhash } from 'mastodon/components/blurhash';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import Motion from 'mastodon/features/ui/util/optional_motion';
|
||||
|
||||
import Motion from '../../ui/util/optional_motion';
|
||||
export const Upload = ({ id, onDragStart, onDragEnter, onDragEnd }) => {
|
||||
const dispatch = useDispatch();
|
||||
const media = useSelector(state => state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id));
|
||||
const sensitive = useSelector(state => state.getIn(['compose', 'spoiler']));
|
||||
|
||||
export default class Upload extends ImmutablePureComponent {
|
||||
const handleUndoClick = useCallback(() => {
|
||||
dispatch(undoUploadCompose(id));
|
||||
}, [dispatch, id]);
|
||||
|
||||
static propTypes = {
|
||||
media: ImmutablePropTypes.map.isRequired,
|
||||
sensitive: PropTypes.bool,
|
||||
onUndo: PropTypes.func.isRequired,
|
||||
onOpenFocalPoint: PropTypes.func.isRequired,
|
||||
};
|
||||
const handleFocalPointClick = useCallback(() => {
|
||||
dispatch(initMediaEditModal(id));
|
||||
}, [dispatch, id]);
|
||||
|
||||
handleUndoClick = e => {
|
||||
e.stopPropagation();
|
||||
this.props.onUndo(this.props.media.get('id'));
|
||||
};
|
||||
const handleDragStart = useCallback(() => {
|
||||
onDragStart(id);
|
||||
}, [onDragStart, id]);
|
||||
|
||||
handleFocalPointClick = e => {
|
||||
e.stopPropagation();
|
||||
this.props.onOpenFocalPoint(this.props.media.get('id'));
|
||||
};
|
||||
|
||||
render () {
|
||||
const { media, sensitive } = this.props;
|
||||
const handleDragEnter = useCallback(() => {
|
||||
onDragEnter(id);
|
||||
}, [onDragEnter, id]);
|
||||
|
||||
if (!media) {
|
||||
return null;
|
||||
|
@ -50,7 +49,7 @@ export default class Upload extends ImmutablePureComponent {
|
|||
const missingDescription = (media.get('description') || '').length === 0;
|
||||
|
||||
return (
|
||||
<div className='compose-form__upload'>
|
||||
<div className='compose-form__upload' draggable onDragStart={handleDragStart} onDragEnter={handleDragEnter} onDragEnd={onDragEnd}>
|
||||
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
|
||||
{({ scale }) => (
|
||||
<div className='compose-form__upload__thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: !sensitive ? `url(${media.get('preview_url')})` : null, backgroundPosition: `${x}% ${y}%` }}>
|
||||
|
@ -60,18 +59,23 @@ export default class Upload extends ImmutablePureComponent {
|
|||
/>}
|
||||
|
||||
<div className='compose-form__upload__actions'>
|
||||
<button type='button' className='icon-button compose-form__upload__delete' onClick={this.handleUndoClick}><Icon icon={CloseIcon} /></button>
|
||||
<button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon icon={EditIcon} /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>
|
||||
<button type='button' className='icon-button compose-form__upload__delete' onClick={handleUndoClick}><Icon icon={CloseIcon} /></button>
|
||||
<button type='button' className='icon-button' onClick={handleFocalPointClick}><Icon icon={EditIcon} /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>
|
||||
</div>
|
||||
|
||||
<div className='compose-form__upload__warning'>
|
||||
<button type='button' className={classNames('icon-button', { active: missingDescription })} onClick={this.handleFocalPointClick}>{missingDescription && <Icon icon={WarningIcon} />} ALT</button>
|
||||
<button type='button' className={classNames('icon-button', { active: missingDescription })} onClick={handleFocalPointClick}>{missingDescription && <Icon icon={WarningIcon} />} ALT</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Motion>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
Upload.propTypes = {
|
||||
id: PropTypes.string,
|
||||
onDragEnter: PropTypes.func,
|
||||
onDragStart: PropTypes.func,
|
||||
onDragEnd: PropTypes.func,
|
||||
};
|
||||
|
|
|
@ -1,31 +1,53 @@
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { useRef, useCallback } from 'react';
|
||||
|
||||
import UploadContainer from '../containers/upload_container';
|
||||
import UploadProgressContainer from '../containers/upload_progress_container';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
export default class UploadForm extends ImmutablePureComponent {
|
||||
import { changeMediaOrder } from 'mastodon/actions/compose';
|
||||
|
||||
static propTypes = {
|
||||
mediaIds: ImmutablePropTypes.list.isRequired,
|
||||
};
|
||||
import { Upload } from './upload';
|
||||
import { UploadProgress } from './upload_progress';
|
||||
|
||||
render () {
|
||||
const { mediaIds } = this.props;
|
||||
export const UploadForm = () => {
|
||||
const dispatch = useDispatch();
|
||||
const mediaIds = useSelector(state => state.getIn(['compose', 'media_attachments']).map(item => item.get('id')));
|
||||
const active = useSelector(state => state.getIn(['compose', 'is_uploading']));
|
||||
const progress = useSelector(state => state.getIn(['compose', 'progress']));
|
||||
const isProcessing = useSelector(state => state.getIn(['compose', 'is_processing']));
|
||||
|
||||
const dragItem = useRef();
|
||||
const dragOverItem = useRef();
|
||||
|
||||
const handleDragStart = useCallback(id => {
|
||||
dragItem.current = id;
|
||||
}, [dragItem]);
|
||||
|
||||
const handleDragEnter = useCallback(id => {
|
||||
dragOverItem.current = id;
|
||||
}, [dragOverItem]);
|
||||
|
||||
const handleDragEnd = useCallback(() => {
|
||||
dispatch(changeMediaOrder(dragItem.current, dragOverItem.current));
|
||||
dragItem.current = null;
|
||||
dragOverItem.current = null;
|
||||
}, [dispatch, dragItem, dragOverItem]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<UploadProgressContainer />
|
||||
<UploadProgress active={active} progress={progress} isProcessing={isProcessing} />
|
||||
|
||||
{mediaIds.size > 0 && (
|
||||
<div className='compose-form__uploads'>
|
||||
{mediaIds.map(id => (
|
||||
<UploadContainer id={id} key={id} />
|
||||
<Upload
|
||||
key={id}
|
||||
id={id}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragEnd={handleDragEnd}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
|
@ -10,17 +9,7 @@ import { Icon } from 'mastodon/components/icon';
|
|||
|
||||
import Motion from '../../ui/util/optional_motion';
|
||||
|
||||
export default class UploadProgress extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
active: PropTypes.bool,
|
||||
progress: PropTypes.number,
|
||||
isProcessing: PropTypes.bool,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { active, progress, isProcessing } = this.props;
|
||||
|
||||
export const UploadProgress = ({ active, progress, isProcessing }) => {
|
||||
if (!active) {
|
||||
return null;
|
||||
}
|
||||
|
@ -50,6 +39,10 @@ export default class UploadProgress extends PureComponent {
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
UploadProgress.propTypes = {
|
||||
active: PropTypes.bool,
|
||||
progress: PropTypes.number,
|
||||
isProcessing: PropTypes.bool,
|
||||
};
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import { undoUploadCompose, initMediaEditModal, submitCompose } from '../../../actions/compose';
|
||||
import Upload from '../components/upload';
|
||||
|
||||
const mapStateToProps = (state, { id }) => ({
|
||||
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
|
||||
sensitive: state.getIn(['compose', 'spoiler']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onUndo: id => {
|
||||
dispatch(undoUploadCompose(id));
|
||||
},
|
||||
|
||||
onOpenFocalPoint: id => {
|
||||
dispatch(initMediaEditModal(id));
|
||||
},
|
||||
|
||||
onSubmit (router) {
|
||||
dispatch(submitCompose(router));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Upload);
|
|
@ -1,9 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import UploadForm from '../components/upload_form';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
mediaIds: state.getIn(['compose', 'media_attachments']).map(item => item.get('id')),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(UploadForm);
|
|
@ -1,11 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import UploadProgress from '../components/upload_progress';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
active: state.getIn(['compose', 'is_uploading']),
|
||||
progress: state.getIn(['compose', 'progress']),
|
||||
isProcessing: state.getIn(['compose', 'is_processing']),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(UploadProgress);
|
|
@ -20,7 +20,7 @@ import { Avatar } from 'mastodon/components/avatar';
|
|||
import { Button } from 'mastodon/components/button';
|
||||
import { DisplayName } from 'mastodon/components/display_name';
|
||||
import { ShortNumber } from 'mastodon/components/short_number';
|
||||
import { autoPlayGif, me, unfollowModal } from 'mastodon/initial_state';
|
||||
import { autoPlayGif, me } from 'mastodon/initial_state';
|
||||
import { makeGetAccount } from 'mastodon/selectors';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -48,7 +48,6 @@ const makeMapStateToProps = () => {
|
|||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
onFollow(account) {
|
||||
if (account.getIn(['relationship', 'following'])) {
|
||||
if (unfollowModal) {
|
||||
dispatch(
|
||||
openModal({
|
||||
modalType: 'CONFIRM',
|
||||
|
@ -64,11 +63,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
|
||||
} }),
|
||||
);
|
||||
} else {
|
||||
dispatch(unfollowAccount(account.get('id')));
|
||||
}
|
||||
} else if (account.getIn(['relationship', 'requested'])) {
|
||||
if (unfollowModal) {
|
||||
dispatch(openModal({
|
||||
modalType: 'CONFIRM',
|
||||
modalProps: {
|
||||
|
@ -77,9 +72,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
|
||||
},
|
||||
}));
|
||||
} else {
|
||||
dispatch(unfollowAccount(account.get('id')));
|
||||
}
|
||||
} else {
|
||||
dispatch(followAccount(account.get('id')));
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ class FollowRequests extends ImmutablePureComponent {
|
|||
);
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} icon='user-plus' iconComponent={PersonAddIcon} heading={intl.formatMessage(messages.heading)} alwaysShowBackButton>
|
||||
<Column bindToDocument={!multiColumn} icon='user-plus' iconComponent={PersonAddIcon} heading={intl.formatMessage(messages.heading)}>
|
||||
<ScrollableList
|
||||
scrollKey='follow_requests'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
|
|
|
@ -54,6 +54,7 @@ export default class ColumnSettings extends PureComponent {
|
|||
render () {
|
||||
const { settings, pushSettings, onChange, onClear, alertsEnabled, browserSupport, browserPermission, onRequestNotificationPermission, notificationPolicy } = this.props;
|
||||
|
||||
const filterAdvancedStr = <FormattedMessage id='notifications.column_settings.filter_bar.advanced' defaultMessage='Display all categories' />;
|
||||
const unreadMarkersShowStr = <FormattedMessage id='notifications.column_settings.unread_notifications.highlight' defaultMessage='Highlight unread notifications' />;
|
||||
const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
|
||||
const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
|
||||
|
@ -116,6 +117,16 @@ export default class ColumnSettings extends PureComponent {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<section role='group' aria-labelledby='notifications-filter-bar'>
|
||||
<h3 id='notifications-filter-bar'>
|
||||
<FormattedMessage id='notifications.column_settings.filter_bar.category' defaultMessage='Quick filter bar' />
|
||||
</h3>
|
||||
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle id='advanced-filter-bar' prefix='notifications' settings={settings} settingPath={['quickFilter', 'advanced']} onChange={onChange} label={filterAdvancedStr} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section role='group' aria-labelledby='notifications-follow'>
|
||||
<h3 id='notifications-follow'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></h3>
|
||||
|
||||
|
|
|
@ -41,7 +41,8 @@ export const FilteredNotificationsBanner = () => {
|
|||
</div>
|
||||
|
||||
<div className='filtered-notifications-banner__badge'>
|
||||
{toCappedNumber(policy.getIn(['summary', 'pending_notifications_count']))}
|
||||
<div className='filtered-notifications-banner__badge__badge'>{toCappedNumber(policy.getIn(['summary', 'pending_notifications_count']))}</div>
|
||||
<FormattedMessage id='filtered_notifications_banner.private_mentions' defaultMessage='{count, plural, one {private mention} other {private mentions}}' values={{ count: policy.getIn(['summary', 'pending_notifications_count']) }} />
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
|
|
|
@ -26,6 +26,7 @@ import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
|||
|
||||
import FollowRequestContainer from '../containers/follow_request_container';
|
||||
|
||||
import { RelationshipsSeveranceEvent } from './relationships_severance_event';
|
||||
import Report from './report';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -38,6 +39,7 @@ const messages = defineMessages({
|
|||
update: { id: 'notification.update', defaultMessage: '{name} edited a post' },
|
||||
adminSignUp: { id: 'notification.admin.sign_up', defaultMessage: '{name} signed up' },
|
||||
adminReport: { id: 'notification.admin.report', defaultMessage: '{name} reported {target}' },
|
||||
relationshipsSevered: { id: 'notification.relationships_severance_event', defaultMessage: 'Lost connections with {name}' },
|
||||
});
|
||||
|
||||
const notificationForScreenReader = (intl, message, timestamp) => {
|
||||
|
@ -358,6 +360,29 @@ class Notification extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
renderRelationshipsSevered (notification) {
|
||||
const { intl, unread, hidden } = this.props;
|
||||
const event = notification.get('event');
|
||||
|
||||
if (!event) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<div className={classNames('notification notification-severed-relationships focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.relationshipsSevered, { name: notification.getIn(['event', 'target_name']) }), notification.get('created_at'))}>
|
||||
<RelationshipsSeveranceEvent
|
||||
type={event.get('type')}
|
||||
target={event.get('target_name')}
|
||||
followersCount={event.get('followers_count')}
|
||||
followingCount={event.get('following_count')}
|
||||
hidden={hidden}
|
||||
/>
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
renderAdminSignUp (notification, account, link) {
|
||||
const { intl, unread } = this.props;
|
||||
|
||||
|
@ -429,6 +454,8 @@ class Notification extends ImmutablePureComponent {
|
|||
return this.renderUpdate(notification, link);
|
||||
case 'poll':
|
||||
return this.renderPoll(notification, account);
|
||||
case 'severed_relationships':
|
||||
return this.renderRelationshipsSevered(notification);
|
||||
case 'admin.sign_up':
|
||||
return this.renderAdminSignUp(notification, account, link);
|
||||
case 'admin.report':
|
||||
|
|
|
@ -7,8 +7,8 @@ import { Link } from 'react-router-dom';
|
|||
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
|
||||
import DoneIcon from '@/material-icons/400-24px/done.svg?react';
|
||||
import VolumeOffIcon from '@/material-icons/400-24px/volume_off.svg?react';
|
||||
import { acceptNotificationRequest, dismissNotificationRequest } from 'mastodon/actions/notifications';
|
||||
import { Avatar } from 'mastodon/components/avatar';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
|
@ -51,7 +51,7 @@ export const NotificationRequest = ({ id, accountId, notificationsCount }) => {
|
|||
</Link>
|
||||
|
||||
<div className='notification-request__actions'>
|
||||
<IconButton iconComponent={VolumeOffIcon} onClick={handleDismiss} title={intl.formatMessage(messages.dismiss)} />
|
||||
<IconButton iconComponent={DeleteIcon} onClick={handleDismiss} title={intl.formatMessage(messages.dismiss)} />
|
||||
<IconButton iconComponent={DoneIcon} onClick={handleAccept} title={intl.formatMessage(messages.accept)} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import HeartBrokenIcon from '@/material-icons/400-24px/heart_broken-fill.svg?react';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { domain } from 'mastodon/initial_state';
|
||||
|
||||
// This needs to be kept in sync with app/models/relationships_severance_event.rb
|
||||
const messages = defineMessages({
|
||||
account_suspension: { id: 'notification.relationships_severance_event.account_suspension', defaultMessage: 'An admin from {from} has suspended {target}, which means you can no longer receive updates from them or interact with them.' },
|
||||
domain_block: { id: 'notification.relationships_severance_event.domain_block', defaultMessage: 'An admin from {from} has blocked {target}, including {followersCount} of your followers and {followingCount, plural, one {# account} other {# accounts}} you follow.' },
|
||||
user_domain_block: { id: 'notification.relationships_severance_event.user_domain_block', defaultMessage: 'You have blocked {target}, removing {followersCount} of your followers and {followingCount, plural, one {# account} other {# accounts}} you follow.' },
|
||||
});
|
||||
|
||||
export const RelationshipsSeveranceEvent = ({ type, target, followingCount, followersCount, hidden }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
if (hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<a href='/severed_relationships' target='_blank' rel='noopener noreferrer' className='notification__relationships-severance-event'>
|
||||
<Icon id='heart_broken' icon={HeartBrokenIcon} />
|
||||
|
||||
<div className='notification__relationships-severance-event__content'>
|
||||
<p>{intl.formatMessage(messages[type], { from: <strong>{domain}</strong>, target: <strong>{target}</strong>, followingCount, followersCount })}</p>
|
||||
<span className='link-button'><FormattedMessage id='notification.relationships_severance_event.learn_more' defaultMessage='Learn more' /></span>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
RelationshipsSeveranceEvent.propTypes = {
|
||||
type: PropTypes.oneOf([
|
||||
'account_suspension',
|
||||
'domain_block',
|
||||
'user_domain_block',
|
||||
]).isRequired,
|
||||
target: PropTypes.string.isRequired,
|
||||
followersCount: PropTypes.number.isRequired,
|
||||
followingCount: PropTypes.number.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
};
|
|
@ -5,7 +5,7 @@ import FilterBar from '../components/filter_bar';
|
|||
|
||||
const makeMapStateToProps = state => ({
|
||||
selectedFilter: state.getIn(['settings', 'notifications', 'quickFilter', 'active']),
|
||||
advancedMode: false,
|
||||
advancedMode: state.getIn(['settings', 'notifications', 'quickFilter', 'advanced']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
|
|
@ -22,7 +22,7 @@ import { GIFV } from 'mastodon/components/gifv';
|
|||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import Audio from 'mastodon/features/audio';
|
||||
import { CharacterCounter } from 'mastodon/features/compose/components/character_counter';
|
||||
import UploadProgress from 'mastodon/features/compose/components/upload_progress';
|
||||
import { UploadProgress } from 'mastodon/features/compose/components/upload_progress';
|
||||
import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import { assetHost } from 'mastodon/utils/config';
|
||||
|
|
|
@ -38,7 +38,6 @@
|
|||
* @property {string} title
|
||||
* @property {boolean} show_trends
|
||||
* @property {boolean} trends_as_landing_page
|
||||
* @property {boolean} unfollow_modal
|
||||
* @property {boolean} use_blurhash
|
||||
* @property {boolean=} use_pending_items
|
||||
* @property {string} version
|
||||
|
@ -99,7 +98,6 @@ export const source_url = getMeta('source_url');
|
|||
export const timelinePreview = getMeta('timeline_preview');
|
||||
export const title = getMeta('title');
|
||||
export const trendsAsLanding = getMeta('trends_as_landing_page');
|
||||
export const unfollowModal = getMeta('unfollow_modal');
|
||||
export const useBlurhash = getMeta('use_blurhash');
|
||||
export const usePendingItems = getMeta('use_pending_items');
|
||||
export const version = getMeta('version');
|
||||
|
|
|
@ -89,6 +89,7 @@
|
|||
"announcement.announcement": "إعلان",
|
||||
"attachments_list.unprocessed": "(غير معالَج)",
|
||||
"audio.hide": "إخفاء المقطع الصوتي",
|
||||
"block_modal.remote_users_caveat": "Do t’i kërkojmë shërbyesit {domain} të respektojë vendimin tuaj. Por, pajtimi s’është i garantuar, ngaqë disa shërbyes mund t’i trajtojnë ndryshe bllokimet. Psotimet publike mundet të jenë ende të dukshme për përdorues pa bërë hyrje në llogari.",
|
||||
"boost_modal.combo": "يُمكنك الضّغط على {combo} لتخطي هذا في المرة المُقبلة",
|
||||
"bundle_column_error.copy_stacktrace": "انسخ تقرير الخطأ",
|
||||
"bundle_column_error.error.body": "لا يمكن تقديم الصفحة المطلوبة. قد يكون بسبب خطأ في التعليمات البرمجية، أو مشكلة توافق المتصفح.",
|
||||
|
|
|
@ -471,6 +471,7 @@
|
|||
"notification.own_poll": "Ваша апытанне скончылася",
|
||||
"notification.poll": "Апытанне, дзе вы прынялі ўдзел, скончылася",
|
||||
"notification.reblog": "{name} пашырыў ваш допіс",
|
||||
"notification.severed_relationships": "Зносіны з {name} былі разарваныя",
|
||||
"notification.status": "Новы допіс ад {name}",
|
||||
"notification.update": "Допіс {name} адрэдагаваны",
|
||||
"notification_requests.accept": "Прыняць",
|
||||
|
@ -483,6 +484,7 @@
|
|||
"notifications.column_settings.admin.sign_up": "Новыя ўваходы:",
|
||||
"notifications.column_settings.alert": "Апавяшчэнні на працоўным стале",
|
||||
"notifications.column_settings.favourite": "Упадабанае:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Паказаць усе катэгорыі",
|
||||
"notifications.column_settings.follow": "Новыя падпісчыкі:",
|
||||
"notifications.column_settings.follow_request": "Новыя запыты на падпіску:",
|
||||
"notifications.column_settings.mention": "Згадванні:",
|
||||
|
@ -587,6 +589,8 @@
|
|||
"refresh": "Абнавiць",
|
||||
"regeneration_indicator.label": "Загрузка…",
|
||||
"regeneration_indicator.sublabel": "Пачакайце, рыхтуем вашу стужку!",
|
||||
"relationship_severance_notification.types.user_domain_block": "Вы заблакіравалі гэты дамен",
|
||||
"relationship_severance_notification.view": "Праглядзець",
|
||||
"relative_time.days": "{number} д",
|
||||
"relative_time.full.days": "{number, plural, one {# дзень} few {# дні} many {# дзён} other {# дня}} таму",
|
||||
"relative_time.full.hours": "{number, plural, one {# гадзіна} few {# гадзіны} many {# гадзін} other {# гадзіны}} таму",
|
||||
|
|
|
@ -434,7 +434,7 @@
|
|||
"mute_modal.they_can_mention_and_follow": "Могат да ви споменават и последват, но няма да ги виждате.",
|
||||
"mute_modal.they_wont_know": "Няма да узнаят, че са били заглушени.",
|
||||
"mute_modal.title": "Заглушавате ли потребител?",
|
||||
"mute_modal.you_wont_see_mentions": "Няма да виждате споменаващи ги публикации.",
|
||||
"mute_modal.you_wont_see_mentions": "Няма да виждате споменаващите ги публикации.",
|
||||
"mute_modal.you_wont_see_posts": "Още могат да виждат публикациите ви, но вие техните не.",
|
||||
"navigation_bar.about": "Относно",
|
||||
"navigation_bar.advanced_interface": "Отваряне в разширен уебинтерфейс",
|
||||
|
@ -471,6 +471,7 @@
|
|||
"notification.own_poll": "Анкетата ви приключи",
|
||||
"notification.poll": "Анкета, в която гласувахте, приключи",
|
||||
"notification.reblog": "{name} подсили ваша публикация",
|
||||
"notification.severed_relationships": "Връзката с {name} е прекъсната",
|
||||
"notification.status": "{name} току-що публикува",
|
||||
"notification.update": "{name} промени публикация",
|
||||
"notification_requests.accept": "Приемам",
|
||||
|
@ -587,6 +588,12 @@
|
|||
"refresh": "Опресняване",
|
||||
"regeneration_indicator.label": "Зареждане…",
|
||||
"regeneration_indicator.sublabel": "Подготовка на началния ви инфоканал!",
|
||||
"relationship_severance_notification.purged_data": "прочистено от администраторите",
|
||||
"relationship_severance_notification.relationships": "{count, plural, one {# връзка} other {# връзки}}",
|
||||
"relationship_severance_notification.types.account_suspension": "Акаунтът е спрян",
|
||||
"relationship_severance_notification.types.domain_block": "Домейнът е спрян",
|
||||
"relationship_severance_notification.types.user_domain_block": "Блокирахте този домейн",
|
||||
"relationship_severance_notification.view": "Преглед",
|
||||
"relative_time.days": "{number} д.",
|
||||
"relative_time.full.days": "преди {number, plural, one {# ден} other {# дни}}",
|
||||
"relative_time.full.hours": "преди {number, plural, one {# час} other {# часа}}",
|
||||
|
|
|
@ -471,6 +471,7 @@
|
|||
"notification.own_poll": "La teva enquesta ha finalitzat",
|
||||
"notification.poll": "Ha finalitzat una enquesta en què has votat",
|
||||
"notification.reblog": "{name} t'ha impulsat",
|
||||
"notification.severed_relationships": "S'han eliminat les relacions amb {name}",
|
||||
"notification.status": "{name} acaba de publicar",
|
||||
"notification.update": "{name} ha editat un tut",
|
||||
"notification_requests.accept": "Accepta",
|
||||
|
@ -587,6 +588,12 @@
|
|||
"refresh": "Actualitza",
|
||||
"regeneration_indicator.label": "Es carrega…",
|
||||
"regeneration_indicator.sublabel": "Es prepara la teva línia de temps d'Inici!",
|
||||
"relationship_severance_notification.purged_data": "purgat pels administradors",
|
||||
"relationship_severance_notification.relationships": "{count, plural, one {# relació} other {# relacions}}",
|
||||
"relationship_severance_notification.types.account_suspension": "S'ha suspès el compte",
|
||||
"relationship_severance_notification.types.domain_block": "S'ha suspès el domini",
|
||||
"relationship_severance_notification.types.user_domain_block": "Heu blocat aquest domini",
|
||||
"relationship_severance_notification.view": "Visualitza",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.full.days": "fa {number, plural, one {# dia} other {# dies}}",
|
||||
"relative_time.full.hours": "fa {number, plural, one {# hora} other {# hores}}",
|
||||
|
|
|
@ -220,7 +220,7 @@
|
|||
"domain_pill.activitypub_lets_connect": "Det muliggør at komme i forbindelse og interagere med folk ikke kun på Mastodon, men også på tværs af forskellige sociale apps.",
|
||||
"domain_pill.activitypub_like_language": "ActivityPub er \"sproget\", Mastodon taler med andre sociale netværk.",
|
||||
"domain_pill.server": "Server",
|
||||
"domain_pill.their_handle": "Deres handle:",
|
||||
"domain_pill.their_handle": "Vedkommendes handle:",
|
||||
"domain_pill.username": "Brugernavn",
|
||||
"domain_pill.whats_in_a_handle": "Hvad er der i et handle (@brugernavn)?",
|
||||
"domain_pill.who_they_are": "Da et handle fortæller, hvem nogen er, og hvor de er, kan man interagere med folk på tværs af det sociale net af <button>ActivityPub-drevne platforme</button>.",
|
||||
|
@ -481,6 +481,8 @@
|
|||
"notifications.column_settings.admin.sign_up": "Nye tilmeldinger:",
|
||||
"notifications.column_settings.alert": "Computernotifikationer",
|
||||
"notifications.column_settings.favourite": "Favoritter:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Vis alle kategorier",
|
||||
"notifications.column_settings.filter_bar.category": "Hurtigfiltreringsbjælke",
|
||||
"notifications.column_settings.follow": "Nye følgere:",
|
||||
"notifications.column_settings.follow_request": "Nye følgeanmodninger:",
|
||||
"notifications.column_settings.mention": "Omtaler:",
|
||||
|
@ -585,6 +587,12 @@
|
|||
"refresh": "Genindlæs",
|
||||
"regeneration_indicator.label": "Indlæser…",
|
||||
"regeneration_indicator.sublabel": "Din hjemmetidslinje klargøres!",
|
||||
"relationship_severance_notification.purged_data": "renset af administratorer",
|
||||
"relationship_severance_notification.relationships": "{count, plural, one {# forhold} other {# forhold}}",
|
||||
"relationship_severance_notification.types.account_suspension": "Konto er blevet suspenderet",
|
||||
"relationship_severance_notification.types.domain_block": "Domæne er blevet suspenderet",
|
||||
"relationship_severance_notification.types.user_domain_block": "Dette domæne blev blokeret",
|
||||
"relationship_severance_notification.view": "Vis",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.full.days": "{number, plural, one {# dag} other {# dage}} siden",
|
||||
"relative_time.full.hours": "{number, plural, one {# time} other {# timer}} siden",
|
||||
|
|
|
@ -471,6 +471,7 @@
|
|||
"notification.own_poll": "Deine Umfrage ist beendet",
|
||||
"notification.poll": "Eine Umfrage, an der du teilgenommen hast, ist beendet",
|
||||
"notification.reblog": "{name} teilte deinen Beitrag",
|
||||
"notification.severed_relationships": "Beziehungen zu {name} getrennt",
|
||||
"notification.status": "{name} hat gerade etwas gepostet",
|
||||
"notification.update": "{name} bearbeitete einen Beitrag",
|
||||
"notification_requests.accept": "Akzeptieren",
|
||||
|
@ -483,6 +484,8 @@
|
|||
"notifications.column_settings.admin.sign_up": "Neue Registrierungen:",
|
||||
"notifications.column_settings.alert": "Desktop-Benachrichtigungen",
|
||||
"notifications.column_settings.favourite": "Favoriten:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Alle Filterkategorien anzeigen",
|
||||
"notifications.column_settings.filter_bar.category": "Filterleiste",
|
||||
"notifications.column_settings.follow": "Neue Follower:",
|
||||
"notifications.column_settings.follow_request": "Neue Follower-Anfragen:",
|
||||
"notifications.column_settings.mention": "Erwähnungen:",
|
||||
|
@ -587,6 +590,12 @@
|
|||
"refresh": "Aktualisieren",
|
||||
"regeneration_indicator.label": "Wird geladen …",
|
||||
"regeneration_indicator.sublabel": "Deine Startseite wird gerade vorbereitet!",
|
||||
"relationship_severance_notification.purged_data": "von Administrator*innen entfernt",
|
||||
"relationship_severance_notification.relationships": "{count, plural, one {# Beziehung} other {# Beziehungen}}",
|
||||
"relationship_severance_notification.types.account_suspension": "Konto wurde gesperrt",
|
||||
"relationship_severance_notification.types.domain_block": "Domain wurde gesperrt",
|
||||
"relationship_severance_notification.types.user_domain_block": "Du hast diese Domain blockiert",
|
||||
"relationship_severance_notification.view": "Anzeigen",
|
||||
"relative_time.days": "{number} T.",
|
||||
"relative_time.full.days": "vor {number, plural, one {# Tag} other {# Tagen}}",
|
||||
"relative_time.full.hours": "vor {number, plural, one {# Stunde} other {# Stunden}}",
|
||||
|
|
|
@ -298,6 +298,7 @@
|
|||
"filter_modal.select_filter.title": "Filter this post",
|
||||
"filter_modal.title.status": "Filter a post",
|
||||
"filtered_notifications_banner.pending_requests": "Notifications from {count, plural, =0 {no one} one {one person} other {# people}} you may know",
|
||||
"filtered_notifications_banner.private_mentions": "{count, plural, one {private mention} other {private mentions}}",
|
||||
"filtered_notifications_banner.title": "Filtered notifications",
|
||||
"firehose.all": "All",
|
||||
"firehose.local": "This server",
|
||||
|
@ -471,6 +472,11 @@
|
|||
"notification.own_poll": "Your poll has ended",
|
||||
"notification.poll": "A poll you have voted in has ended",
|
||||
"notification.reblog": "{name} boosted your post",
|
||||
"notification.relationships_severance_event": "Lost connections with {name}",
|
||||
"notification.relationships_severance_event.account_suspension": "An admin from {from} has suspended {target}, which means you can no longer receive updates from them or interact with them.",
|
||||
"notification.relationships_severance_event.domain_block": "An admin from {from} has blocked {target}, including {followersCount} of your followers and {followingCount, plural, one {# account} other {# accounts}} you follow.",
|
||||
"notification.relationships_severance_event.learn_more": "Learn more",
|
||||
"notification.relationships_severance_event.user_domain_block": "You have blocked {target}, removing {followersCount} of your followers and {followingCount, plural, one {# account} other {# accounts}} you follow.",
|
||||
"notification.status": "{name} just posted",
|
||||
"notification.update": "{name} edited a post",
|
||||
"notification_requests.accept": "Accept",
|
||||
|
@ -483,6 +489,8 @@
|
|||
"notifications.column_settings.admin.sign_up": "New sign-ups:",
|
||||
"notifications.column_settings.alert": "Desktop notifications",
|
||||
"notifications.column_settings.favourite": "Favorites:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Display all categories",
|
||||
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
||||
"notifications.column_settings.follow": "New followers:",
|
||||
"notifications.column_settings.follow_request": "New follow requests:",
|
||||
"notifications.column_settings.mention": "Mentions:",
|
||||
|
|
|
@ -427,7 +427,7 @@
|
|||
"loading_indicator.label": "Cargando…",
|
||||
"media_gallery.toggle_visible": "Ocultar {number, plural, one {imagen} other {imágenes}}",
|
||||
"moved_to_account_banner.text": "Tu cuenta {disabledAccount} está actualmente deshabilitada porque te mudaste a {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Ocultar de las notificaciones",
|
||||
"mute_modal.hide_from_notifications": "Ocultar en las notificaciones",
|
||||
"mute_modal.hide_options": "Ocultar opciones",
|
||||
"mute_modal.indefinite": "Hasta que deje de silenciarlos",
|
||||
"mute_modal.show_options": "Mostrar opciones",
|
||||
|
@ -471,6 +471,7 @@
|
|||
"notification.own_poll": "Tu encuesta finalizó",
|
||||
"notification.poll": "Finalizó una encuesta en la que votaste",
|
||||
"notification.reblog": "{name} adhirió a tu mensaje",
|
||||
"notification.severed_relationships": "Relaciones con {name} cortadas",
|
||||
"notification.status": "{name} acaba de enviar un mensaje",
|
||||
"notification.update": "{name} editó un mensaje",
|
||||
"notification_requests.accept": "Aceptar",
|
||||
|
@ -483,6 +484,8 @@
|
|||
"notifications.column_settings.admin.sign_up": "Nuevos registros:",
|
||||
"notifications.column_settings.alert": "Notificaciones de escritorio",
|
||||
"notifications.column_settings.favourite": "Favoritos:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Mostrar todas las categorías",
|
||||
"notifications.column_settings.filter_bar.category": "Barra de filtrado rápido",
|
||||
"notifications.column_settings.follow": "Nuevos seguidores:",
|
||||
"notifications.column_settings.follow_request": "Nuevas solicitudes de seguimiento:",
|
||||
"notifications.column_settings.mention": "Menciones:",
|
||||
|
@ -587,6 +590,12 @@
|
|||
"refresh": "Refrescar",
|
||||
"regeneration_indicator.label": "Cargando…",
|
||||
"regeneration_indicator.sublabel": "¡Se está preparando tu línea temporal principal!",
|
||||
"relationship_severance_notification.purged_data": "purgada por administradores",
|
||||
"relationship_severance_notification.relationships": "{count, plural, one {# relación} other {# relaciones}}",
|
||||
"relationship_severance_notification.types.account_suspension": "La cuenta fue suspendida",
|
||||
"relationship_severance_notification.types.domain_block": "El dominio fue suspendido",
|
||||
"relationship_severance_notification.types.user_domain_block": "Bloqueaste este dominio",
|
||||
"relationship_severance_notification.view": "Ver",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.full.days": "{number, plural,one {hace # día} other {hace # días}}",
|
||||
"relative_time.full.hours": "{number, plural,one {hace # hora} other {hace # horas}}",
|
||||
|
|
|
@ -471,6 +471,7 @@
|
|||
"notification.own_poll": "Tu encuesta ha terminado",
|
||||
"notification.poll": "Una encuesta en la que has votado ha terminado",
|
||||
"notification.reblog": "{name} ha retooteado tu estado",
|
||||
"notification.severed_relationships": "Se han cortado las relaciones con {name}",
|
||||
"notification.status": "{name} acaba de publicar",
|
||||
"notification.update": "{name} editó una publicación",
|
||||
"notification_requests.accept": "Aceptar",
|
||||
|
@ -483,6 +484,8 @@
|
|||
"notifications.column_settings.admin.sign_up": "Registros nuevos:",
|
||||
"notifications.column_settings.alert": "Notificaciones de escritorio",
|
||||
"notifications.column_settings.favourite": "Favoritos:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Mostrar todas las categorías",
|
||||
"notifications.column_settings.filter_bar.category": "Barra de filtrado rápido",
|
||||
"notifications.column_settings.follow": "Nuevos seguidores:",
|
||||
"notifications.column_settings.follow_request": "Nuevas solicitudes de seguimiento:",
|
||||
"notifications.column_settings.mention": "Menciones:",
|
||||
|
@ -587,6 +590,12 @@
|
|||
"refresh": "Actualizar",
|
||||
"regeneration_indicator.label": "Cargando…",
|
||||
"regeneration_indicator.sublabel": "¡Tu historia de inicio se está preparando!",
|
||||
"relationship_severance_notification.purged_data": "purgado por administradores",
|
||||
"relationship_severance_notification.relationships": "{count, plural, one {# relación} other {# relaciones}}",
|
||||
"relationship_severance_notification.types.account_suspension": "La cuenta ha sido suspendida",
|
||||
"relationship_severance_notification.types.domain_block": "El dominio ha sido suspendido",
|
||||
"relationship_severance_notification.types.user_domain_block": "Bloqueaste este dominio",
|
||||
"relationship_severance_notification.view": "Ver",
|
||||
"relative_time.days": "{number} d",
|
||||
"relative_time.full.days": "{number, plural, one {# día} other {# días hace}}",
|
||||
"relative_time.full.hours": "{number, plural, one {# hora} other {# horas}} hace",
|
||||
|
|
|
@ -471,6 +471,7 @@
|
|||
"notification.own_poll": "Tu encuesta ha terminado",
|
||||
"notification.poll": "Una encuesta en la que has votado ha terminado",
|
||||
"notification.reblog": "{name} ha impulsado tu publicación",
|
||||
"notification.severed_relationships": "Se han cortado las relaciones con {name}",
|
||||
"notification.status": "{name} acaba de publicar",
|
||||
"notification.update": "{name} editó una publicación",
|
||||
"notification_requests.accept": "Aceptar",
|
||||
|
@ -483,6 +484,8 @@
|
|||
"notifications.column_settings.admin.sign_up": "Nuevos registros:",
|
||||
"notifications.column_settings.alert": "Notificaciones de escritorio",
|
||||
"notifications.column_settings.favourite": "Favoritos:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Mostrar todas las categorías",
|
||||
"notifications.column_settings.filter_bar.category": "Barra de filtrado rápido",
|
||||
"notifications.column_settings.follow": "Nuevos seguidores:",
|
||||
"notifications.column_settings.follow_request": "Nuevas solicitudes de seguimiento:",
|
||||
"notifications.column_settings.mention": "Menciones:",
|
||||
|
@ -587,6 +590,12 @@
|
|||
"refresh": "Actualizar",
|
||||
"regeneration_indicator.label": "Cargando…",
|
||||
"regeneration_indicator.sublabel": "¡Tu historia de inicio se está preparando!",
|
||||
"relationship_severance_notification.purged_data": "purgado por administradores",
|
||||
"relationship_severance_notification.relationships": "{count, plural, one {# relación} other {# relaciones}}",
|
||||
"relationship_severance_notification.types.account_suspension": "La cuenta ha sido suspendida",
|
||||
"relationship_severance_notification.types.domain_block": "El dominio ha sido suspendido",
|
||||
"relationship_severance_notification.types.user_domain_block": "Bloqueaste este dominio",
|
||||
"relationship_severance_notification.view": "Ver",
|
||||
"relative_time.days": "{number} d",
|
||||
"relative_time.full.days": "hace {number, plural, one {# día} other {# días}}",
|
||||
"relative_time.full.hours": "hace {number, plural, one {# hora} other {# horas}}",
|
||||
|
|
|
@ -471,6 +471,7 @@
|
|||
"notification.own_poll": "Zure inkesta amaitu da",
|
||||
"notification.poll": "Zuk erantzun duzun inkesta bat bukatu da",
|
||||
"notification.reblog": "{name}(e)k bultzada eman dio zure bidalketari",
|
||||
"notification.severed_relationships": "{name} erabiltzailearekin zenuen erlazioa galdu da",
|
||||
"notification.status": "{name} erabiltzaileak bidalketa egin berri du",
|
||||
"notification.update": "{name} erabiltzaileak bidalketa bat editatu du",
|
||||
"notification_requests.accept": "Onartu",
|
||||
|
@ -483,6 +484,8 @@
|
|||
"notifications.column_settings.admin.sign_up": "Izen-emate berriak:",
|
||||
"notifications.column_settings.alert": "Mahaigaineko jakinarazpenak",
|
||||
"notifications.column_settings.favourite": "Gogokoak:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Bistaratu kategoria guztiak",
|
||||
"notifications.column_settings.filter_bar.category": "Iragazki-barra bizkorra",
|
||||
"notifications.column_settings.follow": "Jarraitzaile berriak:",
|
||||
"notifications.column_settings.follow_request": "Jarraitzeko eskaera berriak:",
|
||||
"notifications.column_settings.mention": "Aipamenak:",
|
||||
|
@ -587,6 +590,12 @@
|
|||
"refresh": "Berritu",
|
||||
"regeneration_indicator.label": "Kargatzen…",
|
||||
"regeneration_indicator.sublabel": "Zure hasiera-jarioa prestatzen ari da!",
|
||||
"relationship_severance_notification.purged_data": "administratzaileek kendua",
|
||||
"relationship_severance_notification.relationships": "{count, plural, one {Erlazio #} other {# erlazio}}",
|
||||
"relationship_severance_notification.types.account_suspension": "Kontua bertan behera utzi da",
|
||||
"relationship_severance_notification.types.domain_block": "Domeinua bertan behera utzi da",
|
||||
"relationship_severance_notification.types.user_domain_block": "Domeinu hau blokeatu duzu",
|
||||
"relationship_severance_notification.view": "Ikusi",
|
||||
"relative_time.days": "{number}e",
|
||||
"relative_time.full.days": "Duela {number, plural, one {egun #} other {# egun}}",
|
||||
"relative_time.full.hours": "Duela {number, plural, one {ordu #} other {# ordu}}",
|
||||
|
|
|
@ -462,6 +462,8 @@
|
|||
"notifications.permission_denied": "آگاهیهای میزکار به دلیل رد کردن درخواست اجازهٔ پیشین مرورگر، در دسترس نیستند",
|
||||
"notifications.permission_denied_alert": "از آنجا که پیش از این اجازهٔ مرورگر رد شده است، آگاهیهای میزکار نمیتوانند به کار بیفتند",
|
||||
"notifications.permission_required": "آگاهیهای میزکار در دسترس نیستند زیرا اجازههای لازم، اعطا نشده.",
|
||||
"notifications.policy.filter_not_followers_title": "کسانی که شما را دنبال میکنند",
|
||||
"notifications.policy.filter_not_following_hint": "",
|
||||
"notifications_permission_banner.enable": "به کار انداختن آگاهیهای میزکار",
|
||||
"notifications_permission_banner.how_to_control": "برای دریافت آگاهیها هنگام باز نبودن ماستودون، آگاهیهای میزکار را به کار بیندازید. پس از به کار افتادنشان میتوانید گونههای دقیق برهمکنشهایی که آگاهیهای میزکار تولید میکنند را از {icon} بالا واپایید.",
|
||||
"notifications_permission_banner.title": "هرگز چیزی را از دست ندهید",
|
||||
|
|
|
@ -471,6 +471,7 @@
|
|||
"notification.own_poll": "Äänestyksesi on päättynyt",
|
||||
"notification.poll": "Kysely, johon osallistuit, on päättynyt",
|
||||
"notification.reblog": "{name} tehosti julkaisuasi",
|
||||
"notification.severed_relationships": "Suhteet palvelimeen {name} katkenneet",
|
||||
"notification.status": "{name} julkaisi juuri",
|
||||
"notification.update": "{name} muokkasi julkaisua",
|
||||
"notification_requests.accept": "Hyväksy",
|
||||
|
@ -483,6 +484,8 @@
|
|||
"notifications.column_settings.admin.sign_up": "Uudet rekisteröitymiset:",
|
||||
"notifications.column_settings.alert": "Työpöytäilmoitukset",
|
||||
"notifications.column_settings.favourite": "Suosikit:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Näytä kaikki luokat",
|
||||
"notifications.column_settings.filter_bar.category": "Pikasuodatuspalkki",
|
||||
"notifications.column_settings.follow": "Uudet seuraajat:",
|
||||
"notifications.column_settings.follow_request": "Uudet seuraamispyynnöt:",
|
||||
"notifications.column_settings.mention": "Maininnat:",
|
||||
|
@ -587,6 +590,12 @@
|
|||
"refresh": "Päivitä",
|
||||
"regeneration_indicator.label": "Ladataan…",
|
||||
"regeneration_indicator.sublabel": "Kotisyötettäsi valmistellaan!",
|
||||
"relationship_severance_notification.purged_data": "ylläpitäjien tyhjentämä",
|
||||
"relationship_severance_notification.relationships": "{count, plural, one {# suhde} other {# suhdetta}}",
|
||||
"relationship_severance_notification.types.account_suspension": "Tili on jäädytetty",
|
||||
"relationship_severance_notification.types.domain_block": "Verkkotunnus on jäädytetty",
|
||||
"relationship_severance_notification.types.user_domain_block": "Estit tämän verkkotunnuksen",
|
||||
"relationship_severance_notification.view": "Näytä",
|
||||
"relative_time.days": "{number} pv",
|
||||
"relative_time.full.days": "{number, plural, one {# päivä} other {# päivää}} sitten",
|
||||
"relative_time.full.hours": "{number, plural, one {# tunti} other {# tuntia}} sitten",
|
||||
|
|
|
@ -471,6 +471,7 @@
|
|||
"notification.own_poll": "Tín atkvøðugreiðsla er endað",
|
||||
"notification.poll": "Ein atkvøðugreiðsla, har tú hevur atkvøtt, er endað",
|
||||
"notification.reblog": "{name} lyfti tín post",
|
||||
"notification.severed_relationships": "Tilknýti við {name} avbrotið",
|
||||
"notification.status": "{name} hevur júst postað",
|
||||
"notification.update": "{name} rættaði ein post",
|
||||
"notification_requests.accept": "Góðtak",
|
||||
|
@ -483,6 +484,8 @@
|
|||
"notifications.column_settings.admin.sign_up": "Nýggjar tilmeldingar:",
|
||||
"notifications.column_settings.alert": "Skriviborðsfráboðanir",
|
||||
"notifications.column_settings.favourite": "Dámdir postar:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Vís allar bólkar",
|
||||
"notifications.column_settings.filter_bar.category": "Skjótfilturbjálki",
|
||||
"notifications.column_settings.follow": "Nýggir fylgjarar:",
|
||||
"notifications.column_settings.follow_request": "Nýggjar umbønir um at fylgja:",
|
||||
"notifications.column_settings.mention": "Umrøður:",
|
||||
|
@ -587,6 +590,12 @@
|
|||
"refresh": "Endurles",
|
||||
"regeneration_indicator.label": "Innlesur…",
|
||||
"regeneration_indicator.sublabel": "Tín heimarás verður gjørd klár!",
|
||||
"relationship_severance_notification.purged_data": "reinsað av umsitarum",
|
||||
"relationship_severance_notification.relationships": "{count, plural, one {# tilknýti} other {# tilknýti}}",
|
||||
"relationship_severance_notification.types.account_suspension": "Kontan er ógildað",
|
||||
"relationship_severance_notification.types.domain_block": "Økisnavn er ógildað",
|
||||
"relationship_severance_notification.types.user_domain_block": "Tú hevur forðað hesum økisnavni",
|
||||
"relationship_severance_notification.view": "Vís",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.full.days": "{number, plural, one {# dagur} other {# dagar}} síðani",
|
||||
"relative_time.full.hours": "{number, plural, one {# tími} other {# tímar}} síðani",
|
||||
|
|
|
@ -89,6 +89,14 @@
|
|||
"announcement.announcement": "Annonce",
|
||||
"attachments_list.unprocessed": "(non traité)",
|
||||
"audio.hide": "Masquer l'audio",
|
||||
"block_modal.remote_users_caveat": "Nous allons demander au serveur {domain} de respecter votre décision. Cependant, ce respect n'est pas garanti, car certains serveurs peuvent gérer différemment les blocages. Les messages publics peuvent rester visibles par les utilisateurs non connectés.",
|
||||
"block_modal.show_less": "Afficher moins",
|
||||
"block_modal.show_more": "Afficher plus",
|
||||
"block_modal.they_cant_mention": "Il ne peut pas vous mentionner ou vous suivre.",
|
||||
"block_modal.they_cant_see_posts": "Il peut toujours voir vos publications, mais vous ne verrez pas les siennes.",
|
||||
"block_modal.they_will_know": "Il peut voir qu'il est bloqué.",
|
||||
"block_modal.title": "Bloquer l'utilisateur ?",
|
||||
"block_modal.you_wont_see_mentions": "Vous ne verrez pas les publications qui le mentionne.",
|
||||
"boost_modal.combo": "Vous pouvez appuyer sur {combo} pour sauter ceci la prochaine fois",
|
||||
"bundle_column_error.copy_stacktrace": "Copier le rapport d'erreur",
|
||||
"bundle_column_error.error.body": "La page demandée n'a pas pu être affichée. Cela pourrait être dû à un bogue dans notre code, ou à un problème de compatibilité avec le navigateur.",
|
||||
|
@ -169,6 +177,7 @@
|
|||
"confirmations.delete_list.message": "Voulez-vous vraiment supprimer définitivement cette liste?",
|
||||
"confirmations.discard_edit_media.confirm": "Rejeter",
|
||||
"confirmations.discard_edit_media.message": "Vous avez des modifications non enregistrées de la description ou de l'aperçu du média, voulez-vous quand même les supprimer?",
|
||||
"confirmations.domain_block.confirm": "Bloquer le serveur",
|
||||
"confirmations.domain_block.message": "Voulez-vous vraiment, vraiment bloquer {domain} en entier? Dans la plupart des cas, quelques blocages ou masquages ciblés sont suffisants et préférables. Vous ne verrez plus de contenu provenant de ce domaine, ni dans vos fils publics, ni dans vos notifications. Vos abonné·e·s utilisant ce domaine seront retiré·e·s.",
|
||||
"confirmations.edit.confirm": "Éditer",
|
||||
"confirmations.edit.message": "Modifier maintenant écrasera votre message en cours de rédaction. Voulez-vous vraiment continuer ?",
|
||||
|
@ -200,6 +209,27 @@
|
|||
"dismissable_banner.explore_statuses": "Voici des publications venant de tout le web social gagnant en popularité aujourd’hui. Les nouvelles publications avec plus de boosts et de favoris sont classés plus haut.",
|
||||
"dismissable_banner.explore_tags": "Ces hashtags sont présentement en train de gagner de l'ampleur parmi des personnes sur les serveurs du réseau décentralisé dont celui-ci.",
|
||||
"dismissable_banner.public_timeline": "Ce sont les messages publics les plus récents de personnes sur le web social que les gens de {domain} suivent.",
|
||||
"domain_block_modal.block": "Bloquer le serveur",
|
||||
"domain_block_modal.block_account_instead": "Bloquer @{name} à la place",
|
||||
"domain_block_modal.they_can_interact_with_old_posts": "Les personnes de ce serveur peuvent interagir avec vos anciennes publications.",
|
||||
"domain_block_modal.they_cant_follow": "Personne de ce serveur ne peut vous suivre.",
|
||||
"domain_block_modal.they_wont_know": "Il ne saura pas qu'il a été bloqué.",
|
||||
"domain_block_modal.title": "Bloquer le domaine ?",
|
||||
"domain_block_modal.you_will_lose_followers": "Tous vos abonnés de ce serveur seront supprimés.",
|
||||
"domain_block_modal.you_wont_see_posts": "Vous ne verrez plus les publications ou les notifications des utilisateurs de ce serveur.",
|
||||
"domain_pill.activitypub_lets_connect": "Cela vous permet de vous connecter et d'interagir avec les autres non seulement sur Mastodon, mais également sur d'autres applications de réseaux sociaux.",
|
||||
"domain_pill.activitypub_like_language": "ActivityPub est comme une langue que Mastodon utilise pour communiquer avec les autres réseaux sociaux.",
|
||||
"domain_pill.server": "Serveur",
|
||||
"domain_pill.their_handle": "Son identifiant :",
|
||||
"domain_pill.their_server": "Son foyer numérique, là où tous ses posts résident.",
|
||||
"domain_pill.their_username": "Son identifiant unique sur leur serveur. Il est possible de rencontrer des utilisateurs avec le même nom sur différents serveurs.",
|
||||
"domain_pill.username": "Nom d’utilisateur",
|
||||
"domain_pill.whats_in_a_handle": "Qu'est-ce qu'un identifiant ?",
|
||||
"domain_pill.who_they_are": "Comme un identifiant contient le nom et le service hébergeant une personne, vous pouvez interagir sur <button>les plateformes sociales implémentant ActivityPub</button>.",
|
||||
"domain_pill.who_you_are": "Comme un identifiant indique votre nom et le service vous hébergeant, vous pouvez interagir avec <button>les autres plateformes sociales implémentant ActivityPub</button>.",
|
||||
"domain_pill.your_handle": "Votre identifiant :",
|
||||
"domain_pill.your_server": "Votre foyer numérique, là où vos messages résident. Vous souhaitez changer ? Lancez un transfert vers un autre serveur quand vous le voulez et vos abonné·e·s suivront automatiquement.",
|
||||
"domain_pill.your_username": "Votre identifiant unique sur ce serveur. Il est possible de trouver des utilisateurs ayant le même nom d'utilisateur sur différents serveurs.",
|
||||
"embed.instructions": "Intégrez cette publication à votre site en copiant le code ci-dessous.",
|
||||
"embed.preview": "Voici comment il apparaîtra:",
|
||||
"emoji_button.activity": "Activité",
|
||||
|
@ -236,6 +266,7 @@
|
|||
"empty_column.list": "Il n’y a rien dans cette liste pour l’instant. Quand des membres de cette liste publieront de nouvelles publications, elles apparaîtront ici.",
|
||||
"empty_column.lists": "Vous n’avez pas encore de liste. Lorsque vous en créerez une, elle apparaîtra ici.",
|
||||
"empty_column.mutes": "Vous n’avez masqué aucun compte pour le moment.",
|
||||
"empty_column.notification_requests": "C'est fini ! Il n'y a plus rien ici. Lorsque vous recevez de nouvelles notifications, elles apparaitront ici conformément à vos préférences.",
|
||||
"empty_column.notifications": "Vous n'avez pas encore de notifications. Quand d'autres personnes interagissent avec vous, vous en verrez ici.",
|
||||
"empty_column.public": "Il n’y a rien ici! Écrivez quelque chose publiquement, ou bien suivez manuellement des personnes d’autres serveurs pour remplir le fil public",
|
||||
"error.unexpected_crash.explanation": "En raison d’un bogue dans notre code ou d’un problème de compatibilité avec votre navigateur, cette page n’a pas pu être affichée correctement.",
|
||||
|
@ -396,6 +427,15 @@
|
|||
"loading_indicator.label": "Chargement…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Cacher l’image} other {Cacher les images}}",
|
||||
"moved_to_account_banner.text": "Votre compte {disabledAccount} est actuellement désactivé parce que vous avez déménagé sur {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Cacher des notifications",
|
||||
"mute_modal.hide_options": "Masquer les options",
|
||||
"mute_modal.indefinite": "Jusqu'à ce que je les réactive",
|
||||
"mute_modal.show_options": "Afficher les options",
|
||||
"mute_modal.they_can_mention_and_follow": "Ils peuvent vous mentionner et vous suivre, mais vous ne les verrez pas.",
|
||||
"mute_modal.they_wont_know": "Ils ne sauront pas qu'ils ont été rendus silencieux.",
|
||||
"mute_modal.title": "Rendre cet utilisateur silencieux ?",
|
||||
"mute_modal.you_wont_see_mentions": "Vous ne verrez pas les publications qui le mentionne.",
|
||||
"mute_modal.you_wont_see_posts": "Il peut toujours voir vos publications, mais vous ne verrez pas les siennes.",
|
||||
"navigation_bar.about": "À propos",
|
||||
"navigation_bar.advanced_interface": "Ouvrir dans l’interface avancée",
|
||||
"navigation_bar.blocks": "Comptes bloqués",
|
||||
|
@ -431,8 +471,13 @@
|
|||
"notification.own_poll": "Votre sondage est terminé",
|
||||
"notification.poll": "Un sondage auquel vous avez participé est terminé",
|
||||
"notification.reblog": "{name} a boosté votre message",
|
||||
"notification.severed_relationships": "Relation avec {name} rompues",
|
||||
"notification.status": "{name} vient de publier",
|
||||
"notification.update": "{name} a modifié une publication",
|
||||
"notification_requests.accept": "Accepter",
|
||||
"notification_requests.dismiss": "Rejeter",
|
||||
"notification_requests.notifications_from": "Notifications de {name}",
|
||||
"notification_requests.title": "Notifications filtrées",
|
||||
"notifications.clear": "Effacer notifications",
|
||||
"notifications.clear_confirmation": "Voulez-vous vraiment effacer toutes vos notifications?",
|
||||
"notifications.column_settings.admin.report": "Nouveaux signalements:",
|
||||
|
@ -464,6 +509,15 @@
|
|||
"notifications.permission_denied": "Les notifications de bureau ne sont pas disponibles en raison d'une demande de permission de navigateur précédemment refusée",
|
||||
"notifications.permission_denied_alert": "Les notifications de bureau ne peuvent pas être activées, car l’autorisation du navigateur a précedemment été refusée",
|
||||
"notifications.permission_required": "Les notifications de bureau ne sont pas disponibles car l’autorisation requise n’a pas été accordée.",
|
||||
"notifications.policy.filter_new_accounts.hint": "Créés au cours des derniers {days, plural, one {un jour} other {# jours}}",
|
||||
"notifications.policy.filter_new_accounts_title": "Nouveaux comptes",
|
||||
"notifications.policy.filter_not_followers_hint": "Incluant les personnes qui vous suivent depuis moins de {days, plural, one {un jour} other {# jours}}",
|
||||
"notifications.policy.filter_not_followers_title": "Personnes qui ne vous suivent pas",
|
||||
"notifications.policy.filter_not_following_hint": "Jusqu'à ce que vous les validiez manuellement",
|
||||
"notifications.policy.filter_not_following_title": "Personnes que vous ne suivez pas",
|
||||
"notifications.policy.filter_private_mentions_hint": "Filtré sauf si c'est en réponse à une mention de vous ou si vous suivez l'expéditeur",
|
||||
"notifications.policy.filter_private_mentions_title": "Mentions privées non sollicitées",
|
||||
"notifications.policy.title": "Filtrer les notifications de…",
|
||||
"notifications_permission_banner.enable": "Activer les notifications de bureau",
|
||||
"notifications_permission_banner.how_to_control": "Pour recevoir des notifications lorsque Mastodon n’est pas ouvert, activez les notifications de bureau. Vous pouvez contrôler précisément quels types d’interactions génèrent des notifications de bureau via le bouton {icon} ci-dessus une fois qu’elles sont activées.",
|
||||
"notifications_permission_banner.title": "Ne rien rater",
|
||||
|
@ -534,6 +588,12 @@
|
|||
"refresh": "Actualiser",
|
||||
"regeneration_indicator.label": "Chargement…",
|
||||
"regeneration_indicator.sublabel": "Votre fil d'accueil est en cours de préparation!",
|
||||
"relationship_severance_notification.purged_data": "supprimées par les administrateurs",
|
||||
"relationship_severance_notification.relationships": "{count, plural, one {# relation} other {# relations}}",
|
||||
"relationship_severance_notification.types.account_suspension": "Le compte a été suspendu",
|
||||
"relationship_severance_notification.types.domain_block": "Le domaine a été suspendu",
|
||||
"relationship_severance_notification.types.user_domain_block": "Vous avez bloqué ce domaine",
|
||||
"relationship_severance_notification.view": "Afficher",
|
||||
"relative_time.days": "{number} j",
|
||||
"relative_time.full.days": "il y a {number, plural, one {# jour} other {# jours}}",
|
||||
"relative_time.full.hours": "il y a {number, plural, one {# heure} other {# heures}}",
|
||||
|
@ -640,9 +700,11 @@
|
|||
"status.direct": "Mention privée @{name}",
|
||||
"status.direct_indicator": "Mention privée",
|
||||
"status.edit": "Modifier",
|
||||
"status.edited": "Dernière modification le {date}",
|
||||
"status.edited_x_times": "Modifiée {count, plural, one {{count} fois} other {{count} fois}}",
|
||||
"status.embed": "Intégrer",
|
||||
"status.favourite": "Ajouter aux favoris",
|
||||
"status.favourites": "{count, plural, one {favori} other {favoris}}",
|
||||
"status.filter": "Filtrer cette publication",
|
||||
"status.filtered": "Filtrée",
|
||||
"status.hide": "Masquer le message",
|
||||
|
@ -663,6 +725,7 @@
|
|||
"status.reblog": "Booster",
|
||||
"status.reblog_private": "Booster avec visibilité originale",
|
||||
"status.reblogged_by": "{name} a boosté",
|
||||
"status.reblogs": "{count, plural, one {boost} other {boosts}}",
|
||||
"status.reblogs.empty": "Personne n’a encore boosté cette publication. Lorsque quelqu’un le fera, elle apparaîtra ici.",
|
||||
"status.redraft": "Supprimer et réécrire",
|
||||
"status.remove_bookmark": "Retirer des signets",
|
||||
|
|
|
@ -89,6 +89,14 @@
|
|||
"announcement.announcement": "Annonce",
|
||||
"attachments_list.unprocessed": "(non traité)",
|
||||
"audio.hide": "Masquer l'audio",
|
||||
"block_modal.remote_users_caveat": "Nous allons demander au serveur {domain} de respecter votre décision. Cependant, ce respect n'est pas garanti, car certains serveurs peuvent gérer différemment les blocages. Les messages publics peuvent rester visibles par les utilisateurs non connectés.",
|
||||
"block_modal.show_less": "Afficher moins",
|
||||
"block_modal.show_more": "Afficher plus",
|
||||
"block_modal.they_cant_mention": "Il ne peut pas vous mentionner ou vous suivre.",
|
||||
"block_modal.they_cant_see_posts": "Il peut toujours voir vos publications, mais vous ne verrez pas les siennes.",
|
||||
"block_modal.they_will_know": "Il peut voir qu'il est bloqué.",
|
||||
"block_modal.title": "Bloquer l'utilisateur ?",
|
||||
"block_modal.you_wont_see_mentions": "Vous ne verrez pas les publications qui le mentionne.",
|
||||
"boost_modal.combo": "Vous pouvez appuyer sur {combo} pour passer ceci la prochaine fois",
|
||||
"bundle_column_error.copy_stacktrace": "Copier le rapport d'erreur",
|
||||
"bundle_column_error.error.body": "La page demandée n'a pas pu être affichée. Cela peut être dû à un bogue dans notre code, ou à un problème de compatibilité avec le navigateur.",
|
||||
|
@ -169,6 +177,7 @@
|
|||
"confirmations.delete_list.message": "Voulez-vous vraiment supprimer définitivement cette liste ?",
|
||||
"confirmations.discard_edit_media.confirm": "Rejeter",
|
||||
"confirmations.discard_edit_media.message": "Vous avez des modifications non enregistrées de la description ou de l'aperçu du média, les supprimer quand même ?",
|
||||
"confirmations.domain_block.confirm": "Bloquer le serveur",
|
||||
"confirmations.domain_block.message": "Voulez-vous vraiment, vraiment bloquer {domain} en entier ? Dans la plupart des cas, quelques blocages ou masquages ciblés sont suffisants et préférables. Vous ne verrez plus de contenu provenant de ce domaine, ni dans vos fils publics, ni dans vos notifications. Vos abonné·e·s utilisant ce domaine seront retiré·e·s.",
|
||||
"confirmations.edit.confirm": "Modifier",
|
||||
"confirmations.edit.message": "Modifier maintenant écrasera votre message en cours de rédaction. Voulez-vous vraiment continuer ?",
|
||||
|
@ -200,6 +209,27 @@
|
|||
"dismissable_banner.explore_statuses": "Ces messages venant de tout le web social gagnent en popularité aujourd’hui. Les nouveaux messages avec plus de boosts et de favoris sont classés plus haut.",
|
||||
"dismissable_banner.explore_tags": "Ces hashtags sont actuellement en train de gagner de l'ampleur parmi les personnes sur les serveurs du réseau décentralisé dont celui-ci.",
|
||||
"dismissable_banner.public_timeline": "Ce sont les posts publics les plus récents de personnes sur le web social que les gens sur {domain} suivent.",
|
||||
"domain_block_modal.block": "Bloquer le serveur",
|
||||
"domain_block_modal.block_account_instead": "Bloquer @{name} à la place",
|
||||
"domain_block_modal.they_can_interact_with_old_posts": "Les personnes de ce serveur peuvent interagir avec vos anciennes publications.",
|
||||
"domain_block_modal.they_cant_follow": "Personne de ce serveur ne peut vous suivre.",
|
||||
"domain_block_modal.they_wont_know": "Il ne saura pas qu'il a été bloqué.",
|
||||
"domain_block_modal.title": "Bloquer le domaine ?",
|
||||
"domain_block_modal.you_will_lose_followers": "Tous vos abonnés de ce serveur seront supprimés.",
|
||||
"domain_block_modal.you_wont_see_posts": "Vous ne verrez plus les publications ou les notifications des utilisateurs de ce serveur.",
|
||||
"domain_pill.activitypub_lets_connect": "Cela vous permet de vous connecter et d'interagir avec les autres non seulement sur Mastodon, mais également sur d'autres applications de réseaux sociaux.",
|
||||
"domain_pill.activitypub_like_language": "ActivityPub est comme une langue que Mastodon utilise pour communiquer avec les autres réseaux sociaux.",
|
||||
"domain_pill.server": "Serveur",
|
||||
"domain_pill.their_handle": "Son identifiant :",
|
||||
"domain_pill.their_server": "Son foyer numérique, là où tous ses posts résident.",
|
||||
"domain_pill.their_username": "Son identifiant unique sur leur serveur. Il est possible de rencontrer des utilisateurs avec le même nom sur différents serveurs.",
|
||||
"domain_pill.username": "Nom d’utilisateur",
|
||||
"domain_pill.whats_in_a_handle": "Qu'est-ce qu'un identifiant ?",
|
||||
"domain_pill.who_they_are": "Comme un identifiant contient le nom et le service hébergeant une personne, vous pouvez interagir sur <button>les plateformes sociales implémentant ActivityPub</button>.",
|
||||
"domain_pill.who_you_are": "Comme un identifiant indique votre nom et le service vous hébergeant, vous pouvez interagir avec <button>les autres plateformes sociales implémentant ActivityPub</button>.",
|
||||
"domain_pill.your_handle": "Votre identifiant :",
|
||||
"domain_pill.your_server": "Votre foyer numérique, là où vos messages résident. Vous souhaitez changer ? Lancez un transfert vers un autre serveur quand vous le voulez et vos abonné·e·s suivront automatiquement.",
|
||||
"domain_pill.your_username": "Votre identifiant unique sur ce serveur. Il est possible de trouver des utilisateurs ayant le même nom d'utilisateur sur différents serveurs.",
|
||||
"embed.instructions": "Intégrez ce message à votre site en copiant le code ci-dessous.",
|
||||
"embed.preview": "Il apparaîtra comme cela :",
|
||||
"emoji_button.activity": "Activités",
|
||||
|
@ -236,6 +266,7 @@
|
|||
"empty_column.list": "Il n’y a rien dans cette liste pour l’instant. Quand des membres de cette liste publieront de nouveaux messages, ils apparaîtront ici.",
|
||||
"empty_column.lists": "Vous n’avez pas encore de liste. Lorsque vous en créerez une, elle apparaîtra ici.",
|
||||
"empty_column.mutes": "Vous n’avez masqué aucun compte pour le moment.",
|
||||
"empty_column.notification_requests": "C'est fini ! Il n'y a plus rien ici. Lorsque vous recevez de nouvelles notifications, elles apparaitront ici conformément à vos préférences.",
|
||||
"empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres personnes pour débuter la conversation.",
|
||||
"empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des personnes d’autres serveurs pour remplir le fil public",
|
||||
"error.unexpected_crash.explanation": "En raison d’un bug dans notre code ou d’un problème de compatibilité avec votre navigateur, cette page n’a pas pu être affichée correctement.",
|
||||
|
@ -396,6 +427,15 @@
|
|||
"loading_indicator.label": "Chargement…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Cacher l’image} other {Cacher les images}}",
|
||||
"moved_to_account_banner.text": "Votre compte {disabledAccount} est actuellement désactivé parce que vous l'avez déplacé à {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Cacher des notifications",
|
||||
"mute_modal.hide_options": "Masquer les options",
|
||||
"mute_modal.indefinite": "Jusqu'à ce que je les réactive",
|
||||
"mute_modal.show_options": "Afficher les options",
|
||||
"mute_modal.they_can_mention_and_follow": "Ils peuvent vous mentionner et vous suivre, mais vous ne les verrez pas.",
|
||||
"mute_modal.they_wont_know": "Ils ne sauront pas qu'ils ont été rendus silencieux.",
|
||||
"mute_modal.title": "Rendre cet utilisateur silencieux ?",
|
||||
"mute_modal.you_wont_see_mentions": "Vous ne verrez pas les publications qui le mentionne.",
|
||||
"mute_modal.you_wont_see_posts": "Il peut toujours voir vos publications, mais vous ne verrez pas les siennes.",
|
||||
"navigation_bar.about": "À propos",
|
||||
"navigation_bar.advanced_interface": "Ouvrir dans l’interface avancée",
|
||||
"navigation_bar.blocks": "Comptes bloqués",
|
||||
|
@ -431,8 +471,13 @@
|
|||
"notification.own_poll": "Votre sondage est terminé",
|
||||
"notification.poll": "Un sondage auquel vous avez participé vient de se terminer",
|
||||
"notification.reblog": "{name} a partagé votre message",
|
||||
"notification.severed_relationships": "Relation avec {name} rompues",
|
||||
"notification.status": "{name} vient de publier",
|
||||
"notification.update": "{name} a modifié un message",
|
||||
"notification_requests.accept": "Accepter",
|
||||
"notification_requests.dismiss": "Rejeter",
|
||||
"notification_requests.notifications_from": "Notifications de {name}",
|
||||
"notification_requests.title": "Notifications filtrées",
|
||||
"notifications.clear": "Effacer les notifications",
|
||||
"notifications.clear_confirmation": "Voulez-vous vraiment effacer toutes vos notifications ?",
|
||||
"notifications.column_settings.admin.report": "Nouveaux signalements :",
|
||||
|
@ -464,6 +509,15 @@
|
|||
"notifications.permission_denied": "Impossible d’activer les notifications de bureau car l’autorisation a été refusée.",
|
||||
"notifications.permission_denied_alert": "Les notifications de bureau ne peuvent pas être activées, car l’autorisation du navigateur a été refusée avant",
|
||||
"notifications.permission_required": "Les notifications de bureau ne sont pas disponibles car l’autorisation requise n’a pas été accordée.",
|
||||
"notifications.policy.filter_new_accounts.hint": "Créés au cours des derniers {days, plural, one {un jour} other {# jours}}",
|
||||
"notifications.policy.filter_new_accounts_title": "Nouveaux comptes",
|
||||
"notifications.policy.filter_not_followers_hint": "Incluant les personnes qui vous suivent depuis moins de {days, plural, one {un jour} other {# jours}}",
|
||||
"notifications.policy.filter_not_followers_title": "Personnes qui ne vous suivent pas",
|
||||
"notifications.policy.filter_not_following_hint": "Jusqu'à ce que vous les validiez manuellement",
|
||||
"notifications.policy.filter_not_following_title": "Personnes que vous ne suivez pas",
|
||||
"notifications.policy.filter_private_mentions_hint": "Filtré sauf si c'est en réponse à une mention de vous ou si vous suivez l'expéditeur",
|
||||
"notifications.policy.filter_private_mentions_title": "Mentions privées non sollicitées",
|
||||
"notifications.policy.title": "Filtrer les notifications de…",
|
||||
"notifications_permission_banner.enable": "Activer les notifications de bureau",
|
||||
"notifications_permission_banner.how_to_control": "Pour recevoir des notifications lorsque Mastodon n’est pas ouvert, activez les notifications du bureau. Vous pouvez contrôler précisément quels types d’interactions génèrent des notifications de bureau via le bouton {icon} ci-dessus une fois qu’elles sont activées.",
|
||||
"notifications_permission_banner.title": "Toujours au courant",
|
||||
|
@ -534,6 +588,12 @@
|
|||
"refresh": "Actualiser",
|
||||
"regeneration_indicator.label": "Chargement…",
|
||||
"regeneration_indicator.sublabel": "Votre fil principal est en cours de préparation !",
|
||||
"relationship_severance_notification.purged_data": "supprimées par les administrateurs",
|
||||
"relationship_severance_notification.relationships": "{count, plural, one {# relation} other {# relations}}",
|
||||
"relationship_severance_notification.types.account_suspension": "Le compte a été suspendu",
|
||||
"relationship_severance_notification.types.domain_block": "Le domaine a été suspendu",
|
||||
"relationship_severance_notification.types.user_domain_block": "Vous avez bloqué ce domaine",
|
||||
"relationship_severance_notification.view": "Afficher",
|
||||
"relative_time.days": "{number} j",
|
||||
"relative_time.full.days": "il y a {number, plural, one {# jour} other {# jours}}",
|
||||
"relative_time.full.hours": "il y a {number, plural, one {# heure} other {# heures}}",
|
||||
|
@ -640,9 +700,11 @@
|
|||
"status.direct": "Mention privée @{name}",
|
||||
"status.direct_indicator": "Mention privée",
|
||||
"status.edit": "Modifier",
|
||||
"status.edited": "Dernière modification le {date}",
|
||||
"status.edited_x_times": "Modifié {count, plural, one {{count} fois} other {{count} fois}}",
|
||||
"status.embed": "Intégrer",
|
||||
"status.favourite": "Ajouter aux favoris",
|
||||
"status.favourites": "{count, plural, one {favori} other {favoris}}",
|
||||
"status.filter": "Filtrer ce message",
|
||||
"status.filtered": "Filtré",
|
||||
"status.hide": "Masquer le message",
|
||||
|
@ -663,6 +725,7 @@
|
|||
"status.reblog": "Partager",
|
||||
"status.reblog_private": "Partager à l’audience originale",
|
||||
"status.reblogged_by": "{name} a partagé",
|
||||
"status.reblogs": "{count, plural, one {boost} other {boosts}}",
|
||||
"status.reblogs.empty": "Personne n’a encore partagé ce message. Lorsque quelqu’un le fera, il apparaîtra ici.",
|
||||
"status.redraft": "Supprimer et réécrire",
|
||||
"status.remove_bookmark": "Retirer des marque-pages",
|
||||
|
|
|
@ -471,6 +471,7 @@
|
|||
"notification.own_poll": "Thàinig an cunntas-bheachd agad gu crìoch",
|
||||
"notification.poll": "Thàinig cunntas-bheachd sa bhòt thu gu crìoch",
|
||||
"notification.reblog": "Bhrosnaich {name} am post agad",
|
||||
"notification.severed_relationships": "Chaidh na dàimhean le {name} a dhealachadh",
|
||||
"notification.status": "Phostaich {name} rud",
|
||||
"notification.update": "Dheasaich {name} post",
|
||||
"notification_requests.accept": "Gabh ris",
|
||||
|
@ -508,13 +509,13 @@
|
|||
"notifications.permission_denied": "Chan eil brathan deasga ri fhaighinn on a chaidh iarrtas ceadan a’ bhrabhsair a dhiùltadh cheana",
|
||||
"notifications.permission_denied_alert": "Cha ghabh brathan deasga a chur an comas on a chaidh iarrtas ceadan a’ bhrabhsair a dhiùltadh cheana",
|
||||
"notifications.permission_required": "Chan eil brathan deasga ri fhaighinn on nach deach an cead riatanach a thoirt seachad.",
|
||||
"notifications.policy.filter_new_accounts.hint": "Chaidh a chruthachadh o chionn {count, plural, one {# latha} two {# latha} few {# làithean} other {# latha}}",
|
||||
"notifications.policy.filter_new_accounts.hint": "Chaidh a chruthachadh o chionn {days, plural, one {# latha} two {# latha} few {# làithean} other {# latha}}",
|
||||
"notifications.policy.filter_new_accounts_title": "Cunntasan ùra",
|
||||
"notifications.policy.filter_not_followers_hint": "A’ gabhail a-staigh an fheadhainn a lean ort nas lugha na {count, plural, one {# latha} two {# latha} few {# làithean} other {# latha}} seo chaidh",
|
||||
"notifications.policy.filter_not_followers_hint": "A’ gabhail a-staigh an fheadhainn a lean ort nas lugha na {days, plural, one {# latha} two {# latha} few {# làithean} other {# latha}} seo chaidh",
|
||||
"notifications.policy.filter_not_followers_title": "Daoine nach eil gad leantainn",
|
||||
"notifications.policy.filter_not_following_hint": "Gus an aontaich thu riutha a làimh",
|
||||
"notifications.policy.filter_not_following_title": "Daoine nach eil thu a’ leantainn",
|
||||
"notifications.policy.filter_private_mentions_hint": "Criathraichte ach ma tha e a’ freagairt do dh’iomradh agad fhèin no ma tha thu a’ leantainn an seòladair",
|
||||
"notifications.policy.filter_private_mentions_hint": "Criathraichte ach ma tha e a’ freagairt do dh’iomradh agad fhèin no ma tha thu a’ leantainn an t-seòladair",
|
||||
"notifications.policy.filter_private_mentions_title": "Iomraidhean prìobhaideach o choigrich",
|
||||
"notifications.policy.title": "Falamhaich na brathan o…",
|
||||
"notifications_permission_banner.enable": "Cuir brathan deasga an comas",
|
||||
|
@ -587,6 +588,12 @@
|
|||
"refresh": "Ath-nuadhaich",
|
||||
"regeneration_indicator.label": "’Ga luchdadh…",
|
||||
"regeneration_indicator.sublabel": "Tha do dhachaigh ’ga ullachadh!",
|
||||
"relationship_severance_notification.purged_data": "chaidh a phurgaideachadh leis na rianairean",
|
||||
"relationship_severance_notification.relationships": "{count, plural, one {# dàimh} two {# dhàimh} few {# dàimhean} other {# dàimh}}",
|
||||
"relationship_severance_notification.types.account_suspension": "Chaidh cunntas a chur à rèim",
|
||||
"relationship_severance_notification.types.domain_block": "Chaidh àrainn a chur à rèim",
|
||||
"relationship_severance_notification.types.user_domain_block": "Bhac thu an àrainn seo",
|
||||
"relationship_severance_notification.view": "Seall",
|
||||
"relative_time.days": "{number}l",
|
||||
"relative_time.full.days": "{number, plural, one {# latha} two {# latha} few {# làithean} other {# latha}} air ais",
|
||||
"relative_time.full.hours": "{number, plural, one {# uair a thìde} two {# uair a thìde} few {# uairean a thìde} other {# uair a thìde}} air ais",
|
||||
|
@ -694,10 +701,10 @@
|
|||
"status.direct_indicator": "Iomradh prìobhaideach",
|
||||
"status.edit": "Deasaich",
|
||||
"status.edited": "An deasachadh mu dheireadh {date}",
|
||||
"status.edited_x_times": "Chaidh a dheasachadh {count, plural, one {{counter} turas} two {{counter} thuras} few {{counter} tursan} other {{counter} turas}}",
|
||||
"status.edited_x_times": "Chaidh a dheasachadh {count, plural, one {{count} turas} two {{count} thuras} few {{count} tursan} other {{count} turas}}",
|
||||
"status.embed": "Leabaich",
|
||||
"status.favourite": "Cuir ris na h-annsachdan",
|
||||
"status.favourites": "{count, plural, one {annsachd} two {annsachd} few {annsachdan} other {annsachd}",
|
||||
"status.favourites": "{count, plural, one {annsachd} two {annsachd} few {annsachdan} other {annsachd}}",
|
||||
"status.filter": "Criathraich am post seo",
|
||||
"status.filtered": "Criathraichte",
|
||||
"status.hide": "Falaich am post",
|
||||
|
@ -718,7 +725,7 @@
|
|||
"status.reblog": "Brosnaich",
|
||||
"status.reblog_private": "Brosnaich leis an t-so-fhaicsinneachd tùsail",
|
||||
"status.reblogged_by": "’Ga bhrosnachadh le {name}",
|
||||
"status.reblogs": "{count, plural, one {bhrosnachadh} two {bhrosnachadh} few {brosnachaidhean} other {brosnachadh}",
|
||||
"status.reblogs": "{count, plural, one {bhrosnachadh} two {bhrosnachadh} few {brosnachaidhean} other {brosnachadh}}",
|
||||
"status.reblogs.empty": "Chan deach am post seo a bhrosnachadh le duine sam bith fhathast. Nuair a bhrosnaicheas cuideigin e, nochdaidh iad an-seo.",
|
||||
"status.redraft": "Sguab às ⁊ dèan dreachd ùr",
|
||||
"status.remove_bookmark": "Thoir an comharra-lìn air falbh",
|
||||
|
|
|
@ -471,6 +471,7 @@
|
|||
"notification.own_poll": "A túa enquisa rematou",
|
||||
"notification.poll": "Rematou a enquisa na que votaches",
|
||||
"notification.reblog": "{name} compartiu a túa publicación",
|
||||
"notification.severed_relationships": "Cortouse a relación con {name}",
|
||||
"notification.status": "{name} publicou",
|
||||
"notification.update": "{name} editou unha publicación",
|
||||
"notification_requests.accept": "Aceptar",
|
||||
|
@ -587,6 +588,12 @@
|
|||
"refresh": "Actualizar",
|
||||
"regeneration_indicator.label": "Estase a cargar…",
|
||||
"regeneration_indicator.sublabel": "Estase a preparar a túa cronoloxía de inicio!",
|
||||
"relationship_severance_notification.purged_data": "purgada pola administración",
|
||||
"relationship_severance_notification.relationships": "{count, plural, one {# relación} other {# relacións}}",
|
||||
"relationship_severance_notification.types.account_suspension": "A conta foi suspendida",
|
||||
"relationship_severance_notification.types.domain_block": "O dominio foi suspendido",
|
||||
"relationship_severance_notification.types.user_domain_block": "Bloqueaches este dominio",
|
||||
"relationship_severance_notification.view": "Ver",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.full.days": "hai {number, plural, one {# día} other {# días}}",
|
||||
"relative_time.full.hours": "hai {number, plural, one {# hora} other {# horas}}",
|
||||
|
|
|
@ -248,7 +248,7 @@
|
|||
"emoji_button.symbols": "סמלים",
|
||||
"emoji_button.travel": "טיולים ואתרים",
|
||||
"empty_column.account_hides_collections": "המשתמש.ת בחר.ה להסתיר מידע זה",
|
||||
"empty_column.account_suspended": "חשבון מושהה",
|
||||
"empty_column.account_suspended": "חשבון מושעה",
|
||||
"empty_column.account_timeline": "אין עדיין אף הודעה!",
|
||||
"empty_column.account_unavailable": "פרופיל לא זמין",
|
||||
"empty_column.blocks": "עדיין לא חסמתם משתמשים אחרים.",
|
||||
|
@ -471,6 +471,7 @@
|
|||
"notification.own_poll": "הסקר שלך הסתיים",
|
||||
"notification.poll": "סקר שהצבעת בו הסתיים",
|
||||
"notification.reblog": "הודעתך הודהדה על ידי {name}",
|
||||
"notification.severed_relationships": "חתכתם כל קשר עם {name}",
|
||||
"notification.status": "{name} הרגע פרסמו",
|
||||
"notification.update": "{name} ערכו הודעה",
|
||||
"notification_requests.accept": "לקבל",
|
||||
|
@ -483,6 +484,8 @@
|
|||
"notifications.column_settings.admin.sign_up": "הרשמות חדשות:",
|
||||
"notifications.column_settings.alert": "התראות לשולחן העבודה",
|
||||
"notifications.column_settings.favourite": "חיבובים:",
|
||||
"notifications.column_settings.filter_bar.advanced": "להציג את כל הקטגוריות",
|
||||
"notifications.column_settings.filter_bar.category": "שורת סינון מהיר",
|
||||
"notifications.column_settings.follow": "עוקבים חדשים:",
|
||||
"notifications.column_settings.follow_request": "בקשות מעקב חדשות:",
|
||||
"notifications.column_settings.mention": "פניות:",
|
||||
|
@ -587,6 +590,12 @@
|
|||
"refresh": "רענון",
|
||||
"regeneration_indicator.label": "טוען…",
|
||||
"regeneration_indicator.sublabel": "פיד הבית שלך בהכנה!",
|
||||
"relationship_severance_notification.purged_data": "המידע נמחק על ידי ההנהלה",
|
||||
"relationship_severance_notification.relationships": "{count, plural, one {קשר אחד} other {# קשרים}}",
|
||||
"relationship_severance_notification.types.account_suspension": "החשבון הושעה",
|
||||
"relationship_severance_notification.types.domain_block": "השרת הושעה",
|
||||
"relationship_severance_notification.types.user_domain_block": "חסמת שרת זה",
|
||||
"relationship_severance_notification.view": "הצג",
|
||||
"relative_time.days": "{number} ימים",
|
||||
"relative_time.full.days": "לפני {number, plural, one {# יום} other {# ימים}}",
|
||||
"relative_time.full.hours": "לפני {number, plural, one {# שעה} other {# שעות}}",
|
||||
|
|
|
@ -471,6 +471,7 @@
|
|||
"notification.own_poll": "A szavazásod véget ért",
|
||||
"notification.poll": "Egy szavazás, melyben részt vettél, véget ért",
|
||||
"notification.reblog": "{name} megtolta a bejegyzésedet",
|
||||
"notification.severed_relationships": "A kapcsolatok megszakítva ezzel: {name}",
|
||||
"notification.status": "{name} bejegyzést tett közzé",
|
||||
"notification.update": "{name} szerkesztett egy bejegyzést",
|
||||
"notification_requests.accept": "Elfogadás",
|
||||
|
@ -587,6 +588,12 @@
|
|||
"refresh": "Frissítés",
|
||||
"regeneration_indicator.label": "Betöltés…",
|
||||
"regeneration_indicator.sublabel": "A saját idővonalad épp készül!",
|
||||
"relationship_severance_notification.purged_data": "rendszergazdák által véglegesen törölve",
|
||||
"relationship_severance_notification.relationships": "{count, plural, one {# kapcsolat} other {# kapcsolat}}",
|
||||
"relationship_severance_notification.types.account_suspension": "A fiók fel van függesztve",
|
||||
"relationship_severance_notification.types.domain_block": "A domain fel van függesztve",
|
||||
"relationship_severance_notification.types.user_domain_block": "Blokkoltad ezt a domaint",
|
||||
"relationship_severance_notification.view": "Megtekintés",
|
||||
"relative_time.days": "{number}n",
|
||||
"relative_time.full.days": "{number, plural, one {# napja} other {# napja}}",
|
||||
"relative_time.full.hours": "{number, plural, one {# órája} other {# órája}}",
|
||||
|
|
|
@ -483,6 +483,8 @@
|
|||
"notifications.column_settings.admin.sign_up": "Nýjar skráningar:",
|
||||
"notifications.column_settings.alert": "Tilkynningar á skjáborði",
|
||||
"notifications.column_settings.favourite": "Eftirlæti:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Birta alla flokka",
|
||||
"notifications.column_settings.filter_bar.category": "Skyndisíustika",
|
||||
"notifications.column_settings.follow": "Nýir fylgjendur:",
|
||||
"notifications.column_settings.follow_request": "Nýjar beiðnir um að fylgjast með:",
|
||||
"notifications.column_settings.mention": "Tilvísanir:",
|
||||
|
@ -587,6 +589,8 @@
|
|||
"refresh": "Endurlesa",
|
||||
"regeneration_indicator.label": "Hleð inn…",
|
||||
"regeneration_indicator.sublabel": "Verið er að útbúa heimastreymið þitt!",
|
||||
"relationship_severance_notification.relationships": "{count, plural, one {# tengsl} other {# tengsl}}",
|
||||
"relationship_severance_notification.view": "Skoða",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.full.days": "Fyrir {number, plural, one {# degi} other {# dögum}} síðan",
|
||||
"relative_time.full.hours": "Fyrir {number, plural, one {# klukkustund} other {# klukkustundum}} síðan",
|
||||
|
|
|
@ -89,6 +89,14 @@
|
|||
"announcement.announcement": "Annuncio",
|
||||
"attachments_list.unprocessed": "(non elaborato)",
|
||||
"audio.hide": "Nascondi audio",
|
||||
"block_modal.remote_users_caveat": "Chiederemo al server {domain} di rispettare la tua decisione. Tuttavia, la conformità non è garantita poiché alcuni server potrebbero gestire i blocchi in modo diverso. I post pubblici potrebbero essere ancora visibili agli utenti che non hanno effettuato l'accesso.",
|
||||
"block_modal.show_less": "Mostra meno",
|
||||
"block_modal.show_more": "Mostra di più",
|
||||
"block_modal.they_cant_mention": "Non possono menzionarti o seguirti.",
|
||||
"block_modal.they_cant_see_posts": "Non possono vedere i tuoi post e tu non vedrai i loro.",
|
||||
"block_modal.they_will_know": "Possono vedere che sono bloccati.",
|
||||
"block_modal.title": "Bloccare l'utente?",
|
||||
"block_modal.you_wont_see_mentions": "Non vedrai i post che li menzionano.",
|
||||
"boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio, la prossima volta",
|
||||
"bundle_column_error.copy_stacktrace": "Copia rapporto sull'errore",
|
||||
"bundle_column_error.error.body": "Impossibile rendedrizzare la pagina richiesta. Potrebbe dipendere da un bug nel nostro codice o da un problema di compatibilità di un browser.",
|
||||
|
@ -169,6 +177,7 @@
|
|||
"confirmations.delete_list.message": "Sei sicuro di voler eliminare permanentemente questa lista?",
|
||||
"confirmations.discard_edit_media.confirm": "Scarta",
|
||||
"confirmations.discard_edit_media.message": "Hai delle modifiche non salvate alla descrizione o anteprima del media, scartarle comunque?",
|
||||
"confirmations.domain_block.confirm": "Blocca il server",
|
||||
"confirmations.domain_block.message": "Sei davvero sicuro di voler bloccare l'intero {domain}? In gran parte dei casi, è sufficiente e preferibile bloccare o silenziare alcuni profili. Non visualizzerai i contenuti da quel dominio in alcuna cronologia pubblica o tra le tue notifiche. I tuoi seguaci da quel dominio saranno rimossi.",
|
||||
"confirmations.edit.confirm": "Modifica",
|
||||
"confirmations.edit.message": "Modificare ora sovrascriverà il messaggio che stai correntemente componendo. Sei sicuro di voler procedere?",
|
||||
|
@ -200,6 +209,27 @@
|
|||
"dismissable_banner.explore_statuses": "Questi sono post da tutto il social web che stanno guadagnando popolarità oggi. I post più recenti con più condivisioni e preferiti sono classificati più in alto.",
|
||||
"dismissable_banner.explore_tags": "Questi hashtag stanno ottenendo popolarità tra le persone su questo e altri server della rete decentralizzata, al momento.",
|
||||
"dismissable_banner.public_timeline": "Questi sono i post pubblici più recenti di persone sul social che le persone su {domain} seguono.",
|
||||
"domain_block_modal.block": "Blocca il server",
|
||||
"domain_block_modal.block_account_instead": "Blocca invece @{name}",
|
||||
"domain_block_modal.they_can_interact_with_old_posts": "Le persone da questo server possono interagire con i tuoi vecchi post.",
|
||||
"domain_block_modal.they_cant_follow": "Nessuno da questo server può seguirti.",
|
||||
"domain_block_modal.they_wont_know": "Non sapranno di essere stati bloccati.",
|
||||
"domain_block_modal.title": "Bloccare il dominio?",
|
||||
"domain_block_modal.you_will_lose_followers": "Tutti i tuoi seguaci da questo server verranno rimossi.",
|
||||
"domain_block_modal.you_wont_see_posts": "Non vedrai post o notifiche dagli utenti su questo server.",
|
||||
"domain_pill.activitypub_lets_connect": "Ti consente di connetterti e interagire con le persone non solo su Mastodon, ma anche su diverse app social.",
|
||||
"domain_pill.activitypub_like_language": "ActivityPub è come la lingua che Mastodon parla con altri social network.",
|
||||
"domain_pill.server": "Server",
|
||||
"domain_pill.their_handle": "Il loro nome univoco:",
|
||||
"domain_pill.their_server": "La loro casa digitale, dove risiedono tutti i loro post.",
|
||||
"domain_pill.their_username": "Il loro identificatore univoco sul loro server. È possibile trovare utenti con lo stesso nome utente su server diversi.",
|
||||
"domain_pill.username": "Nome utente",
|
||||
"domain_pill.whats_in_a_handle": "Cosa c'è in un nome univoco?",
|
||||
"domain_pill.who_they_are": "Poiché i nomi univoci indicano chi sia qualcuno e dove si trovi, puoi interagire con le persone attraverso la rete sociale delle <button>piattaforme basate su ActivityPub</button>.",
|
||||
"domain_pill.who_you_are": "Poiché il tuo nome univoco indica chi tu sia e dove ti trovi, le persone possono interagire con te sulla rete sociale delle <button>piattaforme basate su ActivityPub</button>.",
|
||||
"domain_pill.your_handle": "Il tuo nome univoco:",
|
||||
"domain_pill.your_server": "La tua casa digitale, dove vivono tutti i tuoi post. Non ti piace questa? Cambia server in qualsiasi momento e porta con te anche i tuoi seguaci.",
|
||||
"domain_pill.your_username": "Il tuo identificatore univoco su questo server. È possibile trovare utenti con lo stesso nome utente su server diversi.",
|
||||
"embed.instructions": "Incorpora questo post sul tuo sito web, copiando il seguente codice.",
|
||||
"embed.preview": "Ecco come apparirà:",
|
||||
"emoji_button.activity": "Attività",
|
||||
|
@ -397,6 +427,15 @@
|
|||
"loading_indicator.label": "Caricamento…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Nascondi immagine} other {Nascondi immagini}}",
|
||||
"moved_to_account_banner.text": "Il tuo profilo {disabledAccount} è correntemente disabilitato perché ti sei spostato a {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Nascondi dalle notifiche",
|
||||
"mute_modal.hide_options": "Nascondi le opzioni",
|
||||
"mute_modal.indefinite": "Finché io non le riattivo",
|
||||
"mute_modal.show_options": "Mostre le opzioni",
|
||||
"mute_modal.they_can_mention_and_follow": "Possono menzionarti e seguirti, ma non li vedrai.",
|
||||
"mute_modal.they_wont_know": "Non sapranno di essere stati silenziati.",
|
||||
"mute_modal.title": "Silenziare l'utente?",
|
||||
"mute_modal.you_wont_see_mentions": "Non vedrai i post che li menzionano.",
|
||||
"mute_modal.you_wont_see_posts": "Possono ancora vedere i tuoi post, ma tu non vedrai i loro.",
|
||||
"navigation_bar.about": "Info",
|
||||
"navigation_bar.advanced_interface": "Apri nell'interfaccia web avanzata",
|
||||
"navigation_bar.blocks": "Utenti bloccati",
|
||||
|
@ -432,6 +471,7 @@
|
|||
"notification.own_poll": "Il tuo sondaggio è terminato",
|
||||
"notification.poll": "Un sondaggio in cui hai votato è terminato",
|
||||
"notification.reblog": "{name} ha rebloggato il tuo post",
|
||||
"notification.severed_relationships": "Relazioni interrotte con {name}",
|
||||
"notification.status": "{name} ha appena pubblicato un post",
|
||||
"notification.update": "{name} ha modificato un post",
|
||||
"notification_requests.accept": "Accetta",
|
||||
|
@ -444,6 +484,8 @@
|
|||
"notifications.column_settings.admin.sign_up": "Nuove iscrizioni:",
|
||||
"notifications.column_settings.alert": "Notifiche desktop",
|
||||
"notifications.column_settings.favourite": "Preferiti:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Mostra tutte le categorie",
|
||||
"notifications.column_settings.filter_bar.category": "Barra del filtro veloce",
|
||||
"notifications.column_settings.follow": "Nuovi seguaci:",
|
||||
"notifications.column_settings.follow_request": "Nuove richieste di seguirti:",
|
||||
"notifications.column_settings.mention": "Menzioni:",
|
||||
|
@ -548,6 +590,12 @@
|
|||
"refresh": "Ricarica",
|
||||
"regeneration_indicator.label": "Caricamento…",
|
||||
"regeneration_indicator.sublabel": "Il feed della tua home è in preparazione!",
|
||||
"relationship_severance_notification.purged_data": "rimossi dagli amministratori",
|
||||
"relationship_severance_notification.relationships": "{count, plural,one {# relazione} other {# relazioni}}",
|
||||
"relationship_severance_notification.types.account_suspension": "L'account è stato sospeso",
|
||||
"relationship_severance_notification.types.domain_block": "Il dominio è stato sospeso",
|
||||
"relationship_severance_notification.types.user_domain_block": "Hai bloccato questo dominio",
|
||||
"relationship_severance_notification.view": "Visualizza",
|
||||
"relative_time.days": "{number}g",
|
||||
"relative_time.full.days": "{number, plural, one {# giorno} other {# giorni}} fa",
|
||||
"relative_time.full.hours": "{number, plural, one {# ora} other {# ore}} fa",
|
||||
|
|
|
@ -89,6 +89,14 @@
|
|||
"announcement.announcement": "お知らせ",
|
||||
"attachments_list.unprocessed": "(未処理)",
|
||||
"audio.hide": "音声を閉じる",
|
||||
"block_modal.remote_users_caveat": "このサーバーはあなたのブロックの意思を尊重するように {domain} へ通知します。しかしながら、ブロックの扱い方はサーバーによってさまざまで、相手のサーバーは必ずしもこのブロックを適切に取り扱うものではないことに留意が必要です。また、あなたの公開投稿はサーバーからログアウトすれば誰からも見ることができます。",
|
||||
"block_modal.show_less": "注意事項を閉じる",
|
||||
"block_modal.show_more": "注意事項",
|
||||
"block_modal.they_cant_mention": "相手はあなたへの返信やフォローができなくなります。",
|
||||
"block_modal.they_cant_see_posts": "相手はあなたの投稿を閲覧できなくなり、あなたも相手の投稿を閲覧できなくなります。",
|
||||
"block_modal.they_will_know": "ブロックは相手からわかります。",
|
||||
"block_modal.title": "ユーザーをブロックしますか?",
|
||||
"block_modal.you_wont_see_mentions": "宛先に相手が入っている投稿も閲覧できなくなります。",
|
||||
"boost_modal.combo": "次からは{combo}を押せばスキップできます",
|
||||
"bundle_column_error.copy_stacktrace": "エラーレポートをコピー",
|
||||
"bundle_column_error.error.body": "要求されたページをレンダリングできませんでした。コードのバグ、またはブラウザの互換性の問題が原因である可能性があります。",
|
||||
|
@ -200,6 +208,14 @@
|
|||
"dismissable_banner.explore_statuses": "ネットワーク上で注目を集めている投稿です。ブーストやお気に入り登録の多い新しい投稿が上位に表示されます。",
|
||||
"dismissable_banner.explore_tags": "ネットワーク上でトレンドになっているハッシュタグです。たくさんのユーザーに使われたタグほど上位に表示されます。",
|
||||
"dismissable_banner.public_timeline": "{domain}のユーザーがリモートフォローしているアカウントからの公開投稿のタイムラインです。",
|
||||
"domain_block_modal.block": "サーバーをブロック",
|
||||
"domain_block_modal.block_account_instead": "@{name} さんのみをブロック",
|
||||
"domain_block_modal.they_can_interact_with_old_posts": "あなたの今までの投稿は、引き続きこのサーバーのユーザーが閲覧できます。",
|
||||
"domain_block_modal.they_cant_follow": "このサーバーのユーザーはあなたをフォローできなくなります。",
|
||||
"domain_block_modal.they_wont_know": "ドメインブロックは相手からはわかりません。",
|
||||
"domain_block_modal.title": "ドメインをブロックしますか?",
|
||||
"domain_block_modal.you_will_lose_followers": "このサーバーのフォロワーはすべてフォロー解除されます。",
|
||||
"domain_block_modal.you_wont_see_posts": "このサーバーのユーザーからの投稿や通知が閲覧できなくなります。",
|
||||
"embed.instructions": "下記のコードをコピーしてウェブサイトに埋め込みます。",
|
||||
"embed.preview": "表示例:",
|
||||
"emoji_button.activity": "活動",
|
||||
|
@ -397,6 +413,15 @@
|
|||
"loading_indicator.label": "読み込み中…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {画像を閉じる} other {画像を閉じる}}",
|
||||
"moved_to_account_banner.text": "あなたのアカウント『{disabledAccount}』は『{movedToAccount}』に移動したため現在無効になっています。",
|
||||
"mute_modal.hide_from_notifications": "通知をオフにする",
|
||||
"mute_modal.hide_options": "オプションを閉じる",
|
||||
"mute_modal.indefinite": "無期限",
|
||||
"mute_modal.show_options": "オプションを表示",
|
||||
"mute_modal.they_can_mention_and_follow": "相手はあなたへの返信やフォローができますが、あなたには見えません。",
|
||||
"mute_modal.they_wont_know": "ミュートは相手からはわかりません。",
|
||||
"mute_modal.title": "ユーザーをミュートしますか?",
|
||||
"mute_modal.you_wont_see_mentions": "宛先に相手が入っている投稿も閲覧できなくなります。",
|
||||
"mute_modal.you_wont_see_posts": "相手はあなたの投稿を今までどおり閲覧できますが、あなたは相手の投稿を閲覧できなくなります。",
|
||||
"navigation_bar.about": "概要",
|
||||
"navigation_bar.advanced_interface": "上級者向けUIに戻る",
|
||||
"navigation_bar.blocks": "ブロックしたユーザー",
|
||||
|
|
|
@ -220,7 +220,7 @@
|
|||
"domain_pill.activitypub_lets_connect": "이것은 마스토돈 뿐만이 아니라 다른 소셜 앱들을 넘나들며 사람들을 연결하고 상호작용 할 수 있게 합니다.",
|
||||
"domain_pill.activitypub_like_language": "액티비티펍은 마스토돈이 다른 소셜 네트워크와 대화할 때 쓰는 언어 같은 것입니다.",
|
||||
"domain_pill.server": "서버",
|
||||
"domain_pill.their_handle": "그의 핸들:",
|
||||
"domain_pill.their_handle": "이 사람의 핸들:",
|
||||
"domain_pill.their_server": "그의 게시물이 살고 있는 디지털 거처입니다.",
|
||||
"domain_pill.their_username": "그의 서버에서 유일한 식별자입니다. 다른 서버에서 같은 사용자명을 가진 사용자를 찾을 수도 있습니다.",
|
||||
"domain_pill.username": "사용자명",
|
||||
|
@ -471,6 +471,7 @@
|
|||
"notification.own_poll": "설문을 마침",
|
||||
"notification.poll": "참여한 설문이 종료됨",
|
||||
"notification.reblog": "{name} 님이 부스트했습니다",
|
||||
"notification.severed_relationships": "{name} 님과의 관계가 단절되었습니다",
|
||||
"notification.status": "{name} 님이 방금 게시물을 올렸습니다",
|
||||
"notification.update": "{name} 님이 게시물을 수정했습니다",
|
||||
"notification_requests.accept": "수락",
|
||||
|
@ -483,6 +484,7 @@
|
|||
"notifications.column_settings.admin.sign_up": "새로운 가입:",
|
||||
"notifications.column_settings.alert": "데스크탑 알림",
|
||||
"notifications.column_settings.favourite": "좋아요:",
|
||||
"notifications.column_settings.filter_bar.advanced": "모든 범주 표시",
|
||||
"notifications.column_settings.follow": "새 팔로워:",
|
||||
"notifications.column_settings.follow_request": "새 팔로우 요청:",
|
||||
"notifications.column_settings.mention": "멘션:",
|
||||
|
@ -587,6 +589,12 @@
|
|||
"refresh": "새로고침",
|
||||
"regeneration_indicator.label": "불러오는 중…",
|
||||
"regeneration_indicator.sublabel": "홈 피드를 준비하고 있습니다!",
|
||||
"relationship_severance_notification.purged_data": "관리자에 의해 제거되었습니다",
|
||||
"relationship_severance_notification.relationships": "{count, plural, other {# 건의 관계}}",
|
||||
"relationship_severance_notification.types.account_suspension": "계정이 정지되었습니다",
|
||||
"relationship_severance_notification.types.domain_block": "도메인이 정지되었습니다",
|
||||
"relationship_severance_notification.types.user_domain_block": "내가 이 도메인을 차단했습니다",
|
||||
"relationship_severance_notification.view": "보기",
|
||||
"relative_time.days": "{number}일 전",
|
||||
"relative_time.full.days": "{number} 일 전",
|
||||
"relative_time.full.hours": "{number} 시간 전",
|
||||
|
|
|
@ -91,7 +91,10 @@
|
|||
"audio.hide": "Eskonde audio",
|
||||
"block_modal.show_less": "Amostra manko",
|
||||
"block_modal.show_more": "Amostra mas",
|
||||
"block_modal.they_cant_mention": "No te puede enmentar ni segir.",
|
||||
"block_modal.they_will_know": "Puede ver ke esta blokado.",
|
||||
"block_modal.title": "Bloka utilizador?",
|
||||
"block_modal.you_wont_see_mentions": "No veras publikasyones ke lo enmentan.",
|
||||
"boost_modal.combo": "Puedes klikar {combo} para ometer esto la proksima vez",
|
||||
"bundle_column_error.copy_stacktrace": "Kopia el raporto de yerro",
|
||||
"bundle_column_error.error.body": "La pajina solisitada no pudo ser renderada. Podria ser por un yerro en muestro kodiche o un problem de kompatibilita kon el navigador.",
|
||||
|
@ -205,9 +208,17 @@
|
|||
"dismissable_banner.explore_tags": "Estas etiketas estan agora popularas en la red sosyala. Etiketas uzadas por mas djente aparesen primero.",
|
||||
"dismissable_banner.public_timeline": "Estas son las publikasyones publikas mas resientes de personas en la red sosyala a las kualas la djente de {domain} sige.",
|
||||
"domain_block_modal.block": "Bloka sirvidor",
|
||||
"domain_block_modal.block_account_instead": "Bloka @{name} en su lugar",
|
||||
"domain_block_modal.they_cant_follow": "Dingun de este sirvidor puede segirte.",
|
||||
"domain_block_modal.they_wont_know": "No savra ke tiene sido blokado.",
|
||||
"domain_block_modal.title": "Bloka el domeno?",
|
||||
"domain_block_modal.you_will_lose_followers": "Se efasaran todos tus suivantes de este sirvidor.",
|
||||
"domain_block_modal.you_wont_see_posts": "No veras publikasyones ni avizos de utilizadores en este sirvidor.",
|
||||
"domain_pill.server": "Sirvidor",
|
||||
"domain_pill.their_handle": "Su alias:",
|
||||
"domain_pill.username": "Nombre de utilizador",
|
||||
"domain_pill.whats_in_a_handle": "En ke konsiste el alias?",
|
||||
"domain_pill.your_handle": "Tu alias:",
|
||||
"embed.instructions": "Enkrusta esta publikasyon en tu sitio internetiko kopiando este kodiche.",
|
||||
"embed.preview": "Paresera ansina:",
|
||||
"emoji_button.activity": "Aktivita",
|
||||
|
@ -404,6 +415,13 @@
|
|||
"loading_indicator.label": "Eskargando…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Eskonde imaje} other {Eskonde imajes}}",
|
||||
"moved_to_account_banner.text": "Tu kuento {disabledAccount} esta aktualmente inkapasitado porke transferates a {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Eskonde de avizos",
|
||||
"mute_modal.hide_options": "Eskonde opsyones",
|
||||
"mute_modal.indefinite": "Asta ke desho de silensyarlo",
|
||||
"mute_modal.show_options": "Amostra opsyones",
|
||||
"mute_modal.they_wont_know": "No savra ke tiene sido silensyado.",
|
||||
"mute_modal.title": "Silensiar utilizador?",
|
||||
"mute_modal.you_wont_see_mentions": "No veras publikasyones ke lo enmentan.",
|
||||
"navigation_bar.about": "Sovre mozotros",
|
||||
"navigation_bar.advanced_interface": "Avre en la enterfaz avanzada",
|
||||
"navigation_bar.blocks": "Utilizadores blokados",
|
||||
|
@ -553,6 +571,8 @@
|
|||
"refresh": "Arefreska",
|
||||
"regeneration_indicator.label": "Eskargando…",
|
||||
"regeneration_indicator.sublabel": "Tu linya de tiempo prinsipala esta preparando!",
|
||||
"relationship_severance_notification.types.user_domain_block": "Blokates este domeno",
|
||||
"relationship_severance_notification.view": "Mira",
|
||||
"relative_time.days": "{number} d",
|
||||
"relative_time.full.days": "antes {number, plural, one {# diya} other {# diyas}}",
|
||||
"relative_time.full.hours": "antes {number, plural, one {# ora} other {# oras}}",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue