Merge remote-tracking branch 'parent/main' into upstream-20240913
This commit is contained in:
commit
fc9d27ff91
392 changed files with 3757 additions and 3233 deletions
|
@ -316,7 +316,7 @@ module.exports = defineConfig({
|
|||
],
|
||||
|
||||
parserOptions: {
|
||||
project: true,
|
||||
projectService: true,
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
|
||||
|
|
3
.github/codecov.yml
vendored
3
.github/codecov.yml
vendored
|
@ -1,4 +1,3 @@
|
|||
annotations: false
|
||||
comment: false # Do not leave PR comments
|
||||
coverage:
|
||||
status:
|
||||
|
@ -10,3 +9,5 @@ coverage:
|
|||
default:
|
||||
# GitHub status check is not blocking
|
||||
informational: true
|
||||
github_checks:
|
||||
annotations: false
|
||||
|
|
20
Gemfile.lock
20
Gemfile.lock
|
@ -100,17 +100,17 @@ GEM
|
|||
attr_required (1.0.2)
|
||||
awrence (1.2.1)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.970.0)
|
||||
aws-sdk-core (3.203.0)
|
||||
aws-partitions (1.974.0)
|
||||
aws-sdk-core (3.205.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.89.0)
|
||||
aws-sdk-core (~> 3, >= 3.203.0)
|
||||
aws-sdk-kms (1.91.0)
|
||||
aws-sdk-core (~> 3, >= 3.205.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.160.0)
|
||||
aws-sdk-core (~> 3, >= 3.203.0)
|
||||
aws-sdk-s3 (1.162.0)
|
||||
aws-sdk-core (~> 3, >= 3.205.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.9.1)
|
||||
|
@ -458,7 +458,7 @@ GEM
|
|||
nokogiri (1.16.7)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
oj (3.16.5)
|
||||
oj (3.16.6)
|
||||
bigdecimal (>= 3.0)
|
||||
ostruct (>= 0.2)
|
||||
omniauth (2.1.2)
|
||||
|
@ -590,7 +590,7 @@ GEM
|
|||
parslet (2.0.0)
|
||||
pastel (0.8.0)
|
||||
tty-color (~> 0.5)
|
||||
pg (1.5.7)
|
||||
pg (1.5.8)
|
||||
pghero (3.6.0)
|
||||
activerecord (>= 6.1)
|
||||
premailer (1.27.0)
|
||||
|
@ -601,7 +601,7 @@ GEM
|
|||
actionmailer (>= 3)
|
||||
net-smtp
|
||||
premailer (~> 1.7, >= 1.7.9)
|
||||
propshaft (0.9.1)
|
||||
propshaft (1.0.0)
|
||||
actionpack (>= 7.0.0)
|
||||
activesupport (>= 7.0.0)
|
||||
rack
|
||||
|
@ -691,7 +691,7 @@ GEM
|
|||
redlock (1.3.2)
|
||||
redis (>= 3.0.0, < 6.0)
|
||||
regexp_parser (2.9.2)
|
||||
reline (0.5.9)
|
||||
reline (0.5.10)
|
||||
io-console (~> 0.5)
|
||||
request_store (1.6.0)
|
||||
rack (>= 1.4)
|
||||
|
|
|
@ -8,6 +8,16 @@ module WebAppControllerConcern
|
|||
|
||||
before_action :redirect_unauthenticated_to_permalinks!
|
||||
before_action :set_app_body_class
|
||||
|
||||
content_security_policy do |p|
|
||||
policy = ContentSecurityPolicy.new
|
||||
|
||||
if policy.sso_host.present?
|
||||
p.form_action policy.sso_host
|
||||
else
|
||||
p.form_action :none
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def skip_csrf_meta_tags?
|
||||
|
|
|
@ -4,7 +4,6 @@ class Redirect::BaseController < ApplicationController
|
|||
vary_by 'Accept-Language'
|
||||
|
||||
before_action :set_resource
|
||||
before_action :set_app_body_class
|
||||
|
||||
def show
|
||||
@redirect_path = ActivityPub::TagManager.instance.url_for(@resource)
|
||||
|
@ -14,10 +13,6 @@ class Redirect::BaseController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def set_app_body_class
|
||||
@body_classes = 'app-body'
|
||||
end
|
||||
|
||||
def set_resource
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
|
|
@ -2,14 +2,30 @@
|
|||
|
||||
class Settings::VerificationsController < Settings::BaseController
|
||||
before_action :set_account
|
||||
before_action :set_verified_links
|
||||
|
||||
def show
|
||||
@verified_links = @account.fields.select(&:verified?)
|
||||
def show; end
|
||||
|
||||
def update
|
||||
if UpdateAccountService.new.call(@account, account_params)
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||
redirect_to settings_verification_path, notice: I18n.t('generic.changes_saved_msg')
|
||||
else
|
||||
render :show
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def account_params
|
||||
params.require(:account).permit(:attribution_domains_as_text)
|
||||
end
|
||||
|
||||
def set_account
|
||||
@account = current_account
|
||||
end
|
||||
|
||||
def set_verified_links
|
||||
@verified_links = @account.fields.select(&:verified?)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,6 @@ class StatusesController < ApplicationController
|
|||
before_action :require_account_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_status
|
||||
before_action :redirect_to_original, only: :show
|
||||
before_action :set_body_classes, only: :embed
|
||||
|
||||
after_action :set_link_headers
|
||||
|
||||
|
@ -51,10 +50,6 @@ class StatusesController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'with-modals'
|
||||
end
|
||||
|
||||
def set_link_headers
|
||||
response.headers['Link'] = LinkHeader.new(
|
||||
[[ActivityPub::TagManager.instance.uri_for(@status), [%w(rel alternate), %w(type application/activity+json)]]]
|
||||
|
|
|
@ -19,14 +19,6 @@ module AccountsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def account_action_button(account)
|
||||
return if account.memorial? || account.moved?
|
||||
|
||||
link_to ActivityPub::TagManager.instance.url_for(account), class: 'button logo-button', target: '_new' do
|
||||
safe_join([logo_as_symbol, t('accounts.follow')])
|
||||
end
|
||||
end
|
||||
|
||||
def account_formatted_stat(value)
|
||||
number_to_human(value, precision: 3, strip_insignificant_zeros: true)
|
||||
end
|
||||
|
|
|
@ -250,10 +250,6 @@ module ApplicationHelper
|
|||
EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s
|
||||
end
|
||||
|
||||
def prerender_custom_emojis_from_hash(html, custom_emojis_hash)
|
||||
prerender_custom_emojis(html, JSON.parse([custom_emojis_hash].to_json, object_class: OpenStruct)) # rubocop:disable Style/OpenStructUse
|
||||
end
|
||||
|
||||
def mascot_url
|
||||
full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg'))
|
||||
end
|
||||
|
|
|
@ -50,6 +50,7 @@ module ContextHelper
|
|||
'cipherText' => 'toot:cipherText',
|
||||
},
|
||||
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },
|
||||
attribution_domains: { 'toot' => 'http://joinmastodon.org/ns#', 'attributionDomains' => { '@id' => 'toot:attributionDomains', '@type' => '@id' } },
|
||||
}.freeze
|
||||
|
||||
def full_context
|
||||
|
|
|
@ -57,26 +57,6 @@ module MediaComponentHelper
|
|||
end
|
||||
end
|
||||
|
||||
def render_card_component(status, **options)
|
||||
component_params = {
|
||||
sensitive: sensitive_viewer?(status, current_account),
|
||||
card: serialize_status_card(status).as_json,
|
||||
}.merge(**options)
|
||||
|
||||
react_component :card, component_params
|
||||
end
|
||||
|
||||
def render_poll_component(status, **options)
|
||||
component_params = {
|
||||
disabled: true,
|
||||
poll: serialize_status_poll(status).as_json,
|
||||
}.merge(**options)
|
||||
|
||||
react_component :poll, component_params do
|
||||
render partial: 'statuses/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: prefers_autoplay? }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def serialize_media_attachment(attachment)
|
||||
|
@ -86,22 +66,6 @@ module MediaComponentHelper
|
|||
)
|
||||
end
|
||||
|
||||
def serialize_status_card(status)
|
||||
ActiveModelSerializers::SerializableResource.new(
|
||||
status.preview_card,
|
||||
serializer: REST::PreviewCardSerializer
|
||||
)
|
||||
end
|
||||
|
||||
def serialize_status_poll(status)
|
||||
ActiveModelSerializers::SerializableResource.new(
|
||||
status.preloadable_poll,
|
||||
serializer: REST::PollSerializer,
|
||||
scope: current_user,
|
||||
scope_name: :current_user
|
||||
)
|
||||
end
|
||||
|
||||
def sensitive_viewer?(status, account)
|
||||
if !account.nil? && account.id == status.account_id
|
||||
status.sensitive
|
||||
|
|
|
@ -4,6 +4,16 @@ module StatusesHelper
|
|||
EMBEDDED_CONTROLLER = 'statuses'
|
||||
EMBEDDED_ACTION = 'embed'
|
||||
|
||||
VISIBLITY_ICONS = {
|
||||
public: 'globe',
|
||||
unlisted: 'lock_open',
|
||||
private: 'lock',
|
||||
direct: 'alternate_email',
|
||||
public_unlisted: 'cloud',
|
||||
login: 'key',
|
||||
limited: 'shield',
|
||||
}.freeze
|
||||
|
||||
def nothing_here(extra_classes = '')
|
||||
content_tag(:div, class: "nothing-here #{extra_classes}") do
|
||||
t('accounts.nothing_here')
|
||||
|
@ -57,23 +67,8 @@ module StatusesHelper
|
|||
embedded_view? ? '_blank' : nil
|
||||
end
|
||||
|
||||
def fa_visibility_icon(status)
|
||||
case status.visibility
|
||||
when 'public'
|
||||
material_symbol 'globe'
|
||||
when 'unlisted'
|
||||
material_symbol 'lock_open'
|
||||
when 'public_unlisted'
|
||||
material_symbol 'cloud'
|
||||
when 'login'
|
||||
material_symbol 'key'
|
||||
when 'private'
|
||||
material_symbol 'lock'
|
||||
when 'limited'
|
||||
material_symbol 'shield'
|
||||
when 'direct'
|
||||
material_symbol 'alternate_email'
|
||||
end
|
||||
def visibility_icon(status)
|
||||
VISIBLITY_ICONS[status.visibility.to_sym]
|
||||
end
|
||||
|
||||
def embedded_view?
|
||||
|
|
74
app/javascript/entrypoints/embed.tsx
Normal file
74
app/javascript/entrypoints/embed.tsx
Normal file
|
@ -0,0 +1,74 @@
|
|||
import './public-path';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { afterInitialRender } from 'mastodon/../hooks/useRenderSignal';
|
||||
|
||||
import { start } from '../mastodon/common';
|
||||
import { Status } from '../mastodon/features/standalone/status';
|
||||
import { loadPolyfills } from '../mastodon/polyfills';
|
||||
import ready from '../mastodon/ready';
|
||||
|
||||
start();
|
||||
|
||||
function loaded() {
|
||||
const mountNode = document.getElementById('mastodon-status');
|
||||
|
||||
if (mountNode) {
|
||||
const attr = mountNode.getAttribute('data-props');
|
||||
|
||||
if (!attr) return;
|
||||
|
||||
const props = JSON.parse(attr) as { id: string; locale: string };
|
||||
const root = createRoot(mountNode);
|
||||
|
||||
root.render(<Status {...props} />);
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
ready(loaded).catch((error: unknown) => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
loadPolyfills()
|
||||
.then(main)
|
||||
.catch((error: unknown) => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
interface SetHeightMessage {
|
||||
type: 'setHeight';
|
||||
id: string;
|
||||
height: number;
|
||||
}
|
||||
|
||||
function isSetHeightMessage(data: unknown): data is SetHeightMessage {
|
||||
if (
|
||||
data &&
|
||||
typeof data === 'object' &&
|
||||
'type' in data &&
|
||||
data.type === 'setHeight'
|
||||
)
|
||||
return true;
|
||||
else return false;
|
||||
}
|
||||
|
||||
window.addEventListener('message', (e) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- typings are not correct, it can be null in very rare cases
|
||||
if (!e.data || !isSetHeightMessage(e.data) || !window.parent) return;
|
||||
|
||||
const data = e.data;
|
||||
|
||||
// We use a timeout to allow for the React page to render before calculating the height
|
||||
afterInitialRender(() => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: 'setHeight',
|
||||
id: data.id,
|
||||
height: document.getElementsByTagName('html')[0]?.scrollHeight,
|
||||
},
|
||||
'*',
|
||||
);
|
||||
});
|
||||
});
|
|
@ -37,43 +37,6 @@ const messages = defineMessages({
|
|||
},
|
||||
});
|
||||
|
||||
interface SetHeightMessage {
|
||||
type: 'setHeight';
|
||||
id: string;
|
||||
height: number;
|
||||
}
|
||||
|
||||
function isSetHeightMessage(data: unknown): data is SetHeightMessage {
|
||||
if (
|
||||
data &&
|
||||
typeof data === 'object' &&
|
||||
'type' in data &&
|
||||
data.type === 'setHeight'
|
||||
)
|
||||
return true;
|
||||
else return false;
|
||||
}
|
||||
|
||||
window.addEventListener('message', (e) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- typings are not correct, it can be null in very rare cases
|
||||
if (!e.data || !isSetHeightMessage(e.data) || !window.parent) return;
|
||||
|
||||
const data = e.data;
|
||||
|
||||
ready(() => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: 'setHeight',
|
||||
id: data.id,
|
||||
height: document.getElementsByTagName('html')[0]?.scrollHeight,
|
||||
},
|
||||
'*',
|
||||
);
|
||||
}).catch((e: unknown) => {
|
||||
console.error('Error in setHeightMessage postMessage', e);
|
||||
});
|
||||
});
|
||||
|
||||
function loaded() {
|
||||
const { messages: localeData } = getLocale();
|
||||
|
||||
|
|
32
app/javascript/hooks/useRenderSignal.ts
Normal file
32
app/javascript/hooks/useRenderSignal.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
// This hook allows a component to signal that it's done rendering in a way that
|
||||
// can be used by e.g. our embed code to determine correct iframe height
|
||||
|
||||
let renderSignalReceived = false;
|
||||
|
||||
type Callback = () => void;
|
||||
|
||||
let onInitialRender: Callback;
|
||||
|
||||
export const afterInitialRender = (callback: Callback) => {
|
||||
if (renderSignalReceived) {
|
||||
callback();
|
||||
} else {
|
||||
onInitialRender = callback;
|
||||
}
|
||||
};
|
||||
|
||||
export const useRenderSignal = () => {
|
||||
return () => {
|
||||
if (renderSignalReceived) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderSignalReceived = true;
|
||||
|
||||
if (typeof onInitialRender !== 'undefined') {
|
||||
window.requestAnimationFrame(() => {
|
||||
onInitialRender();
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
|
@ -65,7 +65,7 @@ export const synchronouslySubmitMarkers = createAppAsyncThunk(
|
|||
client.setRequestHeader('Content-Type', 'application/json');
|
||||
client.setRequestHeader('Authorization', `Bearer ${accessToken}`);
|
||||
client.send(JSON.stringify(params));
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// Do not make the BeforeUnload handler error out
|
||||
}
|
||||
},
|
||||
|
|
|
@ -53,11 +53,13 @@ export function fetchStatusRequest(id, skipLoading) {
|
|||
};
|
||||
}
|
||||
|
||||
export function fetchStatus(id, forceFetch = false) {
|
||||
export function fetchStatus(id, forceFetch = false, alsoFetchContext = true) {
|
||||
return (dispatch, getState) => {
|
||||
const skipLoading = !forceFetch && getState().getIn(['statuses', id], null) !== null;
|
||||
|
||||
dispatch(fetchContext(id));
|
||||
if (alsoFetchContext) {
|
||||
dispatch(fetchContext(id));
|
||||
}
|
||||
|
||||
if (skipLoading) {
|
||||
return;
|
||||
|
|
|
@ -156,7 +156,7 @@ async function refreshHomeTimelineAndNotification(dispatch, getState) {
|
|||
// TODO: polling for merged notifications
|
||||
try {
|
||||
await dispatch(pollRecentGroupNotifications());
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// TODO
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -5,7 +5,7 @@ export function start() {
|
|||
|
||||
try {
|
||||
Rails.start();
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// If called twice
|
||||
}
|
||||
}
|
||||
|
|
90
app/javascript/mastodon/components/copy_paste_text.tsx
Normal file
90
app/javascript/mastodon/components/copy_paste_text.tsx
Normal file
|
@ -0,0 +1,90 @@
|
|||
import { useRef, useState, useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react';
|
||||
import { useTimeout } from 'mastodon/../hooks/useTimeout';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
|
||||
export const CopyPasteText: React.FC<{ value: string }> = ({ value }) => {
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [focused, setFocused] = useState(false);
|
||||
const [setAnimationTimeout] = useTimeout();
|
||||
|
||||
const handleInputClick = useCallback(() => {
|
||||
setCopied(false);
|
||||
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
inputRef.current.select();
|
||||
inputRef.current.setSelectionRange(0, value.length);
|
||||
}
|
||||
}, [setCopied, value]);
|
||||
|
||||
const handleButtonClick = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
void navigator.clipboard.writeText(value);
|
||||
inputRef.current?.blur();
|
||||
setCopied(true);
|
||||
setAnimationTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 700);
|
||||
},
|
||||
[setCopied, setAnimationTimeout, value],
|
||||
);
|
||||
|
||||
const handleKeyUp = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
if (e.key !== ' ') return;
|
||||
void navigator.clipboard.writeText(value);
|
||||
setCopied(true);
|
||||
setAnimationTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 700);
|
||||
},
|
||||
[setCopied, setAnimationTimeout, value],
|
||||
);
|
||||
|
||||
const handleFocus = useCallback(() => {
|
||||
setFocused(true);
|
||||
}, [setFocused]);
|
||||
|
||||
const handleBlur = useCallback(() => {
|
||||
setFocused(false);
|
||||
}, [setFocused]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('copy-paste-text', { copied, focused })}
|
||||
tabIndex={0}
|
||||
role='button'
|
||||
onClick={handleInputClick}
|
||||
onKeyUp={handleKeyUp}
|
||||
>
|
||||
<textarea
|
||||
readOnly
|
||||
value={value}
|
||||
ref={inputRef}
|
||||
onClick={handleInputClick}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
/>
|
||||
|
||||
<button className='button' onClick={handleButtonClick}>
|
||||
<Icon id='copy' icon={ContentCopyIcon} />{' '}
|
||||
{copied ? (
|
||||
<FormattedMessage id='copypaste.copied' defaultMessage='Copied' />
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='copypaste.copy_to_clipboard'
|
||||
defaultMessage='Copy to clipboard'
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -60,8 +60,8 @@ export default class ErrorBoundary extends PureComponent {
|
|||
try {
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
} catch (e) {
|
||||
|
||||
} catch {
|
||||
// do nothing
|
||||
} finally {
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,13 @@ export const WordmarkLogo: React.FC = () => (
|
|||
</svg>
|
||||
);
|
||||
|
||||
export const IconLogo: React.FC = () => (
|
||||
<svg viewBox='0 0 79 79' className='logo logo--icon' role='img'>
|
||||
<title>Mastodon</title>
|
||||
<use xlinkHref='#logo-symbol-icon' />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const SymbolLogo: React.FC = () => (
|
||||
<img src={logo} alt='Mastodon' className='logo logo--icon' />
|
||||
);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
|
@ -10,17 +10,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
|
||||
import { Blurhash } from 'mastodon/components/blurhash';
|
||||
|
||||
import { autoPlayGif, displayMedia, useBlurhash } from '../initial_state';
|
||||
|
||||
import { IconButton } from './icon_button';
|
||||
|
||||
const messages = defineMessages({
|
||||
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: '{number, plural, one {Hide image} other {Hide images}}' },
|
||||
});
|
||||
|
||||
class Item extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -231,7 +224,6 @@ class MediaGallery extends PureComponent {
|
|||
size: PropTypes.object,
|
||||
height: PropTypes.number.isRequired,
|
||||
onOpenMedia: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
defaultWidth: PropTypes.number,
|
||||
cacheWidth: PropTypes.func,
|
||||
visible: PropTypes.bool,
|
||||
|
@ -308,7 +300,7 @@ class MediaGallery extends PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { media, lang, intl, sensitive, defaultWidth, autoplay, compact } = this.props;
|
||||
const { media, lang, sensitive, defaultWidth, autoplay, compact } = this.props;
|
||||
const { visible } = this.state;
|
||||
const width = this.state.width || defaultWidth;
|
||||
|
||||
|
@ -340,9 +332,7 @@ class MediaGallery extends PureComponent {
|
|||
</span>
|
||||
</button>
|
||||
);
|
||||
} else if (visible) {
|
||||
spoilerButton = <IconButton title={intl.formatMessage(messages.toggle_visible, { number: size })} icon='eye-slash' iconComponent={VisibilityOffIcon} overlay onClick={this.handleOpen} ariaHidden />;
|
||||
} else {
|
||||
} else if (!visible) {
|
||||
spoilerButton = (
|
||||
<button type='button' onClick={this.handleOpen} className='spoiler-button__overlay'>
|
||||
<span className='spoiler-button__overlay__label'>
|
||||
|
@ -361,17 +351,30 @@ class MediaGallery extends PureComponent {
|
|||
'media-gallery--column2';
|
||||
const compactClass = compact ? 'media-gallery__compact' : null;
|
||||
|
||||
const classList = ['media-gallery', `media-gallery--layout-${size}`];
|
||||
if (size > 4) {
|
||||
classList.push(rowClass, columnClass, compactClass);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames('media-gallery', rowClass, columnClass, compactClass)} style={style} ref={this.handleRef}>
|
||||
<div className={classNames('spoiler-button', { 'spoiler-button--minified': visible && !uncached, 'spoiler-button--click-thru': uncached })}>
|
||||
{spoilerButton}
|
||||
</div>
|
||||
<div className={classNames(classList)} style={style} ref={this.handleRef}>
|
||||
{(!visible || uncached) && (
|
||||
<div className={classNames('spoiler-button', { 'spoiler-button--click-thru': uncached })}>
|
||||
{spoilerButton}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{children}
|
||||
|
||||
{(visible && !uncached) && (
|
||||
<div className='media-gallery__actions'>
|
||||
<button className='media-gallery__actions__pill' onClick={this.handleOpen}><FormattedMessage id='media_gallery.hide' defaultMessage='Hide' /></button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(MediaGallery);
|
||||
export default MediaGallery;
|
||||
|
|
|
@ -2,14 +2,12 @@ import PropTypes from 'prop-types';
|
|||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { IconLogo } from 'mastodon/components/logo';
|
||||
import { AuthorLink } from 'mastodon/features/explore/components/author_link';
|
||||
|
||||
export const MoreFromAuthor = ({ accountId }) => (
|
||||
<div className='more-from-author'>
|
||||
<svg viewBox='0 0 79 79' className='logo logo--icon' role='img'>
|
||||
<use xlinkHref='#logo-symbol-icon' />
|
||||
</svg>
|
||||
|
||||
<IconLogo />
|
||||
<FormattedMessage id='link_preview.more_from_author' defaultMessage='More from {name}' values={{ name: <AuthorLink accountId={accountId} /> }} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -14,7 +14,6 @@ import QuoteIcon from '@/material-icons/400-24px/format_quote.svg?react';
|
|||
import ReferenceIcon from '@/material-icons/400-24px/link.svg?react';
|
||||
import PushPinIcon from '@/material-icons/400-24px/push_pin.svg?react';
|
||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
import LimitedIcon from '@/material-icons/400-24px/shield.svg?react';
|
||||
import TimerIcon from '@/material-icons/400-24px/timer.svg?react';
|
||||
import AttachmentList from 'mastodon/components/attachment_list';
|
||||
|
@ -41,6 +40,7 @@ import { RelativeTimestamp } from './relative_timestamp';
|
|||
import StatusActionBar from './status_action_bar';
|
||||
import StatusContent from './status_content';
|
||||
import StatusEmojiReactionsBar from './status_emoji_reactions_bar';
|
||||
import { StatusThreadLabel } from './status_thread_label';
|
||||
import { VisibilityIcon } from './visibility_icon';
|
||||
|
||||
const domParser = new DOMParser();
|
||||
|
@ -427,7 +427,7 @@ class Status extends ImmutablePureComponent {
|
|||
if (featured) {
|
||||
prepend = (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend-icon-wrapper'><Icon id='thumb-tack' icon={PushPinIcon} className='status__prepend-icon' /></div>
|
||||
<div className='status__prepend__icon'><Icon id='thumb-tack' icon={PushPinIcon} /></div>
|
||||
<FormattedMessage id='status.pinned' defaultMessage='Pinned post' />
|
||||
</div>
|
||||
);
|
||||
|
@ -436,8 +436,8 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
prepend = (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend-icon-wrapper'><Icon id='retweet' icon={RepeatIcon} className='status__prepend-icon' /></div>
|
||||
<div className='status__prepend-icon-wrapper'><VisibilityIcon visibility={visibilityName} className='status__prepend-icon' /></div>
|
||||
<div className='status__prepend__icon'><Icon id='retweet' icon={RepeatIcon} /></div>
|
||||
<div className='status__prepend__icon'><VisibilityIcon visibility={visibilityName} /></div>
|
||||
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(['account', 'id'])} data-hover-card-account={status.getIn(['account', 'id'])} href={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
|
||||
</div>
|
||||
);
|
||||
|
@ -449,18 +449,13 @@ class Status extends ImmutablePureComponent {
|
|||
} else if (status.get('visibility') === 'direct') {
|
||||
prepend = (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend-icon-wrapper'><Icon id='at' icon={AlternateEmailIcon} className='status__prepend-icon' /></div>
|
||||
<div className='status__prepend__icon'><Icon id='at' icon={AlternateEmailIcon} /></div>
|
||||
<FormattedMessage id='status.direct_indicator' defaultMessage='Private mention' />
|
||||
</div>
|
||||
);
|
||||
} else if (showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id'])) {
|
||||
const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
|
||||
|
||||
} else if (showThread && status.get('in_reply_to_id')) {
|
||||
prepend = (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend-icon-wrapper'><Icon id='reply' icon={ReplyIcon} className='status__prepend-icon' /></div>
|
||||
<FormattedMessage id='status.replied_to' defaultMessage='Replied to {name}' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(['account', 'id'])} data-hover-card-account={status.getIn(['account', 'id'])} href={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
|
||||
</div>
|
||||
<StatusThreadLabel accountId={status.getIn(['account', 'id'])} inReplyToAccountId={status.get('in_reply_to_account_id')} />
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ const messages = defineMessages({
|
|||
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
|
||||
pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
|
||||
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
|
||||
embed: { id: 'status.embed', defaultMessage: 'Embed' },
|
||||
embed: { id: 'status.embed', defaultMessage: 'Get embed code' },
|
||||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
||||
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this post in the moderation interface' },
|
||||
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
|
||||
|
|
50
app/javascript/mastodon/components/status_thread_label.tsx
Normal file
50
app/javascript/mastodon/components/status_thread_label.tsx
Normal file
|
@ -0,0 +1,50 @@
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { DisplayedName } from 'mastodon/features/notifications_v2/components/displayed_name';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
|
||||
export const StatusThreadLabel: React.FC<{
|
||||
accountId: string;
|
||||
inReplyToAccountId: string;
|
||||
}> = ({ accountId, inReplyToAccountId }) => {
|
||||
const inReplyToAccount = useAppSelector((state) =>
|
||||
state.accounts.get(inReplyToAccountId),
|
||||
);
|
||||
|
||||
let label;
|
||||
|
||||
if (accountId === inReplyToAccountId) {
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='status.continued_thread'
|
||||
defaultMessage='Continued thread'
|
||||
/>
|
||||
);
|
||||
} else if (inReplyToAccount) {
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='status.replied_to'
|
||||
defaultMessage='Replied to {name}'
|
||||
values={{ name: <DisplayedName accountIds={[inReplyToAccountId]} /> }}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='status.replied_in_thread'
|
||||
defaultMessage='Replied in thread'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend__icon'>
|
||||
<Icon id='reply' icon={ReplyIcon} />
|
||||
</div>
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -6,7 +6,6 @@ import {
|
|||
unmuteAccount,
|
||||
unblockAccount,
|
||||
} from '../actions/accounts';
|
||||
import { showAlertForError } from '../actions/alerts';
|
||||
import { initBlockModal } from '../actions/blocks';
|
||||
import {
|
||||
replyCompose,
|
||||
|
@ -126,10 +125,7 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({
|
|||
onEmbed (status) {
|
||||
dispatch(openModal({
|
||||
modalType: 'EMBED',
|
||||
modalProps: {
|
||||
id: status.get('id'),
|
||||
onError: error => dispatch(showAlertForError(error)),
|
||||
},
|
||||
modalProps: { id: status.get('id') },
|
||||
}));
|
||||
},
|
||||
|
||||
|
|
|
@ -132,7 +132,7 @@ class LoginForm extends React.PureComponent {
|
|||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch(_) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||
import ReplyIcon from '@/material-icons/400-24px/reply-fill.svg?react';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
|
@ -47,7 +49,7 @@ export const NotificationMention: React.FC<{
|
|||
status.get('visibility') === 'direct',
|
||||
status.get('in_reply_to_account_id') === me,
|
||||
] as const;
|
||||
});
|
||||
}, isEqual);
|
||||
|
||||
let labelRenderer = mentionLabelRenderer;
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|||
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
import { isEqual } from 'lodash';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
import DoneAllIcon from '@/material-icons/400-24px/done_all.svg?react';
|
||||
|
@ -62,7 +63,7 @@ export const Notifications: React.FC<{
|
|||
multiColumn?: boolean;
|
||||
}> = ({ columnId, multiColumn }) => {
|
||||
const intl = useIntl();
|
||||
const notifications = useAppSelector(selectNotificationGroups);
|
||||
const notifications = useAppSelector(selectNotificationGroups, isEqual);
|
||||
const dispatch = useAppDispatch();
|
||||
const isLoading = useAppSelector((s) => s.notificationGroups.isLoading);
|
||||
const hasMore = notifications.at(-1)?.type === 'gap';
|
||||
|
|
|
@ -10,8 +10,8 @@ import { Link } from 'react-router-dom';
|
|||
import SwipeableViews from 'react-swipeable-views';
|
||||
|
||||
import ArrowRightAltIcon from '@/material-icons/400-24px/arrow_right_alt.svg?react';
|
||||
import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react';
|
||||
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
||||
import { CopyPasteText } from 'mastodon/components/copy_paste_text';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { me, domain } from 'mastodon/initial_state';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
|
@ -20,67 +20,6 @@ const messages = defineMessages({
|
|||
shareableMessage: { id: 'onboarding.share.message', defaultMessage: 'I\'m {username} on #Mastodon! Come follow me at {url}' },
|
||||
});
|
||||
|
||||
class CopyPasteText extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
value: PropTypes.string,
|
||||
};
|
||||
|
||||
state = {
|
||||
copied: false,
|
||||
focused: false,
|
||||
};
|
||||
|
||||
setRef = c => {
|
||||
this.input = c;
|
||||
};
|
||||
|
||||
handleInputClick = () => {
|
||||
this.setState({ copied: false });
|
||||
this.input.focus();
|
||||
this.input.select();
|
||||
this.input.setSelectionRange(0, this.props.value.length);
|
||||
};
|
||||
|
||||
handleButtonClick = e => {
|
||||
e.stopPropagation();
|
||||
|
||||
const { value } = this.props;
|
||||
navigator.clipboard.writeText(value);
|
||||
this.input.blur();
|
||||
this.setState({ copied: true });
|
||||
this.timeout = setTimeout(() => this.setState({ copied: false }), 700);
|
||||
};
|
||||
|
||||
handleFocus = () => {
|
||||
this.setState({ focused: true });
|
||||
};
|
||||
|
||||
handleBlur = () => {
|
||||
this.setState({ focused: false });
|
||||
};
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.timeout) clearTimeout(this.timeout);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { value } = this.props;
|
||||
const { copied, focused } = this.state;
|
||||
|
||||
return (
|
||||
<div className={classNames('copy-paste-text', { copied, focused })} tabIndex='0' role='button' onClick={this.handleInputClick}>
|
||||
<textarea readOnly value={value} ref={this.setRef} onClick={this.handleInputClick} onFocus={this.handleFocus} onBlur={this.handleBlur} />
|
||||
|
||||
<button className='button' onClick={this.handleButtonClick}>
|
||||
<Icon id='copy' icon={ContentCopyIcon} /> {copied ? <FormattedMessage id='copypaste.copied' defaultMessage='Copied' /> : <FormattedMessage id='copypaste.copy_to_clipboard' defaultMessage='Copy to clipboard' />}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TipCarousel extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
|
87
app/javascript/mastodon/features/standalone/status/index.tsx
Normal file
87
app/javascript/mastodon/features/standalone/status/index.tsx
Normal file
|
@ -0,0 +1,87 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-return,
|
||||
@typescript-eslint/no-explicit-any,
|
||||
@typescript-eslint/no-unsafe-assignment */
|
||||
|
||||
import { useEffect, useCallback } from 'react';
|
||||
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { useRenderSignal } from 'mastodon/../hooks/useRenderSignal';
|
||||
import { fetchStatus, toggleStatusSpoilers } from 'mastodon/actions/statuses';
|
||||
import { hydrateStore } from 'mastodon/actions/store';
|
||||
import { Router } from 'mastodon/components/router';
|
||||
import { DetailedStatus } from 'mastodon/features/status/components/detailed_status';
|
||||
import initialState from 'mastodon/initial_state';
|
||||
import { IntlProvider } from 'mastodon/locales';
|
||||
import { makeGetStatus, makeGetPictureInPicture } from 'mastodon/selectors';
|
||||
import { store, useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
const getStatus = makeGetStatus() as unknown as (arg0: any, arg1: any) => any;
|
||||
const getPictureInPicture = makeGetPictureInPicture() as unknown as (
|
||||
arg0: any,
|
||||
arg1: any,
|
||||
) => any;
|
||||
|
||||
const Embed: React.FC<{ id: string }> = ({ id }) => {
|
||||
const status = useAppSelector((state) => getStatus(state, { id }));
|
||||
const pictureInPicture = useAppSelector((state) =>
|
||||
getPictureInPicture(state, { id }),
|
||||
);
|
||||
const domain = useAppSelector((state) => state.meta.get('domain'));
|
||||
const dispatch = useAppDispatch();
|
||||
const dispatchRenderSignal = useRenderSignal();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchStatus(id, false, false));
|
||||
}, [dispatch, id]);
|
||||
|
||||
const handleToggleHidden = useCallback(() => {
|
||||
dispatch(toggleStatusSpoilers(id));
|
||||
}, [dispatch, id]);
|
||||
|
||||
// This allows us to calculate the correct page height for embeds
|
||||
if (status) {
|
||||
dispatchRenderSignal();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
||||
const permalink = status?.get('url') as string;
|
||||
|
||||
return (
|
||||
<div className='embed'>
|
||||
<DetailedStatus
|
||||
status={status}
|
||||
domain={domain}
|
||||
pictureInPicture={pictureInPicture}
|
||||
onToggleHidden={handleToggleHidden}
|
||||
withLogo
|
||||
/>
|
||||
|
||||
<a
|
||||
className='embed__overlay'
|
||||
href={permalink}
|
||||
target='_blank'
|
||||
rel='noreferrer noopener'
|
||||
aria-label=''
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Status: React.FC<{ id: string }> = ({ id }) => {
|
||||
useEffect(() => {
|
||||
if (initialState) {
|
||||
store.dispatch(hydrateStore(initialState));
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<IntlProvider>
|
||||
<Provider store={store}>
|
||||
<Router>
|
||||
<Embed id={id} />
|
||||
</Router>
|
||||
</Provider>
|
||||
</IntlProvider>
|
||||
);
|
||||
};
|
|
@ -58,7 +58,7 @@ const messages = defineMessages({
|
|||
share: { id: 'status.share', defaultMessage: 'Share' },
|
||||
pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
|
||||
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
|
||||
embed: { id: 'status.embed', defaultMessage: 'Embed' },
|
||||
embed: { id: 'status.embed', defaultMessage: 'Get embed code' },
|
||||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
||||
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this post in the moderation interface' },
|
||||
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
|
||||
|
|
|
@ -1,391 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { FormattedDate, FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||
import { AnimatedNumber } from 'mastodon/components/animated_number';
|
||||
import { ContentWarning } from 'mastodon/components/content_warning';
|
||||
import EditedTimestamp from 'mastodon/components/edited_timestamp';
|
||||
import { getHashtagBarForStatus } from 'mastodon/components/hashtag_bar';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
|
||||
import { SearchabilityIcon } from 'mastodon/components/searchability_icon';
|
||||
import { VisibilityIcon } from 'mastodon/components/visibility_icon';
|
||||
import { enableEmojiReaction, isHideItem } from 'mastodon/initial_state';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
import { Avatar } from '../../../components/avatar';
|
||||
import { DisplayName } from '../../../components/display_name';
|
||||
import MediaGallery from '../../../components/media_gallery';
|
||||
import StatusContent from '../../../components/status_content';
|
||||
import StatusEmojiReactionsBar from '../../../components/status_emoji_reactions_bar';
|
||||
import CompactedStatusContainer from '../../../containers/compacted_status_container';
|
||||
import Audio from '../../audio';
|
||||
import scheduleIdleTask from '../../ui/util/schedule_idle_task';
|
||||
import Video from '../../video';
|
||||
|
||||
import Card from './card';
|
||||
|
||||
class DetailedStatus extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map,
|
||||
onOpenMedia: PropTypes.func.isRequired,
|
||||
onOpenVideo: PropTypes.func.isRequired,
|
||||
onToggleHidden: PropTypes.func.isRequired,
|
||||
onTranslate: PropTypes.func.isRequired,
|
||||
measureHeight: PropTypes.bool,
|
||||
onHeightChange: PropTypes.func,
|
||||
domain: PropTypes.string.isRequired,
|
||||
compact: PropTypes.bool,
|
||||
showMedia: PropTypes.bool,
|
||||
pictureInPicture: ImmutablePropTypes.contains({
|
||||
inUse: PropTypes.bool,
|
||||
available: PropTypes.bool,
|
||||
}),
|
||||
onToggleMediaVisibility: PropTypes.func,
|
||||
onEmojiReact: PropTypes.func,
|
||||
onUnEmojiReact: PropTypes.func,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
state = {
|
||||
height: null,
|
||||
};
|
||||
|
||||
handleAccountClick = (e) => {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey) && this.props.history) {
|
||||
e.preventDefault();
|
||||
this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
handleOpenVideo = (options) => {
|
||||
this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), options);
|
||||
};
|
||||
|
||||
handleExpandedToggle = () => {
|
||||
this.props.onToggleHidden(this.props.status);
|
||||
};
|
||||
|
||||
_measureHeight (heightJustChanged) {
|
||||
if (this.props.measureHeight && this.node) {
|
||||
scheduleIdleTask(() => this.node && this.setState({ height: Math.ceil(this.node.scrollHeight) + 1 }));
|
||||
|
||||
if (this.props.onHeightChange && heightJustChanged) {
|
||||
this.props.onHeightChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.node = c;
|
||||
this._measureHeight();
|
||||
};
|
||||
|
||||
componentDidUpdate (prevProps, prevState) {
|
||||
this._measureHeight(prevState.height !== this.state.height);
|
||||
}
|
||||
|
||||
handleModalLink = e => {
|
||||
e.preventDefault();
|
||||
|
||||
let href;
|
||||
|
||||
if (e.target.nodeName !== 'A') {
|
||||
href = e.target.parentNode.href;
|
||||
} else {
|
||||
href = e.target.href;
|
||||
}
|
||||
|
||||
window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
|
||||
};
|
||||
|
||||
handleTranslate = () => {
|
||||
const { onTranslate, status } = this.props;
|
||||
onTranslate(status);
|
||||
};
|
||||
|
||||
_properStatus () {
|
||||
const { status } = this.props;
|
||||
|
||||
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||
return status.get('reblog');
|
||||
} else {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
getAttachmentAspectRatio () {
|
||||
const attachments = this._properStatus().get('media_attachments');
|
||||
|
||||
if (attachments.getIn([0, 'type']) === 'video') {
|
||||
return `${attachments.getIn([0, 'meta', 'original', 'width'])} / ${attachments.getIn([0, 'meta', 'original', 'height'])}`;
|
||||
} else if (attachments.getIn([0, 'type']) === 'audio') {
|
||||
return '16 / 9';
|
||||
} else {
|
||||
return (attachments.size === 1 && attachments.getIn([0, 'meta', 'small', 'aspect'])) ? attachments.getIn([0, 'meta', 'small', 'aspect']) : '3 / 2';
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const status = this._properStatus();
|
||||
const outerStyle = { boxSizing: 'border-box' };
|
||||
const { compact, pictureInPicture } = this.props;
|
||||
|
||||
if (!status) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let media = '';
|
||||
let applicationLink = '';
|
||||
let reblogLink = '';
|
||||
let favouriteLink = '';
|
||||
let emojiReactionsLink = '';
|
||||
let statusReferencesLink = '';
|
||||
|
||||
if (this.props.measureHeight) {
|
||||
outerStyle.height = `${this.state.height}px`;
|
||||
}
|
||||
|
||||
const language = status.getIn(['translation', 'language']) || status.get('language');
|
||||
|
||||
if (pictureInPicture.get('inUse')) {
|
||||
media = <PictureInPicturePlaceholder aspectRatio={this.getAttachmentAspectRatio()} />;
|
||||
} else if (status.get('media_attachments').size > 0) {
|
||||
if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
|
||||
const attachment = status.getIn(['media_attachments', 0]);
|
||||
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
|
||||
|
||||
media = (
|
||||
<Audio
|
||||
src={attachment.get('url')}
|
||||
alt={description}
|
||||
lang={language}
|
||||
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
|
||||
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
||||
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
|
||||
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
|
||||
accentColor={attachment.getIn(['meta', 'colors', 'accent'])}
|
||||
sensitive={status.get('sensitive')}
|
||||
visible={this.props.showMedia}
|
||||
blurhash={attachment.get('blurhash')}
|
||||
height={150}
|
||||
onToggleVisibility={this.props.onToggleMediaVisibility}
|
||||
/>
|
||||
);
|
||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
const attachment = status.getIn(['media_attachments', 0]);
|
||||
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
|
||||
|
||||
media = (
|
||||
<Video
|
||||
preview={attachment.get('preview_url')}
|
||||
frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
|
||||
aspectRatio={`${attachment.getIn(['meta', 'original', 'width'])} / ${attachment.getIn(['meta', 'original', 'height'])}`}
|
||||
blurhash={attachment.get('blurhash')}
|
||||
src={attachment.get('url')}
|
||||
alt={description}
|
||||
lang={language}
|
||||
width={300}
|
||||
height={150}
|
||||
onOpenVideo={this.handleOpenVideo}
|
||||
sensitive={status.get('sensitive')}
|
||||
visible={this.props.showMedia}
|
||||
onToggleVisibility={this.props.onToggleMediaVisibility}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
media = (
|
||||
<MediaGallery
|
||||
standalone
|
||||
sensitive={status.get('sensitive')}
|
||||
media={status.get('media_attachments')}
|
||||
lang={language}
|
||||
height={300}
|
||||
onOpenMedia={this.props.onOpenMedia}
|
||||
visible={this.props.showMedia}
|
||||
onToggleVisibility={this.props.onToggleMediaVisibility}
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else if (status.get('card')) {
|
||||
media = <Card sensitive={status.get('sensitive') && !status.get('spoiler_text')} onOpenMedia={this.props.onOpenMedia} card={status.get('card', null)} />;
|
||||
}
|
||||
|
||||
let emojiReactionsBar = null;
|
||||
const emojiReactionAvailableServer = !isHideItem('emoji_reaction_unavailable_server') || status.getIn(['account', 'server_features', 'emoji_reaction']);
|
||||
if (status.get('emoji_reactions')) {
|
||||
const emojiReactions = status.get('emoji_reactions');
|
||||
if (emojiReactions.size > 0 && enableEmojiReaction && emojiReactionAvailableServer) {
|
||||
emojiReactionsBar = <StatusEmojiReactionsBar emojiReactions={emojiReactions} status={status} onEmojiReact={this.props.onEmojiReact} onUnEmojiReact={this.props.onUnEmojiReact} />;
|
||||
}
|
||||
}
|
||||
|
||||
if (status.get('application')) {
|
||||
applicationLink = <>·<a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener noreferrer'>{status.getIn(['application', 'name'])}</a></>;
|
||||
}
|
||||
|
||||
const visibilityLink = <>·<VisibilityIcon visibility={status.get('limited_scope') || status.get('visibility_ex')} /></>;
|
||||
const searchabilityLink = <>·<SearchabilityIcon searchability={status.get('searchability')} /></>;
|
||||
|
||||
if (['private', 'direct'].includes(status.get('visibility_ex'))) {
|
||||
reblogLink = '';
|
||||
} else if (this.props.history) {
|
||||
reblogLink = (
|
||||
<Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/reblogs`} className='detailed-status__link'>
|
||||
<span className='detailed-status__reblogs'>
|
||||
<AnimatedNumber value={status.get('reblogs_count')} />
|
||||
</span>
|
||||
<FormattedMessage id='status.reblogs' defaultMessage='{count, plural, one {boost} other {boosts}}' values={{ count: status.get('reblogs_count') }} />
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
reblogLink = (
|
||||
<a href={`/interact/${status.get('id')}?type=reblog`} className='detailed-status__link' onClick={this.handleModalLink}>
|
||||
<span className='detailed-status__reblogs'>
|
||||
<AnimatedNumber value={status.get('reblogs_count')} />
|
||||
</span>
|
||||
<FormattedMessage id='status.reblogs' defaultMessage='{count, plural, one {boost} other {boosts}}' values={{ count: status.get('reblogs_count') }} />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.history) {
|
||||
favouriteLink = (
|
||||
<Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/favourites`} className='detailed-status__link'>
|
||||
<span className='detailed-status__favorites'>
|
||||
<AnimatedNumber value={status.get('favourites_count')} />
|
||||
</span>
|
||||
<FormattedMessage id='status.favourites' defaultMessage='{count, plural, one {favorite} other {favorites}}' values={{ count: status.get('favourites_count') }} />
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
favouriteLink = (
|
||||
<a href={`/interact/${status.get('id')}?type=favourite`} className='detailed-status__link' onClick={this.handleModalLink}>
|
||||
<span className='detailed-status__favorites'>
|
||||
<AnimatedNumber value={status.get('favourites_count')} />
|
||||
</span>
|
||||
<FormattedMessage id='status.favourites' defaultMessage='{count, plural, one {favorite} other {favorites}}' values={{ count: status.get('favourites_count') }} />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
if (!enableEmojiReaction || !emojiReactionAvailableServer) {
|
||||
emojiReactionsLink = null;
|
||||
} else if (this.props.history) {
|
||||
emojiReactionsLink = (
|
||||
<Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/emoji_reactions`} className='detailed-status__link'>
|
||||
<span className='detailed-status__favorites'>
|
||||
<AnimatedNumber value={status.get('emoji_reactions_count')} />
|
||||
</span>
|
||||
<FormattedMessage id='status.emoji_reactions' defaultMessage='{count, plural, one {emoji} other {emojis}}' values={{ count: status.get('emoji_reactions_count') }} />
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
emojiReactionsLink = (
|
||||
<a href={`/interact/${status.get('id')}?type=emoji_reactions`} className='detailed-status__link' onClick={this.handleModalLink}>
|
||||
<span className='detailed-status__favorites'>
|
||||
<AnimatedNumber value={status.get('emoji_reactions_count')} />
|
||||
</span>
|
||||
<FormattedMessage id='status.emoji_reactions' defaultMessage='{count, plural, one {emoji} other {emojis}}' values={{ count: status.get('emoji_reactions_count') }} />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.history) {
|
||||
statusReferencesLink = (
|
||||
<Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/references`} className='detailed-status__link'>
|
||||
<span className='detailed-status__favorites'>
|
||||
<AnimatedNumber value={status.get('status_referred_by_count')} />
|
||||
</span>
|
||||
<FormattedMessage id='status.quotes' defaultMessage='{count, plural, one {quote} other {quotes}}' values={{ count: status.get('status_referred_by_count') }} />
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
statusReferencesLink = (
|
||||
<a href={`/interact/${status.get('id')}?type=references`} className='detailed-status__link' onClick={this.handleModalLink}>
|
||||
<span className='detailed-status__favorites'>
|
||||
<AnimatedNumber value={status.get('status_referred_by_count')} />
|
||||
</span>
|
||||
<FormattedMessage id='status.quotes' defaultMessage='{count, plural, one {quote} other {quotes}}' values={{ count: status.get('status_referred_by_count') }} />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
|
||||
const expanded = !status.get('hidden') || status.get('spoiler_text').length === 0;
|
||||
|
||||
const quote = !this.props.muted && status.get('quote_id') && <CompactedStatusContainer id={status.get('quote_id')} history={this.props.history} />;
|
||||
|
||||
return (
|
||||
<div style={outerStyle}>
|
||||
<div ref={this.setRef} className={classNames('detailed-status', { compact })}>
|
||||
{status.get('visibility_ex') === 'direct' && (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend-icon-wrapper'><Icon id='at' icon={AlternateEmailIcon} className='status__prepend-icon' /></div>
|
||||
<FormattedMessage id='status.direct_indicator' defaultMessage='Private mention' />
|
||||
</div>
|
||||
)}
|
||||
<a href={`/@${status.getIn(['account', 'acct'])}`} data-hover-card-account={status.getIn(['account', 'id'])} onClick={this.handleAccountClick} className='detailed-status__display-name'>
|
||||
<div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={46} /></div>
|
||||
<DisplayName account={status.get('account')} localDomain={this.props.domain} />
|
||||
</a>
|
||||
|
||||
{status.get('spoiler_text').length > 0 && <ContentWarning text={status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml')} expanded={expanded} onClick={this.handleExpandedToggle} />}
|
||||
|
||||
{expanded && (
|
||||
<>
|
||||
<StatusContent
|
||||
status={status}
|
||||
onTranslate={this.handleTranslate}
|
||||
{...statusContentProps}
|
||||
/>
|
||||
|
||||
{media}
|
||||
{hashtagBar}
|
||||
{quote}
|
||||
{emojiReactionsBar}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className='detailed-status__meta'>
|
||||
<div className='detailed-status__meta__line'>
|
||||
<a className='detailed-status__datetime' href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} target='_blank' rel='noopener noreferrer'>
|
||||
<FormattedDate value={new Date(status.get('created_at'))} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
|
||||
</a>
|
||||
|
||||
{visibilityLink}
|
||||
{searchabilityLink}
|
||||
|
||||
{applicationLink}
|
||||
</div>
|
||||
|
||||
{status.get('edited_at') && <div className='detailed-status__meta__line'><EditedTimestamp statusId={status.get('id')} timestamp={status.get('edited_at')} /></div>}
|
||||
|
||||
<div className='detailed-status__meta__line'>
|
||||
{reblogLink}
|
||||
{reblogLink && <>·</>}
|
||||
{favouriteLink}
|
||||
·
|
||||
{emojiReactionsLink}
|
||||
·
|
||||
{statusReferencesLink}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withRouter(DetailedStatus);
|
|
@ -0,0 +1,477 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-member-access,
|
||||
@typescript-eslint/no-unsafe-call,
|
||||
@typescript-eslint/no-explicit-any,
|
||||
@typescript-eslint/no-unsafe-assignment */
|
||||
|
||||
import type { CSSProperties } from 'react';
|
||||
import { useState, useRef, useCallback } from 'react';
|
||||
|
||||
import { FormattedDate, FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||
import { AnimatedNumber } from 'mastodon/components/animated_number';
|
||||
import { ContentWarning } from 'mastodon/components/content_warning';
|
||||
import EditedTimestamp from 'mastodon/components/edited_timestamp';
|
||||
import type { StatusLike } from 'mastodon/components/hashtag_bar';
|
||||
import { getHashtagBarForStatus } from 'mastodon/components/hashtag_bar';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { IconLogo } from 'mastodon/components/logo';
|
||||
import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
|
||||
import { SearchabilityIcon } from 'mastodon/components/searchability_icon';
|
||||
import { VisibilityIcon } from 'mastodon/components/visibility_icon';
|
||||
import { enableEmojiReaction, isHideItem } from 'mastodon/initial_state';
|
||||
|
||||
import { Avatar } from '../../../components/avatar';
|
||||
import { DisplayName } from '../../../components/display_name';
|
||||
import MediaGallery from '../../../components/media_gallery';
|
||||
import StatusContent from '../../../components/status_content';
|
||||
import StatusEmojiReactionsBar from '../../../components/status_emoji_reactions_bar';
|
||||
import CompactedStatusContainer from '../../../containers/compacted_status_container';
|
||||
import Audio from '../../audio';
|
||||
import scheduleIdleTask from '../../ui/util/schedule_idle_task';
|
||||
import Video from '../../video';
|
||||
|
||||
import Card from './card';
|
||||
|
||||
interface VideoModalOptions {
|
||||
startTime: number;
|
||||
autoPlay?: boolean;
|
||||
defaultVolume: number;
|
||||
componentIndex: number;
|
||||
}
|
||||
|
||||
export const DetailedStatus: React.FC<{
|
||||
status: any;
|
||||
onOpenMedia?: (status: any, index: number, lang: string) => void;
|
||||
onOpenVideo?: (status: any, lang: string, options: VideoModalOptions) => void;
|
||||
onTranslate?: (status: any) => void;
|
||||
measureHeight?: boolean;
|
||||
onHeightChange?: () => void;
|
||||
domain: string;
|
||||
showMedia?: boolean;
|
||||
withLogo?: boolean;
|
||||
pictureInPicture: any;
|
||||
onToggleHidden?: (status: any) => void;
|
||||
onToggleMediaVisibility?: () => void;
|
||||
onEmojiReact?: (status: any, name: string) => void;
|
||||
onUnEmojiReact?: (status: any, name: string) => void;
|
||||
muted?: boolean;
|
||||
}> = ({
|
||||
status,
|
||||
onOpenMedia,
|
||||
onOpenVideo,
|
||||
onTranslate,
|
||||
measureHeight,
|
||||
onHeightChange,
|
||||
domain,
|
||||
showMedia,
|
||||
withLogo,
|
||||
pictureInPicture,
|
||||
onToggleMediaVisibility,
|
||||
onToggleHidden,
|
||||
onEmojiReact,
|
||||
onUnEmojiReact,
|
||||
muted,
|
||||
}) => {
|
||||
const properStatus = status?.get('reblog') ?? status;
|
||||
const [height, setHeight] = useState(0);
|
||||
const nodeRef = useRef<HTMLDivElement>();
|
||||
|
||||
const handleOpenVideo = useCallback(
|
||||
(options: VideoModalOptions) => {
|
||||
const lang = (status.getIn(['translation', 'language']) ||
|
||||
status.get('language')) as string;
|
||||
if (onOpenVideo)
|
||||
onOpenVideo(status.getIn(['media_attachments', 0]), lang, options);
|
||||
},
|
||||
[onOpenVideo, status],
|
||||
);
|
||||
|
||||
const handleExpandedToggle = useCallback(() => {
|
||||
if (onToggleHidden) onToggleHidden(status);
|
||||
}, [onToggleHidden, status]);
|
||||
|
||||
const _measureHeight = useCallback(
|
||||
(heightJustChanged?: boolean) => {
|
||||
if (measureHeight && nodeRef.current) {
|
||||
scheduleIdleTask(() => {
|
||||
if (nodeRef.current)
|
||||
setHeight(Math.ceil(nodeRef.current.scrollHeight) + 1);
|
||||
});
|
||||
|
||||
if (onHeightChange && heightJustChanged) {
|
||||
onHeightChange();
|
||||
}
|
||||
}
|
||||
},
|
||||
[onHeightChange, measureHeight, setHeight],
|
||||
);
|
||||
|
||||
const handleRef = useCallback(
|
||||
(c: HTMLDivElement) => {
|
||||
nodeRef.current = c;
|
||||
_measureHeight();
|
||||
},
|
||||
[_measureHeight],
|
||||
);
|
||||
|
||||
const handleTranslate = useCallback(() => {
|
||||
if (onTranslate) onTranslate(status);
|
||||
}, [onTranslate, status]);
|
||||
|
||||
if (!properStatus) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let media;
|
||||
let applicationLink;
|
||||
let reblogLink;
|
||||
let attachmentAspectRatio;
|
||||
let emojiReactionsLink;
|
||||
|
||||
if (properStatus.get('media_attachments').getIn([0, 'type']) === 'video') {
|
||||
attachmentAspectRatio = `${properStatus.get('media_attachments').getIn([0, 'meta', 'original', 'width'])} / ${properStatus.get('media_attachments').getIn([0, 'meta', 'original', 'height'])}`;
|
||||
} else if (
|
||||
properStatus.get('media_attachments').getIn([0, 'type']) === 'audio'
|
||||
) {
|
||||
attachmentAspectRatio = '16 / 9';
|
||||
} else {
|
||||
attachmentAspectRatio =
|
||||
properStatus.get('media_attachments').size === 1 &&
|
||||
properStatus
|
||||
.get('media_attachments')
|
||||
.getIn([0, 'meta', 'small', 'aspect'])
|
||||
? properStatus
|
||||
.get('media_attachments')
|
||||
.getIn([0, 'meta', 'small', 'aspect'])
|
||||
: '3 / 2';
|
||||
}
|
||||
|
||||
const outerStyle = { boxSizing: 'border-box' } as CSSProperties;
|
||||
|
||||
if (measureHeight) {
|
||||
outerStyle.height = height;
|
||||
}
|
||||
|
||||
const language =
|
||||
status.getIn(['translation', 'language']) || status.get('language');
|
||||
|
||||
if (pictureInPicture.get('inUse')) {
|
||||
media = <PictureInPicturePlaceholder aspectRatio={attachmentAspectRatio} />;
|
||||
} else if (status.get('media_attachments').size > 0) {
|
||||
if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
|
||||
const attachment = status.getIn(['media_attachments', 0]);
|
||||
const description =
|
||||
attachment.getIn(['translation', 'description']) ||
|
||||
attachment.get('description');
|
||||
|
||||
media = (
|
||||
<Audio
|
||||
src={attachment.get('url')}
|
||||
alt={description}
|
||||
lang={language}
|
||||
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
|
||||
poster={
|
||||
attachment.get('preview_url') ||
|
||||
status.getIn(['account', 'avatar_static'])
|
||||
}
|
||||
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
|
||||
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
|
||||
accentColor={attachment.getIn(['meta', 'colors', 'accent'])}
|
||||
sensitive={status.get('sensitive')}
|
||||
visible={showMedia}
|
||||
blurhash={attachment.get('blurhash')}
|
||||
height={150}
|
||||
onToggleVisibility={onToggleMediaVisibility}
|
||||
/>
|
||||
);
|
||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
const attachment = status.getIn(['media_attachments', 0]);
|
||||
const description =
|
||||
attachment.getIn(['translation', 'description']) ||
|
||||
attachment.get('description');
|
||||
|
||||
media = (
|
||||
<Video
|
||||
preview={attachment.get('preview_url')}
|
||||
frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
|
||||
aspectRatio={`${attachment.getIn(['meta', 'original', 'width'])} / ${attachment.getIn(['meta', 'original', 'height'])}`}
|
||||
blurhash={attachment.get('blurhash')}
|
||||
src={attachment.get('url')}
|
||||
alt={description}
|
||||
lang={language}
|
||||
width={300}
|
||||
height={150}
|
||||
onOpenVideo={handleOpenVideo}
|
||||
sensitive={status.get('sensitive')}
|
||||
visible={showMedia}
|
||||
onToggleVisibility={onToggleMediaVisibility}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
media = (
|
||||
<MediaGallery
|
||||
standalone
|
||||
sensitive={status.get('sensitive')}
|
||||
media={status.get('media_attachments')}
|
||||
lang={language}
|
||||
height={300}
|
||||
onOpenMedia={onOpenMedia}
|
||||
visible={showMedia}
|
||||
onToggleVisibility={onToggleMediaVisibility}
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else if (status.get('card')) {
|
||||
media = (
|
||||
<Card
|
||||
sensitive={status.get('sensitive') && !status.get('spoiler_text')}
|
||||
onOpenMedia={onOpenMedia}
|
||||
card={status.get('card', null)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let emojiReactionsBar = null;
|
||||
const emojiReactionAvailableServer =
|
||||
!isHideItem('emoji_reaction_unavailable_server') ||
|
||||
status.getIn(['account', 'server_features', 'emoji_reaction']);
|
||||
if (status.get('emoji_reactions')) {
|
||||
const emojiReactions = status.get('emoji_reactions');
|
||||
if (
|
||||
emojiReactions.size > 0 &&
|
||||
enableEmojiReaction &&
|
||||
emojiReactionAvailableServer
|
||||
) {
|
||||
emojiReactionsBar = (
|
||||
<StatusEmojiReactionsBar
|
||||
emojiReactions={emojiReactions}
|
||||
status={status}
|
||||
onEmojiReact={onEmojiReact}
|
||||
onUnEmojiReact={onUnEmojiReact}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (status.get('application')) {
|
||||
applicationLink = (
|
||||
<>
|
||||
·
|
||||
<a
|
||||
className='detailed-status__application'
|
||||
href={status.getIn(['application', 'website'])}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
{status.getIn(['application', 'name'])}
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const visibilityLink = (
|
||||
<>
|
||||
·
|
||||
<VisibilityIcon
|
||||
visibility={status.get('limited_scope') || status.get('visibility_ex')}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
const searchabilityLink = (
|
||||
<>
|
||||
·<SearchabilityIcon searchability={status.get('searchability')} />
|
||||
</>
|
||||
);
|
||||
|
||||
if (!enableEmojiReaction || !emojiReactionAvailableServer) {
|
||||
emojiReactionsLink = '';
|
||||
} else {
|
||||
emojiReactionsLink = (
|
||||
<Link
|
||||
to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/emoji_reactions`}
|
||||
className='detailed-status__link'
|
||||
>
|
||||
<span className='detailed-status__favorites'>
|
||||
<AnimatedNumber value={status.get('emoji_reactions_count')} />
|
||||
</span>
|
||||
<FormattedMessage
|
||||
id='status.emoji_reactions'
|
||||
defaultMessage='{count, plural, one {favorite} other {favorites}}'
|
||||
values={{ count: status.get('emoji_reactions_count') }}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
const statusReferencesLink = (
|
||||
<Link
|
||||
to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/references`}
|
||||
className='detailed-status__link'
|
||||
>
|
||||
<span className='detailed-status__reblogs'>
|
||||
<AnimatedNumber value={status.get('status_referred_by_count')} />
|
||||
</span>
|
||||
<FormattedMessage
|
||||
id='status.quotes'
|
||||
defaultMessage='{count, plural, one {boost} other {boosts}}'
|
||||
values={{ count: status.get('status_referred_by_count') }}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
|
||||
if (['private', 'direct'].includes(status.get('visibility') as string)) {
|
||||
reblogLink = '';
|
||||
} else {
|
||||
reblogLink = (
|
||||
<Link
|
||||
to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/reblogs`}
|
||||
className='detailed-status__link'
|
||||
>
|
||||
<span className='detailed-status__reblogs'>
|
||||
<AnimatedNumber value={status.get('reblogs_count')} />
|
||||
</span>
|
||||
<FormattedMessage
|
||||
id='status.reblogs'
|
||||
defaultMessage='{count, plural, one {boost} other {boosts}}'
|
||||
values={{ count: status.get('reblogs_count') }}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
const favouriteLink = (
|
||||
<Link
|
||||
to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/favourites`}
|
||||
className='detailed-status__link'
|
||||
>
|
||||
<span className='detailed-status__favorites'>
|
||||
<AnimatedNumber value={status.get('favourites_count')} />
|
||||
</span>
|
||||
<FormattedMessage
|
||||
id='status.favourites'
|
||||
defaultMessage='{count, plural, one {favorite} other {favorites}}'
|
||||
values={{ count: status.get('favourites_count') }}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
|
||||
const { statusContentProps, hashtagBar } = getHashtagBarForStatus(
|
||||
status as StatusLike,
|
||||
);
|
||||
const expanded =
|
||||
!status.get('hidden') || status.get('spoiler_text').length === 0;
|
||||
|
||||
const quote = !muted && status.get('quote_id') && (
|
||||
<>
|
||||
{/* @ts-expect-error: CompactedStatusContainer class is not typescript still. */}
|
||||
<CompactedStatusContainer id={status.get('quote_id')} history={history} />
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={outerStyle}>
|
||||
<div ref={handleRef} className={classNames('detailed-status')}>
|
||||
{status.get('visibility_ex') === 'direct' && (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend-icon-wrapper'>
|
||||
<Icon
|
||||
id='at'
|
||||
icon={AlternateEmailIcon}
|
||||
className='status__prepend-icon'
|
||||
/>
|
||||
</div>
|
||||
<FormattedMessage
|
||||
id='status.direct_indicator'
|
||||
defaultMessage='Private mention'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Link
|
||||
to={`/@${status.getIn(['account', 'acct'])}`}
|
||||
data-hover-card-account={status.getIn(['account', 'id'])}
|
||||
className='detailed-status__display-name'
|
||||
>
|
||||
<div className='detailed-status__display-avatar'>
|
||||
<Avatar account={status.get('account')} size={46} />
|
||||
</div>
|
||||
<DisplayName account={status.get('account')} localDomain={domain} />
|
||||
{withLogo && (
|
||||
<>
|
||||
<div className='spacer' />
|
||||
<IconLogo />
|
||||
</>
|
||||
)}
|
||||
</Link>
|
||||
|
||||
{status.get('spoiler_text').length > 0 && (
|
||||
<ContentWarning
|
||||
text={
|
||||
status.getIn(['translation', 'spoilerHtml']) ||
|
||||
status.get('spoilerHtml')
|
||||
}
|
||||
expanded={expanded}
|
||||
onClick={handleExpandedToggle}
|
||||
/>
|
||||
)}
|
||||
|
||||
{expanded && (
|
||||
<>
|
||||
<StatusContent
|
||||
status={status}
|
||||
onTranslate={handleTranslate}
|
||||
{...(statusContentProps as any)}
|
||||
/>
|
||||
|
||||
{media}
|
||||
{hashtagBar}
|
||||
{quote}
|
||||
{emojiReactionsBar}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className='detailed-status__meta'>
|
||||
<div className='detailed-status__meta__line'>
|
||||
<a
|
||||
className='detailed-status__datetime'
|
||||
href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
<FormattedDate
|
||||
value={new Date(status.get('created_at') as string)}
|
||||
year='numeric'
|
||||
month='short'
|
||||
day='2-digit'
|
||||
hour='2-digit'
|
||||
minute='2-digit'
|
||||
/>
|
||||
</a>
|
||||
|
||||
{visibilityLink}
|
||||
{searchabilityLink}
|
||||
{applicationLink}
|
||||
</div>
|
||||
|
||||
{status.get('edited_at') && (
|
||||
<div className='detailed-status__meta__line'>
|
||||
<EditedTimestamp
|
||||
statusId={status.get('id')}
|
||||
timestamp={status.get('edited_at')}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='detailed-status__meta__line'>
|
||||
{reblogLink}
|
||||
{reblogLink && <>·</>}
|
||||
{favouriteLink}·{emojiReactionsLink}·{statusReferencesLink}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,154 +0,0 @@
|
|||
import { injectIntl } from 'react-intl';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { showAlertForError } from '../../../actions/alerts';
|
||||
import { initBlockModal } from '../../../actions/blocks';
|
||||
import {
|
||||
replyCompose,
|
||||
mentionCompose,
|
||||
directCompose,
|
||||
} from '../../../actions/compose';
|
||||
import {
|
||||
toggleReblog,
|
||||
toggleFavourite,
|
||||
pin,
|
||||
unpin,
|
||||
emojiReact,
|
||||
unEmojiReact,
|
||||
} from '../../../actions/interactions';
|
||||
import { openModal } from '../../../actions/modal';
|
||||
import { initMuteModal } from '../../../actions/mutes';
|
||||
import { initReport } from '../../../actions/reports';
|
||||
import {
|
||||
muteStatus,
|
||||
unmuteStatus,
|
||||
deleteStatus,
|
||||
toggleStatusSpoilers,
|
||||
} from '../../../actions/statuses';
|
||||
import { deleteModal } from '../../../initial_state';
|
||||
import { makeGetStatus, makeGetPictureInPicture } from '../../../selectors';
|
||||
import DetailedStatus from '../components/detailed_status';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getStatus = makeGetStatus();
|
||||
const getPictureInPicture = makeGetPictureInPicture();
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
status: getStatus(state, props),
|
||||
domain: state.getIn(['meta', 'domain']),
|
||||
pictureInPicture: getPictureInPicture(state, props),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
onReply (status) {
|
||||
dispatch((_, getState) => {
|
||||
let state = getState();
|
||||
if (state.getIn(['compose', 'text']).trim().length !== 0) {
|
||||
dispatch(openModal({ modalType: 'CONFIRM_REPLY', modalProps: { status } }));
|
||||
} else {
|
||||
dispatch(replyCompose(status));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onReblog (status, e) {
|
||||
dispatch(toggleReblog(status.get('id'), e.shiftKey));
|
||||
},
|
||||
|
||||
onReblogForceModal (status) {
|
||||
dispatch(toggleReblog(status.get('id'), true, true));
|
||||
},
|
||||
|
||||
onFavourite (status) {
|
||||
dispatch(toggleFavourite(status.get('id')));
|
||||
},
|
||||
|
||||
onEmojiReact (status, emoji) {
|
||||
dispatch(emojiReact(status, emoji));
|
||||
},
|
||||
|
||||
onUnEmojiReact (status, emoji) {
|
||||
dispatch(unEmojiReact(status, emoji));
|
||||
},
|
||||
|
||||
onPin (status) {
|
||||
if (status.get('pinned')) {
|
||||
dispatch(unpin(status));
|
||||
} else {
|
||||
dispatch(pin(status));
|
||||
}
|
||||
},
|
||||
|
||||
onEmbed (status) {
|
||||
dispatch(openModal({
|
||||
modalType: 'EMBED',
|
||||
modalProps: {
|
||||
id: status.get('id'),
|
||||
onError: error => dispatch(showAlertForError(error)),
|
||||
},
|
||||
}));
|
||||
},
|
||||
|
||||
onDelete (status, withRedraft = false) {
|
||||
if (!deleteModal) {
|
||||
dispatch(deleteStatus(status.get('id'), withRedraft));
|
||||
} else {
|
||||
dispatch(openModal({ modalType: 'CONFIRM_DELETE_STATUS', modalProps: { statusId: status.get('id'), withRedraft } }));
|
||||
}
|
||||
},
|
||||
|
||||
onDirect (account) {
|
||||
dispatch(directCompose(account));
|
||||
},
|
||||
|
||||
onMention (account) {
|
||||
dispatch(mentionCompose(account));
|
||||
},
|
||||
|
||||
onOpenMedia (media, index, lang) {
|
||||
dispatch(openModal({
|
||||
modalType: 'MEDIA',
|
||||
modalProps: { media, index, lang },
|
||||
}));
|
||||
},
|
||||
|
||||
onOpenVideo (media, lang, options) {
|
||||
dispatch(openModal({
|
||||
modalType: 'VIDEO',
|
||||
modalProps: { media, lang, options },
|
||||
}));
|
||||
},
|
||||
|
||||
onBlock (status) {
|
||||
const account = status.get('account');
|
||||
dispatch(initBlockModal(account));
|
||||
},
|
||||
|
||||
onReport (status) {
|
||||
dispatch(initReport(status.get('account'), status));
|
||||
},
|
||||
|
||||
onMute (account) {
|
||||
dispatch(initMuteModal(account));
|
||||
},
|
||||
|
||||
onMuteConversation (status) {
|
||||
if (status.get('muted')) {
|
||||
dispatch(unmuteStatus(status.get('id')));
|
||||
} else {
|
||||
dispatch(muteStatus(status.get('id')));
|
||||
}
|
||||
},
|
||||
|
||||
onToggleHidden (status) {
|
||||
dispatch(toggleStatusSpoilers(status.get('id')));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(DetailedStatus));
|
|
@ -72,7 +72,7 @@ import Column from '../ui/components/column';
|
|||
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen';
|
||||
|
||||
import ActionBar from './components/action_bar';
|
||||
import DetailedStatus from './components/detailed_status';
|
||||
import { DetailedStatus } from './components/detailed_status';
|
||||
|
||||
|
||||
const messages = defineMessages({
|
||||
|
|
|
@ -99,7 +99,7 @@ export const BlockModal = ({ accountId, acct }) => {
|
|||
<FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
|
||||
</button>
|
||||
|
||||
<Button onClick={handleClick}>
|
||||
<Button onClick={handleClick} autoFocus>
|
||||
<FormattedMessage id='confirmations.block.confirm' defaultMessage='Block' />
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
@ -71,7 +71,10 @@ export const ConfirmationModal: React.FC<
|
|||
/>
|
||||
</button>
|
||||
|
||||
<Button onClick={handleClick}>{confirm}</Button>
|
||||
{/* eslint-disable-next-line jsx-a11y/no-autofocus -- we are in a modal and thus autofocusing is justified */}
|
||||
<Button onClick={handleClick} autoFocus>
|
||||
{confirm}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -88,7 +88,7 @@ export const DomainBlockModal = ({ domain, accountId, acct }) => {
|
|||
<FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
|
||||
</button>
|
||||
|
||||
<Button onClick={handleClick}>
|
||||
<Button onClick={handleClick} autoFocus>
|
||||
<FormattedMessage id='domain_block_modal.block' defaultMessage='Block server' />
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import api from 'mastodon/api';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
});
|
||||
|
||||
class EmbedModal extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onError: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
loading: false,
|
||||
oembed: null,
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
const { id } = this.props;
|
||||
|
||||
this.setState({ loading: true });
|
||||
|
||||
api().get(`/api/web/embeds/${id}`).then(res => {
|
||||
this.setState({ loading: false, oembed: res.data });
|
||||
|
||||
const iframeDocument = this.iframe.contentWindow.document;
|
||||
|
||||
iframeDocument.open();
|
||||
iframeDocument.write(res.data.html);
|
||||
iframeDocument.close();
|
||||
|
||||
iframeDocument.body.style.margin = 0;
|
||||
this.iframe.width = iframeDocument.body.scrollWidth;
|
||||
this.iframe.height = iframeDocument.body.scrollHeight;
|
||||
}).catch(error => {
|
||||
this.props.onError(error);
|
||||
});
|
||||
}
|
||||
|
||||
setIframeRef = c => {
|
||||
this.iframe = c;
|
||||
};
|
||||
|
||||
handleTextareaClick = (e) => {
|
||||
e.target.select();
|
||||
};
|
||||
|
||||
render () {
|
||||
const { intl, onClose } = this.props;
|
||||
const { oembed } = this.state;
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal report-modal embed-modal'>
|
||||
<div className='report-modal__target'>
|
||||
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' iconComponent={CloseIcon} onClick={onClose} size={16} />
|
||||
<FormattedMessage id='status.embed' defaultMessage='Embed' />
|
||||
</div>
|
||||
|
||||
<div className='report-modal__container embed-modal__container' style={{ display: 'block' }}>
|
||||
<p className='hint'>
|
||||
<FormattedMessage id='embed.instructions' defaultMessage='Embed this status on your website by copying the code below.' />
|
||||
</p>
|
||||
|
||||
<input
|
||||
type='text'
|
||||
className='embed-modal__html'
|
||||
readOnly
|
||||
value={oembed && oembed.html || ''}
|
||||
onClick={this.handleTextareaClick}
|
||||
/>
|
||||
|
||||
<p className='hint'>
|
||||
<FormattedMessage id='embed.preview' defaultMessage='Here is what it will look like:' />
|
||||
</p>
|
||||
|
||||
<iframe
|
||||
className='embed-modal__iframe'
|
||||
frameBorder='0'
|
||||
ref={this.setIframeRef}
|
||||
sandbox='allow-scripts allow-same-origin'
|
||||
title='preview'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(EmbedModal);
|
116
app/javascript/mastodon/features/ui/components/embed_modal.tsx
Normal file
116
app/javascript/mastodon/features/ui/components/embed_modal.tsx
Normal file
|
@ -0,0 +1,116 @@
|
|||
import { useRef, useState, useEffect } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { showAlertForError } from 'mastodon/actions/alerts';
|
||||
import api from 'mastodon/api';
|
||||
import { Button } from 'mastodon/components/button';
|
||||
import { CopyPasteText } from 'mastodon/components/copy_paste_text';
|
||||
import { useAppDispatch } from 'mastodon/store';
|
||||
|
||||
interface OEmbedResponse {
|
||||
html: string;
|
||||
}
|
||||
|
||||
const EmbedModal: React.FC<{
|
||||
id: string;
|
||||
onClose: () => void;
|
||||
}> = ({ id, onClose }) => {
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||
const intervalRef = useRef<ReturnType<typeof setInterval>>();
|
||||
const [oembed, setOembed] = useState<OEmbedResponse | null>(null);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
api()
|
||||
.get(`/api/web/embeds/${id}`)
|
||||
.then((res) => {
|
||||
const data = res.data as OEmbedResponse;
|
||||
|
||||
setOembed(data);
|
||||
|
||||
const iframeDocument = iframeRef.current?.contentWindow?.document;
|
||||
|
||||
if (!iframeDocument) {
|
||||
return '';
|
||||
}
|
||||
|
||||
iframeDocument.open();
|
||||
iframeDocument.write(data.html);
|
||||
iframeDocument.close();
|
||||
|
||||
iframeDocument.body.style.margin = '0px';
|
||||
|
||||
// This is our best chance to ensure the parent iframe has the correct height...
|
||||
intervalRef.current = setInterval(
|
||||
() =>
|
||||
window.requestAnimationFrame(() => {
|
||||
if (iframeRef.current) {
|
||||
iframeRef.current.width = `${iframeDocument.body.scrollWidth}px`;
|
||||
iframeRef.current.height = `${iframeDocument.body.scrollHeight}px`;
|
||||
}
|
||||
}),
|
||||
100,
|
||||
);
|
||||
|
||||
return '';
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
dispatch(showAlertForError(error));
|
||||
});
|
||||
}, [dispatch, id, setOembed]);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal dialog-modal'>
|
||||
<div className='dialog-modal__header'>
|
||||
<Button onClick={onClose}>
|
||||
<FormattedMessage id='report.close' defaultMessage='Done' />
|
||||
</Button>
|
||||
<span className='dialog-modal__header__title'>
|
||||
<FormattedMessage id='status.embed' defaultMessage='Get embed code' />
|
||||
</span>
|
||||
<Button secondary onClick={onClose}>
|
||||
<FormattedMessage
|
||||
id='confirmation_modal.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className='dialog-modal__content'>
|
||||
<div className='dialog-modal__content__form'>
|
||||
<FormattedMessage
|
||||
id='embed.instructions'
|
||||
defaultMessage='Embed this status on your website by copying the code below.'
|
||||
/>
|
||||
|
||||
<CopyPasteText value={oembed?.html ?? ''} />
|
||||
|
||||
<FormattedMessage
|
||||
id='embed.preview'
|
||||
defaultMessage='Here is what it will look like:'
|
||||
/>
|
||||
|
||||
<iframe
|
||||
frameBorder='0'
|
||||
ref={iframeRef}
|
||||
sandbox='allow-scripts allow-same-origin'
|
||||
title='Preview'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default EmbedModal;
|
|
@ -137,7 +137,7 @@ export const MuteModal = ({ accountId, acct }) => {
|
|||
<FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
|
||||
</button>
|
||||
|
||||
<Button onClick={handleClick}>
|
||||
<Button onClick={handleClick} autoFocus>
|
||||
<FormattedMessage id='confirmations.mute.confirm' defaultMessage='Mute' />
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
@ -3,10 +3,12 @@ import { connect } from 'react-redux';
|
|||
import { openModal, closeModal } from '../../../actions/modal';
|
||||
import ModalRoot from '../components/modal_root';
|
||||
|
||||
const defaultProps = {};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
ignoreFocus: state.getIn(['modal', 'ignoreFocus']),
|
||||
type: state.getIn(['modal', 'stack', 0, 'modalType'], null),
|
||||
props: state.getIn(['modal', 'stack', 0, 'modalProps'], {}),
|
||||
props: state.getIn(['modal', 'stack', 0, 'modalProps'], defaultProps),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
|
|
@ -4,24 +4,11 @@ import { connect } from 'react-redux';
|
|||
|
||||
import { NotificationStack } from 'react-notification';
|
||||
|
||||
import { dismissAlert } from '../../../actions/alerts';
|
||||
import { getAlerts } from '../../../selectors';
|
||||
|
||||
const formatIfNeeded = (intl, message, values) => {
|
||||
if (typeof message === 'object') {
|
||||
return intl.formatMessage(message, values);
|
||||
}
|
||||
|
||||
return message;
|
||||
};
|
||||
import { dismissAlert } from 'mastodon/actions/alerts';
|
||||
import { getAlerts } from 'mastodon/selectors';
|
||||
|
||||
const mapStateToProps = (state, { intl }) => ({
|
||||
notifications: getAlerts(state).map(alert => ({
|
||||
...alert,
|
||||
action: formatIfNeeded(intl, alert.action, alert.values),
|
||||
title: formatIfNeeded(intl, alert.title, alert.values),
|
||||
message: formatIfNeeded(intl, alert.message, alert.values),
|
||||
})),
|
||||
notifications: getAlerts(state, { intl }),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
|
|
@ -349,8 +349,8 @@ class UI extends PureComponent {
|
|||
|
||||
try {
|
||||
e.dataTransfer.dropEffect = 'copy';
|
||||
} catch (err) {
|
||||
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
@ -308,7 +308,6 @@
|
|||
"lists.search": "Buscar entre la chent a la quala sigues",
|
||||
"lists.subheading": "Las tuyas listas",
|
||||
"load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Amaga la imachen} other {Amaga las imáchens}}",
|
||||
"moved_to_account_banner.text": "La tuya cuenta {disabledAccount} ye actualment deshabilitada perque t'has mudau a {movedToAccount}.",
|
||||
"navigation_bar.about": "Sobre",
|
||||
"navigation_bar.blocks": "Usuarios blocaus",
|
||||
|
|
|
@ -229,8 +229,8 @@
|
|||
"domain_pill.their_username": "مُعرّفُهم الفريد على الخادم. من الممكن العثور على مستخدمين بنفس اسم المستخدم على خوادم مختلفة.",
|
||||
"domain_pill.username": "اسم المستخدم",
|
||||
"domain_pill.whats_in_a_handle": "ما المقصود بالمُعرِّف؟",
|
||||
"domain_pill.who_they_are": "بما أن المعرفات تقول من هو الشخص ومكان وجوده، يمكنك التفاعل مع الناس عبر الويب الاجتماعي لل <button>منصات التي تعمل ب ActivityPub</button>.",
|
||||
"domain_pill.who_you_are": "بما أن معرفك يقول من أنت ومكان وجوده، يمكن للناس التفاعل معك عبر الويب الاجتماعي لل <button>منصات التي تعمل ب ActivityPub</button>.",
|
||||
"domain_pill.who_they_are": "بما أن المُعرّفات تحدد هوية الشخص ومكان وجوده، فبإمكانك التفاعل مع الأشخاص عبر الويب الاجتماعي لـ <button>المنصات التي تعمل بواسطة أكتيفيتي بوب</button>.",
|
||||
"domain_pill.who_you_are": "بما أن مُعرّفك يحدد هويتك ومكان وجوده، فبإمكانك الآخرين التفاعل معك عبر الويب الاجتماعي لـ <button>المنصات التي تعمل بواسطة أكتيفيتي بوب</button>.",
|
||||
"domain_pill.your_handle": "عنوانك الكامل:",
|
||||
"domain_pill.your_server": "موطِنك الرقمي، حيث توجد فيه كافة منشوراتك. ألا يعجبك المكان؟ يمكنك الانتقال بين الخوادم في أي وقت واصطحاب متابعيك أيضاً.",
|
||||
"domain_pill.your_username": "معرفك الفريد على هذا الخادم. من الممكن العثور على مستخدمين بنفس إسم المستخدم على خوادم مختلفة.",
|
||||
|
@ -352,6 +352,7 @@
|
|||
"hashtags.and_other": "…و {count, plural, zero {} one {# واحد آخر} two {# اثنان آخران} few {# آخرون} many {# آخَرًا}other {# آخرون}}",
|
||||
"hints.profiles.see_more_followers": "عرض المزيد من المتابعين على {domain}",
|
||||
"hints.profiles.see_more_posts": "عرض المزيد من المنشورات من {domain}",
|
||||
"hints.threads.replies_may_be_missing": "قد تكون الردود الواردة من الخوادم الأخرى غائبة.",
|
||||
"hints.threads.see_more": "اطلع على المزيد من الردود على {domain}",
|
||||
"home.column_settings.show_reblogs": "اعرض المعاد نشرها",
|
||||
"home.column_settings.show_replies": "اعرض الردود",
|
||||
|
@ -442,7 +443,6 @@
|
|||
"lists.subheading": "قوائمك",
|
||||
"load_pending": "{count, plural, one {# عنصر جديد} other {# عناصر جديدة}}",
|
||||
"loading_indicator.label": "جاري التحميل…",
|
||||
"media_gallery.toggle_visible": "{number, plural, zero {} one {اخف الصورة} two {اخف الصورتين} few {اخف الصور} many {اخف الصور} other {اخف الصور}}",
|
||||
"moved_to_account_banner.text": "حسابك {disabledAccount} معطل حاليًا لأنك انتقلت إلى {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "إخفاء من قائمة الإشعارات",
|
||||
"mute_modal.hide_options": "إخفاء الخيارات",
|
||||
|
@ -517,6 +517,7 @@
|
|||
"notification_requests.edit_selection": "تعديل",
|
||||
"notification_requests.exit_selection": "تمّ",
|
||||
"notification_requests.explainer_for_limited_account": "تم تصفية الإشعارات من هذا الحساب لأن الحساب تم تقييده من قبل مشرف.",
|
||||
"notification_requests.minimize_banner": "تصغير شريط الإشعارات المُصفاة",
|
||||
"notification_requests.notifications_from": "إشعارات من {name}",
|
||||
"notification_requests.title": "الإشعارات المصفاة",
|
||||
"notification_requests.view": "عرض الإشعارات",
|
||||
|
|
|
@ -268,7 +268,6 @@
|
|||
"lists.search": "Buscar ente los perfiles que sigues",
|
||||
"lists.subheading": "Les tos llistes",
|
||||
"load_pending": "{count, plural, one {# elementu nuevu} other {# elementos nuevos}}",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Anubrir la imaxe} other {Anubrir les imáxenes}}",
|
||||
"navigation_bar.about": "Tocante a",
|
||||
"navigation_bar.blocks": "Perfiles bloquiaos",
|
||||
"navigation_bar.bookmarks": "Marcadores",
|
||||
|
|
|
@ -437,7 +437,6 @@
|
|||
"lists.subheading": "Вашыя спісы",
|
||||
"load_pending": "{count, plural, one {# новы элемент} few {# новыя элементы} many {# новых элементаў} other {# новых элементаў}}",
|
||||
"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": "Схаваць опцыі",
|
||||
|
|
|
@ -444,7 +444,6 @@
|
|||
"lists.subheading": "Вашите списъци",
|
||||
"load_pending": "{count, plural, one {# нов елемент} other {# нови елемента}}",
|
||||
"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": "Скриване на възможностите",
|
||||
|
|
|
@ -288,7 +288,6 @@
|
|||
"lists.search": "যাদের অনুসরণ করেন তাদের ভেতরে খুঁজুন",
|
||||
"lists.subheading": "আপনার তালিকা",
|
||||
"load_pending": "{count, plural, one {# নতুন জিনিস} other {# নতুন জিনিস}}",
|
||||
"media_gallery.toggle_visible": "দৃশ্যতার অবস্থা বদলান",
|
||||
"navigation_bar.about": "পরিচিতি",
|
||||
"navigation_bar.blocks": "বন্ধ করা ব্যবহারকারী",
|
||||
"navigation_bar.bookmarks": "বুকমার্ক",
|
||||
|
|
|
@ -360,7 +360,6 @@
|
|||
"lists.subheading": "Ho listennoù",
|
||||
"load_pending": "{count, plural, one {# dra nevez} other {# dra nevez}}",
|
||||
"loading_indicator.label": "O kargañ…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Kuzhat ar skeudenn} other {Kuzhat ar skeudenn}}",
|
||||
"navigation_bar.about": "Diwar-benn",
|
||||
"navigation_bar.blocks": "Implijer·ezed·ien berzet",
|
||||
"navigation_bar.bookmarks": "Sinedoù",
|
||||
|
|
|
@ -457,7 +457,7 @@
|
|||
"lists.subheading": "Les teves llistes",
|
||||
"load_pending": "{count, plural, one {# element nou} other {# elements nous}}",
|
||||
"loading_indicator.label": "Es carrega…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Amaga la imatge} other {Amaga les imatges}}",
|
||||
"media_gallery.hide": "Amaga",
|
||||
"moved_to_account_banner.text": "El teu compte {disabledAccount} està desactivat perquè l'has mogut a {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Amaga de les notificacions",
|
||||
"mute_modal.hide_options": "Amaga les opcions",
|
||||
|
@ -780,6 +780,7 @@
|
|||
"status.bookmark": "Marca",
|
||||
"status.cancel_reblog_private": "Desfés l'impuls",
|
||||
"status.cannot_reblog": "No es pot impulsar aquest tut",
|
||||
"status.continued_thread": "Continuació del fil",
|
||||
"status.copy": "Copia l'enllaç al tut",
|
||||
"status.delete": "Elimina",
|
||||
"status.detailed_status": "Vista detallada de la conversa",
|
||||
|
@ -813,6 +814,7 @@
|
|||
"status.reblogs.empty": "Encara no ha impulsat ningú aquest tut. Quan algú ho faci, apareixerà aquí.",
|
||||
"status.redraft": "Esborra i reescriu",
|
||||
"status.remove_bookmark": "Elimina el marcador",
|
||||
"status.replied_in_thread": "Respost al fil",
|
||||
"status.replied_to": "En resposta a {name}",
|
||||
"status.reply": "Respon",
|
||||
"status.replyAll": "Respon al fil",
|
||||
|
|
|
@ -355,7 +355,6 @@
|
|||
"lists.search": "بگەڕێ لەناو ئەو کەسانەی کە شوێنیان کەوتویت",
|
||||
"lists.subheading": "لیستەکانت",
|
||||
"load_pending": "{count, plural, one {# بەڕگەی نوێ} other {# بەڕگەی نوێ}}",
|
||||
"media_gallery.toggle_visible": "شاردنەوەی {number, plural, one {image} other {images}}",
|
||||
"moved_to_account_banner.text": "ئەکاونتەکەت {disabledAccount} لە ئێستادا لەکارخراوە چونکە تۆ چوویتە {movedToAccount}.",
|
||||
"navigation_bar.about": "دەربارە",
|
||||
"navigation_bar.blocks": "بەکارهێنەرە بلۆککراوەکان",
|
||||
|
|
|
@ -214,7 +214,6 @@
|
|||
"lists.search": "Circà indè i vostr'abbunamenti",
|
||||
"lists.subheading": "E vo liste",
|
||||
"load_pending": "{count, plural, one {# entrata nova} other {# entrate nove}}",
|
||||
"media_gallery.toggle_visible": "Piattà {number, plural, one {ritrattu} other {ritratti}}",
|
||||
"navigation_bar.blocks": "Utilizatori bluccati",
|
||||
"navigation_bar.bookmarks": "Segnalibri",
|
||||
"navigation_bar.community_timeline": "Linea pubblica lucale",
|
||||
|
|
|
@ -435,7 +435,6 @@
|
|||
"lists.subheading": "Vaše seznamy",
|
||||
"load_pending": "{count, plural, one {# nová položka} few {# nové položky} many {# nových položek} other {# nových položek}}",
|
||||
"loading_indicator.label": "Načítání…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Skrýt obrázek} few {Skrýt obrázky} many {Skrýt obrázky} other {Skrýt obrázky}}",
|
||||
"moved_to_account_banner.text": "Váš účet {disabledAccount} je momentálně deaktivován, protože jste se přesunul/a na {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Skrýt z notifikací",
|
||||
"mute_modal.hide_options": "Skrýt možnosti",
|
||||
|
|
|
@ -97,7 +97,7 @@
|
|||
"block_modal.title": "Blocio defnyddiwr?",
|
||||
"block_modal.you_wont_see_mentions": "Fyddwch chi ddim yn gweld postiadau sy'n sôn amdanyn nhw.",
|
||||
"boost_modal.combo": "Mae modd pwyso {combo} er mwyn hepgor hyn tro nesa",
|
||||
"boost_modal.reblog": "Hybu postiad",
|
||||
"boost_modal.reblog": "Hybu postiad?",
|
||||
"boost_modal.undo_reblog": "Dad-hybu postiad?",
|
||||
"bundle_column_error.copy_stacktrace": "Copïo'r adroddiad gwall",
|
||||
"bundle_column_error.error.body": "Nid oedd modd cynhyrchu'r dudalen honno. Gall fod oherwydd gwall yn ein cod neu fater cydnawsedd porwr.",
|
||||
|
@ -457,7 +457,7 @@
|
|||
"lists.subheading": "Eich rhestrau",
|
||||
"load_pending": "{count, plural, one {# eitem newydd} other {# eitem newydd}}",
|
||||
"loading_indicator.label": "Yn llwytho…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Cuddio delwedd} other {Cuddio delwedd}}",
|
||||
"media_gallery.hide": "Cuddio",
|
||||
"moved_to_account_banner.text": "Ar hyn y bryd, mae eich cyfrif {disabledAccount} wedi ei analluogi am i chi symud i {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Cuddio rhag hysbysiadau",
|
||||
"mute_modal.hide_options": "Cuddio'r dewis",
|
||||
|
@ -780,6 +780,7 @@
|
|||
"status.bookmark": "Llyfrnodi",
|
||||
"status.cancel_reblog_private": "Dadhybu",
|
||||
"status.cannot_reblog": "Nid oes modd hybu'r postiad hwn",
|
||||
"status.continued_thread": "Edefyn parhaus",
|
||||
"status.copy": "Copïo dolen i'r post",
|
||||
"status.delete": "Dileu",
|
||||
"status.detailed_status": "Golwg manwl o'r sgwrs",
|
||||
|
@ -813,6 +814,7 @@
|
|||
"status.reblogs.empty": "Does neb wedi hybio'r post yma eto. Pan y bydd rhywun yn gwneud, byddent yn ymddangos yma.",
|
||||
"status.redraft": "Dileu ac ailddrafftio",
|
||||
"status.remove_bookmark": "Tynnu nod tudalen",
|
||||
"status.replied_in_thread": "Atebodd mewn edefyn",
|
||||
"status.replied_to": "Wedi ateb {name}",
|
||||
"status.reply": "Ateb",
|
||||
"status.replyAll": "Ateb i edefyn",
|
||||
|
|
|
@ -457,7 +457,7 @@
|
|||
"lists.subheading": "Dine lister",
|
||||
"load_pending": "{count, plural, one {# nyt emne} other {# nye emner}}",
|
||||
"loading_indicator.label": "Indlæser…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Skjul billede} other {Skjul billeder}}",
|
||||
"media_gallery.hide": "Skjul",
|
||||
"moved_to_account_banner.text": "Din konto {disabledAccount} er pt. deaktiveret, da du flyttede til {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Skjul fra notifikationer",
|
||||
"mute_modal.hide_options": "Skjul valgmuligheder",
|
||||
|
@ -780,6 +780,7 @@
|
|||
"status.bookmark": "Bogmærk",
|
||||
"status.cancel_reblog_private": "Fjern boost",
|
||||
"status.cannot_reblog": "Dette indlæg kan ikke fremhæves",
|
||||
"status.continued_thread": "Fortsat tråd",
|
||||
"status.copy": "Kopiér link til indlæg",
|
||||
"status.delete": "Slet",
|
||||
"status.detailed_status": "Detaljeret samtalevisning",
|
||||
|
@ -813,6 +814,7 @@
|
|||
"status.reblogs.empty": "Ingen har endnu fremhævet dette indlæg. Når nogen gør, vil det fremgå hér.",
|
||||
"status.redraft": "Slet og omformulér",
|
||||
"status.remove_bookmark": "Fjern bogmærke",
|
||||
"status.replied_in_thread": "Svaret i tråd",
|
||||
"status.replied_to": "Besvarede {name}",
|
||||
"status.reply": "Besvar",
|
||||
"status.replyAll": "Besvar alle",
|
||||
|
|
|
@ -457,7 +457,7 @@
|
|||
"lists.subheading": "Deine Listen",
|
||||
"load_pending": "{count, plural, one {# neuer Beitrag} other {# neue Beiträge}}",
|
||||
"loading_indicator.label": "Wird geladen …",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Medium ausblenden} other {Medien ausblenden}}",
|
||||
"media_gallery.hide": "Ausblenden",
|
||||
"moved_to_account_banner.text": "Dein Konto {disabledAccount} ist derzeit deaktiviert, weil du zu {movedToAccount} umgezogen bist.",
|
||||
"mute_modal.hide_from_notifications": "Benachrichtigungen ausblenden",
|
||||
"mute_modal.hide_options": "Einstellungen ausblenden",
|
||||
|
@ -780,6 +780,7 @@
|
|||
"status.bookmark": "Beitrag als Lesezeichen setzen",
|
||||
"status.cancel_reblog_private": "Beitrag nicht mehr teilen",
|
||||
"status.cannot_reblog": "Dieser Beitrag kann nicht geteilt werden",
|
||||
"status.continued_thread": "Fortgeführter Thread",
|
||||
"status.copy": "Link zum Beitrag kopieren",
|
||||
"status.delete": "Beitrag löschen",
|
||||
"status.detailed_status": "Detaillierte Ansicht der Unterhaltung",
|
||||
|
@ -813,6 +814,7 @@
|
|||
"status.reblogs.empty": "Diesen Beitrag hat bisher noch niemand geteilt. Sobald es jemand tut, wird das Profil hier erscheinen.",
|
||||
"status.redraft": "Löschen und neu erstellen",
|
||||
"status.remove_bookmark": "Lesezeichen entfernen",
|
||||
"status.replied_in_thread": "Antwortete im Thread",
|
||||
"status.replied_to": "Antwortete {name}",
|
||||
"status.reply": "Antworten",
|
||||
"status.replyAll": "Allen antworten",
|
||||
|
|
|
@ -452,7 +452,6 @@
|
|||
"lists.subheading": "Οι λίστες σου",
|
||||
"load_pending": "{count, plural, one {# νέο στοιχείο} other {# νέα στοιχεία}}",
|
||||
"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": "Απόκρυψη επιλογών",
|
||||
|
|
|
@ -457,7 +457,7 @@
|
|||
"lists.subheading": "Your lists",
|
||||
"load_pending": "{count, plural, one {# new item} other {# new items}}",
|
||||
"loading_indicator.label": "Loading…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Hide image} other {Hide images}}",
|
||||
"media_gallery.hide": "Hide",
|
||||
"moved_to_account_banner.text": "Your account {disabledAccount} is currently disabled because you moved to {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Hide from notifications",
|
||||
"mute_modal.hide_options": "Hide options",
|
||||
|
@ -780,6 +780,7 @@
|
|||
"status.bookmark": "Bookmark",
|
||||
"status.cancel_reblog_private": "Unboost",
|
||||
"status.cannot_reblog": "This post cannot be boosted",
|
||||
"status.continued_thread": "Continued thread",
|
||||
"status.copy": "Copy link to status",
|
||||
"status.delete": "Delete",
|
||||
"status.detailed_status": "Detailed conversation view",
|
||||
|
@ -813,6 +814,7 @@
|
|||
"status.reblogs.empty": "No one has boosted this post yet. When someone does, they will show up here.",
|
||||
"status.redraft": "Delete & re-draft",
|
||||
"status.remove_bookmark": "Remove bookmark",
|
||||
"status.replied_in_thread": "Replied in thread",
|
||||
"status.replied_to": "Replied to {name}",
|
||||
"status.reply": "Reply",
|
||||
"status.replyAll": "Reply to thread",
|
||||
|
|
|
@ -566,7 +566,7 @@
|
|||
"lists.with_antenna": "Antenna",
|
||||
"load_pending": "{count, plural, one {# new item} other {# new items}}",
|
||||
"loading_indicator.label": "Loading…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Hide image} other {Hide images}}",
|
||||
"media_gallery.hide": "Hide",
|
||||
"moved_to_account_banner.text": "Your account {disabledAccount} is currently disabled because you moved to {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Hide from notifications",
|
||||
"mute_modal.hide_options": "Hide options",
|
||||
|
@ -936,6 +936,7 @@
|
|||
"status.cancel_reblog": "Unboost",
|
||||
"status.cancel_reblog_private": "Unboost",
|
||||
"status.cannot_reblog": "This post cannot be boosted",
|
||||
"status.continued_thread": "Continued thread",
|
||||
"status.copy": "Copy link to post",
|
||||
"status.delete": "Delete",
|
||||
"status.detailed_status": "Detailed conversation view",
|
||||
|
@ -944,7 +945,7 @@
|
|||
"status.edit": "Edit",
|
||||
"status.edited": "Last edited {date}",
|
||||
"status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
|
||||
"status.embed": "Embed",
|
||||
"status.embed": "Get embed code",
|
||||
"status.emoji_reaction": "Emoji reaction",
|
||||
"status.emoji_reaction.pick": "Pick emoji reaction",
|
||||
"status.emoji_reactions": "{count, plural, one {emoji} other {emojis}}",
|
||||
|
@ -986,6 +987,7 @@
|
|||
"status.redraft": "Delete & re-draft",
|
||||
"status.reference": "Quiet quote",
|
||||
"status.remove_bookmark": "Remove bookmark",
|
||||
"status.replied_in_thread": "Replied in thread",
|
||||
"status.replied_to": "Replied to {name}",
|
||||
"status.reply": "Reply",
|
||||
"status.replyAll": "Reply to thread",
|
||||
|
|
|
@ -390,7 +390,6 @@
|
|||
"lists.subheading": "Viaj listoj",
|
||||
"load_pending": "{count,plural, one {# nova elemento} other {# novaj elementoj}}",
|
||||
"loading_indicator.label": "Ŝargado…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Kaŝi la bildon} other {Kaŝi la bildojn}}",
|
||||
"moved_to_account_banner.text": "Via konto {disabledAccount} estas malvalidigita ĉar vi movis ĝin al {movedToAccount}.",
|
||||
"navigation_bar.about": "Pri",
|
||||
"navigation_bar.advanced_interface": "Malfermi altnivelan retpaĝan interfacon",
|
||||
|
|
|
@ -457,7 +457,7 @@
|
|||
"lists.subheading": "Tus listas",
|
||||
"load_pending": "{count, plural, one {# elemento nuevo} other {# elementos nuevos}}",
|
||||
"loading_indicator.label": "Cargando…",
|
||||
"media_gallery.toggle_visible": "Ocultar {number, plural, one {imagen} other {imágenes}}",
|
||||
"media_gallery.hide": "Ocultar",
|
||||
"moved_to_account_banner.text": "Tu cuenta {disabledAccount} está actualmente deshabilitada porque te mudaste a {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Ocultar en las notificaciones",
|
||||
"mute_modal.hide_options": "Ocultar opciones",
|
||||
|
@ -780,6 +780,7 @@
|
|||
"status.bookmark": "Marcar",
|
||||
"status.cancel_reblog_private": "Quitar adhesión",
|
||||
"status.cannot_reblog": "No se puede adherir a este mensaje",
|
||||
"status.continued_thread": "Continuación de hilo",
|
||||
"status.copy": "Copiar enlace al mensaje",
|
||||
"status.delete": "Eliminar",
|
||||
"status.detailed_status": "Vista de conversación detallada",
|
||||
|
@ -813,6 +814,7 @@
|
|||
"status.reblogs.empty": "Todavía nadie adhirió a este mensaje. Cuando alguien lo haga, se mostrará acá.",
|
||||
"status.redraft": "Eliminar mensaje original y editarlo",
|
||||
"status.remove_bookmark": "Quitar marcador",
|
||||
"status.replied_in_thread": "Respuesta en hilo",
|
||||
"status.replied_to": "Respondió a {name}",
|
||||
"status.reply": "Responder",
|
||||
"status.replyAll": "Responder al hilo",
|
||||
|
|
|
@ -457,7 +457,6 @@
|
|||
"lists.subheading": "Tus listas",
|
||||
"load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}",
|
||||
"loading_indicator.label": "Cargando…",
|
||||
"media_gallery.toggle_visible": "Cambiar visibilidad",
|
||||
"moved_to_account_banner.text": "Tu cuenta {disabledAccount} está actualmente deshabilitada porque te has mudado a {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Ocultar de las notificaciones",
|
||||
"mute_modal.hide_options": "Ocultar opciones",
|
||||
|
|
|
@ -457,7 +457,6 @@
|
|||
"lists.subheading": "Tus listas",
|
||||
"load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}",
|
||||
"loading_indicator.label": "Cargando…",
|
||||
"media_gallery.toggle_visible": "Cambiar visibilidad",
|
||||
"moved_to_account_banner.text": "Tu cuenta {disabledAccount} está actualmente deshabilitada porque te has mudado a {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Ocultar de las notificaciones",
|
||||
"mute_modal.hide_options": "Ocultar opciones",
|
||||
|
|
|
@ -353,6 +353,14 @@
|
|||
"hashtag.follow": "Jälgi silti",
|
||||
"hashtag.unfollow": "Lõpeta sildi jälgimine",
|
||||
"hashtags.and_other": "…ja {count, plural, one {}other {# veel}}",
|
||||
"hints.profiles.followers_may_be_missing": "Selle profiili jälgijaid võib olla puudu.",
|
||||
"hints.profiles.follows_may_be_missing": "Selle profiili poolt jälgitavaid võib olla puudu.",
|
||||
"hints.profiles.posts_may_be_missing": "Mõned selle profiili postitused võivad olla puudu.",
|
||||
"hints.profiles.see_more_followers": "Vaata rohkem jälgijaid kohas {domain}",
|
||||
"hints.profiles.see_more_follows": "Vaata rohkem jälgitavaid kohas {domain}",
|
||||
"hints.profiles.see_more_posts": "Vaata rohkem postitusi kohas {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Vastuseid teistest serveritest võib olla puudu.",
|
||||
"hints.threads.see_more": "Vaata rohkem vastuseid kohas {domain}",
|
||||
"home.column_settings.show_reblogs": "Näita jagamisi",
|
||||
"home.column_settings.show_replies": "Näita vastuseid",
|
||||
"home.hide_announcements": "Peida teadaanded",
|
||||
|
@ -360,6 +368,17 @@
|
|||
"home.pending_critical_update.link": "Vaata uuendusi",
|
||||
"home.pending_critical_update.title": "Saadaval kriitiline turvauuendus!",
|
||||
"home.show_announcements": "Kuva teadaandeid",
|
||||
"ignore_notifications_modal.disclaimer": "Mastodon ei saa teavitada kasutajaid, et ignoreerisid nende teavitusi. Teavituste ignoreerimine ei peata sõnumite endi saatmist.",
|
||||
"ignore_notifications_modal.filter_instead": "Selle asemel filtreeri",
|
||||
"ignore_notifications_modal.filter_to_act_users": "Saad endiselt kasutajaid vastu võtta, tagasi lükata või neist teatada",
|
||||
"ignore_notifications_modal.filter_to_avoid_confusion": "Filtreerimine aitab vältida võimalikke segaminiajamisi",
|
||||
"ignore_notifications_modal.filter_to_review_separately": "Saad filtreeritud teateid eraldi vaadata",
|
||||
"ignore_notifications_modal.ignore": "Ignoreeri teavitusi",
|
||||
"ignore_notifications_modal.limited_accounts_title": "Ignoreeri modereeritud kontode teavitusi?",
|
||||
"ignore_notifications_modal.new_accounts_title": "Ignoreeri uute kontode teavitusi?",
|
||||
"ignore_notifications_modal.not_followers_title": "Ignoreeri inimeste teavitusi, kes sind ei jälgi?",
|
||||
"ignore_notifications_modal.not_following_title": "Ignoreeri inimeste teavitusi, keda sa ei jälgi?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Ignoreeri soovimatute eraviisiliste mainimiste teateid?",
|
||||
"interaction_modal.description.favourite": "Mastodoni kontoga saad postituse lemmikuks märkida, et autor teaks, et sa hindad seda, ja jätta see hiljemaks alles.",
|
||||
"interaction_modal.description.follow": "Mastodoni kontoga saad jälgida kasutajat {name}, et tema postitusi oma koduvoos näha.",
|
||||
"interaction_modal.description.reblog": "Mastodoni kontoga saad seda postitust levitada, jagades seda oma jälgijatele.",
|
||||
|
@ -438,7 +457,6 @@
|
|||
"lists.subheading": "Sinu nimekirjad",
|
||||
"load_pending": "{count, plural, one {# uus kirje} other {# uut kirjet}}",
|
||||
"loading_indicator.label": "Laadimine…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Varja pilt} other {Varja pildid}}",
|
||||
"moved_to_account_banner.text": "Kontot {disabledAccount} ei ole praegu võimalik kasutada, sest kolisid kontole {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Peida teavituste hulgast",
|
||||
"mute_modal.hide_options": "Peida valikud",
|
||||
|
@ -483,9 +501,13 @@
|
|||
"notification.admin.report_statuses": "{name} raporteeris {target} kategooriast {category}",
|
||||
"notification.admin.report_statuses_other": "{name} raporteeris kohast {target}",
|
||||
"notification.admin.sign_up": "{name} registreerus",
|
||||
"notification.admin.sign_up.name_and_others": "{name} ja {count, plural, one {# veel} other {# teist}} liitus",
|
||||
"notification.favourite": "{name} märkis su postituse lemmikuks",
|
||||
"notification.favourite.name_and_others_with_link": "{name} ja <a>{count, plural, one {# veel} other {# teist}}</a> märkis su postituse lemmikuks",
|
||||
"notification.follow": "{name} alustas su jälgimist",
|
||||
"notification.follow.name_and_others": "{name} ja {count, plural, one {# veel} other {# teist}} hakkas sind jälgima",
|
||||
"notification.follow_request": "{name} soovib sind jälgida",
|
||||
"notification.follow_request.name_and_others": "{name} ja {count, plural, one {# veel} other {# teist}} taotles sinu jälgimist",
|
||||
"notification.label.mention": "Mainimine",
|
||||
"notification.label.private_mention": "Privaatne mainimine",
|
||||
"notification.label.private_reply": "Privaatne vastus",
|
||||
|
@ -503,6 +525,7 @@
|
|||
"notification.own_poll": "Su küsitlus on lõppenud",
|
||||
"notification.poll": "Hääletus, millel osalesid, on lõppenud",
|
||||
"notification.reblog": "{name} jagas edasi postitust",
|
||||
"notification.reblog.name_and_others_with_link": "{name} ja <a>{count, plural, one {# veel} other {# teist}}</a> jagas su postitust",
|
||||
"notification.relationships_severance_event": "Kadunud ühendus kasutajaga {name}",
|
||||
"notification.relationships_severance_event.account_suspension": "{from} admin on kustutanud {target}, mis tähendab, et sa ei saa enam neilt uuendusi või suhelda nendega.",
|
||||
"notification.relationships_severance_event.domain_block": "{from} admin on blokeerinud {target}, sealhulgas {followersCount} sinu jälgijat ja {followingCount, plural, one {# konto} other {# kontot}}, mida jälgid.",
|
||||
|
@ -511,13 +534,24 @@
|
|||
"notification.status": "{name} just postitas",
|
||||
"notification.update": "{name} muutis postitust",
|
||||
"notification_requests.accept": "Nõus",
|
||||
"notification_requests.accept_multiple": "{count, plural, one {Nõustu # taotlusega…} other {Nõustu # taotlusega…}}",
|
||||
"notification_requests.confirm_accept_multiple.button": "{count, plural, one {Nõustu taotlusega} other {Nõustu taotlustega}}",
|
||||
"notification_requests.confirm_accept_multiple.message": "Oled nõustumas {count, plural, one {ühe teavituse taotlusega} other {# teavituse taotlusega}}. Oled kindel, et soovid jätkata?",
|
||||
"notification_requests.confirm_accept_multiple.title": "Nõustuda teavituste taotlustega?",
|
||||
"notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Loobu taotlusest} other {Loobu taotlustest}}",
|
||||
"notification_requests.confirm_dismiss_multiple.message": "Oled loobumas {count, plural, one {ühest teavituse taotlusest} other {# teavituse taotlusest}}. {count, plural, one {Sellele} other {Neile}} pole hiljem lihtne ligi pääseda. Oled kindel, et soovid jätkata?",
|
||||
"notification_requests.confirm_dismiss_multiple.title": "Hüljata teavituse taotlused?",
|
||||
"notification_requests.dismiss": "Hülga",
|
||||
"notification_requests.dismiss_multiple": "{count, plural, one {Loobuda # taotlusest…} other {Loobuda # taotlusest…}}",
|
||||
"notification_requests.edit_selection": "Muuda",
|
||||
"notification_requests.exit_selection": "Valmis",
|
||||
"notification_requests.explainer_for_limited_account": "Sellelt kontolt tulevad teavitused on filtreeritud, sest moderaator on seda kontot piiranud.",
|
||||
"notification_requests.explainer_for_limited_remote_account": "Sellelt kontolt tulevad teavitused on filtreeritud, sest moderaator on seda kontot või serverit piiranud.",
|
||||
"notification_requests.maximize": "Maksimeeri",
|
||||
"notification_requests.minimize_banner": "Minimeeri filtreeritud teavituste bänner",
|
||||
"notification_requests.notifications_from": "Teavitus kasutajalt {name}",
|
||||
"notification_requests.title": "Filtreeritud teavitused",
|
||||
"notification_requests.view": "Vaata teavitusi",
|
||||
"notifications.clear": "Puhasta teated",
|
||||
"notifications.clear_confirmation": "Oled kindel, et soovid püsivalt kõik oma teated eemaldada?",
|
||||
"notifications.clear_title": "Tühjenda teavitus?",
|
||||
|
@ -554,6 +588,12 @@
|
|||
"notifications.permission_denied": "Töölauamärguanded pole saadaval, kuna eelnevalt keelduti lehitsejale teavituste luba andmast",
|
||||
"notifications.permission_denied_alert": "Töölaua märguandeid ei saa lubada, kuna brauseri luba on varem keeldutud",
|
||||
"notifications.permission_required": "Töölaua märguanded ei ole saadaval, kuna vajalik luba pole antud.",
|
||||
"notifications.policy.accept": "Nõustun",
|
||||
"notifications.policy.accept_hint": "Näita teavitustes",
|
||||
"notifications.policy.drop": "Ignoreeri",
|
||||
"notifications.policy.drop_hint": "Saada tühjusse, mitte kunagi seda enam näha",
|
||||
"notifications.policy.filter": "Filter",
|
||||
"notifications.policy.filter_hint": "Saadetud filtreeritud teavituste sisendkasti",
|
||||
"notifications.policy.filter_limited_accounts_hint": "Piiratud serveri moderaatorite poolt",
|
||||
"notifications.policy.filter_limited_accounts_title": "Modereeritud kontod",
|
||||
"notifications.policy.filter_new_accounts.hint": "Loodud viimase {days, plural, one {ühe päeva} other {# päeva}} jooksul",
|
||||
|
@ -564,6 +604,7 @@
|
|||
"notifications.policy.filter_not_following_title": "Inimesed, keda sa ei jälgi",
|
||||
"notifications.policy.filter_private_mentions_hint": "Filtreeritud, kui see pole vastus sinupoolt mainimisele või kui jälgid saatjat",
|
||||
"notifications.policy.filter_private_mentions_title": "Soovimatud privaatsed mainimised",
|
||||
"notifications.policy.title": "Halda teavitusi kohast…",
|
||||
"notifications_permission_banner.enable": "Luba töölaua märguanded",
|
||||
"notifications_permission_banner.how_to_control": "Et saada teateid, ajal mil Mastodon pole avatud, luba töölauamärguanded. Saad täpselt määrata, mis tüüpi tegevused tekitavad märguandeid, kasutates peale teadaannete sisse lülitamist üleval olevat nuppu {icon}.",
|
||||
"notifications_permission_banner.title": "Ära jää millestki ilma",
|
||||
|
|
|
@ -457,7 +457,6 @@
|
|||
"lists.subheading": "Zure zerrendak",
|
||||
"load_pending": "{count, plural, one {elementu berri #} other {# elementu berri}}",
|
||||
"loading_indicator.label": "Kargatzen…",
|
||||
"media_gallery.toggle_visible": "Txandakatu ikusgaitasuna",
|
||||
"moved_to_account_banner.text": "Zure {disabledAccount} kontua desgaituta dago une honetan, {movedToAccount} kontura aldatu zinelako.",
|
||||
"mute_modal.hide_from_notifications": "Ezkutatu jakinarazpenetatik",
|
||||
"mute_modal.hide_options": "Ezkutatu aukerak",
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"about.not_available": "این اطّلاعات روی این کارساز موجود نشده.",
|
||||
"about.powered_by": "رسانهٔ اجتماعی نامتمرکز قدرت گرفته از {mastodon}",
|
||||
"about.rules": "قوانین کارساز",
|
||||
"account.account_note_header": "یادداشت شخصی",
|
||||
"account.add_or_remove_from_list": "افزودن یا برداشتن از سیاههها",
|
||||
"account.badges.bot": "خودکار",
|
||||
"account.badges.group": "گروه",
|
||||
|
@ -33,7 +34,9 @@
|
|||
"account.follow_back": "دنبال کردن متقابل",
|
||||
"account.followers": "پیگیرندگان",
|
||||
"account.followers.empty": "هنوز کسی پیگیر این کاربر نیست.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} پیگیرنده} other {{counter} پیگیرنده}}",
|
||||
"account.following": "پی میگیرید",
|
||||
"account.following_counter": "{count, plural, one {{counter} پیگرفته} other {{counter} پیگرفته}}",
|
||||
"account.follows.empty": "این کاربر هنوز پیگیر کسی نیست.",
|
||||
"account.go_to_profile": "رفتن به نمایه",
|
||||
"account.hide_reblogs": "نهفتن تقویتهای @{name}",
|
||||
|
@ -59,6 +62,7 @@
|
|||
"account.requested_follow": "{name} درخواست پیگیریتان را داد",
|
||||
"account.share": "همرسانی نمایهٔ @{name}",
|
||||
"account.show_reblogs": "نمایش تقویتهای @{name}",
|
||||
"account.statuses_counter": "{count, plural, one {{counter} فرسته} other {{counter} فرسته}}",
|
||||
"account.unblock": "رفع مسدودیت @{name}",
|
||||
"account.unblock_domain": "رفع مسدودیت دامنهٔ {domain}",
|
||||
"account.unblock_short": "رفع مسدودیت",
|
||||
|
@ -86,9 +90,14 @@
|
|||
"audio.hide": "نهفتن صدا",
|
||||
"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} را بزنید تا دیگر این را نبینید",
|
||||
"boost_modal.reblog": "تقویت فرسته؟",
|
||||
"boost_modal.undo_reblog": "ناتقویت فرسته؟",
|
||||
"bundle_column_error.copy_stacktrace": "رونوشت از گزارش خطا",
|
||||
"bundle_column_error.error.body": "صفحهٔ درخواستی نتوانست پرداخت شود. ممکن است به خاطر اشکالی در کدمان یا مشکل سازگاری مرورگر باشد.",
|
||||
"bundle_column_error.error.title": "وای، نه!",
|
||||
|
@ -184,6 +193,8 @@
|
|||
"confirmations.unfollow.confirm": "پینگرفتن",
|
||||
"confirmations.unfollow.message": "مطمئنید که میخواهید به پیگیری از {name} پایان دهید؟",
|
||||
"confirmations.unfollow.title": "ناپیگیری کاربر؟",
|
||||
"content_warning.hide": "نهفتن فرسته",
|
||||
"content_warning.show": "نمایش به هر روی",
|
||||
"conversation.delete": "حذف گفتگو",
|
||||
"conversation.mark_as_read": "علامتگذاری به عنوان خوانده شده",
|
||||
"conversation.open": "دیدن گفتگو",
|
||||
|
@ -204,9 +215,24 @@
|
|||
"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": "فرستهها یا آگاهیها از کاربران روی این کارساز را نخواهید دید.",
|
||||
"domain_pill.server": "کارساز",
|
||||
"domain_pill.their_handle": "شناسهاش:",
|
||||
"domain_pill.their_server": "خانهٔ رقمیش. جایی که همهٔ فرستههایش میزیند.",
|
||||
"domain_pill.their_username": "شناسهٔ یکتایش در کارسازش. ممکن است کاربرانی با نام کاربری مشابه روی کارسازهای مختلف باشند.",
|
||||
"domain_pill.username": "نام کاربری",
|
||||
"domain_pill.whats_in_a_handle": "شناسه چیست؟",
|
||||
"domain_pill.who_they_are": "از آنجا که شناسهها کیستی و کجایی افراد را میگویند، میتوانید با افرادی در سراسر وب اجتماعی <button>بنسازههای قدرت گرفته از اکتیویتی پاپ</button> تعامل داشته باشید.",
|
||||
"domain_pill.who_you_are": "از آنجا که شناسهها کیستی و کجاییتان را میگویند، افراد میتوانند از سراسر وب اجتماعی <button>بنسازههای قدرت گرفته از اکتیویتی پاپ</button> با شما تعامل داشته باشند.",
|
||||
"domain_pill.your_handle": "شناسهتان:",
|
||||
"domain_pill.your_server": "خانهٔ رقمیتان. جایی که همهٔ فرستههایتان میزیند. دوستش ندارید؟ در هر زمان کارسازتان را جابهجا کرده و پیگیرندگانتان را نیز بیاورید.",
|
||||
"domain_pill.your_username": "شناسهٔ یکتایتان روی این کارساز. ممکن است کاربرانی با نام کاربری مشابه روی کارسازهای دیگر باشند.",
|
||||
"embed.instructions": "جاسازی این فرسته روی پایگاهتان با رونوشت کردن کد زیر.",
|
||||
"embed.preview": "این گونه دیده خواهد شد:",
|
||||
"emoji_button.activity": "فعالیت",
|
||||
|
@ -273,6 +299,9 @@
|
|||
"filter_modal.select_filter.subtitle": "استفاده از یک دستهً موجود یا ایجاد دستهای جدید",
|
||||
"filter_modal.select_filter.title": "پالایش این فرسته",
|
||||
"filter_modal.title.status": "پالایش یک فرسته",
|
||||
"filter_warning.matches_filter": "مطابق با پالایهٔ «{title}»",
|
||||
"filtered_notifications_banner.pending_requests": "از {count, plural, =0 {هیچکسی} one {فردی} other {# نفر}} که ممکن است بشناسید",
|
||||
"filtered_notifications_banner.title": "آگاهیهای پالوده",
|
||||
"firehose.all": "همه",
|
||||
"firehose.local": "این کارساز",
|
||||
"firehose.remote": "دیگر کارسازها",
|
||||
|
@ -281,6 +310,8 @@
|
|||
"follow_requests.unlocked_explanation": "با این که حسابتان قفل نیست، کارکنان {domain} فکر کردند که ممکن است بخواهید درخواستها از این حسابها را به صورت دستی بازبینی کنید.",
|
||||
"follow_suggestions.curated_suggestion": "گزینش سردبیر",
|
||||
"follow_suggestions.dismiss": "دیگر نشان داده نشود",
|
||||
"follow_suggestions.featured_longer": "دستچین شده به دست گروه {domain}",
|
||||
"follow_suggestions.friends_of_friends_longer": "محبوب بین کسانی که پیگرفتهاید",
|
||||
"follow_suggestions.hints.featured": "این نمایه به دست گروه {domain} دستچین شده.",
|
||||
"follow_suggestions.hints.friends_of_friends": "این نمایه بین کسانی که پی میگیرید محبوب است.",
|
||||
"follow_suggestions.hints.most_followed": "این نمایه روی {domain} بسیار پیگرفته شده.",
|
||||
|
@ -288,6 +319,8 @@
|
|||
"follow_suggestions.hints.similar_to_recently_followed": "این نمایه شبیه نمایههاییست که اخیراً پیگرفتهاید.",
|
||||
"follow_suggestions.personalized_suggestion": "پیشنهاد شخصی",
|
||||
"follow_suggestions.popular_suggestion": "پیشنهاد محبوب",
|
||||
"follow_suggestions.popular_suggestion_longer": "محبوب روی {domain}",
|
||||
"follow_suggestions.similar_to_recently_followed_longer": "شبیه نمایههایی که اخیراً پی گرفتهاید",
|
||||
"follow_suggestions.view_all": "دیدن همه",
|
||||
"follow_suggestions.who_to_follow": "افرادی برای پیگیری",
|
||||
"followed_tags": "برچسبهای پیگرفته",
|
||||
|
@ -316,6 +349,14 @@
|
|||
"hashtag.follow": "پیگرفتن برچسب",
|
||||
"hashtag.unfollow": "پینگرفتن برچسب",
|
||||
"hashtags.and_other": "…و {count, plural, other {# بیشتر}}",
|
||||
"hints.profiles.followers_may_be_missing": "شاید پیگیرندگان این نمایه نباشند.",
|
||||
"hints.profiles.follows_may_be_missing": "شاید پیگرفتههای این نمایه نباشند.",
|
||||
"hints.profiles.posts_may_be_missing": "شاید فرستههایی از این نمایه نباشند.",
|
||||
"hints.profiles.see_more_followers": "دیدن پیگیرندگان بیشتر روی {domain}",
|
||||
"hints.profiles.see_more_follows": "دیدن پیگرفتههای بیشتر روی {domain}",
|
||||
"hints.profiles.see_more_posts": "دیدن فرستههای بیشتر روی {domain}",
|
||||
"hints.threads.replies_may_be_missing": "شاید پاسخها از دیگر کارسازها نباشند.",
|
||||
"hints.threads.see_more": "دیدن پاسخهای بیشتر روی {domain}",
|
||||
"home.column_settings.show_reblogs": "نمایش تقویتها",
|
||||
"home.column_settings.show_replies": "نمایش پاسخها",
|
||||
"home.hide_announcements": "نهفتن اعلامیهها",
|
||||
|
@ -323,6 +364,11 @@
|
|||
"home.pending_critical_update.link": "دیدن بهروز رسانیها",
|
||||
"home.pending_critical_update.title": "بهروز رسانی امنیتی بحرانی موجود است!",
|
||||
"home.show_announcements": "نمایش اعلامیهها",
|
||||
"ignore_notifications_modal.ignore": "چشمپوشی از آگاهیها",
|
||||
"ignore_notifications_modal.limited_accounts_title": "چشمپوشی از آگاهیهای حسابهای نظارت شده؟",
|
||||
"ignore_notifications_modal.new_accounts_title": "چشمپوشی از آگاهیهای حسابهای جدید؟",
|
||||
"ignore_notifications_modal.not_followers_title": "چشمپوشی از آگاهیهای افرادی که پیتان نمیگیرند؟",
|
||||
"ignore_notifications_modal.not_following_title": "چشمپوشی از آگاهیهای افرادی که پیشان نمیگیرید؟",
|
||||
"interaction_modal.description.favourite": "با حسابی روی ماستودون میتوانید این فرسته را برگزیده تا نگارنده بداند قدردانش هستید و برای آینده ذخیرهاش میکنید.",
|
||||
"interaction_modal.description.follow": "با حسابی روی ماستودون میتوانید {name} را برای دریافت فرستههایش در خوراک خانگیتان دنبال کنید.",
|
||||
"interaction_modal.description.reblog": "با حسابی روی ماستودون میتوانید این فرسته را با پیگیران خودتان همرسانی کنید.",
|
||||
|
@ -383,6 +429,8 @@
|
|||
"limited_account_hint.action": "به هر روی نمایه نشان داده شود",
|
||||
"limited_account_hint.title": "این نمایه از سوی ناظمهای {domain} پنهان شده.",
|
||||
"link_preview.author": "از {name}",
|
||||
"link_preview.more_from_author": "بیشتر از {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} فرسته} other {{counter} فرسته}}",
|
||||
"lists.account.add": "افزودن به سیاهه",
|
||||
"lists.account.remove": "برداشتن از سیاهه",
|
||||
"lists.delete": "حذف سیاهه",
|
||||
|
@ -399,11 +447,17 @@
|
|||
"lists.subheading": "سیاهههایتان",
|
||||
"load_pending": "{count, plural, one {# مورد جدید} other {# مورد جدید}}",
|
||||
"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_wont_know": "نخواهند دانست که خموش شدهاند.",
|
||||
"mute_modal.title": "خموشی کاربر؟",
|
||||
"mute_modal.you_wont_see_mentions": "فرستههایی که به او اشاره کردهاند را نخواهید دید.",
|
||||
"mute_modal.you_wont_see_posts": "هنوز میتوانند فرستههایتان را ببینند، ولی فرستههایشان را نمیبینید.",
|
||||
"navigation_bar.about": "درباره",
|
||||
"navigation_bar.administration": "مدیریت",
|
||||
"navigation_bar.advanced_interface": "بازکردن در رابط کاربری وب پیشرفته",
|
||||
"navigation_bar.blocks": "کاربران مسدود شده",
|
||||
"navigation_bar.bookmarks": "نشانکها",
|
||||
|
@ -420,6 +474,7 @@
|
|||
"navigation_bar.follows_and_followers": "پیگرفتگان و پیگیرندگان",
|
||||
"navigation_bar.lists": "سیاههها",
|
||||
"navigation_bar.logout": "خروج",
|
||||
"navigation_bar.moderation": "نظارت",
|
||||
"navigation_bar.mutes": "کاربران خموشانده",
|
||||
"navigation_bar.opened_in_classic_interface": "فرستهها، حسابها و دیگر صفحههای خاص به طور پیشگزیده در میانای وب کلاسیک گشوده میشوند.",
|
||||
"navigation_bar.personal": "شخصی",
|
||||
|
@ -430,26 +485,54 @@
|
|||
"navigation_bar.security": "امنیت",
|
||||
"not_signed_in_indicator.not_signed_in": "برای دسترسی به این منبع باید وارد شوید.",
|
||||
"notification.admin.report": "{name}، {target} را گزارش داد",
|
||||
"notification.admin.report_statuses_other": "{name}، {target} را گزارش داد",
|
||||
"notification.admin.sign_up": "{name} ثبت نام کرد",
|
||||
"notification.favourite": "{name} فرستهتان را برگزید",
|
||||
"notification.follow": "{name} پیگیرتان شد",
|
||||
"notification.follow_request": "{name} درخواست پیگیریتان را داد",
|
||||
"notification.label.mention": "اشاره",
|
||||
"notification.label.private_mention": "اشارهٔ خصوصی",
|
||||
"notification.label.private_reply": "پاسخ خصوصی",
|
||||
"notification.label.reply": "پاسخ",
|
||||
"notification.mention": "اشاره",
|
||||
"notification.moderation-warning.learn_more": "بیشتر بدانید",
|
||||
"notification.moderation_warning": "هشداری مدیریتی گرفتهاید",
|
||||
"notification.moderation_warning.action_delete_statuses": "برخی از فرستههایتان برداشته شدند.",
|
||||
"notification.moderation_warning.action_disable": "حسابتان از کار افتاد.",
|
||||
"notification.moderation_warning.action_mark_statuses_as_sensitive": "برخی از فرستههایتان به عنوان حسّاس علامت خوردند.",
|
||||
"notification.moderation_warning.action_none": "حسابتان هشداری مدیریتی گرفت.",
|
||||
"notification.moderation_warning.action_sensitive": "فرستههایتان از اکنون به عنوان حسّاس علامت خواهند خورد.",
|
||||
"notification.moderation_warning.action_silence": "حسابتان محدود شده.",
|
||||
"notification.moderation_warning.action_suspend": "حسابتان معلّق شده.",
|
||||
"notification.own_poll": "نظرسنجیتان پایان یافت",
|
||||
"notification.poll": "نظرسنجیای که در آن رأی دادید به پایان رسید",
|
||||
"notification.reblog": "{name} فرستهتان را تقویت کرد",
|
||||
"notification.relationships_severance_event": "قطع ارتباط با {name}",
|
||||
"notification.relationships_severance_event.learn_more": "بیشتر بدانید",
|
||||
"notification.status": "{name} چیزی فرستاد",
|
||||
"notification.update": "{name} فرستهای را ویرایش کرد",
|
||||
"notification_requests.accept": "پذیرش",
|
||||
"notification_requests.confirm_accept_multiple.title": "پذیرش درخواستهای آگاهی؟",
|
||||
"notification_requests.confirm_dismiss_multiple.title": "رد کردن درخواستهای آگاهی؟",
|
||||
"notification_requests.dismiss": "دورانداختن",
|
||||
"notification_requests.edit_selection": "ویرایش",
|
||||
"notification_requests.exit_selection": "انجام شد",
|
||||
"notification_requests.maximize": "بیشنه",
|
||||
"notification_requests.minimize_banner": "کمینه کردن بیرق آگاهیهای پالوده",
|
||||
"notification_requests.notifications_from": "آگاهیها از {name}",
|
||||
"notification_requests.title": "آگاهیهای پالوده",
|
||||
"notification_requests.view": "دیدن آگاهیها",
|
||||
"notifications.clear": "پاکسازی آگاهیها",
|
||||
"notifications.clear_confirmation": "مطمئنید میخواهید همهٔ آگاهیهایتان را برای همیشه پاک کنید؟",
|
||||
"notifications.clear_title": "پاکسازی آگاهیها؟",
|
||||
"notifications.column_settings.admin.report": "گزارشهای جدید:",
|
||||
"notifications.column_settings.admin.sign_up": "ثبت نامهای جدید:",
|
||||
"notifications.column_settings.alert": "آگاهیهای میزکار",
|
||||
"notifications.column_settings.beta.category": "ویژگیهای آزمایشی",
|
||||
"notifications.column_settings.beta.grouping": "گروهبندی آگاهیها",
|
||||
"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": "اشارهها:",
|
||||
|
@ -475,8 +558,19 @@
|
|||
"notifications.permission_denied": "آگاهیهای میزکار به دلیل رد کردن درخواست اجازهٔ پیشین مرورگر، در دسترس نیستند",
|
||||
"notifications.permission_denied_alert": "از آنجا که پیش از این اجازهٔ مرورگر رد شده است، آگاهیهای میزکار نمیتوانند به کار بیفتند",
|
||||
"notifications.permission_required": "آگاهیهای میزکار در دسترس نیستند زیرا اجازههای لازم، اعطا نشده.",
|
||||
"notifications.policy.accept": "پذیرش",
|
||||
"notifications.policy.accept_hint": "نمایش در آگاهیها",
|
||||
"notifications.policy.drop": "چشمپوشی",
|
||||
"notifications.policy.drop_hint": "فرستادن به هیچ. دیگر هرگز دیده نخواهند شد",
|
||||
"notifications.policy.filter": "پالایش",
|
||||
"notifications.policy.filter_hint": "فرستادن به صندوق آگاهیهای پالوده",
|
||||
"notifications.policy.filter_limited_accounts_hint": "محدود شده به دست ناظمهای کارساز",
|
||||
"notifications.policy.filter_limited_accounts_title": "حسابهای مدیریت شده",
|
||||
"notifications.policy.filter_new_accounts_title": "حسابهای جدید",
|
||||
"notifications.policy.filter_not_followers_title": "کسانی که شما را دنبال میکنند",
|
||||
"notifications.policy.filter_not_following_hint": "",
|
||||
"notifications.policy.filter_not_following_title": "کسانی که پی نمیگیرید",
|
||||
"notifications.policy.title": "مدیریت آگاهیها از…",
|
||||
"notifications_permission_banner.enable": "به کار انداختن آگاهیهای میزکار",
|
||||
"notifications_permission_banner.how_to_control": "برای دریافت آگاهیها هنگام باز نبودن ماستودون، آگاهیهای میزکار را به کار بیندازید. پس از به کار افتادنشان میتوانید گونههای دقیق برهمکنشهایی که آگاهیهای میزکار تولید میکنند را از {icon} بالا واپایید.",
|
||||
"notifications_permission_banner.title": "هرگز چیزی را از دست ندهید",
|
||||
|
@ -609,6 +703,7 @@
|
|||
"report_notification.categories.spam": "هرزنامه",
|
||||
"report_notification.categories.spam_sentence": "هرزنامه",
|
||||
"report_notification.categories.violation": "تخطّی از قانون",
|
||||
"report_notification.categories.violation_sentence": "تخطّی از قانون",
|
||||
"report_notification.open": "گشودن گزارش",
|
||||
"search.no_recent_searches": "جستوجوی اخیری نیست",
|
||||
"search.placeholder": "جستوجو",
|
||||
|
@ -653,9 +748,11 @@
|
|||
"status.direct": "اشارهٔ خصوصی به @{name}",
|
||||
"status.direct_indicator": "اشارهٔ خصوصی",
|
||||
"status.edit": "ویرایش",
|
||||
"status.edited": "آخرین ویرایش {date}",
|
||||
"status.edited_x_times": "{count, plural, one {{count} مرتبه} other {{count} مرتبه}} ویرایش شد",
|
||||
"status.embed": "جاسازی",
|
||||
"status.favourite": "برگزیده",
|
||||
"status.favourites": "{count, plural, one {برگزیده} other {برگزیده}}",
|
||||
"status.filter": "پالایش این فرسته",
|
||||
"status.history.created": "توسط {name} در {date} ایجاد شد",
|
||||
"status.history.edited": "توسط {name} در {date} ویرایش شد",
|
||||
|
@ -674,6 +771,7 @@
|
|||
"status.reblog": "تقویت",
|
||||
"status.reblog_private": "تقویت برای مخاطبان نخستین",
|
||||
"status.reblogged_by": "{name} تقویت کرد",
|
||||
"status.reblogs": "{count, plural, one {تقویت} other {تقویت}}",
|
||||
"status.reblogs.empty": "هنوز هیچ کسی این فرسته را تقویت نکرده است. وقتی کسی چنین کاری کند، اینجا نمایش داده خواهد شد.",
|
||||
"status.redraft": "حذف و بازنویسی",
|
||||
"status.remove_bookmark": "برداشتن نشانک",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"about.blocks": "Moderoidut palvelimet",
|
||||
"about.contact": "Yhteystiedot:",
|
||||
"about.contact": "Yhteydenotto:",
|
||||
"about.disclaimer": "Mastodon on vapaa avoimen lähdekoodin ohjelmisto ja Mastodon gGmbH:n tavaramerkki.",
|
||||
"about.domain_blocks.no_reason_available": "Syy ei ole tiedossa",
|
||||
"about.domain_blocks.preamble": "Mastodonin avulla voi yleensä tarkastella minkä tahansa fediversumiin kuuluvan palvelimen sisältöä ja olla yhteyksissä eri palvelinten käyttäjien kanssa. Nämä poikkeukset koskevat yksin tätä palvelinta.",
|
||||
|
@ -457,7 +457,7 @@
|
|||
"lists.subheading": "Omat listasi",
|
||||
"load_pending": "{count, plural, one {# uusi kohde} other {# uutta kohdetta}}",
|
||||
"loading_indicator.label": "Ladataan…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Piilota kuva} other {Piilota kuvat}}",
|
||||
"media_gallery.hide": "Piilota",
|
||||
"moved_to_account_banner.text": "Tilisi {disabledAccount} on tällä hetkellä poissa käytöstä, koska teit siirron tiliin {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Piilota ilmoituksista",
|
||||
"mute_modal.hide_options": "Piilota vaihtoehdot",
|
||||
|
@ -780,6 +780,7 @@
|
|||
"status.bookmark": "Lisää kirjanmerkki",
|
||||
"status.cancel_reblog_private": "Peru tehostus",
|
||||
"status.cannot_reblog": "Tätä julkaisua ei voi tehostaa",
|
||||
"status.continued_thread": "Jatkoi ketjua",
|
||||
"status.copy": "Kopioi linkki julkaisuun",
|
||||
"status.delete": "Poista",
|
||||
"status.detailed_status": "Yksityiskohtainen keskustelunäkymä",
|
||||
|
@ -813,6 +814,7 @@
|
|||
"status.reblogs.empty": "Kukaan ei ole vielä tehostanut tätä julkaisua. Kun joku tekee niin, tulee hän tähän näkyviin.",
|
||||
"status.redraft": "Poista ja palauta muokattavaksi",
|
||||
"status.remove_bookmark": "Poista kirjanmerkki",
|
||||
"status.replied_in_thread": "Vastasi ketjuun",
|
||||
"status.replied_to": "Vastaus käyttäjälle {name}",
|
||||
"status.reply": "Vastaa",
|
||||
"status.replyAll": "Vastaa ketjuun",
|
||||
|
|
|
@ -457,7 +457,7 @@
|
|||
"lists.subheading": "Tínir listar",
|
||||
"load_pending": "{count, plural, one {# nýtt evni} other {# nýggj evni}}",
|
||||
"loading_indicator.label": "Innlesur…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Fjal mynd} other {Fjal myndir}}",
|
||||
"media_gallery.hide": "Fjal",
|
||||
"moved_to_account_banner.text": "Konta tín {disabledAccount} er í løtuni óvirkin, tí tú flutti til {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Fjal boð",
|
||||
"mute_modal.hide_options": "Fjal valmøguleikar",
|
||||
|
@ -780,6 +780,7 @@
|
|||
"status.bookmark": "Goym",
|
||||
"status.cancel_reblog_private": "Strika stimbran",
|
||||
"status.cannot_reblog": "Tað ber ikki til at stimbra hendan postin",
|
||||
"status.continued_thread": "Framhaldandi tráður",
|
||||
"status.copy": "Kopiera leinki til postin",
|
||||
"status.delete": "Strika",
|
||||
"status.detailed_status": "Útgreinað samrøðusýni",
|
||||
|
@ -813,6 +814,7 @@
|
|||
"status.reblogs.empty": "Eingin hevur stimbrað hendan postin enn. Tá onkur stimbrar postin, verður hann sjónligur her.",
|
||||
"status.redraft": "Strika & ger nýggja kladdu",
|
||||
"status.remove_bookmark": "Gloym",
|
||||
"status.replied_in_thread": "Svaraði í tráðnum",
|
||||
"status.replied_to": "Svaraði {name}",
|
||||
"status.reply": "Svara",
|
||||
"status.replyAll": "Svara tráðnum",
|
||||
|
|
|
@ -456,7 +456,6 @@
|
|||
"lists.subheading": "Vos listes",
|
||||
"load_pending": "{count, plural, one {# nouvel élément} other {# nouveaux éléments}}",
|
||||
"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",
|
||||
|
|
|
@ -456,7 +456,6 @@
|
|||
"lists.subheading": "Vos listes",
|
||||
"load_pending": "{count, plural, one {# nouvel élément} other {# nouveaux éléments}}",
|
||||
"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",
|
||||
|
|
|
@ -457,7 +457,6 @@
|
|||
"lists.subheading": "Jo listen",
|
||||
"load_pending": "{count, plural, one {# nij item} other {# nije items}}",
|
||||
"loading_indicator.label": "Lade…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {ôfbylding ferstopje} other {ôfbyldingen ferstopje}}",
|
||||
"moved_to_account_banner.text": "Omdat jo nei {movedToAccount} ferhuze binne is jo account {disabledAccount} op dit stuit útskeakele.",
|
||||
"mute_modal.hide_from_notifications": "Meldingen ferstopje",
|
||||
"mute_modal.hide_options": "Opsjes ferstopje",
|
||||
|
|
|
@ -457,7 +457,6 @@
|
|||
"lists.subheading": "Do liostaí",
|
||||
"load_pending": "{count, plural, one {# mír nua} two {# mír nua} few {# mír nua} many {# mír nua} other {# mír nua}}",
|
||||
"loading_indicator.label": "Á lódáil…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Folaigh íomhá} two {Folaigh íomhánna} few {Folaigh íomhánna} many {Folaigh íomhánna} other {Folaigh íomhánna}}",
|
||||
"moved_to_account_banner.text": "Tá do chuntas {disabledAccount} díchumasaithe faoi láthair toisc gur bhog tú go {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Folaigh ó fhógraí",
|
||||
"mute_modal.hide_options": "Folaigh roghanna",
|
||||
|
|
|
@ -457,7 +457,6 @@
|
|||
"lists.subheading": "Na liostaichean agad",
|
||||
"load_pending": "{count, plural, one {# nì ùr} two {# nì ùr} few {# nithean ùra} other {# nì ùr}}",
|
||||
"loading_indicator.label": "’Ga luchdadh…",
|
||||
"media_gallery.toggle_visible": "{number, plural, 1 {Falaich an dealbh} one {Falaich na dealbhan} two {Falaich na dealbhan} few {Falaich na dealbhan} other {Falaich na dealbhan}}",
|
||||
"moved_to_account_banner.text": "Tha an cunntas {disabledAccount} agad à comas on a rinn thu imrich gu {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Falaich o na brathan",
|
||||
"mute_modal.hide_options": "Roghainnean falaich",
|
||||
|
|
|
@ -223,7 +223,7 @@
|
|||
"domain_block_modal.title": "Bloquear dominio?",
|
||||
"domain_block_modal.you_will_lose_followers": "Vanse eliminar todas as túas seguidoras deste servidor.",
|
||||
"domain_block_modal.you_wont_see_posts": "Non verás publicacións ou notificacións das usuarias deste servidor.",
|
||||
"domain_pill.activitypub_lets_connect": "Permíteche conectar e interactuar con persoas non só de Mastodon, se non tamén con outras apps sociais.",
|
||||
"domain_pill.activitypub_lets_connect": "Permíteche conectar e interactuar con persoas non só de Mastodon, se non tamén con outras sociais.",
|
||||
"domain_pill.activitypub_like_language": "ActivityPub é algo así como o idioma que Mastodon fala con outras redes sociais.",
|
||||
"domain_pill.server": "Servidor",
|
||||
"domain_pill.their_handle": "O seu alcume:",
|
||||
|
@ -231,8 +231,8 @@
|
|||
"domain_pill.their_username": "O seu identificador único no seu servidor. É posible atopar usuarias co mesmo nome de usuaria en diferentes servidores.",
|
||||
"domain_pill.username": "Nome de usuaria",
|
||||
"domain_pill.whats_in_a_handle": "As partes do alcume?",
|
||||
"domain_pill.who_they_are": "O alcume dinos quen é esa persoa e onde está, para que poidas interactuar con ela en toda a web social de <button>plataformas ActivityPub</button>.",
|
||||
"domain_pill.who_you_are": "Como o teu alcume informa de quen es e onde estás, as persoas poden interactuar contigo desde toda a web social de <button>plataformas ActivityPub</button>.",
|
||||
"domain_pill.who_they_are": "O alcume dinos quen é esa persoa e onde está, para que poidas interactuar con ela en toda a web social das <button>plataformas ActivityPub</button>.",
|
||||
"domain_pill.who_you_are": "Como o teu alcume informa de quen es e onde estás, as persoas poden interactuar contigo desde toda a web social das <button>plataformas ActivityPub</button>.",
|
||||
"domain_pill.your_handle": "O teu alcume:",
|
||||
"domain_pill.your_server": "O teu fogar dixital, onde están as túas publicacións. Non é do teu agrado? Podes cambiar de servidor cando queiras levando as túas seguidoras contigo.",
|
||||
"domain_pill.your_username": "O teu identificador único neste servidor. É posible que atopes usuarias co mesmo nome de usuaria en outros servidores.",
|
||||
|
@ -272,7 +272,7 @@
|
|||
"empty_column.list": "Aínda non hai nada nesta listaxe. Cando as usuarias incluídas na listaxe publiquen mensaxes, amosaranse aquí.",
|
||||
"empty_column.lists": "Aínda non tes listaxes. Cando crees unha, amosarase aquí.",
|
||||
"empty_column.mutes": "Aínda non silenciaches a ningúnha usuaria.",
|
||||
"empty_column.notification_requests": "Todo ben! Nada por aquí. Cando recibas novas notificación aparecerán aquí seguindo o criterio dos teus axustes.",
|
||||
"empty_column.notification_requests": "Todo ben! Nada por aquí. Cando recibas novas notificacións aparecerán aquí seguindo o criterio dos teus axustes.",
|
||||
"empty_column.notifications": "Aínda non tes notificacións. Aparecerán cando outras persoas interactúen contigo.",
|
||||
"empty_column.public": "Nada por aquí! Escribe algo de xeito público, ou segue de xeito manual usuarias doutros servidores para ir enchéndoo",
|
||||
"error.unexpected_crash.explanation": "Debido a un erro no noso código ou a unha compatilidade co teu navegador, esta páxina non pode ser amosada correctamente.",
|
||||
|
@ -315,7 +315,7 @@
|
|||
"follow_suggestions.curated_suggestion": "Suxestións do Servidor",
|
||||
"follow_suggestions.dismiss": "Non mostrar máis",
|
||||
"follow_suggestions.featured_longer": "Elección persoal do equipo de {domain}",
|
||||
"follow_suggestions.friends_of_friends_longer": "Popular entre as persoas que sigues",
|
||||
"follow_suggestions.friends_of_friends_longer": "Popular entre as persoas que segues",
|
||||
"follow_suggestions.hints.featured": "Este perfil foi escollido pola administración de {domain}.",
|
||||
"follow_suggestions.hints.friends_of_friends": "Este perfil é popular entre as persoas que segues.",
|
||||
"follow_suggestions.hints.most_followed": "Este perfil é un dos máis seguidos en {domain}.",
|
||||
|
@ -457,9 +457,9 @@
|
|||
"lists.subheading": "As túas listaxes",
|
||||
"load_pending": "{count, plural, one {# novo elemento} other {# novos elementos}}",
|
||||
"loading_indicator.label": "Estase a cargar…",
|
||||
"media_gallery.toggle_visible": "Agochar {number, plural, one {imaxe} other {imaxes}}",
|
||||
"media_gallery.hide": "Agochar",
|
||||
"moved_to_account_banner.text": "A túa conta {disabledAccount} está actualmente desactivada porque movéchela a {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Ocultar nas notificacións",
|
||||
"mute_modal.hide_from_notifications": "Agochar nas notificacións",
|
||||
"mute_modal.hide_options": "Opcións ao ocultar",
|
||||
"mute_modal.indefinite": "Ata que as reactive",
|
||||
"mute_modal.show_options": "Mostrar opcións",
|
||||
|
@ -641,7 +641,7 @@
|
|||
"onboarding.steps.publish_status.title": "Escribe a túa primeira publicación",
|
||||
"onboarding.steps.setup_profile.body": "Ao engadir información ao teu perfil é máis probable que teñas máis interaccións.",
|
||||
"onboarding.steps.setup_profile.title": "Personaliza o perfil",
|
||||
"onboarding.steps.share_profile.body": "Dille ás amizades como poden atoparte en Mastodon!",
|
||||
"onboarding.steps.share_profile.body": "Dille ás amizades como poden atoparte en Mastodon.",
|
||||
"onboarding.steps.share_profile.title": "Comparte o teu perfil en Mastodon",
|
||||
"onboarding.tips.2fa": "<strong>Sabes que?</strong> Podes protexer a túa conta configurando un segundo factor de autenticación nos axustes. Funciona con calquera app TOTP, non precisas un número de teléfono!",
|
||||
"onboarding.tips.accounts_from_other_servers": "<strong>Sabes que?</strong> Como Mastodon é descentralizado, algúns perfís que atopes estarán en servidores diferentes ao teu. Pero podes interactuar igualmente con eles! O seu servidor é o que ven despois da @ no seu identificador!",
|
||||
|
@ -780,6 +780,7 @@
|
|||
"status.bookmark": "Marcar",
|
||||
"status.cancel_reblog_private": "Desfacer compartido",
|
||||
"status.cannot_reblog": "Esta publicación non pode ser promovida",
|
||||
"status.continued_thread": "Continua co fío",
|
||||
"status.copy": "Copiar ligazón á publicación",
|
||||
"status.delete": "Eliminar",
|
||||
"status.detailed_status": "Vista detallada da conversa",
|
||||
|
@ -813,6 +814,7 @@
|
|||
"status.reblogs.empty": "Aínda ninguén promoveu esta publicación. Cando alguén o faga, amosarase aquí.",
|
||||
"status.redraft": "Eliminar e reescribir",
|
||||
"status.remove_bookmark": "Eliminar marcador",
|
||||
"status.replied_in_thread": "Respondeu nun fío",
|
||||
"status.replied_to": "Respondeu a {name}",
|
||||
"status.reply": "Responder",
|
||||
"status.replyAll": "Responder ao tema",
|
||||
|
|
|
@ -457,7 +457,7 @@
|
|||
"lists.subheading": "הרשימות שלך",
|
||||
"load_pending": "{count, plural, one {# פריט חדש} other {# פריטים חדשים}}",
|
||||
"loading_indicator.label": "בטעינה…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {להסתיר תמונה} two {להסתיר תמונותיים} many {להסתיר תמונות} other {להסתיר תמונות}}",
|
||||
"media_gallery.hide": "להסתיר",
|
||||
"moved_to_account_banner.text": "חשבונך {disabledAccount} אינו פעיל כרגע עקב מעבר ל{movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "להסתיר מהתראות",
|
||||
"mute_modal.hide_options": "הסתרת אפשרויות",
|
||||
|
@ -780,6 +780,7 @@
|
|||
"status.bookmark": "סימניה",
|
||||
"status.cancel_reblog_private": "הסרת הדהוד",
|
||||
"status.cannot_reblog": "לא ניתן להדהד חצרוץ זה",
|
||||
"status.continued_thread": "שרשור מתמשך",
|
||||
"status.copy": "העתק/י קישור להודעה זו",
|
||||
"status.delete": "מחיקה",
|
||||
"status.detailed_status": "תצוגת שיחה מפורטת",
|
||||
|
@ -813,6 +814,7 @@
|
|||
"status.reblogs.empty": "עוד לא הידהדו את ההודעה הזו. כאשר זה יקרה, ההדהודים יופיעו כאן.",
|
||||
"status.redraft": "מחיקה ועריכה מחדש",
|
||||
"status.remove_bookmark": "הסרת סימניה",
|
||||
"status.replied_in_thread": "תגובה לשרשור",
|
||||
"status.replied_to": "בתגובה לחשבון {name}",
|
||||
"status.reply": "תגובה",
|
||||
"status.replyAll": "תגובה לשרשור",
|
||||
|
|
|
@ -367,6 +367,7 @@
|
|||
"lists.replies_policy.none": "कोई नहीं",
|
||||
"lists.replies_policy.title": "इसके जवाब दिखाएं:",
|
||||
"lists.subheading": "आपकी सूचियाँ",
|
||||
"media_gallery.hide": "छिपाएं",
|
||||
"navigation_bar.about": "विवरण",
|
||||
"navigation_bar.blocks": "ब्लॉक्ड यूज़र्स",
|
||||
"navigation_bar.bookmarks": "पुस्तकचिह्न:",
|
||||
|
|
|
@ -310,7 +310,6 @@
|
|||
"lists.replies_policy.none": "Nitko",
|
||||
"lists.search": "Traži među praćenim ljudima",
|
||||
"lists.subheading": "Vaše liste",
|
||||
"media_gallery.toggle_visible": "Sakrij {number, plural, one {sliku} other {slike}}",
|
||||
"navigation_bar.about": "O aplikaciji",
|
||||
"navigation_bar.advanced_interface": "Otvori u naprednom web sučelju",
|
||||
"navigation_bar.blocks": "Blokirani korisnici",
|
||||
|
|
|
@ -457,7 +457,7 @@
|
|||
"lists.subheading": "Saját listák",
|
||||
"load_pending": "{count, plural, one {# új elem} other {# új elem}}",
|
||||
"loading_indicator.label": "Betöltés…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Kép elrejtése} other {Képek elrejtése}}",
|
||||
"media_gallery.hide": "Elrejtés",
|
||||
"moved_to_account_banner.text": "A(z) {disabledAccount} fiókod jelenleg le van tiltva, mert átköltöztél ide: {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Elrejtés az értesítések közül",
|
||||
"mute_modal.hide_options": "Beállítások elrejtése",
|
||||
|
@ -780,6 +780,7 @@
|
|||
"status.bookmark": "Könyvjelzőzés",
|
||||
"status.cancel_reblog_private": "Megtolás visszavonása",
|
||||
"status.cannot_reblog": "Ezt a bejegyzést nem lehet megtolni",
|
||||
"status.continued_thread": "Folytatott szál",
|
||||
"status.copy": "Link másolása bejegyzésbe",
|
||||
"status.delete": "Törlés",
|
||||
"status.detailed_status": "Részletes beszélgetési nézet",
|
||||
|
@ -813,6 +814,7 @@
|
|||
"status.reblogs.empty": "Senki sem tolta még meg ezt a bejegyzést. Ha valaki megteszi, itt fog megjelenni.",
|
||||
"status.redraft": "Törlés és újraírás",
|
||||
"status.remove_bookmark": "Könyvjelző eltávolítása",
|
||||
"status.replied_in_thread": "Válaszolva a szálban",
|
||||
"status.replied_to": "Megválaszolva {name} számára",
|
||||
"status.reply": "Válasz",
|
||||
"status.replyAll": "Válasz a beszélgetésre",
|
||||
|
|
|
@ -289,7 +289,6 @@
|
|||
"lists.search": "Փնտրել քո հետեւած մարդկանց մէջ",
|
||||
"lists.subheading": "Քո ցանկերը",
|
||||
"load_pending": "{count, plural, one {# նոր նիւթ} other {# նոր նիւթ}}",
|
||||
"media_gallery.toggle_visible": "Ցուցադրել/թաքցնել",
|
||||
"navigation_bar.about": "Մասին",
|
||||
"navigation_bar.blocks": "Արգելափակուած օգտատէրեր",
|
||||
"navigation_bar.bookmarks": "Էջանիշեր",
|
||||
|
|
|
@ -180,6 +180,7 @@
|
|||
"confirmations.discard_edit_media.message": "Tu ha cambiamentos non salvate in le description o previsualisation del objecto multimedial. Abandonar los?",
|
||||
"confirmations.edit.confirm": "Modificar",
|
||||
"confirmations.edit.message": "Si tu modifica isto ora, le message in curso de composition essera perdite. Es tu secur de voler continuar?",
|
||||
"confirmations.edit.title": "Superscriber le message?",
|
||||
"confirmations.logout.confirm": "Clauder session",
|
||||
"confirmations.logout.message": "Es tu secur que tu vole clauder le session?",
|
||||
"confirmations.logout.title": "Clauder session?",
|
||||
|
@ -189,6 +190,7 @@
|
|||
"confirmations.redraft.title": "Deler e rescriber le message?",
|
||||
"confirmations.reply.confirm": "Responder",
|
||||
"confirmations.reply.message": "Si tu responde ora, le message in curso de composition essera perdite. Es tu secur de voler continuar?",
|
||||
"confirmations.reply.title": "Superscriber le message?",
|
||||
"confirmations.unfollow.confirm": "Non plus sequer",
|
||||
"confirmations.unfollow.message": "Es tu secur que tu vole cessar de sequer {name}?",
|
||||
"confirmations.unfollow.title": "Cessar de sequer le usator?",
|
||||
|
@ -361,6 +363,12 @@
|
|||
"home.pending_critical_update.link": "Vider actualisationes",
|
||||
"home.pending_critical_update.title": "Actualisation de securitate critic disponibile!",
|
||||
"home.show_announcements": "Monstrar annuncios",
|
||||
"ignore_notifications_modal.ignore": "Ignorar le notificationes",
|
||||
"ignore_notifications_modal.limited_accounts_title": "Ignorar le notificationes de contos moderate?",
|
||||
"ignore_notifications_modal.new_accounts_title": "Ignorar le notificationes de nove contos?",
|
||||
"ignore_notifications_modal.not_followers_title": "Ignorar notificationes de personas qui non te seque?",
|
||||
"ignore_notifications_modal.not_following_title": "Ignorar notificationes de personas que tu non seque?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Ignorar notificationes de mentiones private non requestate?",
|
||||
"interaction_modal.description.favourite": "Con un conto sur Mastodon, tu pote marcar iste message como favorite pro informar le autor que tu lo apprecia e lo salva pro plus tarde.",
|
||||
"interaction_modal.description.follow": "Con un conto sur Mastodon, tu pote sequer {name} e reciper su messages in tu fluxo de initio.",
|
||||
"interaction_modal.description.reblog": "Con un conto sur Mastodon, tu pote impulsar iste message pro condivider lo con tu proprie sequitores.",
|
||||
|
@ -439,7 +447,7 @@
|
|||
"lists.subheading": "Tu listas",
|
||||
"load_pending": "{count, plural, one {# nove entrata} other {# nove entratas}}",
|
||||
"loading_indicator.label": "Cargante…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Celar imagine} other {Celar imagines}}",
|
||||
"media_gallery.hide": "Celar",
|
||||
"moved_to_account_banner.text": "Tu conto {disabledAccount} es actualmente disactivate perque tu ha cambiate de conto a {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Celar in notificationes",
|
||||
"mute_modal.hide_options": "Celar optiones",
|
||||
|
@ -479,6 +487,8 @@
|
|||
"navigation_bar.security": "Securitate",
|
||||
"not_signed_in_indicator.not_signed_in": "Es necessari aperir session pro acceder a iste ressource.",
|
||||
"notification.admin.report": "{name} ha reportate {target}",
|
||||
"notification.admin.report_account": "{name} ha reportate {count, plural, one {un message} other {# messages}} de {target} per {category}",
|
||||
"notification.admin.report_account_other": "{name} ha reportate {count, plural, one {un message} other {# messages}} de {target}",
|
||||
"notification.admin.report_statuses": "{name} ha reportate {target} pro {category}",
|
||||
"notification.admin.report_statuses_other": "{name} ha reportate {target}",
|
||||
"notification.admin.sign_up": "{name} se ha inscribite",
|
||||
|
@ -774,6 +784,7 @@
|
|||
"status.reblogs.empty": "Necuno ha ancora impulsate iste message. Quando alcuno lo face, le impulsos apparera hic.",
|
||||
"status.redraft": "Deler e reconciper",
|
||||
"status.remove_bookmark": "Remover marcapagina",
|
||||
"status.replied_in_thread": "Respondite in le discussion",
|
||||
"status.replied_to": "Respondite a {name}",
|
||||
"status.reply": "Responder",
|
||||
"status.replyAll": "Responder al discussion",
|
||||
|
|
|
@ -403,7 +403,6 @@
|
|||
"lists.subheading": "Daftar Anda",
|
||||
"load_pending": "{count, plural, other {# item baru}}",
|
||||
"loading_indicator.label": "Memuat…",
|
||||
"media_gallery.toggle_visible": "Tampil/Sembunyikan",
|
||||
"moved_to_account_banner.text": "Akun {disabledAccount} Anda kini dinonaktifkan karena Anda pindah ke {movedToAccount}.",
|
||||
"mute_modal.hide_options": "Sembunyikan opsi",
|
||||
"mute_modal.title": "Bisukan pengguna?",
|
||||
|
|
|
@ -419,7 +419,6 @@
|
|||
"lists.subheading": "Tui listes",
|
||||
"load_pending": "{count, plural, one {# nov element} other {# nov elementes}}",
|
||||
"loading_indicator.label": "Cargant…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Celar image} other {Celar images}}",
|
||||
"moved_to_account_banner.text": "Tui conto {disabledAccount} es actualmen desactivisat pro que tu movet te a {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Celar de notificationes",
|
||||
"mute_modal.hide_options": "Celar optiones",
|
||||
|
|
|
@ -17,7 +17,7 @@ function onProviderError(error: unknown) {
|
|||
error &&
|
||||
typeof error === 'object' &&
|
||||
error instanceof Error &&
|
||||
error.message.match('MISSING_DATA')
|
||||
/MISSING_DATA/.exec(error.message)
|
||||
) {
|
||||
console.warn(error.message);
|
||||
}
|
||||
|
|
|
@ -362,7 +362,6 @@
|
|||
"lists.subheading": "Vua listi",
|
||||
"load_pending": "{count, plural, one {# nova kozo} other {# nova kozi}}",
|
||||
"loading_indicator.label": "Kargante…",
|
||||
"media_gallery.toggle_visible": "Chanjar videbleso",
|
||||
"moved_to_account_banner.text": "Vua konto {disabledAccount} es nune desaktiva pro ke vu movis a {movedToAccount}.",
|
||||
"navigation_bar.about": "Pri co",
|
||||
"navigation_bar.advanced_interface": "Apertez per retintervizajo",
|
||||
|
|
|
@ -457,7 +457,7 @@
|
|||
"lists.subheading": "Listarnir þínir",
|
||||
"load_pending": "{count, plural, one {# nýtt atriði} other {# ný atriði}}",
|
||||
"loading_indicator.label": "Hleð inn…",
|
||||
"media_gallery.toggle_visible": "Víxla sýnileika",
|
||||
"media_gallery.hide": "Fela",
|
||||
"moved_to_account_banner.text": "Aðgangurinn þinn {disabledAccount} er óvirkur í augnablikinu vegna þess að þú fluttir þig yfir á {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Fela úr tilkynningum",
|
||||
"mute_modal.hide_options": "Fela valkosti",
|
||||
|
@ -780,6 +780,7 @@
|
|||
"status.bookmark": "Bókamerki",
|
||||
"status.cancel_reblog_private": "Taka úr endurbirtingu",
|
||||
"status.cannot_reblog": "Þessa færslu er ekki hægt að endurbirta",
|
||||
"status.continued_thread": "Hélt samtali áfram",
|
||||
"status.copy": "Afrita tengil í færslu",
|
||||
"status.delete": "Eyða",
|
||||
"status.detailed_status": "Nákvæm spjallþráðasýn",
|
||||
|
@ -813,6 +814,7 @@
|
|||
"status.reblogs.empty": "Enginn hefur ennþá endurbirt þessa færslu. Þegar einhver gerir það, mun það birtast hér.",
|
||||
"status.redraft": "Eyða og endurvinna drög",
|
||||
"status.remove_bookmark": "Fjarlægja bókamerki",
|
||||
"status.replied_in_thread": "Svaraði í samtali",
|
||||
"status.replied_to": "Svaraði til {name}",
|
||||
"status.reply": "Svara",
|
||||
"status.replyAll": "Svara þræði",
|
||||
|
|
|
@ -457,7 +457,7 @@
|
|||
"lists.subheading": "Le tue liste",
|
||||
"load_pending": "{count, plural, one {# nuovo oggetto} other {# nuovi oggetti}}",
|
||||
"loading_indicator.label": "Caricamento…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Nascondi immagine} other {Nascondi immagini}}",
|
||||
"media_gallery.hide": "Nascondi",
|
||||
"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",
|
||||
|
|
|
@ -555,7 +555,6 @@
|
|||
"lists.subheading": "あなたのリスト",
|
||||
"load_pending": "{count}件の新着",
|
||||
"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": "オプションを閉じる",
|
||||
|
|
|
@ -153,7 +153,6 @@
|
|||
"lists.new.title_placeholder": "ახალი სიის სათაური",
|
||||
"lists.search": "ძებნა ადამიანებს შორის რომელთაც მიჰყვებით",
|
||||
"lists.subheading": "თქვენი სიები",
|
||||
"media_gallery.toggle_visible": "ხილვადობის ჩართვა",
|
||||
"navigation_bar.blocks": "დაბლოკილი მომხმარებლები",
|
||||
"navigation_bar.community_timeline": "ლოკალური თაიმლაინი",
|
||||
"navigation_bar.compose": "Compose new toot",
|
||||
|
|
|
@ -351,7 +351,6 @@
|
|||
"lists.subheading": "Tibdarin-ik·im",
|
||||
"load_pending": "{count, plural, one {# n uferdis amaynut} other {# n yiferdisen imaynuten}}",
|
||||
"loading_indicator.label": "Yessalay-d …",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Ffer tugna} other {Ffer tugniwin}}",
|
||||
"mute_modal.hide_from_notifications": "Ffer-it deg ulɣuten",
|
||||
"mute_modal.hide_options": "Ffer tinefrunin",
|
||||
"mute_modal.indefinite": "Alamma ssnesreɣ asgugem fell-as",
|
||||
|
|
|
@ -222,7 +222,6 @@
|
|||
"lists.search": "Сіз іздеген адамдар арасында іздеу",
|
||||
"lists.subheading": "Тізімдеріңіз",
|
||||
"load_pending": "{count, plural, one {# жаңа нәрсе} other {# жаңа нәрсе}}",
|
||||
"media_gallery.toggle_visible": "Көрінуді қосу",
|
||||
"navigation_bar.blocks": "Бұғатталғандар",
|
||||
"navigation_bar.bookmarks": "Бетбелгілер",
|
||||
"navigation_bar.community_timeline": "Жергілікті желі",
|
||||
|
|
|
@ -34,9 +34,9 @@
|
|||
"account.follow_back": "맞팔로우 하기",
|
||||
"account.followers": "팔로워",
|
||||
"account.followers.empty": "아직 아무도 이 사용자를 팔로우하고 있지 않습니다.",
|
||||
"account.followers_counter": "{count, plural, other {{counter} 팔로워}}",
|
||||
"account.followers_counter": "{count, plural, other {팔로워 {counter}명}}",
|
||||
"account.following": "팔로잉",
|
||||
"account.following_counter": "{count, plural, other {{counter} 팔로잉}}",
|
||||
"account.following_counter": "{count, plural, other {팔로잉 {counter}명}}",
|
||||
"account.follows.empty": "이 사용자는 아직 아무도 팔로우하고 있지 않습니다.",
|
||||
"account.go_to_profile": "프로필로 이동",
|
||||
"account.hide_reblogs": "@{name}의 부스트를 숨기기",
|
||||
|
@ -62,7 +62,7 @@
|
|||
"account.requested_follow": "{name} 님이 팔로우 요청을 보냈습니다",
|
||||
"account.share": "@{name}의 프로필 공유",
|
||||
"account.show_reblogs": "@{name}의 부스트 보기",
|
||||
"account.statuses_counter": "{count, plural, other {{counter} 게시물}}",
|
||||
"account.statuses_counter": "{count, plural, other {게시물 {counter}개}}",
|
||||
"account.unblock": "차단 해제",
|
||||
"account.unblock_domain": "도메인 {domain} 차단 해제",
|
||||
"account.unblock_short": "차단 해제",
|
||||
|
@ -156,7 +156,7 @@
|
|||
"compose_form.placeholder": "지금 무슨 생각을 하고 있나요?",
|
||||
"compose_form.poll.duration": "투표 기간",
|
||||
"compose_form.poll.multiple": "다중 선택",
|
||||
"compose_form.poll.option_placeholder": "{option}번째 항목",
|
||||
"compose_form.poll.option_placeholder": "{number}번째 옵션",
|
||||
"compose_form.poll.single": "단일 선택",
|
||||
"compose_form.poll.switch_to_multiple": "다중 선택이 가능한 투표로 변경",
|
||||
"compose_form.poll.switch_to_single": "단일 선택 투표로 변경",
|
||||
|
@ -347,12 +347,12 @@
|
|||
"hashtag.column_settings.tag_mode.any": "어느것이든",
|
||||
"hashtag.column_settings.tag_mode.none": "이것들을 제외하고",
|
||||
"hashtag.column_settings.tag_toggle": "추가 해시태그를 이 컬럼에 추가합니다",
|
||||
"hashtag.counter_by_accounts": "{count, plural, other {{counter} 명의 참여자}}",
|
||||
"hashtag.counter_by_uses": "{count, plural, other {{counter} 개의 게시물}}",
|
||||
"hashtag.counter_by_uses_today": "오늘 {count, plural, other {{counter} 개의 게시물}}",
|
||||
"hashtag.follow": "팔로우",
|
||||
"hashtag.unfollow": "팔로우 해제",
|
||||
"hashtags.and_other": "…그리고 {count, plural,other {# 개 더}}",
|
||||
"hashtag.counter_by_accounts": "{count, plural, other {참여자 {counter}명}}",
|
||||
"hashtag.counter_by_uses": "{count, plural, other {게시물 {counter}개}}",
|
||||
"hashtag.counter_by_uses_today": "금일 {count, plural, other {게시물 {counter}개}}",
|
||||
"hashtag.follow": "해시태그 팔로우",
|
||||
"hashtag.unfollow": "해시태그 팔로우 해제",
|
||||
"hashtags.and_other": "…및 {count, plural,other {#개}}",
|
||||
"hints.profiles.followers_may_be_missing": "이 프로필의 팔로워 목록은 일부 누락되었을 수 있습니다.",
|
||||
"hints.profiles.follows_may_be_missing": "이 프로필의 팔로우 목록은 일부 누락되었을 수 있습니다.",
|
||||
"hints.profiles.posts_may_be_missing": "이 프로필의 게시물은 일부 누락되었을 수 있습니다.",
|
||||
|
@ -457,7 +457,7 @@
|
|||
"lists.subheading": "리스트",
|
||||
"load_pending": "{count, plural, other {#}} 개의 새 항목",
|
||||
"loading_indicator.label": "불러오는 중...",
|
||||
"media_gallery.toggle_visible": "이미지 숨기기",
|
||||
"media_gallery.hide": "숨기기",
|
||||
"moved_to_account_banner.text": "당신의 계정 {disabledAccount}는 {movedToAccount}로 이동하였기 때문에 현재 비활성화 상태입니다.",
|
||||
"mute_modal.hide_from_notifications": "알림에서 숨기기",
|
||||
"mute_modal.hide_options": "옵션 숨기기",
|
||||
|
|
|
@ -313,7 +313,6 @@
|
|||
"lists.search": "Di navbera kesên ku te dişopînin bigere",
|
||||
"lists.subheading": "Lîsteyên te",
|
||||
"load_pending": "{count, plural, one {# hêmaneke nû} other {#hêmaneke nû}}",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Wêneyê veşêre} other {Wêneyan veşêre}}",
|
||||
"moved_to_account_banner.text": "Ajimêrê te {disabledAccount} niha neçalak e ji ber ku te bar kir bo {movedToAccount}.",
|
||||
"navigation_bar.about": "Derbar",
|
||||
"navigation_bar.blocks": "Bikarhênerên astengkirî",
|
||||
|
|
|
@ -213,7 +213,6 @@
|
|||
"lists.search": "Hwilas yn-mysk tus a holyewgh",
|
||||
"lists.subheading": "Agas rolyow",
|
||||
"load_pending": "{count, plural, one {# daklennowydh} other {# a daklennow nowydh}}",
|
||||
"media_gallery.toggle_visible": "Hide {number, plural, one {aven} other {aven}}",
|
||||
"navigation_bar.blocks": "Devnydhyoryon lettys",
|
||||
"navigation_bar.bookmarks": "Folennosow",
|
||||
"navigation_bar.community_timeline": "Amserlin leel",
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
"announcement.announcement": "Proclamatio",
|
||||
"attachments_list.unprocessed": "(immūtātus)",
|
||||
"block_modal.you_wont_see_mentions": "Nuntios quibus eos commemorant non videbis.",
|
||||
"boost_modal.combo": "Potes premēre {combo} ut hoc iterum transilīre",
|
||||
"bundle_column_error.error.title": "Eheu!",
|
||||
"bundle_column_error.retry": "Retemptare",
|
||||
"bundle_column_error.routing.title": "CCCCIIII",
|
||||
|
@ -72,7 +73,10 @@
|
|||
"empty_column.account_timeline": "Hic nulla contributa!",
|
||||
"empty_column.account_unavailable": "Notio non impetrabilis",
|
||||
"empty_column.blocks": "Nondum quemquam usorem obsēcāvisti.",
|
||||
"empty_column.bookmarked_statuses": "Nūllae adhuc postēs notātī habēs. Ubi unum notāverīs, hic apparebit.",
|
||||
"empty_column.direct": "Nōn habēs adhūc ullo mentionēs prīvātās. Cum ūnam mīseris aut accipis, hīc apparēbit.",
|
||||
"empty_column.favourited_statuses": "Nūllae adhuc postēs praeferendī habēs. Ubi unum praeferās, hic apparebit.",
|
||||
"empty_column.follow_requests": "Nūllae adhuc petitionēs sequendi habēs. Ubi unum accipīs, hic apparebit.",
|
||||
"empty_column.followed_tags": "Nōn adhūc aliquem hastāginem secūtus es. Cum id fēceris, hic ostendētur.",
|
||||
"empty_column.home": "Tua linea temporum domesticus vacua est! Sequere plures personas ut eam compleas.",
|
||||
"empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
|
||||
|
@ -84,12 +88,19 @@
|
|||
"firehose.all": "Omnis",
|
||||
"footer.about": "De",
|
||||
"generic.saved": "Servavit",
|
||||
"hashtag.column_header.tag_mode.none": "sine {additional}",
|
||||
"hashtag.column_settings.tag_mode.all": "Haec omnia",
|
||||
"hashtag.column_settings.tag_toggle": "Include additional tags in this column",
|
||||
"hashtag.counter_by_accounts": "{count, plural, one {{counter} particeps} other {{counter} participēs}}",
|
||||
"hashtag.counter_by_uses": "{count, plural, one {{counter} nuntius} other {{counter} nuntii}}",
|
||||
"hashtag.counter_by_uses_today": "{count, plural, one {{counter} nuntius} other {{counter} nuntii}} hodie",
|
||||
"hashtags.and_other": "…et {count, plural, other {# plus}}",
|
||||
"ignore_notifications_modal.filter_to_act_users": "Adhuc poteris accipere, reicere, vel referre usores",
|
||||
"ignore_notifications_modal.filter_to_review_separately": "Percolantur notificatiōnes separātim recensere potes",
|
||||
"interaction_modal.description.favourite": "Cum accūntū in Mastodon, hanc postem praeferre potes ut auctōrī indicēs tē eam aestimāre et ad posterius servēs.",
|
||||
"interaction_modal.description.follow": "Cum accūntū in Mastodon, {name} sequī potes ut eōrum postēs in tēlā domī tuā recipiās.",
|
||||
"interaction_modal.description.reply": "Mastodon de Ratione, huic nuntio respondere potes.",
|
||||
"interaction_modal.sign_in": "Ad hōc servientem nōn dēlūxī. Ubi accūntum tuum hospitātum est?",
|
||||
"intervals.full.days": "{number, plural, one {# die} other {# dies}}",
|
||||
"intervals.full.hours": "{number, plural, one {# hora} other {# horae}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# minutum} other {# minuta}}",
|
||||
|
@ -130,7 +141,6 @@
|
|||
"lists.new.create": "Addere tabella",
|
||||
"lists.subheading": "Tuae tabulae",
|
||||
"load_pending": "{count, plural, one {# novum item} other {# nova itema}}",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Cēla imaginem} other {Cēla imagines}}",
|
||||
"moved_to_account_banner.text": "Tua ratione {disabledAccount} interdum reposita est, quod ad {movedToAccount} migrāvisti.",
|
||||
"mute_modal.you_wont_see_mentions": "Non videbis nuntios quī eōs commemorant.",
|
||||
"navigation_bar.about": "De",
|
||||
|
@ -155,6 +165,8 @@
|
|||
"notification.status": "{name} nuper publicavit",
|
||||
"notification.update": "{name} nuntium correxit",
|
||||
"notification_requests.accept": "Accipe",
|
||||
"notification_requests.confirm_accept_multiple.message": "Tu es accepturus {count, plural, one {una notitia petitionem} other {# notitia petitiones}}. Certus esne procedere vis?",
|
||||
"notification_requests.confirm_dismiss_multiple.message": "Tu {count, plural, one {unam petitionem notificationis} other {# petitiones notificationum}} abrogāre prōximum es. {count, plural, one {Illa} other {Eae}} facile accessū nōn erit. Certus es tē procedere velle?",
|
||||
"notifications.filter.all": "Omnia",
|
||||
"notifications.filter.polls": "Eventus electionis",
|
||||
"notifications.group": "Notificātiōnēs",
|
||||
|
@ -163,6 +175,8 @@
|
|||
"onboarding.follows.lead": "Tua domus feed est principalis via Mastodon experīrī. Quō plūrēs persōnas sequeris, eō actīvior et interessantior erit. Ad tē incipiendum, ecce quaedam suāsiones:",
|
||||
"onboarding.follows.title": "Popular on Mastodon",
|
||||
"onboarding.profile.display_name_hint": "Tuum nomen completum aut tuum nomen ludens…",
|
||||
"onboarding.profile.lead": "Hoc semper postea in ratiōnibus complērī potest, ubi etiam plūrēs optiōnēs personalizātiōnis praesto sunt.",
|
||||
"onboarding.profile.note_hint": "Alios hominēs vel #hashtags @nōmināre potes…",
|
||||
"onboarding.start.lead": "Nunc pars es Mastodonis, singularis, socialis medii platformae decentralis ubi—non algorismus—tuam ipsius experientiam curas. Incipiāmus in nova hac socialis regione:",
|
||||
"onboarding.start.skip": "Want to skip right ahead?",
|
||||
"onboarding.start.title": "Perfecisti eam!",
|
||||
|
@ -206,8 +220,11 @@
|
|||
"report.mute_explanation": "Non videbis eōrum nuntiōs. Possunt adhuc tē sequī et tuōs nuntiōs vidēre, nec sciēbunt sē tacitōs esse.",
|
||||
"report.next": "Secundum",
|
||||
"report.placeholder": "Commentāriī adiūnctī",
|
||||
"report.reasons.legal_description": "Putās id legem tuae aut servientis patriae violāre.",
|
||||
"report.reasons.violation_description": "Scis quod certa praecepta frangit",
|
||||
"report.submit": "Mittere",
|
||||
"report.target": "Report {target}",
|
||||
"report.unfollow_explanation": "Tu hanc rationem secutus es. Non videre stationes suas in domo tua amplius pascere, eas sequere.",
|
||||
"report_notification.attached_statuses": "{count, plural, one {{count} nuntius} other {{count} nuntii}} attachiatus",
|
||||
"report_notification.categories.other": "Altera",
|
||||
"search.placeholder": "Quaerere",
|
||||
|
|
|
@ -418,7 +418,6 @@
|
|||
"lists.subheading": "Tus listas",
|
||||
"load_pending": "{count, plural, one {# muevo elemento} other {# muevos elementos}}",
|
||||
"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",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue