Merge commit '4896d2c4c6d3bd6b878c5a075b6611c65d4203b2' into glitch-soc/merge-upstream
Conflicts: - `app/views/settings/preferences/appearance/show.html.haml`: Upstream changed stuff too close to glitch-soc's theming system changes. Applied upstream's changes. - `streaming/index.js`: Upstream refactored a bunch of stuff where our code was different due to local-only posts. Applied upstream's changes while taking care of local-only posts.
This commit is contained in:
@@ -318,21 +318,3 @@ MAX_POLL_OPTION_CHARS=100
|
|||||||
# -----------------------
|
# -----------------------
|
||||||
IP_RETENTION_PERIOD=31556952
|
IP_RETENTION_PERIOD=31556952
|
||||||
SESSION_RETENTION_PERIOD=31556952
|
SESSION_RETENTION_PERIOD=31556952
|
||||||
|
|
||||||
# Fetch All Replies Behavior
|
|
||||||
# --------------------------
|
|
||||||
|
|
||||||
# Period to wait between fetching replies (in minutes)
|
|
||||||
FETCH_REPLIES_COOLDOWN_MINUTES=15
|
|
||||||
|
|
||||||
# Period to wait after a post is first created before fetching its replies (in minutes)
|
|
||||||
FETCH_REPLIES_INITIAL_WAIT_MINUTES=5
|
|
||||||
|
|
||||||
# Max number of replies to fetch - total, recursively through a whole reply tree
|
|
||||||
FETCH_REPLIES_MAX_GLOBAL=1000
|
|
||||||
|
|
||||||
# Max number of replies to fetch - for a single post
|
|
||||||
FETCH_REPLIES_MAX_SINGLE=500
|
|
||||||
|
|
||||||
# Max number of replies Collection pages to fetch - total
|
|
||||||
FETCH_REPLIES_MAX_PAGES=500
|
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,6 +23,7 @@
|
|||||||
/public/packs
|
/public/packs
|
||||||
/public/packs-dev
|
/public/packs-dev
|
||||||
/public/packs-test
|
/public/packs-test
|
||||||
|
stats.html
|
||||||
.env
|
.env
|
||||||
.env.production
|
.env.production
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|||||||
33
Gemfile.lock
33
Gemfile.lock
@@ -90,7 +90,7 @@ GEM
|
|||||||
public_suffix (>= 2.0.2, < 7.0)
|
public_suffix (>= 2.0.2, < 7.0)
|
||||||
aes_key_wrap (1.1.0)
|
aes_key_wrap (1.1.0)
|
||||||
android_key_attestation (0.3.0)
|
android_key_attestation (0.3.0)
|
||||||
annotaterb (4.19.0)
|
annotaterb (4.20.0)
|
||||||
activerecord (>= 6.0.0)
|
activerecord (>= 6.0.0)
|
||||||
activesupport (>= 6.0.0)
|
activesupport (>= 6.0.0)
|
||||||
ast (2.4.3)
|
ast (2.4.3)
|
||||||
@@ -116,7 +116,7 @@ GEM
|
|||||||
base64 (0.3.0)
|
base64 (0.3.0)
|
||||||
bcp47_spec (0.2.1)
|
bcp47_spec (0.2.1)
|
||||||
bcrypt (3.1.20)
|
bcrypt (3.1.20)
|
||||||
benchmark (0.4.1)
|
benchmark (0.5.0)
|
||||||
better_errors (2.10.1)
|
better_errors (2.10.1)
|
||||||
erubi (>= 1.0.0)
|
erubi (>= 1.0.0)
|
||||||
rack (>= 0.9.0)
|
rack (>= 0.9.0)
|
||||||
@@ -168,7 +168,7 @@ GEM
|
|||||||
cose (1.3.1)
|
cose (1.3.1)
|
||||||
cbor (~> 0.5.9)
|
cbor (~> 0.5.9)
|
||||||
openssl-signature_algorithm (~> 1.0)
|
openssl-signature_algorithm (~> 1.0)
|
||||||
crack (1.0.0)
|
crack (1.0.1)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
rexml
|
rexml
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
@@ -190,10 +190,10 @@ GEM
|
|||||||
railties (>= 4.1.0)
|
railties (>= 4.1.0)
|
||||||
responders
|
responders
|
||||||
warden (~> 1.2.3)
|
warden (~> 1.2.3)
|
||||||
devise-two-factor (6.1.0)
|
devise-two-factor (6.2.0)
|
||||||
activesupport (>= 7.0, < 8.1)
|
activesupport (>= 7.0, < 8.2)
|
||||||
devise (~> 4.0)
|
devise (~> 4.0)
|
||||||
railties (>= 7.0, < 8.1)
|
railties (>= 7.0, < 8.2)
|
||||||
rotp (~> 6.0)
|
rotp (~> 6.0)
|
||||||
devise_pam_authenticatable2 (9.2.0)
|
devise_pam_authenticatable2 (9.2.0)
|
||||||
devise (>= 4.0.0)
|
devise (>= 4.0.0)
|
||||||
@@ -224,7 +224,7 @@ GEM
|
|||||||
mail (~> 2.7)
|
mail (~> 2.7)
|
||||||
email_validator (2.2.4)
|
email_validator (2.2.4)
|
||||||
activemodel
|
activemodel
|
||||||
erb (5.0.2)
|
erb (5.1.1)
|
||||||
erubi (1.13.1)
|
erubi (1.13.1)
|
||||||
et-orbi (1.4.0)
|
et-orbi (1.4.0)
|
||||||
tzinfo
|
tzinfo
|
||||||
@@ -426,7 +426,8 @@ GEM
|
|||||||
loofah (2.24.1)
|
loofah (2.24.1)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.12.0)
|
nokogiri (>= 1.12.0)
|
||||||
mail (2.8.1)
|
mail (2.9.0)
|
||||||
|
logger
|
||||||
mini_mime (>= 0.1.1)
|
mini_mime (>= 0.1.1)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
@@ -442,7 +443,7 @@ GEM
|
|||||||
mime-types-data (3.2025.0924)
|
mime-types-data (3.2025.0924)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.9)
|
mini_portile2 (2.8.9)
|
||||||
minitest (5.25.5)
|
minitest (5.26.0)
|
||||||
msgpack (1.8.0)
|
msgpack (1.8.0)
|
||||||
multi_json (1.17.0)
|
multi_json (1.17.0)
|
||||||
mutex_m (0.3.0)
|
mutex_m (0.3.0)
|
||||||
@@ -705,9 +706,9 @@ GEM
|
|||||||
io-console (~> 0.5)
|
io-console (~> 0.5)
|
||||||
request_store (1.7.0)
|
request_store (1.7.0)
|
||||||
rack (>= 1.4)
|
rack (>= 1.4)
|
||||||
responders (3.1.1)
|
responders (3.2.0)
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 7.0)
|
||||||
railties (>= 5.2)
|
railties (>= 7.0)
|
||||||
rexml (3.4.4)
|
rexml (3.4.4)
|
||||||
rotp (6.3.0)
|
rotp (6.3.0)
|
||||||
rouge (4.6.1)
|
rouge (4.6.1)
|
||||||
@@ -821,9 +822,9 @@ GEM
|
|||||||
thor (>= 1.0, < 3.0)
|
thor (>= 1.0, < 3.0)
|
||||||
simple-navigation (4.4.0)
|
simple-navigation (4.4.0)
|
||||||
activesupport (>= 2.3.2)
|
activesupport (>= 2.3.2)
|
||||||
simple_form (5.3.1)
|
simple_form (5.4.0)
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 7.0)
|
||||||
activemodel (>= 5.2)
|
activemodel (>= 7.0)
|
||||||
simplecov (0.22.0)
|
simplecov (0.22.0)
|
||||||
docile (~> 1.1)
|
docile (~> 1.1)
|
||||||
simplecov-html (~> 0.11)
|
simplecov-html (~> 0.11)
|
||||||
@@ -910,7 +911,7 @@ GEM
|
|||||||
activesupport
|
activesupport
|
||||||
faraday (~> 2.0)
|
faraday (~> 2.0)
|
||||||
faraday-follow_redirects
|
faraday-follow_redirects
|
||||||
webmock (3.25.1)
|
webmock (3.26.0)
|
||||||
addressable (>= 2.8.0)
|
addressable (>= 2.8.0)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff (>= 0.4.0, < 2.0.0)
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ class Api::V1::StatusesController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def set_quoted_status
|
def set_quoted_status
|
||||||
@quoted_status = Status.find(status_params[:quoted_status_id]) if status_params[:quoted_status_id].present?
|
@quoted_status = Status.find(status_params[:quoted_status_id])&.proper if status_params[:quoted_status_id].present?
|
||||||
authorize(@quoted_status, :quote?) if @quoted_status.present?
|
authorize(@quoted_status, :quote?) if @quoted_status.present?
|
||||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
# TODO: distinguish between non-existing and non-quotable posts
|
# TODO: distinguish between non-existing and non-quotable posts
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ function loaded() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
document.querySelectorAll('.emojify').forEach((content) => {
|
document.querySelectorAll('.emojify').forEach((content) => {
|
||||||
content.innerHTML = emojify(content.innerHTML, {}, true); // Force emojify as public doesn't load the new emoji system.
|
content.innerHTML = emojify(content.innerHTML);
|
||||||
});
|
});
|
||||||
|
|
||||||
document
|
document
|
||||||
|
|||||||
@@ -624,6 +624,7 @@ export function fetchComposeSuggestions(token) {
|
|||||||
fetchComposeSuggestionsEmojis(dispatch, getState, token);
|
fetchComposeSuggestionsEmojis(dispatch, getState, token);
|
||||||
break;
|
break;
|
||||||
case '#':
|
case '#':
|
||||||
|
case '#':
|
||||||
fetchComposeSuggestionsTags(dispatch, getState, token);
|
fetchComposeSuggestionsTags(dispatch, getState, token);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -665,11 +666,11 @@ export function selectComposeSuggestion(position, token, suggestion, path) {
|
|||||||
|
|
||||||
dispatch(useEmoji(suggestion));
|
dispatch(useEmoji(suggestion));
|
||||||
} else if (suggestion.type === 'hashtag') {
|
} else if (suggestion.type === 'hashtag') {
|
||||||
completion = `#${suggestion.name}`;
|
completion = suggestion.name.slice(token.length - 1);
|
||||||
startPosition = position - 1;
|
startPosition = position + token.length;
|
||||||
} else if (suggestion.type === 'account') {
|
} else if (suggestion.type === 'account') {
|
||||||
completion = getState().getIn(['accounts', suggestion.id, 'acct']);
|
completion = `@${getState().getIn(['accounts', suggestion.id, 'acct'])}`;
|
||||||
startPosition = position;
|
startPosition = position - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't want to replace hashtags that vary only in case due to accessibility, but we need to fire off an event so that
|
// We don't want to replace hashtags that vary only in case due to accessibility, but we need to fire off an event so that
|
||||||
@@ -729,7 +730,7 @@ function insertIntoTagHistory(recognizedTags, text) {
|
|||||||
// complicated because of new normalization rules, it's no longer just
|
// complicated because of new normalization rules, it's no longer just
|
||||||
// a case sensitivity issue
|
// a case sensitivity issue
|
||||||
const names = recognizedTags.map(tag => {
|
const names = recognizedTags.map(tag => {
|
||||||
const matches = text.match(new RegExp(`#${tag.name}`, 'i'));
|
const matches = text.match(new RegExp(`[##]${tag.name}`, 'i'));
|
||||||
|
|
||||||
if (matches && matches.length > 0) {
|
if (matches && matches.length > 0) {
|
||||||
return matches[0].slice(1);
|
return matches[0].slice(1);
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import escapeTextContentForBrowser from 'escape-html';
|
import escapeTextContentForBrowser from 'escape-html';
|
||||||
|
|
||||||
import { makeEmojiMap } from 'mastodon/models/custom_emoji';
|
|
||||||
|
|
||||||
import emojify from '../../features/emoji/emoji';
|
|
||||||
import { expandSpoilers } from '../../initial_state';
|
import { expandSpoilers } from '../../initial_state';
|
||||||
|
|
||||||
const domParser = new DOMParser();
|
const domParser = new DOMParser();
|
||||||
@@ -88,11 +85,10 @@ export function normalizeStatus(status, normalOldStatus) {
|
|||||||
|
|
||||||
const spoilerText = normalStatus.spoiler_text || '';
|
const spoilerText = normalStatus.spoiler_text || '';
|
||||||
const searchContent = ([spoilerText, status.content].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).concat(status.media_attachments.map(att => att.description)).join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n');
|
const searchContent = ([spoilerText, status.content].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).concat(status.media_attachments.map(att => att.description)).join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n');
|
||||||
const emojiMap = makeEmojiMap(normalStatus.emojis);
|
|
||||||
|
|
||||||
normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
|
normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
|
||||||
normalStatus.contentHtml = emojify(normalStatus.content, emojiMap);
|
normalStatus.contentHtml = normalStatus.content;
|
||||||
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
|
normalStatus.spoilerHtml = escapeTextContentForBrowser(spoilerText);
|
||||||
normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive;
|
normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive;
|
||||||
|
|
||||||
// Remove quote fallback link from the DOM so it doesn't mess with paragraph margins
|
// Remove quote fallback link from the DOM so it doesn't mess with paragraph margins
|
||||||
@@ -128,14 +124,12 @@ export function normalizeStatus(status, normalOldStatus) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeStatusTranslation(translation, status) {
|
export function normalizeStatusTranslation(translation, status) {
|
||||||
const emojiMap = makeEmojiMap(status.get('emojis').toJS());
|
|
||||||
|
|
||||||
const normalTranslation = {
|
const normalTranslation = {
|
||||||
detected_source_language: translation.detected_source_language,
|
detected_source_language: translation.detected_source_language,
|
||||||
language: translation.language,
|
language: translation.language,
|
||||||
provider: translation.provider,
|
provider: translation.provider,
|
||||||
contentHtml: emojify(translation.content, emojiMap),
|
contentHtml: translation.content,
|
||||||
spoilerHtml: emojify(escapeTextContentForBrowser(translation.spoiler_text), emojiMap),
|
spoilerHtml: escapeTextContentForBrowser(translation.spoiler_text),
|
||||||
spoiler_text: translation.spoiler_text,
|
spoiler_text: translation.spoiler_text,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -149,9 +143,8 @@ export function normalizeStatusTranslation(translation, status) {
|
|||||||
|
|
||||||
export function normalizeAnnouncement(announcement) {
|
export function normalizeAnnouncement(announcement) {
|
||||||
const normalAnnouncement = { ...announcement };
|
const normalAnnouncement = { ...announcement };
|
||||||
const emojiMap = makeEmojiMap(normalAnnouncement.emojis);
|
|
||||||
|
|
||||||
normalAnnouncement.contentHtml = emojify(normalAnnouncement.content, emojiMap);
|
normalAnnouncement.contentHtml = normalAnnouncement.content;
|
||||||
|
|
||||||
return normalAnnouncement;
|
return normalAnnouncement;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,13 +32,20 @@ import {
|
|||||||
const randomUpTo = max =>
|
const randomUpTo = max =>
|
||||||
Math.floor(Math.random() * Math.floor(max));
|
Math.floor(Math.random() * Math.floor(max));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('mastodon/store').AppDispatch} Dispatch
|
||||||
|
* @typedef {import('mastodon/store').GetState} GetState
|
||||||
|
* @typedef {import('redux').UnknownAction} UnknownAction
|
||||||
|
* @typedef {function(Dispatch, GetState): Promise<void>} FallbackFunction
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} timelineId
|
* @param {string} timelineId
|
||||||
* @param {string} channelName
|
* @param {string} channelName
|
||||||
* @param {Object.<string, string>} params
|
* @param {Object.<string, string>} params
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
* @param {function(Function, Function): Promise<void>} [options.fallback]
|
* @param {FallbackFunction} [options.fallback]
|
||||||
* @param {function(): void} [options.fillGaps]
|
* @param {function(): UnknownAction} [options.fillGaps]
|
||||||
* @param {function(object): boolean} [options.accept]
|
* @param {function(object): boolean} [options.accept]
|
||||||
* @returns {function(): void}
|
* @returns {function(): void}
|
||||||
*/
|
*/
|
||||||
@@ -46,13 +53,14 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
|
|||||||
const { messages } = getLocale();
|
const { messages } = getLocale();
|
||||||
|
|
||||||
return connectStream(channelName, params, (dispatch, getState) => {
|
return connectStream(channelName, params, (dispatch, getState) => {
|
||||||
|
// @ts-ignore
|
||||||
const locale = getState().getIn(['meta', 'locale']);
|
const locale = getState().getIn(['meta', 'locale']);
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
let pollingId;
|
let pollingId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {function(Function, Function): Promise<void>} fallback
|
* @param {FallbackFunction} fallback
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const useFallback = async fallback => {
|
const useFallback = async fallback => {
|
||||||
@@ -132,7 +140,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Function} dispatch
|
* @param {Dispatch} dispatch
|
||||||
*/
|
*/
|
||||||
async function refreshHomeTimelineAndNotification(dispatch) {
|
async function refreshHomeTimelineAndNotification(dispatch) {
|
||||||
await dispatch(expandHomeTimeline({ maxId: undefined }));
|
await dispatch(expandHomeTimeline({ maxId: undefined }));
|
||||||
@@ -151,7 +159,11 @@ async function refreshHomeTimelineAndNotification(dispatch) {
|
|||||||
* @returns {function(): void}
|
* @returns {function(): void}
|
||||||
*/
|
*/
|
||||||
export const connectUserStream = () =>
|
export const connectUserStream = () =>
|
||||||
connectTimelineStream('home', 'user', {}, { fallback: refreshHomeTimelineAndNotification, fillGaps: fillHomeTimelineGaps });
|
connectTimelineStream('home', 'user', {}, {
|
||||||
|
fallback: refreshHomeTimelineAndNotification,
|
||||||
|
// @ts-expect-error
|
||||||
|
fillGaps: fillHomeTimelineGaps
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
@@ -159,7 +171,10 @@ export const connectUserStream = () =>
|
|||||||
* @returns {function(): void}
|
* @returns {function(): void}
|
||||||
*/
|
*/
|
||||||
export const connectCommunityStream = ({ onlyMedia } = {}) =>
|
export const connectCommunityStream = ({ onlyMedia } = {}) =>
|
||||||
connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`, {}, { fillGaps: () => (fillCommunityTimelineGaps({ onlyMedia })) });
|
connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`, {}, {
|
||||||
|
// @ts-expect-error
|
||||||
|
fillGaps: () => (fillCommunityTimelineGaps({ onlyMedia }))
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
@@ -168,7 +183,10 @@ export const connectCommunityStream = ({ onlyMedia } = {}) =>
|
|||||||
* @returns {function(): void}
|
* @returns {function(): void}
|
||||||
*/
|
*/
|
||||||
export const connectPublicStream = ({ onlyMedia, onlyRemote } = {}) =>
|
export const connectPublicStream = ({ onlyMedia, onlyRemote } = {}) =>
|
||||||
connectTimelineStream(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, `public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, {}, { fillGaps: () => fillPublicTimelineGaps({ onlyMedia, onlyRemote }) });
|
connectTimelineStream(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, `public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, {}, {
|
||||||
|
// @ts-expect-error
|
||||||
|
fillGaps: () => fillPublicTimelineGaps({ onlyMedia, onlyRemote })
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} columnId
|
* @param {string} columnId
|
||||||
@@ -191,4 +209,7 @@ export const connectDirectStream = () =>
|
|||||||
* @returns {function(): void}
|
* @returns {function(): void}
|
||||||
*/
|
*/
|
||||||
export const connectListStream = listId =>
|
export const connectListStream = listId =>
|
||||||
connectTimelineStream(`list:${listId}`, 'list', { list: listId }, { fillGaps: () => fillListTimelineGaps(listId) });
|
connectTimelineStream(`list:${listId}`, 'list', { list: listId }, {
|
||||||
|
// @ts-expect-error
|
||||||
|
fillGaps: () => fillListTimelineGaps(listId)
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { useLinks } from 'mastodon/hooks/useLinks';
|
|
||||||
|
|
||||||
import { useAppSelector } from '../store';
|
import { useAppSelector } from '../store';
|
||||||
import { isModernEmojiEnabled } from '../utils/environment';
|
|
||||||
|
|
||||||
import { EmojiHTML } from './emoji/html';
|
import { EmojiHTML } from './emoji/html';
|
||||||
import { useElementHandledLink } from './status/handled_link';
|
import { useElementHandledLink } from './status/handled_link';
|
||||||
@@ -21,22 +16,6 @@ export const AccountBio: React.FC<AccountBioProps> = ({
|
|||||||
accountId,
|
accountId,
|
||||||
showDropdown = false,
|
showDropdown = false,
|
||||||
}) => {
|
}) => {
|
||||||
const handleClick = useLinks(showDropdown);
|
|
||||||
const handleNodeChange = useCallback(
|
|
||||||
(node: HTMLDivElement | null) => {
|
|
||||||
if (
|
|
||||||
!showDropdown ||
|
|
||||||
!node ||
|
|
||||||
node.childNodes.length === 0 ||
|
|
||||||
isModernEmojiEnabled()
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
addDropdownToHashtags(node, accountId);
|
|
||||||
},
|
|
||||||
[showDropdown, accountId],
|
|
||||||
);
|
|
||||||
|
|
||||||
const htmlHandlers = useElementHandledLink({
|
const htmlHandlers = useElementHandledLink({
|
||||||
hashtagAccountId: showDropdown ? accountId : undefined,
|
hashtagAccountId: showDropdown ? accountId : undefined,
|
||||||
});
|
});
|
||||||
@@ -62,30 +41,7 @@ export const AccountBio: React.FC<AccountBioProps> = ({
|
|||||||
htmlString={note}
|
htmlString={note}
|
||||||
extraEmojis={extraEmojis}
|
extraEmojis={extraEmojis}
|
||||||
className={classNames(className, 'translate')}
|
className={classNames(className, 'translate')}
|
||||||
onClickCapture={handleClick}
|
|
||||||
ref={handleNodeChange}
|
|
||||||
{...htmlHandlers}
|
{...htmlHandlers}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function addDropdownToHashtags(node: HTMLElement | null, accountId: string) {
|
|
||||||
if (!node) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const childNode of node.childNodes) {
|
|
||||||
if (!(childNode instanceof HTMLElement)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
childNode instanceof HTMLAnchorElement &&
|
|
||||||
(childNode.classList.contains('hashtag') ||
|
|
||||||
childNode.innerText.startsWith('#')) &&
|
|
||||||
!childNode.dataset.menuHashtag
|
|
||||||
) {
|
|
||||||
childNode.dataset.menuHashtag = accountId;
|
|
||||||
} else if (childNode.childNodes.length > 0) {
|
|
||||||
addDropdownToHashtags(childNode, accountId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
|
|||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
searchTokens: ['@', ':', '#'],
|
searchTokens: ['@', '@', ':', '#', '#'],
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const textAtCursorMatchesToken = (str, caretPosition) => {
|
|||||||
word = str.slice(left, right + caretPosition);
|
word = str.slice(left, right + caretPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!word || word.trim().length < 3 || ['@', ':', '#'].indexOf(word[0]) === -1) {
|
if (!word || word.trim().length < 3 || ['@', '@', ':', '#', '#'].indexOf(word[0]) === -1) {
|
||||||
return [null, null];
|
return [null, null];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,6 @@ export const Linked: Story = {
|
|||||||
acct: username,
|
acct: username,
|
||||||
})
|
})
|
||||||
: undefined;
|
: undefined;
|
||||||
return <LinkedDisplayName {...args} displayProps={{ account }} />;
|
return <LinkedDisplayName displayProps={{ account, ...args }} />;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,9 +9,8 @@ import { Skeleton } from '../skeleton';
|
|||||||
import type { DisplayNameProps } from './index';
|
import type { DisplayNameProps } from './index';
|
||||||
|
|
||||||
export const DisplayNameWithoutDomain: FC<
|
export const DisplayNameWithoutDomain: FC<
|
||||||
Omit<DisplayNameProps, 'variant' | 'localDomain'> &
|
Omit<DisplayNameProps, 'variant'> & ComponentPropsWithoutRef<'span'>
|
||||||
ComponentPropsWithoutRef<'span'>
|
> = ({ account, className, children, localDomain: _, ...props }) => {
|
||||||
> = ({ account, className, children, ...props }) => {
|
|
||||||
return (
|
return (
|
||||||
<AnimateEmojiProvider
|
<AnimateEmojiProvider
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import { EmojiHTML } from '../emoji/html';
|
|||||||
import type { DisplayNameProps } from './index';
|
import type { DisplayNameProps } from './index';
|
||||||
|
|
||||||
export const DisplayNameSimple: FC<
|
export const DisplayNameSimple: FC<
|
||||||
Omit<DisplayNameProps, 'variant' | 'localDomain'> &
|
Omit<DisplayNameProps, 'variant'> & ComponentPropsWithoutRef<'span'>
|
||||||
ComponentPropsWithoutRef<'span'>
|
> = ({ account, localDomain: _, ...props }) => {
|
||||||
> = ({ account, ...props }) => {
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
import { cleanExtraEmojis } from '@/mastodon/features/emoji/normalize';
|
import { cleanExtraEmojis } from '@/mastodon/features/emoji/normalize';
|
||||||
import { autoPlayGif } from '@/mastodon/initial_state';
|
import { autoPlayGif } from '@/mastodon/initial_state';
|
||||||
import { polymorphicForwardRef } from '@/types/polymorphic';
|
import { polymorphicForwardRef } from '@/types/polymorphic';
|
||||||
@@ -65,11 +63,7 @@ export const AnimateEmojiProvider = polymorphicForwardRef<
|
|||||||
const parentContext = useContext(AnimateEmojiContext);
|
const parentContext = useContext(AnimateEmojiContext);
|
||||||
if (parentContext !== null) {
|
if (parentContext !== null) {
|
||||||
return (
|
return (
|
||||||
<Wrapper
|
<Wrapper {...props} className={className} ref={ref}>
|
||||||
{...props}
|
|
||||||
className={classNames(className, 'animate-parent')}
|
|
||||||
ref={ref}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
@@ -78,7 +72,7 @@ export const AnimateEmojiProvider = polymorphicForwardRef<
|
|||||||
return (
|
return (
|
||||||
<Wrapper
|
<Wrapper
|
||||||
{...props}
|
{...props}
|
||||||
className={classNames(className, 'animate-parent')}
|
className={className}
|
||||||
onMouseEnter={handleEnter}
|
onMouseEnter={handleEnter}
|
||||||
onMouseLeave={handleLeave}
|
onMouseLeave={handleLeave}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
import type { CustomEmojiMapArg } from '@/mastodon/features/emoji/types';
|
import type { CustomEmojiMapArg } from '@/mastodon/features/emoji/types';
|
||||||
import { isModernEmojiEnabled } from '@/mastodon/utils/environment';
|
|
||||||
import type {
|
import type {
|
||||||
OnAttributeHandler,
|
OnAttributeHandler,
|
||||||
OnElementHandler,
|
OnElementHandler,
|
||||||
@@ -22,7 +19,7 @@ export interface EmojiHTMLProps {
|
|||||||
onAttribute?: OnAttributeHandler;
|
onAttribute?: OnAttributeHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModernEmojiHTML = polymorphicForwardRef<'div', EmojiHTMLProps>(
|
export const EmojiHTML = polymorphicForwardRef<'div', EmojiHTMLProps>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
extraEmojis,
|
extraEmojis,
|
||||||
@@ -59,32 +56,4 @@ export const ModernEmojiHTML = polymorphicForwardRef<'div', EmojiHTMLProps>(
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
ModernEmojiHTML.displayName = 'ModernEmojiHTML';
|
EmojiHTML.displayName = 'EmojiHTML';
|
||||||
|
|
||||||
export const LegacyEmojiHTML = polymorphicForwardRef<'div', EmojiHTMLProps>(
|
|
||||||
(props, ref) => {
|
|
||||||
const {
|
|
||||||
as: asElement,
|
|
||||||
htmlString,
|
|
||||||
extraEmojis,
|
|
||||||
className,
|
|
||||||
onElement,
|
|
||||||
onAttribute,
|
|
||||||
...rest
|
|
||||||
} = props;
|
|
||||||
const Wrapper = asElement ?? 'div';
|
|
||||||
return (
|
|
||||||
<Wrapper
|
|
||||||
{...rest}
|
|
||||||
ref={ref}
|
|
||||||
dangerouslySetInnerHTML={{ __html: htmlString }}
|
|
||||||
className={classNames(className, 'animate-parent')}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
LegacyEmojiHTML.displayName = 'LegacyEmojiHTML';
|
|
||||||
|
|
||||||
export const EmojiHTML = isModernEmojiEnabled()
|
|
||||||
? ModernEmojiHTML
|
|
||||||
: LegacyEmojiHTML;
|
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ import { domain } from 'mastodon/initial_state';
|
|||||||
import { getAccountHidden } from 'mastodon/selectors/accounts';
|
import { getAccountHidden } from 'mastodon/selectors/accounts';
|
||||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||||
|
|
||||||
import { useLinks } from '../hooks/useLinks';
|
|
||||||
|
|
||||||
export const HoverCardAccount = forwardRef<
|
export const HoverCardAccount = forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
{ accountId?: string }
|
{ accountId?: string }
|
||||||
@@ -66,8 +64,6 @@ export const HoverCardAccount = forwardRef<
|
|||||||
!isMutual &&
|
!isMutual &&
|
||||||
!isFollower;
|
!isFollower;
|
||||||
|
|
||||||
const handleClick = useLinks();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -110,7 +106,7 @@ export const HoverCardAccount = forwardRef<
|
|||||||
className='hover-card__bio'
|
className='hover-card__bio'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='account-fields' onClickCapture={handleClick}>
|
<div className='account-fields'>
|
||||||
<AccountFields
|
<AccountFields
|
||||||
fields={account.fields.take(2)}
|
fields={account.fields.take(2)}
|
||||||
emojis={account.emojis}
|
emojis={account.emojis}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { OnElementHandler } from '@/mastodon/utils/html';
|
|||||||
import { polymorphicForwardRef } from '@/types/polymorphic';
|
import { polymorphicForwardRef } from '@/types/polymorphic';
|
||||||
|
|
||||||
import type { EmojiHTMLProps } from '../emoji/html';
|
import type { EmojiHTMLProps } from '../emoji/html';
|
||||||
import { ModernEmojiHTML } from '../emoji/html';
|
import { EmojiHTML } from '../emoji/html';
|
||||||
import { useElementHandledLink } from '../status/handled_link';
|
import { useElementHandledLink } from '../status/handled_link';
|
||||||
|
|
||||||
export const HTMLBlock = polymorphicForwardRef<
|
export const HTMLBlock = polymorphicForwardRef<
|
||||||
@@ -25,6 +25,6 @@ export const HTMLBlock = polymorphicForwardRef<
|
|||||||
(...args) => onParentElement?.(...args) ?? onLinkElement(...args),
|
(...args) => onParentElement?.(...args) ?? onLinkElement(...args),
|
||||||
[onLinkElement, onParentElement],
|
[onLinkElement, onParentElement],
|
||||||
);
|
);
|
||||||
return <ModernEmojiHTML {...props} onElement={onElement} />;
|
return <EmojiHTML {...props} onElement={onElement} />;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ import CheckIcon from '@/material-icons/400-24px/check.svg?react';
|
|||||||
import { openModal } from 'mastodon/actions/modal';
|
import { openModal } from 'mastodon/actions/modal';
|
||||||
import { fetchPoll, vote } from 'mastodon/actions/polls';
|
import { fetchPoll, vote } from 'mastodon/actions/polls';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
import emojify from 'mastodon/features/emoji/emoji';
|
|
||||||
import { useIdentity } from 'mastodon/identity_context';
|
import { useIdentity } from 'mastodon/identity_context';
|
||||||
import { makeEmojiMap } from 'mastodon/models/custom_emoji';
|
|
||||||
import type * as Model from 'mastodon/models/poll';
|
import type * as Model from 'mastodon/models/poll';
|
||||||
import type { Status } from 'mastodon/models/status';
|
import type { Status } from 'mastodon/models/status';
|
||||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||||
@@ -235,12 +233,11 @@ const PollOption: React.FC<PollOptionProps> = (props) => {
|
|||||||
let titleHtml = option.translation?.titleHtml ?? option.titleHtml;
|
let titleHtml = option.translation?.titleHtml ?? option.titleHtml;
|
||||||
|
|
||||||
if (!titleHtml) {
|
if (!titleHtml) {
|
||||||
const emojiMap = makeEmojiMap(poll.emojis);
|
titleHtml = escapeTextContentForBrowser(title);
|
||||||
titleHtml = emojify(escapeTextContentForBrowser(title), emojiMap);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return titleHtml;
|
return titleHtml;
|
||||||
}, [option, poll, title]);
|
}, [option, title]);
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
const handleOptionChange = useCallback(() => {
|
const handleOptionChange = useCallback(() => {
|
||||||
|
|||||||
@@ -26,7 +26,12 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
|
|||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
// Handle hashtags
|
// Handle hashtags
|
||||||
if (text.startsWith('#') || prevText?.endsWith('#')) {
|
if (
|
||||||
|
text.startsWith('#') ||
|
||||||
|
prevText?.endsWith('#') ||
|
||||||
|
text.startsWith('#') ||
|
||||||
|
prevText?.endsWith('#')
|
||||||
|
) {
|
||||||
const hashtag = text.slice(1).trim();
|
const hashtag = text.slice(1).trim();
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ import { Poll } from 'mastodon/components/poll';
|
|||||||
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
||||||
import { languages as preloadedLanguages } from 'mastodon/initial_state';
|
import { languages as preloadedLanguages } from 'mastodon/initial_state';
|
||||||
|
|
||||||
import { isModernEmojiEnabled } from '../utils/environment';
|
|
||||||
|
|
||||||
import { EmojiHTML } from './emoji/html';
|
import { EmojiHTML } from './emoji/html';
|
||||||
import { HandledLink } from './status/handled_link';
|
import { HandledLink } from './status/handled_link';
|
||||||
|
|
||||||
@@ -72,6 +70,17 @@ const mapStateToProps = state => ({
|
|||||||
languages: state.getIn(['server', 'translationLanguages', 'items']),
|
languages: state.getIn(['server', 'translationLanguages', 'items']),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const compareUrls = (href1, href2) => {
|
||||||
|
try {
|
||||||
|
const url1 = new URL(href1);
|
||||||
|
const url2 = new URL(href2);
|
||||||
|
|
||||||
|
return url1.origin === url2.origin && url1.pathname === url2.pathname && url1.search === url2.search;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class StatusContent extends PureComponent {
|
class StatusContent extends PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
identity: identityContextPropShape,
|
identity: identityContextPropShape,
|
||||||
@@ -108,41 +117,6 @@ class StatusContent extends PureComponent {
|
|||||||
|
|
||||||
onCollapsedToggle(collapsed);
|
onCollapsedToggle(collapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit if modern emoji is enabled, as it handles links using the HandledLink component.
|
|
||||||
if (isModernEmojiEnabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const links = node.querySelectorAll('a');
|
|
||||||
|
|
||||||
let link, mention;
|
|
||||||
|
|
||||||
for (var i = 0; i < links.length; ++i) {
|
|
||||||
link = links[i];
|
|
||||||
|
|
||||||
if (link.classList.contains('status-link')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
link.classList.add('status-link');
|
|
||||||
|
|
||||||
mention = this.props.status.get('mentions').find(item => link.href === item.get('url'));
|
|
||||||
|
|
||||||
if (mention) {
|
|
||||||
link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
|
|
||||||
link.setAttribute('title', `@${mention.get('acct')}`);
|
|
||||||
link.setAttribute('href', `/@${mention.get('acct')}`);
|
|
||||||
link.setAttribute('data-hover-card-account', mention.get('id'));
|
|
||||||
} else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
|
|
||||||
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
|
|
||||||
link.setAttribute('href', `/tags/${link.text.replace(/^#/, '')}`);
|
|
||||||
link.setAttribute('data-menu-hashtag', this.props.status.getIn(['account', 'id']));
|
|
||||||
} else {
|
|
||||||
link.setAttribute('title', link.href);
|
|
||||||
link.classList.add('unhandled-link');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
@@ -153,22 +127,6 @@ class StatusContent extends PureComponent {
|
|||||||
this._updateStatusLinks();
|
this._updateStatusLinks();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMentionClick = (mention, e) => {
|
|
||||||
if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.props.history.push(`/@${mention.get('acct')}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onHashtagClick = (hashtag, e) => {
|
|
||||||
hashtag = hashtag.replace(/^#/, '');
|
|
||||||
|
|
||||||
if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.props.history.push(`/tags/${hashtag}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleMouseDown = (e) => {
|
handleMouseDown = (e) => {
|
||||||
this.startXY = [e.clientX, e.clientY];
|
this.startXY = [e.clientX, e.clientY];
|
||||||
};
|
};
|
||||||
@@ -206,7 +164,7 @@ class StatusContent extends PureComponent {
|
|||||||
|
|
||||||
handleElement = (element, { key, ...props }, children) => {
|
handleElement = (element, { key, ...props }, children) => {
|
||||||
if (element instanceof HTMLAnchorElement) {
|
if (element instanceof HTMLAnchorElement) {
|
||||||
const mention = this.props.status.get('mentions').find(item => element.href === item.get('url'));
|
const mention = this.props.status.get('mentions').find(item => compareUrls(element.href, item.get('url')));
|
||||||
return (
|
return (
|
||||||
<HandledLink
|
<HandledLink
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -1,30 +1,10 @@
|
|||||||
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
||||||
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
|
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
|
||||||
|
|
||||||
import { isModernEmojiEnabled } from '../utils/environment';
|
|
||||||
import type { OnAttributeHandler } from '../utils/html';
|
import type { OnAttributeHandler } from '../utils/html';
|
||||||
|
|
||||||
import { Icon } from './icon';
|
import { Icon } from './icon';
|
||||||
|
|
||||||
const domParser = new DOMParser();
|
|
||||||
|
|
||||||
const stripRelMe = (html: string) => {
|
|
||||||
if (isModernEmojiEnabled()) {
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
const document = domParser.parseFromString(html, 'text/html').documentElement;
|
|
||||||
|
|
||||||
document.querySelectorAll<HTMLAnchorElement>('a[rel]').forEach((link) => {
|
|
||||||
link.rel = link.rel
|
|
||||||
.split(' ')
|
|
||||||
.filter((x: string) => x !== 'me')
|
|
||||||
.join(' ');
|
|
||||||
});
|
|
||||||
|
|
||||||
const body = document.querySelector('body');
|
|
||||||
return body?.innerHTML ?? '';
|
|
||||||
};
|
|
||||||
|
|
||||||
const onAttribute: OnAttributeHandler = (name, value, tagName) => {
|
const onAttribute: OnAttributeHandler = (name, value, tagName) => {
|
||||||
if (name === 'rel' && tagName === 'a') {
|
if (name === 'rel' && tagName === 'a') {
|
||||||
if (value === 'me') {
|
if (value === 'me') {
|
||||||
@@ -47,10 +27,6 @@ interface Props {
|
|||||||
export const VerifiedBadge: React.FC<Props> = ({ link }) => (
|
export const VerifiedBadge: React.FC<Props> = ({ link }) => (
|
||||||
<span className='verified-badge'>
|
<span className='verified-badge'>
|
||||||
<Icon id='check' icon={CheckIcon} className='verified-badge__mark' />
|
<Icon id='check' icon={CheckIcon} className='verified-badge__mark' />
|
||||||
<EmojiHTML
|
<EmojiHTML as='span' htmlString={link} onAttribute={onAttribute} />
|
||||||
as='span'
|
|
||||||
htmlString={stripRelMe(link)}
|
|
||||||
onAttribute={onAttribute}
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ import { ShortNumber } from 'mastodon/components/short_number';
|
|||||||
import { AccountNote } from 'mastodon/features/account/components/account_note';
|
import { AccountNote } from 'mastodon/features/account/components/account_note';
|
||||||
import { DomainPill } from 'mastodon/features/account/components/domain_pill';
|
import { DomainPill } from 'mastodon/features/account/components/domain_pill';
|
||||||
import FollowRequestNoteContainer from 'mastodon/features/account/containers/follow_request_note_container';
|
import FollowRequestNoteContainer from 'mastodon/features/account/containers/follow_request_note_container';
|
||||||
import { useLinks } from 'mastodon/hooks/useLinks';
|
|
||||||
import { useIdentity } from 'mastodon/identity_context';
|
import { useIdentity } from 'mastodon/identity_context';
|
||||||
import { autoPlayGif, me, domain as localDomain } from 'mastodon/initial_state';
|
import { autoPlayGif, me, domain as localDomain } from 'mastodon/initial_state';
|
||||||
import type { Account } from 'mastodon/models/account';
|
import type { Account } from 'mastodon/models/account';
|
||||||
@@ -198,7 +197,6 @@ export const AccountHeader: React.FC<{
|
|||||||
state.relationships.get(accountId),
|
state.relationships.get(accountId),
|
||||||
);
|
);
|
||||||
const hidden = useAppSelector((state) => getAccountHidden(state, accountId));
|
const hidden = useAppSelector((state) => getAccountHidden(state, accountId));
|
||||||
const handleLinkClick = useLinks();
|
|
||||||
|
|
||||||
const handleBlock = useCallback(() => {
|
const handleBlock = useCallback(() => {
|
||||||
if (!account) {
|
if (!account) {
|
||||||
@@ -852,10 +850,7 @@ export const AccountHeader: React.FC<{
|
|||||||
|
|
||||||
{!(suspended || hidden) && (
|
{!(suspended || hidden) && (
|
||||||
<div className='account__header__extra'>
|
<div className='account__header__extra'>
|
||||||
<div
|
<div className='account__header__bio'>
|
||||||
className='account__header__bio'
|
|
||||||
onClickCapture={handleLinkClick}
|
|
||||||
>
|
|
||||||
{account.id !== me && signedIn && (
|
{account.id !== me && signedIn && (
|
||||||
<AccountNote accountId={accountId} />
|
<AccountNote accountId={accountId} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import Trie from 'substring-trie';
|
import Trie from 'substring-trie';
|
||||||
|
|
||||||
import { isModernEmojiEnabled } from '@/mastodon/utils/environment';
|
|
||||||
import { assetHost } from 'mastodon/utils/config';
|
import { assetHost } from 'mastodon/utils/config';
|
||||||
|
|
||||||
import { autoPlayGif } from '../../initial_state';
|
import { autoPlayGif } from '../../initial_state';
|
||||||
@@ -153,13 +152,9 @@ const emojifyNode = (node, customEmojis) => {
|
|||||||
* Legacy emoji processing function.
|
* Legacy emoji processing function.
|
||||||
* @param {string} str
|
* @param {string} str
|
||||||
* @param {object} customEmojis
|
* @param {object} customEmojis
|
||||||
* @param {boolean} force If true, always emojify even if modern emoji is enabled
|
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
const emojify = (str, customEmojis = {}, force = false) => {
|
const emojify = (str, customEmojis = {}) => {
|
||||||
if (isModernEmojiEnabled() && !force) {
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
const wrapper = document.createElement('div');
|
const wrapper = document.createElement('div');
|
||||||
wrapper.innerHTML = str;
|
wrapper.innerHTML = str;
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ import { uncompress as emojiMartUncompress } from 'emoji-mart/dist/utils/data';
|
|||||||
|
|
||||||
import data from './emoji_data.json';
|
import data from './emoji_data.json';
|
||||||
import emojiMap from './emoji_map.json';
|
import emojiMap from './emoji_map.json';
|
||||||
import { unicodeToFilename } from './unicode_to_filename';
|
import { unicodeToFilename, unicodeToUnifiedName } from './unicode_utils';
|
||||||
import { unicodeToUnifiedName } from './unicode_to_unified_name';
|
|
||||||
|
|
||||||
emojiMartUncompress(data);
|
emojiMartUncompress(data);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import type {
|
|||||||
ShortCodesToEmojiData,
|
ShortCodesToEmojiData,
|
||||||
} from 'virtual:mastodon-emoji-compressed';
|
} from 'virtual:mastodon-emoji-compressed';
|
||||||
|
|
||||||
import { unicodeToUnifiedName } from './unicode_to_unified_name';
|
import { unicodeToUnifiedName } from './unicode_utils';
|
||||||
|
|
||||||
type Emojis = Record<
|
type Emojis = Record<
|
||||||
NonNullable<keyof ShortCodesToEmojiData>,
|
NonNullable<keyof ShortCodesToEmojiData>,
|
||||||
@@ -23,7 +23,7 @@ type Emojis = Record<
|
|||||||
|
|
||||||
const [
|
const [
|
||||||
shortCodesToEmojiData,
|
shortCodesToEmojiData,
|
||||||
skins,
|
_skins,
|
||||||
categories,
|
categories,
|
||||||
short_names,
|
short_names,
|
||||||
_emojisWithoutShortCodes,
|
_emojisWithoutShortCodes,
|
||||||
@@ -47,4 +47,4 @@ Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export { emojis, skins, categories, short_names };
|
export { emojis, categories, short_names };
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// This code is largely borrowed from:
|
// This code is largely borrowed from:
|
||||||
// https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/emoji-index.js
|
// https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/emoji-index.js
|
||||||
|
|
||||||
import * as data from './emoji_mart_data_light';
|
import { emojis, categories } from './emoji_mart_data_light';
|
||||||
import { getData, getSanitizedData, uniq, intersect } from './emoji_utils';
|
import { getData, getSanitizedData, uniq, intersect } from './emoji_utils';
|
||||||
|
|
||||||
let originalPool = {};
|
let originalPool = {};
|
||||||
@@ -10,8 +10,8 @@ let emojisList = {};
|
|||||||
let emoticonsList = {};
|
let emoticonsList = {};
|
||||||
let customEmojisList = [];
|
let customEmojisList = [];
|
||||||
|
|
||||||
for (let emoji in data.emojis) {
|
for (let emoji in emojis) {
|
||||||
let emojiData = data.emojis[emoji];
|
let emojiData = emojis[emoji];
|
||||||
let { short_names, emoticons } = emojiData;
|
let { short_names, emoticons } = emojiData;
|
||||||
let id = short_names[0];
|
let id = short_names[0];
|
||||||
|
|
||||||
@@ -84,14 +84,14 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo
|
|||||||
if (include.length || exclude.length) {
|
if (include.length || exclude.length) {
|
||||||
pool = {};
|
pool = {};
|
||||||
|
|
||||||
data.categories.forEach(category => {
|
categories.forEach(category => {
|
||||||
let isIncluded = include && include.length ? include.indexOf(category.name.toLowerCase()) > -1 : true;
|
let isIncluded = include && include.length ? include.indexOf(category.name.toLowerCase()) > -1 : true;
|
||||||
let isExcluded = exclude && exclude.length ? exclude.indexOf(category.name.toLowerCase()) > -1 : false;
|
let isExcluded = exclude && exclude.length ? exclude.indexOf(category.name.toLowerCase()) > -1 : false;
|
||||||
if (!isIncluded || isExcluded) {
|
if (!isIncluded || isExcluded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
category.emojis.forEach(emojiId => pool[emojiId] = data.emojis[emojiId]);
|
category.emojis.forEach(emojiId => pool[emojiId] = emojis[emojiId]);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (custom.length) {
|
if (custom.length) {
|
||||||
@@ -171,7 +171,7 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo
|
|||||||
|
|
||||||
if (results) {
|
if (results) {
|
||||||
if (emojisToShowFilter) {
|
if (emojisToShowFilter) {
|
||||||
results = results.filter((result) => emojisToShowFilter(data.emojis[result.id]));
|
results = results.filter((result) => emojisToShowFilter(emojis[result.id]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (results && results.length > maxResults) {
|
if (results && results.length > maxResults) {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import type { EmojiProps, PickerProps } from 'emoji-mart';
|
|||||||
import EmojiRaw from 'emoji-mart/dist-es/components/emoji/nimble-emoji';
|
import EmojiRaw from 'emoji-mart/dist-es/components/emoji/nimble-emoji';
|
||||||
import PickerRaw from 'emoji-mart/dist-es/components/picker/nimble-picker';
|
import PickerRaw from 'emoji-mart/dist-es/components/picker/nimble-picker';
|
||||||
|
|
||||||
import { isModernEmojiEnabled } from '@/mastodon/utils/environment';
|
|
||||||
import { assetHost } from 'mastodon/utils/config';
|
import { assetHost } from 'mastodon/utils/config';
|
||||||
|
|
||||||
import { EMOJI_MODE_NATIVE } from './constants';
|
import { EMOJI_MODE_NATIVE } from './constants';
|
||||||
@@ -27,7 +26,7 @@ const Emoji = ({
|
|||||||
sheetSize={sheetSize}
|
sheetSize={sheetSize}
|
||||||
sheetColumns={sheetColumns}
|
sheetColumns={sheetColumns}
|
||||||
sheetRows={sheetRows}
|
sheetRows={sheetRows}
|
||||||
native={mode === EMOJI_MODE_NATIVE && isModernEmojiEnabled()}
|
native={mode === EMOJI_MODE_NATIVE}
|
||||||
backgroundImageFn={backgroundImageFn}
|
backgroundImageFn={backgroundImageFn}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -51,7 +50,7 @@ const Picker = ({
|
|||||||
sheetColumns={sheetColumns}
|
sheetColumns={sheetColumns}
|
||||||
sheetRows={sheetRows}
|
sheetRows={sheetRows}
|
||||||
backgroundImageFn={backgroundImageFn}
|
backgroundImageFn={backgroundImageFn}
|
||||||
native={mode === EMOJI_MODE_NATIVE && isModernEmojiEnabled()}
|
native={mode === EMOJI_MODE_NATIVE}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import type {
|
|||||||
ShortCodesToEmojiDataKey,
|
ShortCodesToEmojiDataKey,
|
||||||
} from 'virtual:mastodon-emoji-compressed';
|
} from 'virtual:mastodon-emoji-compressed';
|
||||||
|
|
||||||
import { unicodeToFilename } from './unicode_to_filename';
|
import { unicodeToFilename } from './unicode_utils';
|
||||||
|
|
||||||
type UnicodeMapping = Record<
|
type UnicodeMapping = Record<
|
||||||
FilenameData[number][0],
|
FilenameData[number][0],
|
||||||
|
|||||||
@@ -209,50 +209,9 @@ function intersect(a, b) {
|
|||||||
return uniqA.filter(item => uniqB.indexOf(item) >= 0);
|
return uniqA.filter(item => uniqB.indexOf(item) >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function deepMerge(a, b) {
|
|
||||||
let o = {};
|
|
||||||
|
|
||||||
for (let key in a) {
|
|
||||||
let originalValue = a[key],
|
|
||||||
value = originalValue;
|
|
||||||
|
|
||||||
if (Object.hasOwn(b, key)) {
|
|
||||||
value = b[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === 'object') {
|
|
||||||
value = deepMerge(originalValue, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
o[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/sonicdoe/measure-scrollbar
|
|
||||||
function measureScrollbar() {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
|
|
||||||
div.style.width = '100px';
|
|
||||||
div.style.height = '100px';
|
|
||||||
div.style.overflow = 'scroll';
|
|
||||||
div.style.position = 'absolute';
|
|
||||||
div.style.top = '-9999px';
|
|
||||||
|
|
||||||
document.body.appendChild(div);
|
|
||||||
const scrollbarWidth = div.offsetWidth - div.clientWidth;
|
|
||||||
document.body.removeChild(div);
|
|
||||||
|
|
||||||
return scrollbarWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getData,
|
getData,
|
||||||
getSanitizedData,
|
getSanitizedData,
|
||||||
uniq,
|
uniq,
|
||||||
intersect,
|
intersect,
|
||||||
deepMerge,
|
|
||||||
unifiedToNative,
|
|
||||||
measureScrollbar,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
import { autoPlayGif } from '@/mastodon/initial_state';
|
|
||||||
|
|
||||||
const PARENT_MAX_DEPTH = 10;
|
|
||||||
|
|
||||||
export function handleAnimateGif(event: MouseEvent) {
|
|
||||||
// We already check this in ui/index.jsx, but just to be sure.
|
|
||||||
if (autoPlayGif) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { target, type } = event;
|
|
||||||
const animate = type === 'mouseover'; // Mouse over = animate, mouse out = don't animate.
|
|
||||||
|
|
||||||
if (target instanceof HTMLImageElement) {
|
|
||||||
setAnimateGif(target, animate);
|
|
||||||
} else if (!(target instanceof HTMLElement) || target === document.body) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let parent: HTMLElement | null = null;
|
|
||||||
let iter = 0;
|
|
||||||
|
|
||||||
if (target.classList.contains('animate-parent')) {
|
|
||||||
parent = target;
|
|
||||||
} else {
|
|
||||||
// Iterate up to PARENT_MAX_DEPTH levels up the DOM tree to find a parent with the class 'animate-parent'.
|
|
||||||
let current: HTMLElement | null = target;
|
|
||||||
while (current) {
|
|
||||||
if (iter >= PARENT_MAX_DEPTH) {
|
|
||||||
return; // We can just exit right now.
|
|
||||||
}
|
|
||||||
current = current.parentElement;
|
|
||||||
if (current?.classList.contains('animate-parent')) {
|
|
||||||
parent = current;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
iter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Affect all animated children within the parent.
|
|
||||||
if (parent) {
|
|
||||||
const animatedChildren =
|
|
||||||
parent.querySelectorAll<HTMLImageElement>('img.custom-emoji');
|
|
||||||
for (const child of animatedChildren) {
|
|
||||||
setAnimateGif(child, animate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setAnimateGif(image: HTMLImageElement, animate: boolean) {
|
|
||||||
const { classList, dataset } = image;
|
|
||||||
if (
|
|
||||||
!classList.contains('custom-emoji') ||
|
|
||||||
!dataset.static ||
|
|
||||||
!dataset.original
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
image.src = animate ? dataset.original : dataset.static;
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { initialState } from '@/mastodon/initial_state';
|
import { initialState } from '@/mastodon/initial_state';
|
||||||
import { loadWorker } from '@/mastodon/utils/workers';
|
|
||||||
|
|
||||||
import { toSupportedLocale } from './locale';
|
import { toSupportedLocale } from './locale';
|
||||||
import { emojiLogger } from './utils';
|
import { emojiLogger } from './utils';
|
||||||
|
// eslint-disable-next-line import/default -- Importing via worker loader.
|
||||||
|
import EmojiWorker from './worker?worker&inline';
|
||||||
|
|
||||||
const userLocale = toSupportedLocale(initialState?.meta.locale ?? 'en');
|
const userLocale = toSupportedLocale(initialState?.meta.locale ?? 'en');
|
||||||
|
|
||||||
@@ -16,9 +17,7 @@ export function initializeEmoji() {
|
|||||||
log('initializing emojis');
|
log('initializing emojis');
|
||||||
if (!worker && 'Worker' in window) {
|
if (!worker && 'Worker' in window) {
|
||||||
try {
|
try {
|
||||||
worker = loadWorker(new URL('./worker', import.meta.url), {
|
worker = new EmojiWorker();
|
||||||
type: 'module',
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Error creating web worker:', err);
|
console.warn('Error creating web worker:', err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
// taken from:
|
|
||||||
// https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866
|
|
||||||
export const unicodeToFilename = (str) => {
|
|
||||||
let result = '';
|
|
||||||
let charCode = 0;
|
|
||||||
let p = 0;
|
|
||||||
let i = 0;
|
|
||||||
while (i < str.length) {
|
|
||||||
charCode = str.charCodeAt(i++);
|
|
||||||
if (p) {
|
|
||||||
if (result.length > 0) {
|
|
||||||
result += '-';
|
|
||||||
}
|
|
||||||
result += (0x10000 + ((p - 0xD800) << 10) + (charCode - 0xDC00)).toString(16);
|
|
||||||
p = 0;
|
|
||||||
} else if (0xD800 <= charCode && charCode <= 0xDBFF) {
|
|
||||||
p = charCode;
|
|
||||||
} else {
|
|
||||||
if (result.length > 0) {
|
|
||||||
result += '-';
|
|
||||||
}
|
|
||||||
result += charCode.toString(16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
function padLeft(str, num) {
|
|
||||||
while (str.length < num) {
|
|
||||||
str = '0' + str;
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const unicodeToUnifiedName = (str) => {
|
|
||||||
let output = '';
|
|
||||||
|
|
||||||
for (let i = 0; i < str.length; i += 2) {
|
|
||||||
if (i > 0) {
|
|
||||||
output += '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
output += padLeft(str.codePointAt(i).toString(16).toUpperCase(), 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
};
|
|
||||||
43
app/javascript/mastodon/features/emoji/unicode_utils.ts
Normal file
43
app/javascript/mastodon/features/emoji/unicode_utils.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// taken from:
|
||||||
|
// https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866
|
||||||
|
export function unicodeToFilename(str: string) {
|
||||||
|
let result = '';
|
||||||
|
let charCode = 0;
|
||||||
|
let p = 0;
|
||||||
|
let i = 0;
|
||||||
|
while (i < str.length) {
|
||||||
|
charCode = str.charCodeAt(i++);
|
||||||
|
if (p) {
|
||||||
|
if (result.length > 0) {
|
||||||
|
result += '-';
|
||||||
|
}
|
||||||
|
result += (0x10000 + ((p - 0xd800) << 10) + (charCode - 0xdc00)).toString(
|
||||||
|
16,
|
||||||
|
);
|
||||||
|
p = 0;
|
||||||
|
} else if (0xd800 <= charCode && charCode <= 0xdbff) {
|
||||||
|
p = charCode;
|
||||||
|
} else {
|
||||||
|
if (result.length > 0) {
|
||||||
|
result += '-';
|
||||||
|
}
|
||||||
|
result += charCode.toString(16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unicodeToUnifiedName(str: string) {
|
||||||
|
let output = '';
|
||||||
|
|
||||||
|
for (let i = 0; i < str.length; i += 2) {
|
||||||
|
if (i > 0) {
|
||||||
|
output += '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
output +=
|
||||||
|
str.codePointAt(i)?.toString(16).toUpperCase().padStart(4, '0') ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
@@ -24,6 +24,14 @@ import StatusListContainer from '../ui/containers/status_list_container';
|
|||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
title: { id: 'column.firehose', defaultMessage: 'Live feeds' },
|
title: { id: 'column.firehose', defaultMessage: 'Live feeds' },
|
||||||
|
title_local: {
|
||||||
|
id: 'column.firehose_local',
|
||||||
|
defaultMessage: 'Live feed for this server',
|
||||||
|
},
|
||||||
|
title_singular: {
|
||||||
|
id: 'column.firehose_singular',
|
||||||
|
defaultMessage: 'Live feed',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ColumnSettings = () => {
|
const ColumnSettings = () => {
|
||||||
@@ -161,13 +169,23 @@ const Firehose = ({ feedType, multiColumn }) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let title;
|
||||||
|
|
||||||
|
if (canViewFeed(signedIn, permissions, localLiveFeedAccess) && canViewFeed(signedIn, permissions, remoteLiveFeedAccess)) {
|
||||||
|
title = messages.title;
|
||||||
|
} else if (canViewFeed(signedIn, permissions, localLiveFeedAccess)) {
|
||||||
|
title = messages.title_local;
|
||||||
|
} else {
|
||||||
|
title = messages.title_singular;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column bindToDocument={!multiColumn} ref={columnRef} label={intl.formatMessage(messages.title)}>
|
<Column bindToDocument={!multiColumn} ref={columnRef} label={intl.formatMessage(messages.title)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='globe'
|
icon='globe'
|
||||||
iconComponent={PublicIcon}
|
iconComponent={PublicIcon}
|
||||||
active={hasUnread}
|
active={hasUnread}
|
||||||
title={intl.formatMessage(messages.title)}
|
title={intl.formatMessage(title)}
|
||||||
onPin={handlePin}
|
onPin={handlePin}
|
||||||
onClick={handleHeaderClick}
|
onClick={handleHeaderClick}
|
||||||
multiColumn={multiColumn}
|
multiColumn={multiColumn}
|
||||||
|
|||||||
@@ -1,458 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import { PureComponent, useCallback, useMemo } from 'react';
|
|
||||||
|
|
||||||
import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl';
|
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { withRouter } from 'react-router-dom';
|
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
|
|
||||||
import { animated, useTransition } from '@react-spring/web';
|
|
||||||
import ReactSwipeableViews from 'react-swipeable-views';
|
|
||||||
|
|
||||||
import elephantUIPlane from '@/images/elephant_ui_plane.svg';
|
|
||||||
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
|
||||||
import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
|
|
||||||
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
|
||||||
import { AnimatedNumber } from 'mastodon/components/animated_number';
|
|
||||||
import { Icon } from 'mastodon/components/icon';
|
|
||||||
import { IconButton } from 'mastodon/components/icon_button';
|
|
||||||
import EmojiPickerDropdown from 'mastodon/features/compose/containers/emoji_picker_dropdown_container';
|
|
||||||
import { unicodeMapping } from 'mastodon/features/emoji/emoji_unicode_mapping_light';
|
|
||||||
import { autoPlayGif, reduceMotion, disableSwiping, mascot } from 'mastodon/initial_state';
|
|
||||||
import { assetHost } from 'mastodon/utils/config';
|
|
||||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
|
||||||
previous: { id: 'lightbox.previous', defaultMessage: 'Previous' },
|
|
||||||
next: { id: 'lightbox.next', defaultMessage: 'Next' },
|
|
||||||
});
|
|
||||||
|
|
||||||
class ContentWithRouter extends ImmutablePureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
announcement: ImmutablePropTypes.map.isRequired,
|
|
||||||
...WithRouterPropTypes,
|
|
||||||
};
|
|
||||||
|
|
||||||
setRef = c => {
|
|
||||||
this.node = c;
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
this._updateLinks();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate () {
|
|
||||||
this._updateLinks();
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateLinks () {
|
|
||||||
const node = this.node;
|
|
||||||
|
|
||||||
if (!node) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const links = node.querySelectorAll('a');
|
|
||||||
|
|
||||||
for (var i = 0; i < links.length; ++i) {
|
|
||||||
let link = links[i];
|
|
||||||
|
|
||||||
if (link.classList.contains('status-link')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
link.classList.add('status-link');
|
|
||||||
|
|
||||||
let mention = this.props.announcement.get('mentions').find(item => link.href === item.get('url'));
|
|
||||||
|
|
||||||
if (mention) {
|
|
||||||
link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
|
|
||||||
link.setAttribute('title', mention.get('acct'));
|
|
||||||
} else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
|
|
||||||
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
|
|
||||||
} else {
|
|
||||||
let status = this.props.announcement.get('statuses').find(item => link.href === item.get('url'));
|
|
||||||
if (status) {
|
|
||||||
link.addEventListener('click', this.onStatusClick.bind(this, status), false);
|
|
||||||
}
|
|
||||||
link.setAttribute('title', link.href);
|
|
||||||
link.classList.add('unhandled-link');
|
|
||||||
}
|
|
||||||
|
|
||||||
link.setAttribute('target', '_blank');
|
|
||||||
link.setAttribute('rel', 'noopener');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMentionClick = (mention, e) => {
|
|
||||||
if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.props.history.push(`/@${mention.get('acct')}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onHashtagClick = (hashtag, e) => {
|
|
||||||
hashtag = hashtag.replace(/^#/, '');
|
|
||||||
|
|
||||||
if (this.props.history&& e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.props.history.push(`/tags/${hashtag}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onStatusClick = (status, e) => {
|
|
||||||
if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.props.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { announcement } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className='announcements__item__content translate animate-parent'
|
|
||||||
ref={this.setRef}
|
|
||||||
dangerouslySetInnerHTML={{ __html: announcement.get('contentHtml') }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const Content = withRouter(ContentWithRouter);
|
|
||||||
|
|
||||||
class Emoji extends PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
emoji: PropTypes.string.isRequired,
|
|
||||||
emojiMap: ImmutablePropTypes.map.isRequired,
|
|
||||||
hovered: PropTypes.bool.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { emoji, emojiMap, hovered } = this.props;
|
|
||||||
|
|
||||||
if (unicodeMapping[emoji]) {
|
|
||||||
const { filename, shortCode } = unicodeMapping[this.props.emoji];
|
|
||||||
const title = shortCode ? `:${shortCode}:` : '';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
draggable='false'
|
|
||||||
className='emojione'
|
|
||||||
alt={emoji}
|
|
||||||
title={title}
|
|
||||||
src={`${assetHost}/emoji/${filename}.svg`}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (emojiMap.get(emoji)) {
|
|
||||||
const filename = (autoPlayGif || hovered) ? emojiMap.getIn([emoji, 'url']) : emojiMap.getIn([emoji, 'static_url']);
|
|
||||||
const shortCode = `:${emoji}:`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
draggable='false'
|
|
||||||
className='emojione custom-emoji'
|
|
||||||
alt={shortCode}
|
|
||||||
title={shortCode}
|
|
||||||
src={filename}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class Reaction extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
announcementId: PropTypes.string.isRequired,
|
|
||||||
reaction: ImmutablePropTypes.map.isRequired,
|
|
||||||
addReaction: PropTypes.func.isRequired,
|
|
||||||
removeReaction: PropTypes.func.isRequired,
|
|
||||||
emojiMap: ImmutablePropTypes.map.isRequired,
|
|
||||||
style: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
hovered: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleClick = () => {
|
|
||||||
const { reaction, announcementId, addReaction, removeReaction } = this.props;
|
|
||||||
|
|
||||||
if (reaction.get('me')) {
|
|
||||||
removeReaction(announcementId, reaction.get('name'));
|
|
||||||
} else {
|
|
||||||
addReaction(announcementId, reaction.get('name'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleMouseEnter = () => this.setState({ hovered: true });
|
|
||||||
|
|
||||||
handleMouseLeave = () => this.setState({ hovered: false });
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { reaction } = this.props;
|
|
||||||
|
|
||||||
let shortCode = reaction.get('name');
|
|
||||||
|
|
||||||
if (unicodeMapping[shortCode]) {
|
|
||||||
shortCode = unicodeMapping[shortCode].shortCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<animated.button
|
|
||||||
className={classNames('reactions-bar__item', { active: reaction.get('me') })}
|
|
||||||
onClick={this.handleClick}
|
|
||||||
title={`:${shortCode}:`}
|
|
||||||
style={this.props.style}
|
|
||||||
// This does not use animate-parent as this component is directly rendered by React.
|
|
||||||
onMouseEnter={this.handleMouseEnter}
|
|
||||||
onMouseLeave={this.handleMouseLeave}
|
|
||||||
>
|
|
||||||
<span className='reactions-bar__item__emoji'>
|
|
||||||
<Emoji hovered={this.state.hovered} emoji={reaction.get('name')} emojiMap={this.props.emojiMap} />
|
|
||||||
</span>
|
|
||||||
<span className='reactions-bar__item__count'>
|
|
||||||
<AnimatedNumber value={reaction.get('count')} />
|
|
||||||
</span>
|
|
||||||
</animated.button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const ReactionsBar = ({
|
|
||||||
announcementId,
|
|
||||||
reactions,
|
|
||||||
emojiMap,
|
|
||||||
addReaction,
|
|
||||||
removeReaction,
|
|
||||||
}) => {
|
|
||||||
const visibleReactions = useMemo(() => reactions.filter(x => x.get('count') > 0).toArray(), [reactions]);
|
|
||||||
|
|
||||||
const handleEmojiPick = useCallback((emoji) => {
|
|
||||||
addReaction(announcementId, emoji.native.replaceAll(/:/g, ''));
|
|
||||||
}, [addReaction, announcementId]);
|
|
||||||
|
|
||||||
const transitions = useTransition(visibleReactions, {
|
|
||||||
from: {
|
|
||||||
scale: 0,
|
|
||||||
},
|
|
||||||
enter: {
|
|
||||||
scale: 1,
|
|
||||||
},
|
|
||||||
leave: {
|
|
||||||
scale: 0,
|
|
||||||
},
|
|
||||||
keys: visibleReactions.map(x => x.get('name')),
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classNames('reactions-bar', {
|
|
||||||
'reactions-bar--empty': visibleReactions.length === 0
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{transitions(({ scale }, reaction) => (
|
|
||||||
<Reaction
|
|
||||||
key={reaction.get('name')}
|
|
||||||
reaction={reaction}
|
|
||||||
style={{ transform: scale.to((s) => `scale(${s})`) }}
|
|
||||||
addReaction={addReaction}
|
|
||||||
removeReaction={removeReaction}
|
|
||||||
announcementId={announcementId}
|
|
||||||
emojiMap={emojiMap}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{visibleReactions.length < 8 && (
|
|
||||||
<EmojiPickerDropdown
|
|
||||||
onPickEmoji={handleEmojiPick}
|
|
||||||
button={<Icon id='plus' icon={AddIcon} />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
ReactionsBar.propTypes = {
|
|
||||||
announcementId: PropTypes.string.isRequired,
|
|
||||||
reactions: ImmutablePropTypes.list.isRequired,
|
|
||||||
addReaction: PropTypes.func.isRequired,
|
|
||||||
removeReaction: PropTypes.func.isRequired,
|
|
||||||
emojiMap: ImmutablePropTypes.map.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
class Announcement extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
announcement: ImmutablePropTypes.map.isRequired,
|
|
||||||
emojiMap: ImmutablePropTypes.map.isRequired,
|
|
||||||
addReaction: PropTypes.func.isRequired,
|
|
||||||
removeReaction: PropTypes.func.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
selected: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
unread: !this.props.announcement.get('read'),
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidUpdate () {
|
|
||||||
const { selected, announcement } = this.props;
|
|
||||||
if (!selected && this.state.unread !== !announcement.get('read')) {
|
|
||||||
this.setState({ unread: !announcement.get('read') });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { announcement } = this.props;
|
|
||||||
const { unread } = this.state;
|
|
||||||
const startsAt = announcement.get('starts_at') && new Date(announcement.get('starts_at'));
|
|
||||||
const endsAt = announcement.get('ends_at') && new Date(announcement.get('ends_at'));
|
|
||||||
const now = new Date();
|
|
||||||
const hasTimeRange = startsAt && endsAt;
|
|
||||||
const skipTime = announcement.get('all_day');
|
|
||||||
|
|
||||||
let timestamp = null;
|
|
||||||
if (hasTimeRange) {
|
|
||||||
const skipYear = startsAt.getFullYear() === endsAt.getFullYear() && endsAt.getFullYear() === now.getFullYear();
|
|
||||||
const skipEndDate = startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear();
|
|
||||||
timestamp = (
|
|
||||||
<>
|
|
||||||
<FormattedDate value={startsAt} year={(skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month='short' day='2-digit' hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /> - <FormattedDate value={endsAt} year={(skipYear || endsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month={skipEndDate ? undefined : 'short'} day={skipEndDate ? undefined : '2-digit'} hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const publishedAt = new Date(announcement.get('published_at'));
|
|
||||||
timestamp = (
|
|
||||||
<FormattedDate value={publishedAt} year={publishedAt.getFullYear() === now.getFullYear() ? undefined : 'numeric'} month='short' day='2-digit' hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='announcements__item'>
|
|
||||||
<strong className='announcements__item__range'>
|
|
||||||
<FormattedMessage id='announcement.announcement' defaultMessage='Announcement' />
|
|
||||||
<span> · {timestamp}</span>
|
|
||||||
</strong>
|
|
||||||
|
|
||||||
<Content announcement={announcement} />
|
|
||||||
|
|
||||||
<ReactionsBar
|
|
||||||
reactions={announcement.get('reactions')}
|
|
||||||
announcementId={announcement.get('id')}
|
|
||||||
addReaction={this.props.addReaction}
|
|
||||||
removeReaction={this.props.removeReaction}
|
|
||||||
emojiMap={this.props.emojiMap}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{unread && <span className='announcements__item__unread' />}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class Announcements extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
announcements: ImmutablePropTypes.list,
|
|
||||||
emojiMap: ImmutablePropTypes.map.isRequired,
|
|
||||||
dismissAnnouncement: PropTypes.func.isRequired,
|
|
||||||
addReaction: PropTypes.func.isRequired,
|
|
||||||
removeReaction: PropTypes.func.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
index: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
static getDerivedStateFromProps(props, state) {
|
|
||||||
if (props.announcements.size > 0 && state.index >= props.announcements.size) {
|
|
||||||
return { index: props.announcements.size - 1 };
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
this._markAnnouncementAsRead();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate () {
|
|
||||||
this._markAnnouncementAsRead();
|
|
||||||
}
|
|
||||||
|
|
||||||
_markAnnouncementAsRead () {
|
|
||||||
const { dismissAnnouncement, announcements } = this.props;
|
|
||||||
const { index } = this.state;
|
|
||||||
const announcement = announcements.get(announcements.size - 1 - index);
|
|
||||||
if (!announcement.get('read')) dismissAnnouncement(announcement.get('id'));
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChangeIndex = index => {
|
|
||||||
this.setState({ index: index % this.props.announcements.size });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleNextClick = () => {
|
|
||||||
this.setState({ index: (this.state.index + 1) % this.props.announcements.size });
|
|
||||||
};
|
|
||||||
|
|
||||||
handlePrevClick = () => {
|
|
||||||
this.setState({ index: (this.props.announcements.size + this.state.index - 1) % this.props.announcements.size });
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { announcements, intl } = this.props;
|
|
||||||
const { index } = this.state;
|
|
||||||
|
|
||||||
if (announcements.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='announcements'>
|
|
||||||
<img className='announcements__mastodon' alt='' draggable='false' src={mascot || elephantUIPlane} />
|
|
||||||
|
|
||||||
<div className='announcements__container'>
|
|
||||||
<ReactSwipeableViews animateHeight animateTransitions={!reduceMotion} index={index} onChangeIndex={this.handleChangeIndex}>
|
|
||||||
{announcements.map((announcement, idx) => (
|
|
||||||
<Announcement
|
|
||||||
key={announcement.get('id')}
|
|
||||||
announcement={announcement}
|
|
||||||
emojiMap={this.props.emojiMap}
|
|
||||||
addReaction={this.props.addReaction}
|
|
||||||
removeReaction={this.props.removeReaction}
|
|
||||||
intl={intl}
|
|
||||||
selected={index === idx}
|
|
||||||
disabled={disableSwiping}
|
|
||||||
/>
|
|
||||||
)).reverse()}
|
|
||||||
</ReactSwipeableViews>
|
|
||||||
|
|
||||||
{announcements.size > 1 && (
|
|
||||||
<div className='announcements__pagination'>
|
|
||||||
<IconButton disabled={announcements.size === 1} title={intl.formatMessage(messages.previous)} icon='chevron-left' iconComponent={ChevronLeftIcon} onClick={this.handlePrevClick} size={13} />
|
|
||||||
<span>{index + 1} / {announcements.size}</span>
|
|
||||||
<IconButton disabled={announcements.size === 1} title={intl.formatMessage(messages.next)} icon='chevron-right' iconComponent={ChevronRightIcon} onClick={this.handleNextClick} size={13} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default injectIntl(Announcements);
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { Map as ImmutableMap } from 'immutable';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
|
|
||||||
import { addReaction, removeReaction, dismissAnnouncement } from 'mastodon/actions/announcements';
|
|
||||||
|
|
||||||
import Announcements from '../components/announcements';
|
|
||||||
|
|
||||||
const customEmojiMap = createSelector([state => state.get('custom_emojis')], items => items.reduce((map, emoji) => map.set(emoji.get('shortcode'), emoji), ImmutableMap()));
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
announcements: state.getIn(['announcements', 'items']),
|
|
||||||
emojiMap: customEmojiMap(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
|
||||||
dismissAnnouncement: id => dispatch(dismissAnnouncement(id)),
|
|
||||||
addReaction: (id, name) => dispatch(addReaction(id, name)),
|
|
||||||
removeReaction: (id, name) => dispatch(removeReaction(id, name)),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(Announcements);
|
|
||||||
@@ -10,10 +10,8 @@ import ReactSwipeableViews from 'react-swipeable-views';
|
|||||||
import elephantUIPlane from '@/images/elephant_ui_plane.svg';
|
import elephantUIPlane from '@/images/elephant_ui_plane.svg';
|
||||||
import { CustomEmojiProvider } from '@/mastodon/components/emoji/context';
|
import { CustomEmojiProvider } from '@/mastodon/components/emoji/context';
|
||||||
import { IconButton } from '@/mastodon/components/icon_button';
|
import { IconButton } from '@/mastodon/components/icon_button';
|
||||||
import LegacyAnnouncements from '@/mastodon/features/getting_started/containers/announcements_container';
|
|
||||||
import { mascot, reduceMotion } from '@/mastodon/initial_state';
|
import { mascot, reduceMotion } from '@/mastodon/initial_state';
|
||||||
import { createAppSelector, useAppSelector } from '@/mastodon/store';
|
import { createAppSelector, useAppSelector } from '@/mastodon/store';
|
||||||
import { isModernEmojiEnabled } from '@/mastodon/utils/environment';
|
|
||||||
import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
|
import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
|
||||||
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
||||||
|
|
||||||
@@ -32,7 +30,7 @@ const announcementSelector = createAppSelector(
|
|||||||
(announcements.get('items')?.toJS() as IAnnouncement[] | undefined) ?? [],
|
(announcements.get('items')?.toJS() as IAnnouncement[] | undefined) ?? [],
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ModernAnnouncements: FC = () => {
|
export const Announcements: FC = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const announcements = useAppSelector(announcementSelector);
|
const announcements = useAppSelector(announcementSelector);
|
||||||
@@ -112,7 +110,3 @@ export const ModernAnnouncements: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Announcements = isModernEmojiEnabled()
|
|
||||||
? ModernAnnouncements
|
|
||||||
: LegacyAnnouncements;
|
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ const messages = defineMessages({
|
|||||||
},
|
},
|
||||||
explore: { id: 'explore.title', defaultMessage: 'Trending' },
|
explore: { id: 'explore.title', defaultMessage: 'Trending' },
|
||||||
firehose: { id: 'column.firehose', defaultMessage: 'Live feeds' },
|
firehose: { id: 'column.firehose', defaultMessage: 'Live feeds' },
|
||||||
|
firehose_singular: {
|
||||||
|
id: 'column.firehose_singular',
|
||||||
|
defaultMessage: 'Live feed',
|
||||||
|
},
|
||||||
direct: { id: 'navigation_bar.direct', defaultMessage: 'Private mentions' },
|
direct: { id: 'navigation_bar.direct', defaultMessage: 'Private mentions' },
|
||||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
|
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
|
||||||
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
|
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
|
||||||
@@ -275,7 +279,12 @@ export const NavigationPanel: React.FC<{ multiColumn?: boolean }> = ({
|
|||||||
icon='globe'
|
icon='globe'
|
||||||
iconComponent={PublicIcon}
|
iconComponent={PublicIcon}
|
||||||
isActive={isFirehoseActive}
|
isActive={isFirehoseActive}
|
||||||
text={intl.formatMessage(messages.firehose)}
|
text={intl.formatMessage(
|
||||||
|
canViewFeed(signedIn, permissions, localLiveFeedAccess) &&
|
||||||
|
canViewFeed(signedIn, permissions, remoteLiveFeedAccess)
|
||||||
|
? messages.firehose
|
||||||
|
: messages.firehose_singular,
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,47 +1,17 @@
|
|||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
|
|
||||||
import type { List } from 'immutable';
|
import type { List } from 'immutable';
|
||||||
|
|
||||||
import type { History } from 'history';
|
|
||||||
|
|
||||||
import type { ApiMentionJSON } from '@/mastodon/api_types/statuses';
|
|
||||||
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
||||||
import { useElementHandledLink } from '@/mastodon/components/status/handled_link';
|
import { useElementHandledLink } from '@/mastodon/components/status/handled_link';
|
||||||
import type { Status } from '@/mastodon/models/status';
|
import type { Status } from '@/mastodon/models/status';
|
||||||
import { isModernEmojiEnabled } from '@/mastodon/utils/environment';
|
|
||||||
|
|
||||||
import type { Mention } from './embedded_status';
|
import type { Mention } from './embedded_status';
|
||||||
|
|
||||||
const handleMentionClick = (
|
|
||||||
history: History,
|
|
||||||
mention: ApiMentionJSON,
|
|
||||||
e: MouseEvent,
|
|
||||||
) => {
|
|
||||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
|
||||||
e.preventDefault();
|
|
||||||
history.push(`/@${mention.acct}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleHashtagClick = (
|
|
||||||
history: History,
|
|
||||||
hashtag: string,
|
|
||||||
e: MouseEvent,
|
|
||||||
) => {
|
|
||||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
|
||||||
e.preventDefault();
|
|
||||||
history.push(`/tags/${hashtag.replace(/^#/, '')}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EmbeddedStatusContent: React.FC<{
|
export const EmbeddedStatusContent: React.FC<{
|
||||||
status: Status;
|
status: Status;
|
||||||
className?: string;
|
className?: string;
|
||||||
}> = ({ status, className }) => {
|
}> = ({ status, className }) => {
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
const mentions = useMemo(
|
const mentions = useMemo(
|
||||||
() => (status.get('mentions') as List<Mention>).toJS(),
|
() => (status.get('mentions') as List<Mention>).toJS(),
|
||||||
[status],
|
[status],
|
||||||
@@ -57,55 +27,10 @@ export const EmbeddedStatusContent: React.FC<{
|
|||||||
hrefToMention,
|
hrefToMention,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleContentRef = useCallback(
|
|
||||||
(node: HTMLDivElement | null) => {
|
|
||||||
if (!node || isModernEmojiEnabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const links = node.querySelectorAll<HTMLAnchorElement>('a');
|
|
||||||
|
|
||||||
for (const link of links) {
|
|
||||||
if (link.classList.contains('status-link')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
link.classList.add('status-link');
|
|
||||||
|
|
||||||
const mention = mentions.find((item) => link.href === item.url);
|
|
||||||
|
|
||||||
if (mention) {
|
|
||||||
link.addEventListener(
|
|
||||||
'click',
|
|
||||||
handleMentionClick.bind(null, history, mention),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
link.setAttribute('title', `@${mention.acct}`);
|
|
||||||
link.setAttribute('href', `/@${mention.acct}`);
|
|
||||||
} else if (
|
|
||||||
link.textContent.startsWith('#') ||
|
|
||||||
link.previousSibling?.textContent?.endsWith('#')
|
|
||||||
) {
|
|
||||||
link.addEventListener(
|
|
||||||
'click',
|
|
||||||
handleHashtagClick.bind(null, history, link.text),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
link.setAttribute('href', `/tags/${link.text.replace(/^#/, '')}`);
|
|
||||||
} else {
|
|
||||||
link.setAttribute('title', link.href);
|
|
||||||
link.classList.add('unhandled-link');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[mentions, history],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EmojiHTML
|
<EmojiHTML
|
||||||
{...htmlHandlers}
|
{...htmlHandlers}
|
||||||
className={className}
|
className={className}
|
||||||
ref={handleContentRef}
|
|
||||||
lang={status.get('language') as string}
|
lang={status.get('language') as string}
|
||||||
htmlString={status.get('contentHtml') as string}
|
htmlString={status.get('contentHtml') as string}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { IconButton } from 'mastodon/components/icon_button';
|
|||||||
import InlineAccount from 'mastodon/components/inline_account';
|
import InlineAccount from 'mastodon/components/inline_account';
|
||||||
import MediaAttachments from 'mastodon/components/media_attachments';
|
import MediaAttachments from 'mastodon/components/media_attachments';
|
||||||
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
||||||
import emojify from 'mastodon/features/emoji/emoji';
|
|
||||||
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
||||||
import { CustomEmojiProvider } from '@/mastodon/components/emoji/context';
|
import { CustomEmojiProvider } from '@/mastodon/components/emoji/context';
|
||||||
|
|
||||||
@@ -48,13 +47,8 @@ class CompareHistoryModal extends PureComponent {
|
|||||||
const { index, versions, language, onClose } = this.props;
|
const { index, versions, language, onClose } = this.props;
|
||||||
const currentVersion = versions.get(index);
|
const currentVersion = versions.get(index);
|
||||||
|
|
||||||
const emojiMap = currentVersion.get('emojis').reduce((obj, emoji) => {
|
const content = currentVersion.get('content');
|
||||||
obj[`:${emoji.get('shortcode')}:`] = emoji.toJS();
|
const spoilerContent = escapeTextContentForBrowser(currentVersion.get('spoiler_text'));
|
||||||
return obj;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
const content = emojify(currentVersion.get('content'), emojiMap);
|
|
||||||
const spoilerContent = emojify(escapeTextContentForBrowser(currentVersion.get('spoiler_text')), emojiMap);
|
|
||||||
|
|
||||||
const formattedDate = <RelativeTimestamp timestamp={currentVersion.get('created_at')} short={false} />;
|
const formattedDate = <RelativeTimestamp timestamp={currentVersion.get('created_at')} short={false} />;
|
||||||
const formattedName = <InlineAccount accountId={currentVersion.get('account')} />;
|
const formattedName = <InlineAccount accountId={currentVersion.get('account')} />;
|
||||||
@@ -99,7 +93,7 @@ class CompareHistoryModal extends PureComponent {
|
|||||||
<EmojiHTML
|
<EmojiHTML
|
||||||
as="span"
|
as="span"
|
||||||
className='poll__option__text translate'
|
className='poll__option__text translate'
|
||||||
htmlString={emojify(escapeTextContentForBrowser(option.get('title')), emojiMap)}
|
htmlString={escapeTextContentForBrowser(option.get('title'))}
|
||||||
lang={language}
|
lang={language}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -22,12 +22,11 @@ import { identityContextPropShape, withIdentity } from 'mastodon/identity_contex
|
|||||||
import { layoutFromWindow } from 'mastodon/is_mobile';
|
import { layoutFromWindow } from 'mastodon/is_mobile';
|
||||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||||
|
|
||||||
import { handleAnimateGif } from '../emoji/handlers';
|
|
||||||
import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose';
|
import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose';
|
||||||
import { clearHeight } from '../../actions/height_cache';
|
import { clearHeight } from '../../actions/height_cache';
|
||||||
import { fetchServer, fetchServerTranslationLanguages } from '../../actions/server';
|
import { fetchServer, fetchServerTranslationLanguages } from '../../actions/server';
|
||||||
import { expandHomeTimeline } from '../../actions/timelines';
|
import { expandHomeTimeline } from '../../actions/timelines';
|
||||||
import { initialState, me, owner, singleUserMode, trendsEnabled, landingPage, localLiveFeedAccess, disableHoverCards, autoPlayGif } from '../../initial_state';
|
import { initialState, me, owner, singleUserMode, trendsEnabled, landingPage, localLiveFeedAccess, disableHoverCards } from '../../initial_state';
|
||||||
|
|
||||||
import BundleColumnError from './components/bundle_column_error';
|
import BundleColumnError from './components/bundle_column_error';
|
||||||
import { NavigationBar } from './components/navigation_bar';
|
import { NavigationBar } from './components/navigation_bar';
|
||||||
@@ -382,11 +381,6 @@ class UI extends PureComponent {
|
|||||||
window.addEventListener('beforeunload', this.handleBeforeUnload, false);
|
window.addEventListener('beforeunload', this.handleBeforeUnload, false);
|
||||||
window.addEventListener('resize', this.handleResize, { passive: true });
|
window.addEventListener('resize', this.handleResize, { passive: true });
|
||||||
|
|
||||||
if (!autoPlayGif) {
|
|
||||||
window.addEventListener('mouseover', handleAnimateGif, { passive: true });
|
|
||||||
window.addEventListener('mouseout', handleAnimateGif, { passive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('dragenter', this.handleDragEnter, false);
|
document.addEventListener('dragenter', this.handleDragEnter, false);
|
||||||
document.addEventListener('dragover', this.handleDragOver, false);
|
document.addEventListener('dragover', this.handleDragOver, false);
|
||||||
document.addEventListener('drop', this.handleDrop, false);
|
document.addEventListener('drop', this.handleDrop, false);
|
||||||
@@ -412,8 +406,6 @@ class UI extends PureComponent {
|
|||||||
window.removeEventListener('blur', this.handleWindowBlur);
|
window.removeEventListener('blur', this.handleWindowBlur);
|
||||||
window.removeEventListener('beforeunload', this.handleBeforeUnload);
|
window.removeEventListener('beforeunload', this.handleBeforeUnload);
|
||||||
window.removeEventListener('resize', this.handleResize);
|
window.removeEventListener('resize', this.handleResize);
|
||||||
window.removeEventListener('mouseover', handleAnimateGif);
|
|
||||||
window.removeEventListener('mouseout', handleAnimateGif);
|
|
||||||
|
|
||||||
document.removeEventListener('dragenter', this.handleDragEnter);
|
document.removeEventListener('dragenter', this.handleDragEnter);
|
||||||
document.removeEventListener('dragover', this.handleDragOver);
|
document.removeEventListener('dragover', this.handleDragOver);
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { isFulfilled, isRejected } from '@reduxjs/toolkit';
|
|
||||||
|
|
||||||
import { openURL } from 'mastodon/actions/search';
|
|
||||||
import { useAppDispatch } from 'mastodon/store';
|
|
||||||
|
|
||||||
import { isModernEmojiEnabled } from '../utils/environment';
|
|
||||||
|
|
||||||
const isMentionClick = (element: HTMLAnchorElement) =>
|
|
||||||
element.classList.contains('mention') &&
|
|
||||||
!element.classList.contains('hashtag');
|
|
||||||
|
|
||||||
const isHashtagClick = (element: HTMLAnchorElement) =>
|
|
||||||
element.textContent.startsWith('#') ||
|
|
||||||
element.previousSibling?.textContent?.endsWith('#');
|
|
||||||
|
|
||||||
export const useLinks = (skipHashtags?: boolean) => {
|
|
||||||
const history = useHistory();
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const handleHashtagClick = useCallback(
|
|
||||||
(element: HTMLAnchorElement) => {
|
|
||||||
const { textContent } = element;
|
|
||||||
|
|
||||||
if (!textContent) return;
|
|
||||||
|
|
||||||
history.push(`/tags/${textContent.replace(/^#/, '')}`);
|
|
||||||
},
|
|
||||||
[history],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleMentionClick = useCallback(
|
|
||||||
async (element: HTMLAnchorElement) => {
|
|
||||||
const result = await dispatch(openURL({ url: element.href }));
|
|
||||||
|
|
||||||
if (isFulfilled(result)) {
|
|
||||||
if (result.payload.accounts[0]) {
|
|
||||||
history.push(`/@${result.payload.accounts[0].acct}`);
|
|
||||||
} else if (result.payload.statuses[0]) {
|
|
||||||
history.push(
|
|
||||||
`/@${result.payload.statuses[0].account.acct}/${result.payload.statuses[0].id}`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
window.location.href = element.href;
|
|
||||||
}
|
|
||||||
} else if (isRejected(result)) {
|
|
||||||
window.location.href = element.href;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[dispatch, history],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleClick = useCallback(
|
|
||||||
(e: React.MouseEvent) => {
|
|
||||||
// Exit early if modern emoji is enabled, as this is handled by HandledLink.
|
|
||||||
if (isModernEmojiEnabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const target = (e.target as HTMLElement).closest('a');
|
|
||||||
|
|
||||||
if (!target || e.button !== 0 || e.ctrlKey || e.metaKey) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isMentionClick(target)) {
|
|
||||||
e.preventDefault();
|
|
||||||
void handleMentionClick(target);
|
|
||||||
} else if (isHashtagClick(target) && !skipHashtags) {
|
|
||||||
e.preventDefault();
|
|
||||||
handleHashtagClick(target);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[skipHashtags, handleMentionClick, handleHashtagClick],
|
|
||||||
);
|
|
||||||
|
|
||||||
return handleClick;
|
|
||||||
};
|
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
"account.disable_notifications": "Спиране на известяване при публикуване от @{name}",
|
"account.disable_notifications": "Спиране на известяване при публикуване от @{name}",
|
||||||
"account.domain_blocking": "Блокиране на домейн",
|
"account.domain_blocking": "Блокиране на домейн",
|
||||||
"account.edit_profile": "Редактиране на профила",
|
"account.edit_profile": "Редактиране на профила",
|
||||||
|
"account.edit_profile_short": "Редактиране",
|
||||||
"account.enable_notifications": "Известяване при публикуване от @{name}",
|
"account.enable_notifications": "Известяване при публикуване от @{name}",
|
||||||
"account.endorse": "Представи в профила",
|
"account.endorse": "Представи в профила",
|
||||||
"account.familiar_followers_many": "Последвано от {name1}, {name2}, и {othersCount, plural, one {един друг, когото познавате} other {# други, които познавате}}",
|
"account.familiar_followers_many": "Последвано от {name1}, {name2}, и {othersCount, plural, one {един друг, когото познавате} other {# други, които познавате}}",
|
||||||
@@ -40,6 +41,9 @@
|
|||||||
"account.featured_tags.last_status_never": "Няма публикации",
|
"account.featured_tags.last_status_never": "Няма публикации",
|
||||||
"account.follow": "Последване",
|
"account.follow": "Последване",
|
||||||
"account.follow_back": "Последване взаимно",
|
"account.follow_back": "Последване взаимно",
|
||||||
|
"account.follow_request_cancel": "Отказване на заявката",
|
||||||
|
"account.follow_request_cancel_short": "Отказ",
|
||||||
|
"account.follow_request_short": "Заявка",
|
||||||
"account.followers": "Последователи",
|
"account.followers": "Последователи",
|
||||||
"account.followers.empty": "Още никой не следва потребителя.",
|
"account.followers.empty": "Още никой не следва потребителя.",
|
||||||
"account.followers_counter": "{count, plural, one {{counter} последовател} other {{counter} последователи}}",
|
"account.followers_counter": "{count, plural, one {{counter} последовател} other {{counter} последователи}}",
|
||||||
@@ -238,6 +242,9 @@
|
|||||||
"confirmations.missing_alt_text.secondary": "Все пак да се публикува",
|
"confirmations.missing_alt_text.secondary": "Все пак да се публикува",
|
||||||
"confirmations.missing_alt_text.title": "Добавяте ли алтернативен текст?",
|
"confirmations.missing_alt_text.title": "Добавяте ли алтернативен текст?",
|
||||||
"confirmations.mute.confirm": "Заглушаване",
|
"confirmations.mute.confirm": "Заглушаване",
|
||||||
|
"confirmations.quiet_post_quote_info.dismiss": "Без друго напомняне",
|
||||||
|
"confirmations.quiet_post_quote_info.got_it": "Схванах",
|
||||||
|
"confirmations.quiet_post_quote_info.title": "Цитиране на публикации за тиха публика",
|
||||||
"confirmations.redraft.confirm": "Изтриване и преработване",
|
"confirmations.redraft.confirm": "Изтриване и преработване",
|
||||||
"confirmations.redraft.message": "Наистина ли искате да изтриете тази публикация и да я направите чернова? Означаванията като любими и подсилванията ще се изгубят, а и отговорите към първоначалната публикация ще осиротеят.",
|
"confirmations.redraft.message": "Наистина ли искате да изтриете тази публикация и да я направите чернова? Означаванията като любими и подсилванията ще се изгубят, а и отговорите към първоначалната публикация ще осиротеят.",
|
||||||
"confirmations.redraft.title": "Изтривате и преработвате ли публикацията?",
|
"confirmations.redraft.title": "Изтривате и преработвате ли публикацията?",
|
||||||
@@ -247,7 +254,11 @@
|
|||||||
"confirmations.revoke_quote.confirm": "Премахване на публикация",
|
"confirmations.revoke_quote.confirm": "Премахване на публикация",
|
||||||
"confirmations.revoke_quote.message": "Действието е неотменимо.",
|
"confirmations.revoke_quote.message": "Действието е неотменимо.",
|
||||||
"confirmations.revoke_quote.title": "Премахвате ли публикацията?",
|
"confirmations.revoke_quote.title": "Премахвате ли публикацията?",
|
||||||
|
"confirmations.unblock.confirm": "Отблокиране",
|
||||||
|
"confirmations.unblock.title": "Отблокирате ли @{name}?",
|
||||||
"confirmations.unfollow.confirm": "Без следване",
|
"confirmations.unfollow.confirm": "Без следване",
|
||||||
|
"confirmations.unfollow.title": "Спирате ли следване на {name}?",
|
||||||
|
"confirmations.withdraw_request.confirm": "Оттегляне на заявката",
|
||||||
"content_warning.hide": "Скриване на публ.",
|
"content_warning.hide": "Скриване на публ.",
|
||||||
"content_warning.show": "Нека се покаже",
|
"content_warning.show": "Нека се покаже",
|
||||||
"content_warning.show_more": "Показване на още",
|
"content_warning.show_more": "Показване на още",
|
||||||
@@ -442,10 +453,12 @@
|
|||||||
"ignore_notifications_modal.private_mentions_title": "Пренебрегвате ли известия от непоискани лични споменавания?",
|
"ignore_notifications_modal.private_mentions_title": "Пренебрегвате ли известия от непоискани лични споменавания?",
|
||||||
"info_button.label": "Помощ",
|
"info_button.label": "Помощ",
|
||||||
"info_button.what_is_alt_text": "<h1>Какво е алтернативен текст?</h1> <p>Алтернативният текст осигурява описания на изображение за хора със зрителни увреждания, връзки с ниска честотна лента или търсещите допълнителен контекст.</p> <p>Може да подобрите достъпността и разбираемостта за всеки, пишейки ясен, кратък и обективен алтернативен текст.</p> <ul> <li>Уловете важните елементи</li> <li>Обобщете текста в образите</li> <li>Употребявайте правилна структура на изречението</li> <li>Избягвайте излишна информация</li> <li>Съсредоточете се върху тенденциите и ключови констатации в сложни онагледявания (като диаграми и карти)</li> </ul>",
|
"info_button.what_is_alt_text": "<h1>Какво е алтернативен текст?</h1> <p>Алтернативният текст осигурява описания на изображение за хора със зрителни увреждания, връзки с ниска честотна лента или търсещите допълнителен контекст.</p> <p>Може да подобрите достъпността и разбираемостта за всеки, пишейки ясен, кратък и обективен алтернативен текст.</p> <ul> <li>Уловете важните елементи</li> <li>Обобщете текста в образите</li> <li>Употребявайте правилна структура на изречението</li> <li>Избягвайте излишна информация</li> <li>Съсредоточете се върху тенденциите и ключови констатации в сложни онагледявания (като диаграми и карти)</li> </ul>",
|
||||||
|
"interaction_modal.action": "Трябва да влезете с акаунта си, в който и да е сървър на Mastodon, когото използвате, за да взаимодействате с публикация на {name}.",
|
||||||
"interaction_modal.go": "Напред",
|
"interaction_modal.go": "Напред",
|
||||||
"interaction_modal.no_account_yet": "Още ли нямате акаунт?",
|
"interaction_modal.no_account_yet": "Още ли нямате акаунт?",
|
||||||
"interaction_modal.on_another_server": "На различен сървър",
|
"interaction_modal.on_another_server": "На различен сървър",
|
||||||
"interaction_modal.on_this_server": "На този сървър",
|
"interaction_modal.on_this_server": "На този сървър",
|
||||||
|
"interaction_modal.title": "Влезте, за да продължите",
|
||||||
"interaction_modal.username_prompt": "Напр. {example}",
|
"interaction_modal.username_prompt": "Напр. {example}",
|
||||||
"intervals.full.days": "{number, plural, one {# ден} other {# дни}}",
|
"intervals.full.days": "{number, plural, one {# ден} other {# дни}}",
|
||||||
"intervals.full.hours": "{number, plural, one {# час} other {# часа}}",
|
"intervals.full.hours": "{number, plural, one {# час} other {# часа}}",
|
||||||
@@ -596,6 +609,7 @@
|
|||||||
"notification.moderation_warning.action_suspend": "Вашият акаунт е спрян.",
|
"notification.moderation_warning.action_suspend": "Вашият акаунт е спрян.",
|
||||||
"notification.own_poll": "Анкетата ви приключи",
|
"notification.own_poll": "Анкетата ви приключи",
|
||||||
"notification.poll": "Анкета, в която гласувахте, приключи",
|
"notification.poll": "Анкета, в която гласувахте, приключи",
|
||||||
|
"notification.quoted_update": "{name} редактира публикация, която цитирахте",
|
||||||
"notification.reblog": "{name} подсили ваша публикация",
|
"notification.reblog": "{name} подсили ваша публикация",
|
||||||
"notification.reblog.name_and_others_with_link": "{name} и <a>{count, plural, one {# друг} other {# други}}</a> подсилиха ваша публикация",
|
"notification.reblog.name_and_others_with_link": "{name} и <a>{count, plural, one {# друг} other {# други}}</a> подсилиха ваша публикация",
|
||||||
"notification.relationships_severance_event": "Изгуби се връзката с {name}",
|
"notification.relationships_severance_event": "Изгуби се връзката с {name}",
|
||||||
@@ -715,10 +729,17 @@
|
|||||||
"privacy.private.short": "Последователи",
|
"privacy.private.short": "Последователи",
|
||||||
"privacy.public.long": "Всеки във и извън Mastodon",
|
"privacy.public.long": "Всеки във и извън Mastodon",
|
||||||
"privacy.public.short": "Публично",
|
"privacy.public.short": "Публично",
|
||||||
|
"privacy.quote.anyone": "{visibility}, всеки може да цитира",
|
||||||
|
"privacy.quote.disabled": "{visibility}, цитатите са изключени",
|
||||||
|
"privacy.quote.limited": "{visibility}, цитатите са ограничени",
|
||||||
"privacy.unlisted.additional": "Това действие е точно като публичното, с изключение на това, че публикацията няма да се появява в каналите на живо, хаштаговете, разглеждането или търсенето в Mastodon, дори ако сте избрали да се публично видими на ниво акаунт.",
|
"privacy.unlisted.additional": "Това действие е точно като публичното, с изключение на това, че публикацията няма да се появява в каналите на живо, хаштаговете, разглеждането или търсенето в Mastodon, дори ако сте избрали да се публично видими на ниво акаунт.",
|
||||||
"privacy.unlisted.short": "Тиха публика",
|
"privacy.unlisted.short": "Тиха публика",
|
||||||
"privacy_policy.last_updated": "Последно осъвременяване на {date}",
|
"privacy_policy.last_updated": "Последно осъвременяване на {date}",
|
||||||
"privacy_policy.title": "Политика за поверителност",
|
"privacy_policy.title": "Политика за поверителност",
|
||||||
|
"quote_error.edit": "Не може да се добавят цитати, редайтирайки публикация.",
|
||||||
|
"quote_error.poll": "Не може да се цитира при анкетиране.",
|
||||||
|
"quote_error.unauthorized": "Нямате право да цитирате тази публикация.",
|
||||||
|
"quote_error.upload": "Цитирането не е позволено с мултимедийни прикачвания.",
|
||||||
"recommended": "Препоръчано",
|
"recommended": "Препоръчано",
|
||||||
"refresh": "Опресняване",
|
"refresh": "Опресняване",
|
||||||
"regeneration_indicator.please_stand_by": "Изчакайте.",
|
"regeneration_indicator.please_stand_by": "Изчакайте.",
|
||||||
@@ -734,6 +755,8 @@
|
|||||||
"relative_time.minutes": "{number}м.",
|
"relative_time.minutes": "{number}м.",
|
||||||
"relative_time.seconds": "{number}с.",
|
"relative_time.seconds": "{number}с.",
|
||||||
"relative_time.today": "днес",
|
"relative_time.today": "днес",
|
||||||
|
"remove_quote_hint.button_label": "Схванах",
|
||||||
|
"remove_quote_hint.message": "Може да го направите от менюто възможности {icon}.",
|
||||||
"reply_indicator.attachments": "{count, plural, one {# прикаване} other {# прикачвания}}",
|
"reply_indicator.attachments": "{count, plural, one {# прикаване} other {# прикачвания}}",
|
||||||
"reply_indicator.cancel": "Отказ",
|
"reply_indicator.cancel": "Отказ",
|
||||||
"reply_indicator.poll": "Анкета",
|
"reply_indicator.poll": "Анкета",
|
||||||
@@ -825,13 +848,22 @@
|
|||||||
"status.admin_account": "Отваряне на интерфейс за модериране за @{name}",
|
"status.admin_account": "Отваряне на интерфейс за модериране за @{name}",
|
||||||
"status.admin_domain": "Отваряне на модериращия интерфейс за {domain}",
|
"status.admin_domain": "Отваряне на модериращия интерфейс за {domain}",
|
||||||
"status.admin_status": "Отваряне на публикацията в модериращия интерфейс",
|
"status.admin_status": "Отваряне на публикацията в модериращия интерфейс",
|
||||||
|
"status.all_disabled": "Подсилването и цитатите са изключени",
|
||||||
"status.block": "Блокиране на @{name}",
|
"status.block": "Блокиране на @{name}",
|
||||||
"status.bookmark": "Отмятане",
|
"status.bookmark": "Отмятане",
|
||||||
"status.cancel_reblog_private": "Край на подсилването",
|
"status.cancel_reblog_private": "Край на подсилването",
|
||||||
|
"status.cannot_quote": "Не е позволено да цитирате тази публикация",
|
||||||
"status.cannot_reblog": "Публикацията не може да се подсилва",
|
"status.cannot_reblog": "Публикацията не може да се подсилва",
|
||||||
|
"status.context.loading": "Зареждане на още отговори",
|
||||||
|
"status.context.loading_error": "Не можаха да се заредят нови отговори",
|
||||||
|
"status.context.loading_success": "Новите отговори заредени",
|
||||||
|
"status.context.more_replies_found": "Още намерени отговори",
|
||||||
|
"status.context.retry": "Друг опит",
|
||||||
|
"status.context.show": "Показване",
|
||||||
"status.continued_thread": "Продължена нишка",
|
"status.continued_thread": "Продължена нишка",
|
||||||
"status.copy": "Копиране на връзката към публикация",
|
"status.copy": "Копиране на връзката към публикация",
|
||||||
"status.delete": "Изтриване",
|
"status.delete": "Изтриване",
|
||||||
|
"status.delete.success": "Публикацията е изтрита",
|
||||||
"status.detailed_status": "Подробен изглед на разговора",
|
"status.detailed_status": "Подробен изглед на разговора",
|
||||||
"status.direct": "Частно споменаване на @{name}",
|
"status.direct": "Частно споменаване на @{name}",
|
||||||
"status.direct_indicator": "Частно споменаване",
|
"status.direct_indicator": "Частно споменаване",
|
||||||
@@ -855,23 +887,32 @@
|
|||||||
"status.open": "Разширяване на публикацията",
|
"status.open": "Разширяване на публикацията",
|
||||||
"status.pin": "Закачане в профила",
|
"status.pin": "Закачане в профила",
|
||||||
"status.quote_error.filtered": "Скрито поради един от филтрите ви",
|
"status.quote_error.filtered": "Скрито поради един от филтрите ви",
|
||||||
|
"status.quote_error.limited_account_hint.title": "Този акаунт е бил скрит от модераторите на {domain}.",
|
||||||
"status.quote_error.not_available": "Неналична публикация",
|
"status.quote_error.not_available": "Неналична публикация",
|
||||||
"status.quote_error.pending_approval": "Публикацията чака одобрение",
|
"status.quote_error.pending_approval": "Публикацията чака одобрение",
|
||||||
|
"status.quote_error.revoked": "Премахната публикация от автора",
|
||||||
|
"status.quote_followers_only": "Само последователи могат да цитират тази публикация",
|
||||||
|
"status.quote_manual_review": "Авторът ще преглежда ръчно",
|
||||||
"status.quote_policy_change": "Промяна кой може да цитира",
|
"status.quote_policy_change": "Промяна кой може да цитира",
|
||||||
"status.quote_post_author": "Цитирах публикация от @{name}",
|
"status.quote_post_author": "Цитирах публикация от @{name}",
|
||||||
|
"status.quote_private": "Частните публикации не може да се цитират",
|
||||||
"status.read_more": "Още за четене",
|
"status.read_more": "Още за четене",
|
||||||
"status.reblog": "Подсилване",
|
"status.reblog": "Подсилване",
|
||||||
|
"status.reblog_or_quote": "Подсилване или цитиране",
|
||||||
|
"status.reblog_private": "Споделете пак с последователите си",
|
||||||
"status.reblogged_by": "{name} подсили",
|
"status.reblogged_by": "{name} подсили",
|
||||||
"status.reblogs": "{count, plural, one {подсилване} other {подсилвания}}",
|
"status.reblogs": "{count, plural, one {подсилване} other {подсилвания}}",
|
||||||
"status.reblogs.empty": "Още никого не е подсилвал публикацията. Подсилващият ще се покаже тук.",
|
"status.reblogs.empty": "Още никого не е подсилвал публикацията. Подсилващият ще се покаже тук.",
|
||||||
"status.redraft": "Изтриване и преработване",
|
"status.redraft": "Изтриване и преработване",
|
||||||
"status.remove_bookmark": "Премахване на отметката",
|
"status.remove_bookmark": "Премахване на отметката",
|
||||||
"status.remove_favourite": "Премахване от любими",
|
"status.remove_favourite": "Премахване от любими",
|
||||||
|
"status.remove_quote": "Премахване",
|
||||||
"status.replied_in_thread": "Отговорено в нишката",
|
"status.replied_in_thread": "Отговорено в нишката",
|
||||||
"status.replied_to": "В отговор до {name}",
|
"status.replied_to": "В отговор до {name}",
|
||||||
"status.reply": "Отговор",
|
"status.reply": "Отговор",
|
||||||
"status.replyAll": "Отговор на нишка",
|
"status.replyAll": "Отговор на нишка",
|
||||||
"status.report": "Докладване на @{name}",
|
"status.report": "Докладване на @{name}",
|
||||||
|
"status.request_quote": "Заявка за цитиране",
|
||||||
"status.revoke_quote": "Премахване на моя публикация от публикацията на @{name}",
|
"status.revoke_quote": "Премахване на моя публикация от публикацията на @{name}",
|
||||||
"status.sensitive_warning": "Деликатно съдържание",
|
"status.sensitive_warning": "Деликатно съдържание",
|
||||||
"status.share": "Споделяне",
|
"status.share": "Споделяне",
|
||||||
@@ -910,6 +951,7 @@
|
|||||||
"upload_button.label": "Добавете файл с образ, видео или звук",
|
"upload_button.label": "Добавете файл с образ, видео или звук",
|
||||||
"upload_error.limit": "Превишено ограничението за качване на файлове.",
|
"upload_error.limit": "Превишено ограничението за качване на файлове.",
|
||||||
"upload_error.poll": "Качването на файлове не е позволено с анкети.",
|
"upload_error.poll": "Качването на файлове не е позволено с анкети.",
|
||||||
|
"upload_error.quote": "Цитирайки, не може да качвате файл.",
|
||||||
"upload_form.drag_and_drop.instructions": "Натиснете интервал или enter, за да подберете мултимедийно прикачване. Провлачвайки, ползвайте клавишите със стрелки, за да премествате мултимедията във всяка дадена посока. Натиснете пак интервал или enter, за да се стовари мултимедийното прикачване в новото си положение или натиснете Esc за отмяна.",
|
"upload_form.drag_and_drop.instructions": "Натиснете интервал или enter, за да подберете мултимедийно прикачване. Провлачвайки, ползвайте клавишите със стрелки, за да премествате мултимедията във всяка дадена посока. Натиснете пак интервал или enter, за да се стовари мултимедийното прикачване в новото си положение или натиснете Esc за отмяна.",
|
||||||
"upload_form.drag_and_drop.on_drag_cancel": "Провлачването е отменено. Мултимедийното прикачване {item} е спуснато.",
|
"upload_form.drag_and_drop.on_drag_cancel": "Провлачването е отменено. Мултимедийното прикачване {item} е спуснато.",
|
||||||
"upload_form.drag_and_drop.on_drag_end": "Мултимедийното прикачване {item} е спуснато.",
|
"upload_form.drag_and_drop.on_drag_end": "Мултимедийното прикачване {item} е спуснато.",
|
||||||
@@ -935,6 +977,11 @@
|
|||||||
"video.volume_up": "Увеличаване на звука",
|
"video.volume_up": "Увеличаване на звука",
|
||||||
"visibility_modal.button_title": "Задаване на видимост",
|
"visibility_modal.button_title": "Задаване на видимост",
|
||||||
"visibility_modal.header": "Видимост и взаимодействие",
|
"visibility_modal.header": "Видимост и взаимодействие",
|
||||||
|
"visibility_modal.helper.privacy_editing": "Видимостта не може да се променя след публикуване на публикацията.",
|
||||||
|
"visibility_modal.privacy_label": "Видимост",
|
||||||
"visibility_modal.quote_followers": "Само последователи",
|
"visibility_modal.quote_followers": "Само последователи",
|
||||||
"visibility_modal.quote_public": "Някой"
|
"visibility_modal.quote_label": "Кой може да цитира",
|
||||||
|
"visibility_modal.quote_nobody": "Само аз",
|
||||||
|
"visibility_modal.quote_public": "Някой",
|
||||||
|
"visibility_modal.save": "Запазване"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,6 +173,8 @@
|
|||||||
"column.edit_list": "Edit list",
|
"column.edit_list": "Edit list",
|
||||||
"column.favourites": "Favorites",
|
"column.favourites": "Favorites",
|
||||||
"column.firehose": "Live feeds",
|
"column.firehose": "Live feeds",
|
||||||
|
"column.firehose_local": "Live feed for this server",
|
||||||
|
"column.firehose_singular": "Live feed",
|
||||||
"column.follow_requests": "Follow requests",
|
"column.follow_requests": "Follow requests",
|
||||||
"column.home": "Home",
|
"column.home": "Home",
|
||||||
"column.list_members": "Manage list members",
|
"column.list_members": "Manage list members",
|
||||||
|
|||||||
@@ -333,6 +333,7 @@
|
|||||||
"empty_column.bookmarked_statuses": "Järjehoidjatesse pole veel lisatud postitusi. Kui lisad mõne, näed neid siin.",
|
"empty_column.bookmarked_statuses": "Järjehoidjatesse pole veel lisatud postitusi. Kui lisad mõne, näed neid siin.",
|
||||||
"empty_column.community": "Kohalik ajajoon on tühi. Kirjuta midagi avalikult, et pall veerema ajada!",
|
"empty_column.community": "Kohalik ajajoon on tühi. Kirjuta midagi avalikult, et pall veerema ajada!",
|
||||||
"empty_column.direct": "Sul pole veel ühtegi privaatset mainimist. Kui saadad või saad mõne, ilmuvad need siin.",
|
"empty_column.direct": "Sul pole veel ühtegi privaatset mainimist. Kui saadad või saad mõne, ilmuvad need siin.",
|
||||||
|
"empty_column.disabled_feed": "See infovoog on serveri peakasutajate poolt välja lülitatud.",
|
||||||
"empty_column.domain_blocks": "Siin ei ole veel peidetud domeene.",
|
"empty_column.domain_blocks": "Siin ei ole veel peidetud domeene.",
|
||||||
"empty_column.explore_statuses": "Praegu pole ühtegi trendi. Tule hiljem tagasi!",
|
"empty_column.explore_statuses": "Praegu pole ühtegi trendi. Tule hiljem tagasi!",
|
||||||
"empty_column.favourited_statuses": "Pole veel lemmikpostitusi. Kui märgid mõne, näed neid siin.",
|
"empty_column.favourited_statuses": "Pole veel lemmikpostitusi. Kui märgid mõne, näed neid siin.",
|
||||||
|
|||||||
@@ -333,6 +333,7 @@
|
|||||||
"empty_column.bookmarked_statuses": "Tú hevur enn einki goymt uppslag. Tú tú goymir eitt uppslag, kemur tað her.",
|
"empty_column.bookmarked_statuses": "Tú hevur enn einki goymt uppslag. Tú tú goymir eitt uppslag, kemur tað her.",
|
||||||
"empty_column.community": "Lokala tíðarlinjan er tóm. Skriva okkurt alment fyri at fáa boltin á rull!",
|
"empty_column.community": "Lokala tíðarlinjan er tóm. Skriva okkurt alment fyri at fáa boltin á rull!",
|
||||||
"empty_column.direct": "Tú hevur ongar privatar umrøður enn. Tá tú sendir ella móttekur eina privata umrøðu, so verður hon sjónlig her.",
|
"empty_column.direct": "Tú hevur ongar privatar umrøður enn. Tá tú sendir ella móttekur eina privata umrøðu, so verður hon sjónlig her.",
|
||||||
|
"empty_column.disabled_feed": "Hendan rásin er gjørd óvirkin av ambætaraumsitarunum hjá tær.",
|
||||||
"empty_column.domain_blocks": "Enn eru eingi blokeraði domenir.",
|
"empty_column.domain_blocks": "Enn eru eingi blokeraði domenir.",
|
||||||
"empty_column.explore_statuses": "Einki rák er beint nú. Royn aftur seinni!",
|
"empty_column.explore_statuses": "Einki rák er beint nú. Royn aftur seinni!",
|
||||||
"empty_column.favourited_statuses": "Tú hevur ongar yndispostar enn. Tá tú gevur einum posti yndismerki, so sært tú hann her.",
|
"empty_column.favourited_statuses": "Tú hevur ongar yndispostar enn. Tá tú gevur einum posti yndismerki, so sært tú hann her.",
|
||||||
|
|||||||
@@ -333,6 +333,7 @@
|
|||||||
"empty_column.bookmarked_statuses": "Níl aon phostáil leabharmharcaithe agat fós. Nuair a dhéanann tú leabharmharc, beidh sé le feiceáil anseo.",
|
"empty_column.bookmarked_statuses": "Níl aon phostáil leabharmharcaithe agat fós. Nuair a dhéanann tú leabharmharc, beidh sé le feiceáil anseo.",
|
||||||
"empty_column.community": "Tá an amlíne áitiúil folamh. Foilsigh rud éigin go poiblí le tús a chur le cúrsaí!",
|
"empty_column.community": "Tá an amlíne áitiúil folamh. Foilsigh rud éigin go poiblí le tús a chur le cúrsaí!",
|
||||||
"empty_column.direct": "Níl aon tagairtí príobháideacha agat fós. Nuair a sheolann tú nó a gheobhaidh tú ceann, beidh sé le feiceáil anseo.",
|
"empty_column.direct": "Níl aon tagairtí príobháideacha agat fós. Nuair a sheolann tú nó a gheobhaidh tú ceann, beidh sé le feiceáil anseo.",
|
||||||
|
"empty_column.disabled_feed": "Tá an fotha seo díchumasaithe ag riarthóirí do fhreastalaí.",
|
||||||
"empty_column.domain_blocks": "Níl aon fearainn bhactha ann go fóill.",
|
"empty_column.domain_blocks": "Níl aon fearainn bhactha ann go fóill.",
|
||||||
"empty_column.explore_statuses": "Níl rud ar bith ag treochtáil faoi láthair. Tar ar ais ar ball!",
|
"empty_column.explore_statuses": "Níl rud ar bith ag treochtáil faoi láthair. Tar ar ais ar ball!",
|
||||||
"empty_column.favourited_statuses": "Níl aon postálacha is fearr leat fós. Nuair is fearr leat ceann, beidh sé le feiceáil anseo.",
|
"empty_column.favourited_statuses": "Níl aon postálacha is fearr leat fós. Nuair is fearr leat ceann, beidh sé le feiceáil anseo.",
|
||||||
|
|||||||
@@ -172,7 +172,7 @@
|
|||||||
"column.domain_blocks": "Dominios blocate",
|
"column.domain_blocks": "Dominios blocate",
|
||||||
"column.edit_list": "Modificar lista",
|
"column.edit_list": "Modificar lista",
|
||||||
"column.favourites": "Favorites",
|
"column.favourites": "Favorites",
|
||||||
"column.firehose": "Fluxos in directo",
|
"column.firehose": "Fluxos in vivo",
|
||||||
"column.follow_requests": "Requestas de sequimento",
|
"column.follow_requests": "Requestas de sequimento",
|
||||||
"column.home": "Initio",
|
"column.home": "Initio",
|
||||||
"column.list_members": "Gerer le membros del lista",
|
"column.list_members": "Gerer le membros del lista",
|
||||||
@@ -333,6 +333,7 @@
|
|||||||
"empty_column.bookmarked_statuses": "Tu non ha ancora messages in marcapaginas. Quando tu adde un message al marcapaginas, illo apparera hic.",
|
"empty_column.bookmarked_statuses": "Tu non ha ancora messages in marcapaginas. Quando tu adde un message al marcapaginas, illo apparera hic.",
|
||||||
"empty_column.community": "Le chronologia local es vacue. Scribe qualcosa public pro poner le cosas in marcha!",
|
"empty_column.community": "Le chronologia local es vacue. Scribe qualcosa public pro poner le cosas in marcha!",
|
||||||
"empty_column.direct": "Tu non ha ancora mentiones private. Quando tu invia o recipe un mention, illo apparera hic.",
|
"empty_column.direct": "Tu non ha ancora mentiones private. Quando tu invia o recipe un mention, illo apparera hic.",
|
||||||
|
"empty_column.disabled_feed": "Iste canal ha essite disactivate per le adminsistratores de tu servitor.",
|
||||||
"empty_column.domain_blocks": "Il non ha dominios blocate ancora.",
|
"empty_column.domain_blocks": "Il non ha dominios blocate ancora.",
|
||||||
"empty_column.explore_statuses": "Il non ha tendentias in iste momento. Reveni plus tarde!",
|
"empty_column.explore_statuses": "Il non ha tendentias in iste momento. Reveni plus tarde!",
|
||||||
"empty_column.favourited_statuses": "Tu non ha alcun message favorite ancora. Quando tu marca un message como favorite, illo apparera hic.",
|
"empty_column.favourited_statuses": "Tu non ha alcun message favorite ancora. Quando tu marca un message como favorite, illo apparera hic.",
|
||||||
@@ -460,7 +461,7 @@
|
|||||||
"ignore_notifications_modal.not_following_title": "Ignorar notificationes de personas que tu non 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?",
|
"ignore_notifications_modal.private_mentions_title": "Ignorar notificationes de mentiones private non requestate?",
|
||||||
"info_button.label": "Adjuta",
|
"info_button.label": "Adjuta",
|
||||||
"info_button.what_is_alt_text": "<h1>Que es texto alternative?</h1><p>Le texto alternative forni descriptiones de imagines a personas con impedimentos visual, con connexiones lente, o qui cerca contexto additional.</p><p>Tu pote meliorar le accessibilitate e le comprension pro totes scribente un texto alternative clar, concise e objective.</p><ul><li>Captura le elementos importante</li><li>Summarisa texto in imagines</li><li>Usa le structura de phrase normal</li><li>Evita information redundante</li><li>In figuras complexe (como diagrammas o mappas), concentra te sur le tendentias e punctos clave</li></ul>",
|
"info_button.what_is_alt_text": "<h1>Que es texto alternative?</h1><p>Le texto alternative forni descriptiones de imagines a personas con impedimentos visual, con connexiones lente a internet, o qui cerca contexto supplementari.</p><p>Tu pote meliorar le accessibilitate e le comprension pro totes si tu scribe un texto alternative clar, concise e objective.</p><ul><li>Captura le elementos importante</li><li>Summarisa texto in imagines</li><li>Usa un structura conventional de phrases</li><li>Evita information redundante</li><li>In figuras complexe (como diagrammas o mappas), concentra te sur le tendentias e punctos clave</li></ul>",
|
||||||
"interaction_modal.action": "Pro interager con le message de {name}, tu debe acceder a tu conto sur le servitor Mastodon que tu usa.",
|
"interaction_modal.action": "Pro interager con le message de {name}, tu debe acceder a tu conto sur le servitor Mastodon que tu usa.",
|
||||||
"interaction_modal.go": "Revenir",
|
"interaction_modal.go": "Revenir",
|
||||||
"interaction_modal.no_account_yet": "Tu non ha ancora un conto?",
|
"interaction_modal.no_account_yet": "Tu non ha ancora un conto?",
|
||||||
@@ -574,8 +575,8 @@
|
|||||||
"navigation_bar.follows_and_followers": "Sequites e sequitores",
|
"navigation_bar.follows_and_followers": "Sequites e sequitores",
|
||||||
"navigation_bar.import_export": "Importar e exportar",
|
"navigation_bar.import_export": "Importar e exportar",
|
||||||
"navigation_bar.lists": "Listas",
|
"navigation_bar.lists": "Listas",
|
||||||
"navigation_bar.live_feed_local": "Canal in directo (local)",
|
"navigation_bar.live_feed_local": "Canal in vivo (local)",
|
||||||
"navigation_bar.live_feed_public": "Canal in directo (public)",
|
"navigation_bar.live_feed_public": "Canal in vivo (public)",
|
||||||
"navigation_bar.logout": "Clauder session",
|
"navigation_bar.logout": "Clauder session",
|
||||||
"navigation_bar.moderation": "Moderation",
|
"navigation_bar.moderation": "Moderation",
|
||||||
"navigation_bar.more": "Plus",
|
"navigation_bar.more": "Plus",
|
||||||
@@ -748,7 +749,7 @@
|
|||||||
"privacy.quote.anyone": "{visibility}, omnes pote citar",
|
"privacy.quote.anyone": "{visibility}, omnes pote citar",
|
||||||
"privacy.quote.disabled": "{visibility}, citation disactivate",
|
"privacy.quote.disabled": "{visibility}, citation disactivate",
|
||||||
"privacy.quote.limited": "{visibility}, citation limitate",
|
"privacy.quote.limited": "{visibility}, citation limitate",
|
||||||
"privacy.unlisted.additional": "Isto es exactemente como public, excepte que le message non apparera in fluxos in directo, in hashtags, in Explorar, o in le recerca de Mastodon, mesmo si tu ha optate pro render tote le conto discoperibile.",
|
"privacy.unlisted.additional": "Isto es exactemente como public, excepte que le message non apparera in fluxos in vivo, in hashtags, in Explorar, o in le recerca de Mastodon, mesmo si tu ha optate pro render tote le conto discoperibile.",
|
||||||
"privacy.unlisted.long": "Non apparera in le resultatos de recerca, tendentias e chronologias public de Mastodon",
|
"privacy.unlisted.long": "Non apparera in le resultatos de recerca, tendentias e chronologias public de Mastodon",
|
||||||
"privacy.unlisted.short": "Public, non listate",
|
"privacy.unlisted.short": "Public, non listate",
|
||||||
"privacy_policy.last_updated": "Ultime actualisation {date}",
|
"privacy_policy.last_updated": "Ultime actualisation {date}",
|
||||||
|
|||||||
@@ -38,6 +38,8 @@
|
|||||||
"account.follow": "Sige",
|
"account.follow": "Sige",
|
||||||
"account.follow_back": "Sige tamyen",
|
"account.follow_back": "Sige tamyen",
|
||||||
"account.follow_back_short": "Sige tambyen",
|
"account.follow_back_short": "Sige tambyen",
|
||||||
|
"account.follow_request": "Solisita segirle",
|
||||||
|
"account.follow_request_cancel": "Anula solisitud",
|
||||||
"account.follow_request_cancel_short": "Anula",
|
"account.follow_request_cancel_short": "Anula",
|
||||||
"account.follow_request_short": "Solisitud",
|
"account.follow_request_short": "Solisitud",
|
||||||
"account.followers": "Suivantes",
|
"account.followers": "Suivantes",
|
||||||
@@ -62,6 +64,7 @@
|
|||||||
"account.mute_short": "Silensia",
|
"account.mute_short": "Silensia",
|
||||||
"account.muted": "Silensiado",
|
"account.muted": "Silensiado",
|
||||||
"account.muting": "Silensyando",
|
"account.muting": "Silensyando",
|
||||||
|
"account.mutual": "Vos sigesh mutualmente",
|
||||||
"account.no_bio": "No ay deskripsion.",
|
"account.no_bio": "No ay deskripsion.",
|
||||||
"account.open_original_page": "Avre pajina orijnala",
|
"account.open_original_page": "Avre pajina orijnala",
|
||||||
"account.posts": "Publikasyones",
|
"account.posts": "Publikasyones",
|
||||||
@@ -97,6 +100,7 @@
|
|||||||
"alert.unexpected.title": "Atyo!",
|
"alert.unexpected.title": "Atyo!",
|
||||||
"alt_text_badge.title": "Teksto alternativo",
|
"alt_text_badge.title": "Teksto alternativo",
|
||||||
"alt_text_modal.add_alt_text": "Adjusta teksto alternativo",
|
"alt_text_modal.add_alt_text": "Adjusta teksto alternativo",
|
||||||
|
"alt_text_modal.add_text_from_image": "Adjusta teksto de imaje",
|
||||||
"alt_text_modal.cancel": "Anula",
|
"alt_text_modal.cancel": "Anula",
|
||||||
"alt_text_modal.change_thumbnail": "Troka minyatura",
|
"alt_text_modal.change_thumbnail": "Troka minyatura",
|
||||||
"alt_text_modal.done": "Fecho",
|
"alt_text_modal.done": "Fecho",
|
||||||
@@ -210,6 +214,7 @@
|
|||||||
"confirmations.logout.message": "Estas siguro ke keres salir de tu kuento?",
|
"confirmations.logout.message": "Estas siguro ke keres salir de tu kuento?",
|
||||||
"confirmations.logout.title": "Salir?",
|
"confirmations.logout.title": "Salir?",
|
||||||
"confirmations.missing_alt_text.confirm": "Adjusta teksto alternativo",
|
"confirmations.missing_alt_text.confirm": "Adjusta teksto alternativo",
|
||||||
|
"confirmations.missing_alt_text.secondary": "Puvlika de todos modos",
|
||||||
"confirmations.missing_alt_text.title": "Adjustar teksto alternativo?",
|
"confirmations.missing_alt_text.title": "Adjustar teksto alternativo?",
|
||||||
"confirmations.mute.confirm": "Silensia",
|
"confirmations.mute.confirm": "Silensia",
|
||||||
"confirmations.quiet_post_quote_info.got_it": "Entyendo",
|
"confirmations.quiet_post_quote_info.got_it": "Entyendo",
|
||||||
@@ -382,6 +387,7 @@
|
|||||||
"hints.profiles.see_more_followers": "Ve mas suivantes en {domain}",
|
"hints.profiles.see_more_followers": "Ve mas suivantes en {domain}",
|
||||||
"hints.profiles.see_more_follows": "Ve mas segidos en {domain}",
|
"hints.profiles.see_more_follows": "Ve mas segidos en {domain}",
|
||||||
"hints.profiles.see_more_posts": "Ve mas puvlikasyones en {domain}",
|
"hints.profiles.see_more_posts": "Ve mas puvlikasyones en {domain}",
|
||||||
|
"home.column_settings.show_quotes": "Muestra sitas",
|
||||||
"home.column_settings.show_reblogs": "Amostra repartajasyones",
|
"home.column_settings.show_reblogs": "Amostra repartajasyones",
|
||||||
"home.column_settings.show_replies": "Amostra repuestas",
|
"home.column_settings.show_replies": "Amostra repuestas",
|
||||||
"home.hide_announcements": "Eskonde pregones",
|
"home.hide_announcements": "Eskonde pregones",
|
||||||
@@ -631,6 +637,7 @@
|
|||||||
"privacy_policy.title": "Politika de privasita",
|
"privacy_policy.title": "Politika de privasita",
|
||||||
"recommended": "Rekomendado",
|
"recommended": "Rekomendado",
|
||||||
"refresh": "Arefreska",
|
"refresh": "Arefreska",
|
||||||
|
"regeneration_indicator.please_stand_by": "Aspera por favor.",
|
||||||
"relative_time.days": "{number} d",
|
"relative_time.days": "{number} d",
|
||||||
"relative_time.full.days": "antes {number, plural, one {# diya} other {# diyas}}",
|
"relative_time.full.days": "antes {number, plural, one {# diya} other {# diyas}}",
|
||||||
"relative_time.full.hours": "antes {number, plural, one {# ora} other {# oras}}",
|
"relative_time.full.hours": "antes {number, plural, one {# ora} other {# oras}}",
|
||||||
@@ -733,8 +740,12 @@
|
|||||||
"status.bookmark": "Marka",
|
"status.bookmark": "Marka",
|
||||||
"status.cancel_reblog_private": "No repartaja",
|
"status.cancel_reblog_private": "No repartaja",
|
||||||
"status.cannot_reblog": "Esta publikasyon no se puede repartajar",
|
"status.cannot_reblog": "Esta publikasyon no se puede repartajar",
|
||||||
|
"status.contains_quote": "Kontriene sita",
|
||||||
|
"status.context.loading_success": "Muevas repuestas kargadas",
|
||||||
|
"status.context.more_replies_found": "Se toparon mas repuestas",
|
||||||
"status.context.retry": "Reprova",
|
"status.context.retry": "Reprova",
|
||||||
"status.context.show": "Amostra",
|
"status.context.show": "Amostra",
|
||||||
|
"status.continued_thread": "Kontinuasion del filo",
|
||||||
"status.copy": "Kopia atadijo de publikasyon",
|
"status.copy": "Kopia atadijo de publikasyon",
|
||||||
"status.delete": "Efasa",
|
"status.delete": "Efasa",
|
||||||
"status.delete.success": "Puvlikasyon kitada",
|
"status.delete.success": "Puvlikasyon kitada",
|
||||||
@@ -760,9 +771,18 @@
|
|||||||
"status.pin": "Fiksa en profil",
|
"status.pin": "Fiksa en profil",
|
||||||
"status.quote": "Sita",
|
"status.quote": "Sita",
|
||||||
"status.quote.cancel": "Anula la sita",
|
"status.quote.cancel": "Anula la sita",
|
||||||
|
"status.quote_error.limited_account_hint.action": "Amostra entanto",
|
||||||
|
"status.quote_error.limited_account_hint.title": "Este kuento fue eskondido por los moderadores de {domain}.",
|
||||||
|
"status.quote_error.not_available": "Puvlikasyon no desponivle",
|
||||||
|
"status.quote_error.pending_approval": "Puvlikasyon esta asperando",
|
||||||
"status.quote_noun": "Sita",
|
"status.quote_noun": "Sita",
|
||||||
|
"status.quote_policy_change": "Troka ken puede sitar",
|
||||||
|
"status.quote_post_author": "Sito una puvlikasyon de @{name}",
|
||||||
|
"status.quote_private": "No se puede sitar puvlikasyones privadas",
|
||||||
|
"status.quotes": "{count, plural, one {sita} other {sitas}}",
|
||||||
"status.read_more": "Melda mas",
|
"status.read_more": "Melda mas",
|
||||||
"status.reblog": "Repartaja",
|
"status.reblog": "Repartaja",
|
||||||
|
"status.reblog_or_quote": "Repartaja o partaja",
|
||||||
"status.reblogged_by": "{name} repartajo",
|
"status.reblogged_by": "{name} repartajo",
|
||||||
"status.reblogs.empty": "Ainda nadie tiene repartajado esta publikasyon. Kuando algien lo aga, se amostrara aki.",
|
"status.reblogs.empty": "Ainda nadie tiene repartajado esta publikasyon. Kuando algien lo aga, se amostrara aki.",
|
||||||
"status.redraft": "Efasa i eskrive de muevo",
|
"status.redraft": "Efasa i eskrive de muevo",
|
||||||
@@ -823,7 +843,12 @@
|
|||||||
"video.pause": "Pauza",
|
"video.pause": "Pauza",
|
||||||
"video.play": "Reproduze",
|
"video.play": "Reproduze",
|
||||||
"video.unmute": "Desilensia",
|
"video.unmute": "Desilensia",
|
||||||
|
"visibility_modal.button_title": "Konfigura la vizibilita",
|
||||||
|
"visibility_modal.header": "Vizibilita i enteraksyon",
|
||||||
"visibility_modal.privacy_label": "Vizivilita",
|
"visibility_modal.privacy_label": "Vizivilita",
|
||||||
"visibility_modal.quote_followers": "Solo suivantes",
|
"visibility_modal.quote_followers": "Solo suivantes",
|
||||||
|
"visibility_modal.quote_label": "Ken puede sitar",
|
||||||
|
"visibility_modal.quote_nobody": "Solo yo",
|
||||||
|
"visibility_modal.quote_public": "Todos",
|
||||||
"visibility_modal.save": "Guadra"
|
"visibility_modal.save": "Guadra"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"account.disable_notifications": "Cancelar notificações de @{name}",
|
"account.disable_notifications": "Cancelar notificações de @{name}",
|
||||||
"account.domain_blocking": "Bloqueando domínio",
|
"account.domain_blocking": "Bloqueando domínio",
|
||||||
"account.edit_profile": "Editar perfil",
|
"account.edit_profile": "Editar perfil",
|
||||||
|
"account.edit_profile_short": "Editar",
|
||||||
"account.enable_notifications": "Notificar novos toots de @{name}",
|
"account.enable_notifications": "Notificar novos toots de @{name}",
|
||||||
"account.endorse": "Recomendar",
|
"account.endorse": "Recomendar",
|
||||||
"account.familiar_followers_many": "Seguido por {name1}, {name2}, e {othersCount, plural, one {um outro que você conhece} other {# outros que você conhece}}",
|
"account.familiar_followers_many": "Seguido por {name1}, {name2}, e {othersCount, plural, one {um outro que você conhece} other {# outros que você conhece}}",
|
||||||
@@ -40,6 +41,11 @@
|
|||||||
"account.featured_tags.last_status_never": "Sem publicações",
|
"account.featured_tags.last_status_never": "Sem publicações",
|
||||||
"account.follow": "Seguir",
|
"account.follow": "Seguir",
|
||||||
"account.follow_back": "Seguir de volta",
|
"account.follow_back": "Seguir de volta",
|
||||||
|
"account.follow_back_short": "Seguir de volta",
|
||||||
|
"account.follow_request": "Pedir para seguir",
|
||||||
|
"account.follow_request_cancel": "Cancelar solicitação",
|
||||||
|
"account.follow_request_cancel_short": "Cancelar",
|
||||||
|
"account.follow_request_short": "Solicitação",
|
||||||
"account.followers": "Seguidores",
|
"account.followers": "Seguidores",
|
||||||
"account.followers.empty": "Nada aqui.",
|
"account.followers.empty": "Nada aqui.",
|
||||||
"account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}",
|
"account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}",
|
||||||
@@ -240,6 +246,8 @@
|
|||||||
"confirmations.mute.confirm": "Silenciar",
|
"confirmations.mute.confirm": "Silenciar",
|
||||||
"confirmations.quiet_post_quote_info.dismiss": "Não me lembrar novamente",
|
"confirmations.quiet_post_quote_info.dismiss": "Não me lembrar novamente",
|
||||||
"confirmations.quiet_post_quote_info.got_it": "Entendi",
|
"confirmations.quiet_post_quote_info.got_it": "Entendi",
|
||||||
|
"confirmations.quiet_post_quote_info.message": "Ao citar uma publicação pública silenciosa, sua postagem será oculta das linhas de tempo em tendência.",
|
||||||
|
"confirmations.quiet_post_quote_info.title": "Citando publicações públicas silenciadas",
|
||||||
"confirmations.redraft.confirm": "Excluir e rascunhar",
|
"confirmations.redraft.confirm": "Excluir e rascunhar",
|
||||||
"confirmations.redraft.message": "Você tem certeza de que quer apagar essa postagem e rascunhá-la? Favoritos e impulsos serão perdidos, e respostas à postagem original ficarão órfãs.",
|
"confirmations.redraft.message": "Você tem certeza de que quer apagar essa postagem e rascunhá-la? Favoritos e impulsos serão perdidos, e respostas à postagem original ficarão órfãs.",
|
||||||
"confirmations.redraft.title": "Excluir e rascunhar publicação?",
|
"confirmations.redraft.title": "Excluir e rascunhar publicação?",
|
||||||
@@ -249,7 +257,12 @@
|
|||||||
"confirmations.revoke_quote.confirm": "Remover publicação",
|
"confirmations.revoke_quote.confirm": "Remover publicação",
|
||||||
"confirmations.revoke_quote.message": "Essa ação não pode ser desfeita.",
|
"confirmations.revoke_quote.message": "Essa ação não pode ser desfeita.",
|
||||||
"confirmations.revoke_quote.title": "Remover publicação?",
|
"confirmations.revoke_quote.title": "Remover publicação?",
|
||||||
|
"confirmations.unblock.confirm": "Desbloquear",
|
||||||
|
"confirmations.unblock.title": "Desbloquear {name}?",
|
||||||
"confirmations.unfollow.confirm": "Deixar de seguir",
|
"confirmations.unfollow.confirm": "Deixar de seguir",
|
||||||
|
"confirmations.unfollow.title": "Deixar de seguir {name}?",
|
||||||
|
"confirmations.withdraw_request.confirm": "Retirar solicitação",
|
||||||
|
"confirmations.withdraw_request.title": "Cancelar solicitação para seguir {name}?",
|
||||||
"content_warning.hide": "Ocultar post",
|
"content_warning.hide": "Ocultar post",
|
||||||
"content_warning.show": "Mostrar mesmo assim",
|
"content_warning.show": "Mostrar mesmo assim",
|
||||||
"content_warning.show_more": "Mostrar mais",
|
"content_warning.show_more": "Mostrar mais",
|
||||||
@@ -320,6 +333,7 @@
|
|||||||
"empty_column.bookmarked_statuses": "Nada aqui. Quando você salvar um toot, ele aparecerá aqui.",
|
"empty_column.bookmarked_statuses": "Nada aqui. Quando você salvar um toot, ele aparecerá aqui.",
|
||||||
"empty_column.community": "A linha local está vazia. Publique algo para começar!",
|
"empty_column.community": "A linha local está vazia. Publique algo para começar!",
|
||||||
"empty_column.direct": "Você ainda não tem mensagens privadas. Quando você enviar ou receber uma, será exibida aqui.",
|
"empty_column.direct": "Você ainda não tem mensagens privadas. Quando você enviar ou receber uma, será exibida aqui.",
|
||||||
|
"empty_column.disabled_feed": "Este feed foi desativado pelos administradores do servidor.",
|
||||||
"empty_column.domain_blocks": "Nada aqui.",
|
"empty_column.domain_blocks": "Nada aqui.",
|
||||||
"empty_column.explore_statuses": "Nada está em alta no momento. Volte mais tarde!",
|
"empty_column.explore_statuses": "Nada está em alta no momento. Volte mais tarde!",
|
||||||
"empty_column.favourited_statuses": "Você ainda não tem publicações favoritas. Quanto você marcar uma como favorita, ela aparecerá aqui.",
|
"empty_column.favourited_statuses": "Você ainda não tem publicações favoritas. Quanto você marcar uma como favorita, ela aparecerá aqui.",
|
||||||
@@ -448,10 +462,12 @@
|
|||||||
"ignore_notifications_modal.private_mentions_title": "Ignorar notificações de menções privadas não solicitadas?",
|
"ignore_notifications_modal.private_mentions_title": "Ignorar notificações de menções privadas não solicitadas?",
|
||||||
"info_button.label": "Ajuda",
|
"info_button.label": "Ajuda",
|
||||||
"info_button.what_is_alt_text": "<h1>O que é texto alternativo?</h1><p>O texto alternativo fornece descrições de imagens para pessoas com deficiências visuais, conexões de internet de baixa largura de banda ou aquelas que buscam mais contexto.</p><p>Você pode melhorar a acessibilidade e a compreensão para todos escrevendo texto alternativo claro, conciso e objetivo.</p> <ul> <li>Capture elementos importantes</li> <li>Resuma textos em imagens</li> <li>Use estrutura de frases regular</li> <li>Evite informações redundantes</li> <li>Foque em tendências e descobertas principais em visuais complexos (como diagramas ou mapas)</li> </ul>",
|
"info_button.what_is_alt_text": "<h1>O que é texto alternativo?</h1><p>O texto alternativo fornece descrições de imagens para pessoas com deficiências visuais, conexões de internet de baixa largura de banda ou aquelas que buscam mais contexto.</p><p>Você pode melhorar a acessibilidade e a compreensão para todos escrevendo texto alternativo claro, conciso e objetivo.</p> <ul> <li>Capture elementos importantes</li> <li>Resuma textos em imagens</li> <li>Use estrutura de frases regular</li> <li>Evite informações redundantes</li> <li>Foque em tendências e descobertas principais em visuais complexos (como diagramas ou mapas)</li> </ul>",
|
||||||
|
"interaction_modal.action": "Para interagir com o post de {name}, você precisa entrar em sua conta em qualquer servidor Mastodon que você use.",
|
||||||
"interaction_modal.go": "Ir",
|
"interaction_modal.go": "Ir",
|
||||||
"interaction_modal.no_account_yet": "Não possui uma conta ainda?",
|
"interaction_modal.no_account_yet": "Não possui uma conta ainda?",
|
||||||
"interaction_modal.on_another_server": "Em um servidor diferente",
|
"interaction_modal.on_another_server": "Em um servidor diferente",
|
||||||
"interaction_modal.on_this_server": "Neste servidor",
|
"interaction_modal.on_this_server": "Neste servidor",
|
||||||
|
"interaction_modal.title": "Faça login para continuar",
|
||||||
"interaction_modal.username_prompt": "p. e.x.: {example}",
|
"interaction_modal.username_prompt": "p. e.x.: {example}",
|
||||||
"intervals.full.days": "{number, plural, one {# dia} other {# dias}}",
|
"intervals.full.days": "{number, plural, one {# dia} other {# dias}}",
|
||||||
"intervals.full.hours": "{number, plural, one {# hora} other {# horas}}",
|
"intervals.full.hours": "{number, plural, one {# hora} other {# horas}}",
|
||||||
@@ -734,9 +750,11 @@
|
|||||||
"privacy.quote.disabled": "{visibility} Citações desabilitadas",
|
"privacy.quote.disabled": "{visibility} Citações desabilitadas",
|
||||||
"privacy.quote.limited": "{visibility} Citações limitadas",
|
"privacy.quote.limited": "{visibility} Citações limitadas",
|
||||||
"privacy.unlisted.additional": "Isso se comporta exatamente como público, exceto que a publicação não aparecerá nos _feeds ao vivo_ ou nas _hashtags_, explorar, ou barra de busca, mesmo que você seja escolhido em toda a conta.",
|
"privacy.unlisted.additional": "Isso se comporta exatamente como público, exceto que a publicação não aparecerá nos _feeds ao vivo_ ou nas _hashtags_, explorar, ou barra de busca, mesmo que você seja escolhido em toda a conta.",
|
||||||
"privacy.unlisted.short": "Público (silencioso)",
|
"privacy.unlisted.long": "Oculto para os resultados de pesquisa do Mastodon, tendências e linhas do tempo públicas",
|
||||||
|
"privacy.unlisted.short": "Público silenciado",
|
||||||
"privacy_policy.last_updated": "Atualizado {date}",
|
"privacy_policy.last_updated": "Atualizado {date}",
|
||||||
"privacy_policy.title": "Política de privacidade",
|
"privacy_policy.title": "Política de privacidade",
|
||||||
|
"quote_error.edit": "Citações não podem ser adicionadas durante a edição de uma publicação.",
|
||||||
"quote_error.poll": "Citações não permitidas com enquetes.",
|
"quote_error.poll": "Citações não permitidas com enquetes.",
|
||||||
"quote_error.quote": "Apenas uma citação por vez é permitido.",
|
"quote_error.quote": "Apenas uma citação por vez é permitido.",
|
||||||
"quote_error.unauthorized": "Você não é autorizado a citar essa publicação.",
|
"quote_error.unauthorized": "Você não é autorizado a citar essa publicação.",
|
||||||
@@ -756,6 +774,9 @@
|
|||||||
"relative_time.minutes": "{number}m",
|
"relative_time.minutes": "{number}m",
|
||||||
"relative_time.seconds": "{number}s",
|
"relative_time.seconds": "{number}s",
|
||||||
"relative_time.today": "hoje",
|
"relative_time.today": "hoje",
|
||||||
|
"remove_quote_hint.button_label": "Entendi",
|
||||||
|
"remove_quote_hint.message": "Você pode fazê-lo no menu de opções {icon}.",
|
||||||
|
"remove_quote_hint.title": "Deseja remover sua citação publicada?",
|
||||||
"reply_indicator.attachments": "{count, plural, one {# attachment} other {# attachments}}",
|
"reply_indicator.attachments": "{count, plural, one {# attachment} other {# attachments}}",
|
||||||
"reply_indicator.cancel": "Cancelar",
|
"reply_indicator.cancel": "Cancelar",
|
||||||
"reply_indicator.poll": "Enquete",
|
"reply_indicator.poll": "Enquete",
|
||||||
@@ -851,7 +872,15 @@
|
|||||||
"status.block": "Bloquear @{name}",
|
"status.block": "Bloquear @{name}",
|
||||||
"status.bookmark": "Salvar",
|
"status.bookmark": "Salvar",
|
||||||
"status.cancel_reblog_private": "Desfazer boost",
|
"status.cancel_reblog_private": "Desfazer boost",
|
||||||
|
"status.cannot_quote": "Você não tem permissão para citar esta publicação",
|
||||||
"status.cannot_reblog": "Este toot não pode receber boost",
|
"status.cannot_reblog": "Este toot não pode receber boost",
|
||||||
|
"status.contains_quote": "Contém citação",
|
||||||
|
"status.context.loading": "Carregando mais respostas",
|
||||||
|
"status.context.loading_error": "Não foi possível carregar novas respostas",
|
||||||
|
"status.context.loading_success": "Novas respostas carregadas",
|
||||||
|
"status.context.more_replies_found": "Mais respostas encontradas",
|
||||||
|
"status.context.retry": "Tentar novamente",
|
||||||
|
"status.context.show": "Mostrar",
|
||||||
"status.continued_thread": "Continuação da conversa",
|
"status.continued_thread": "Continuação da conversa",
|
||||||
"status.copy": "Copiar link",
|
"status.copy": "Copiar link",
|
||||||
"status.delete": "Excluir",
|
"status.delete": "Excluir",
|
||||||
@@ -881,24 +910,33 @@
|
|||||||
"status.quote": "Citar",
|
"status.quote": "Citar",
|
||||||
"status.quote.cancel": "Cancelar citação",
|
"status.quote.cancel": "Cancelar citação",
|
||||||
"status.quote_error.filtered": "Oculto devido a um dos seus filtros",
|
"status.quote_error.filtered": "Oculto devido a um dos seus filtros",
|
||||||
|
"status.quote_error.limited_account_hint.action": "Mostrar mesmo assim",
|
||||||
|
"status.quote_error.limited_account_hint.title": "Esta conta foi oculta pelos moderadores do {domain}.",
|
||||||
"status.quote_error.not_available": "Publicação indisponível",
|
"status.quote_error.not_available": "Publicação indisponível",
|
||||||
"status.quote_error.pending_approval": "Publicação pendente",
|
"status.quote_error.pending_approval": "Publicação pendente",
|
||||||
|
"status.quote_error.pending_approval_popout.body": "No Mastodon, você pode controlar se alguém pode citar você. Esta publicação está pendente enquanto estamos recebendo a aprovação do autor original.",
|
||||||
|
"status.quote_error.revoked": "Publicação removida pelo autor",
|
||||||
"status.quote_followers_only": "Apenas seguidores podem citar sua publicação",
|
"status.quote_followers_only": "Apenas seguidores podem citar sua publicação",
|
||||||
"status.quote_manual_review": "Autor irá revisar manualmente",
|
"status.quote_manual_review": "Autor irá revisar manualmente",
|
||||||
|
"status.quote_noun": "Citar",
|
||||||
"status.quote_policy_change": "Mude quem pode citar",
|
"status.quote_policy_change": "Mude quem pode citar",
|
||||||
"status.quote_post_author": "Publicação citada por @{name}",
|
"status.quote_post_author": "Publicação citada por @{name}",
|
||||||
"status.quote_private": "Publicações privadas não podem ser citadas",
|
"status.quote_private": "Publicações privadas não podem ser citadas",
|
||||||
"status.quotes": "{count, plural, one {# voto} other {# votos}}",
|
"status.quotes": "{count, plural, one {# voto} other {# votos}}",
|
||||||
"status.quotes.empty": "Ninguém citou essa publicação até agora. Quando alguém citar aparecerá aqui.",
|
"status.quotes.empty": "Ninguém citou essa publicação até agora. Quando alguém citar aparecerá aqui.",
|
||||||
|
"status.quotes.local_other_disclaimer": "Citações rejeitadas pelo autor não serão exibidas.",
|
||||||
|
"status.quotes.remote_other_disclaimer": "Apenas citações do {domain} têm a garantia de serem exibidas aqui. Citações rejeitadas pelo autor não serão exibidas.",
|
||||||
"status.read_more": "Ler mais",
|
"status.read_more": "Ler mais",
|
||||||
"status.reblog": "Dar boost",
|
"status.reblog": "Dar boost",
|
||||||
"status.reblog_or_quote": "Acelerar ou citar",
|
"status.reblog_or_quote": "Acelerar ou citar",
|
||||||
|
"status.reblog_private": "Compartilhar novamente com seus seguidores",
|
||||||
"status.reblogged_by": "{name} deu boost",
|
"status.reblogged_by": "{name} deu boost",
|
||||||
"status.reblogs": "{count, plural, one {boost} other {boosts}}",
|
"status.reblogs": "{count, plural, one {boost} other {boosts}}",
|
||||||
"status.reblogs.empty": "Nada aqui. Quando alguém der boost, o usuário aparecerá aqui.",
|
"status.reblogs.empty": "Nada aqui. Quando alguém der boost, o usuário aparecerá aqui.",
|
||||||
"status.redraft": "Excluir e rascunhar",
|
"status.redraft": "Excluir e rascunhar",
|
||||||
"status.remove_bookmark": "Remover do Salvos",
|
"status.remove_bookmark": "Remover do Salvos",
|
||||||
"status.remove_favourite": "Remover dos favoritos",
|
"status.remove_favourite": "Remover dos favoritos",
|
||||||
|
"status.remove_quote": "Remover",
|
||||||
"status.replied_in_thread": "Respondido na conversa",
|
"status.replied_in_thread": "Respondido na conversa",
|
||||||
"status.replied_to": "Em resposta a {name}",
|
"status.replied_to": "Em resposta a {name}",
|
||||||
"status.reply": "Responder",
|
"status.reply": "Responder",
|
||||||
@@ -970,6 +1008,8 @@
|
|||||||
"visibility_modal.button_title": "Selecionar Visibilidade",
|
"visibility_modal.button_title": "Selecionar Visibilidade",
|
||||||
"visibility_modal.header": "Visibilidade e interação",
|
"visibility_modal.header": "Visibilidade e interação",
|
||||||
"visibility_modal.helper.direct_quoting": "Menções privadas escritas no Mastodon.",
|
"visibility_modal.helper.direct_quoting": "Menções privadas escritas no Mastodon.",
|
||||||
|
"visibility_modal.helper.privacy_editing": "A visibilidade não pode ser alterada após uma publicação ser publicada.",
|
||||||
|
"visibility_modal.helper.privacy_private_self_quote": "As auto-citações de publicações privadas não podem ser públicas.",
|
||||||
"visibility_modal.helper.private_quoting": "Posts somente para seguidores feitos no Mastodon não podem ser citados por outros.",
|
"visibility_modal.helper.private_quoting": "Posts somente para seguidores feitos no Mastodon não podem ser citados por outros.",
|
||||||
"visibility_modal.helper.unlisted_quoting": "Quando as pessoas citam você, sua publicação também será ocultada das linhas de tempo de tendência.",
|
"visibility_modal.helper.unlisted_quoting": "Quando as pessoas citam você, sua publicação também será ocultada das linhas de tempo de tendência.",
|
||||||
"visibility_modal.instructions": "Controle quem pode interagir com este post. Você também pode aplicar as configurações para todos os posts futuros navegando para <link>Preferências > Postagem padrão</link>.",
|
"visibility_modal.instructions": "Controle quem pode interagir com este post. Você também pode aplicar as configurações para todos os posts futuros navegando para <link>Preferências > Postagem padrão</link>.",
|
||||||
|
|||||||
@@ -333,6 +333,7 @@
|
|||||||
"empty_column.bookmarked_statuses": "Ainda não tem nenhuma publicação salva. Quando salvar uma, ela aparecerá aqui.",
|
"empty_column.bookmarked_statuses": "Ainda não tem nenhuma publicação salva. Quando salvar uma, ela aparecerá aqui.",
|
||||||
"empty_column.community": "A cronologia local está vazia. Escreve algo publicamente para começar!",
|
"empty_column.community": "A cronologia local está vazia. Escreve algo publicamente para começar!",
|
||||||
"empty_column.direct": "Ainda não tens qualquer menção privada. Quando enviares ou receberes uma, ela irá aparecer aqui.",
|
"empty_column.direct": "Ainda não tens qualquer menção privada. Quando enviares ou receberes uma, ela irá aparecer aqui.",
|
||||||
|
"empty_column.disabled_feed": "Esta cronologia foi desativada pelos administradores do seu servidor.",
|
||||||
"empty_column.domain_blocks": "Ainda não há qualquer domínio bloqueado.",
|
"empty_column.domain_blocks": "Ainda não há qualquer domínio bloqueado.",
|
||||||
"empty_column.explore_statuses": "Não há nada em destaque neste momento. Volte mais tarde!",
|
"empty_column.explore_statuses": "Não há nada em destaque neste momento. Volte mais tarde!",
|
||||||
"empty_column.favourited_statuses": "Ainda não assinalaste qualquer publicação como favorita. Quando o fizeres, ela aparecerá aqui.",
|
"empty_column.favourited_statuses": "Ainda não assinalaste qualquer publicação como favorita. Quando o fizeres, ela aparecerá aqui.",
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ import { me, reduceMotion } from 'mastodon/initial_state';
|
|||||||
import ready from 'mastodon/ready';
|
import ready from 'mastodon/ready';
|
||||||
import { store } from 'mastodon/store';
|
import { store } from 'mastodon/store';
|
||||||
|
|
||||||
import {
|
import { initializeEmoji } from './features/emoji';
|
||||||
isProduction,
|
import { isProduction, isDevelopment } from './utils/environment';
|
||||||
isDevelopment,
|
|
||||||
isModernEmojiEnabled,
|
|
||||||
} from './utils/environment';
|
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
perf.start('main()');
|
perf.start('main()');
|
||||||
@@ -33,10 +30,7 @@ function main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isModernEmojiEnabled()) {
|
|
||||||
const { initializeEmoji } = await import('@/mastodon/features/emoji');
|
|
||||||
initializeEmoji();
|
initializeEmoji();
|
||||||
}
|
|
||||||
|
|
||||||
const root = createRoot(mountNode);
|
const root = createRoot(mountNode);
|
||||||
root.render(<Mastodon {...props} />);
|
root.render(<Mastodon {...props} />);
|
||||||
|
|||||||
@@ -8,11 +8,10 @@ import type {
|
|||||||
ApiAccountRoleJSON,
|
ApiAccountRoleJSON,
|
||||||
ApiAccountJSON,
|
ApiAccountJSON,
|
||||||
} from 'mastodon/api_types/accounts';
|
} from 'mastodon/api_types/accounts';
|
||||||
import emojify from 'mastodon/features/emoji/emoji';
|
|
||||||
import { unescapeHTML } from 'mastodon/utils/html';
|
import { unescapeHTML } from 'mastodon/utils/html';
|
||||||
|
|
||||||
import { CustomEmojiFactory, makeEmojiMap } from './custom_emoji';
|
import { CustomEmojiFactory } from './custom_emoji';
|
||||||
import type { CustomEmoji, EmojiMap } from './custom_emoji';
|
import type { CustomEmoji } from './custom_emoji';
|
||||||
|
|
||||||
// AccountField
|
// AccountField
|
||||||
interface AccountFieldShape extends Required<ApiAccountFieldJSON> {
|
interface AccountFieldShape extends Required<ApiAccountFieldJSON> {
|
||||||
@@ -102,17 +101,11 @@ export const accountDefaultValues: AccountShape = {
|
|||||||
|
|
||||||
const AccountFactory = ImmutableRecord<AccountShape>(accountDefaultValues);
|
const AccountFactory = ImmutableRecord<AccountShape>(accountDefaultValues);
|
||||||
|
|
||||||
function createAccountField(
|
function createAccountField(jsonField: ApiAccountFieldJSON) {
|
||||||
jsonField: ApiAccountFieldJSON,
|
|
||||||
emojiMap: EmojiMap,
|
|
||||||
) {
|
|
||||||
return AccountFieldFactory({
|
return AccountFieldFactory({
|
||||||
...jsonField,
|
...jsonField,
|
||||||
name_emojified: emojify(
|
name_emojified: escapeTextContentForBrowser(jsonField.name),
|
||||||
escapeTextContentForBrowser(jsonField.name),
|
value_emojified: jsonField.value,
|
||||||
emojiMap,
|
|
||||||
),
|
|
||||||
value_emojified: emojify(jsonField.value, emojiMap),
|
|
||||||
value_plain: unescapeHTML(jsonField.value),
|
value_plain: unescapeHTML(jsonField.value),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -120,8 +113,6 @@ function createAccountField(
|
|||||||
export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
|
export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
|
||||||
const { moved, ...accountJSON } = serverJSON;
|
const { moved, ...accountJSON } = serverJSON;
|
||||||
|
|
||||||
const emojiMap = makeEmojiMap(accountJSON.emojis);
|
|
||||||
|
|
||||||
const displayName =
|
const displayName =
|
||||||
accountJSON.display_name.trim().length === 0
|
accountJSON.display_name.trim().length === 0
|
||||||
? accountJSON.username
|
? accountJSON.username
|
||||||
@@ -134,7 +125,7 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
|
|||||||
...accountJSON,
|
...accountJSON,
|
||||||
moved: moved?.id,
|
moved: moved?.id,
|
||||||
fields: ImmutableList(
|
fields: ImmutableList(
|
||||||
serverJSON.fields.map((field) => createAccountField(field, emojiMap)),
|
serverJSON.fields.map((field) => createAccountField(field)),
|
||||||
),
|
),
|
||||||
emojis: ImmutableList(
|
emojis: ImmutableList(
|
||||||
serverJSON.emojis.map((emoji) => CustomEmojiFactory(emoji)),
|
serverJSON.emojis.map((emoji) => CustomEmojiFactory(emoji)),
|
||||||
@@ -142,11 +133,8 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
|
|||||||
roles: ImmutableList(
|
roles: ImmutableList(
|
||||||
serverJSON.roles?.map((role) => AccountRoleFactory(role)),
|
serverJSON.roles?.map((role) => AccountRoleFactory(role)),
|
||||||
),
|
),
|
||||||
display_name_html: emojify(
|
display_name_html: escapeTextContentForBrowser(displayName),
|
||||||
escapeTextContentForBrowser(displayName),
|
note_emojified: accountNote,
|
||||||
emojiMap,
|
|
||||||
),
|
|
||||||
note_emojified: emojify(accountNote, emojiMap),
|
|
||||||
note_plain: unescapeHTML(accountNote),
|
note_plain: unescapeHTML(accountNote),
|
||||||
url:
|
url:
|
||||||
accountJSON.url?.startsWith('http://') ||
|
accountJSON.url?.startsWith('http://') ||
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import escapeTextContentForBrowser from 'escape-html';
|
import escapeTextContentForBrowser from 'escape-html';
|
||||||
|
|
||||||
import type { ApiPollJSON, ApiPollOptionJSON } from 'mastodon/api_types/polls';
|
import type { ApiPollJSON, ApiPollOptionJSON } from 'mastodon/api_types/polls';
|
||||||
import emojify from 'mastodon/features/emoji/emoji';
|
|
||||||
|
|
||||||
import { CustomEmojiFactory, makeEmojiMap } from './custom_emoji';
|
import { CustomEmojiFactory } from './custom_emoji';
|
||||||
import type { CustomEmoji, EmojiMap } from './custom_emoji';
|
import type { CustomEmoji } from './custom_emoji';
|
||||||
|
|
||||||
interface PollOptionTranslation {
|
interface PollOptionTranslation {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -17,16 +16,12 @@ export interface PollOption extends ApiPollOptionJSON {
|
|||||||
translation: PollOptionTranslation | null;
|
translation: PollOptionTranslation | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createPollOptionTranslationFromServerJSON(
|
export function createPollOptionTranslationFromServerJSON(translation: {
|
||||||
translation: { title: string },
|
title: string;
|
||||||
emojiMap: EmojiMap,
|
}) {
|
||||||
) {
|
|
||||||
return {
|
return {
|
||||||
...translation,
|
...translation,
|
||||||
titleHtml: emojify(
|
titleHtml: escapeTextContentForBrowser(translation.title),
|
||||||
escapeTextContentForBrowser(translation.title),
|
|
||||||
emojiMap,
|
|
||||||
),
|
|
||||||
} as PollOptionTranslation;
|
} as PollOptionTranslation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,8 +45,6 @@ export function createPollFromServerJSON(
|
|||||||
serverJSON: ApiPollJSON,
|
serverJSON: ApiPollJSON,
|
||||||
previousPoll?: Poll,
|
previousPoll?: Poll,
|
||||||
) {
|
) {
|
||||||
const emojiMap = makeEmojiMap(serverJSON.emojis);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...pollDefaultValues,
|
...pollDefaultValues,
|
||||||
...serverJSON,
|
...serverJSON,
|
||||||
@@ -60,20 +53,15 @@ export function createPollFromServerJSON(
|
|||||||
const option = {
|
const option = {
|
||||||
...optionJSON,
|
...optionJSON,
|
||||||
voted: serverJSON.own_votes?.includes(index) || false,
|
voted: serverJSON.own_votes?.includes(index) || false,
|
||||||
titleHtml: emojify(
|
titleHtml: escapeTextContentForBrowser(optionJSON.title),
|
||||||
escapeTextContentForBrowser(optionJSON.title),
|
|
||||||
emojiMap,
|
|
||||||
),
|
|
||||||
} as PollOption;
|
} as PollOption;
|
||||||
|
|
||||||
const prevOption = previousPoll?.options[index];
|
const prevOption = previousPoll?.options[index];
|
||||||
if (prevOption?.translation && prevOption.title === option.title) {
|
if (prevOption?.translation && prevOption.title === option.title) {
|
||||||
const { translation } = prevOption;
|
const { translation } = prevOption;
|
||||||
|
|
||||||
option.translation = createPollOptionTranslationFromServerJSON(
|
option.translation =
|
||||||
translation,
|
createPollOptionTranslationFromServerJSON(translation);
|
||||||
emojiMap,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return option;
|
return option;
|
||||||
|
|||||||
@@ -2,9 +2,6 @@
|
|||||||
// If there are no polyfills, then this is just Promise.resolve() which means
|
// If there are no polyfills, then this is just Promise.resolve() which means
|
||||||
// it will execute in the same tick of the event loop (i.e. near-instant).
|
// it will execute in the same tick of the event loop (i.e. near-instant).
|
||||||
|
|
||||||
// eslint-disable-next-line import/extensions -- This file is virtual so it thinks it has an extension
|
|
||||||
import 'vite/modulepreload-polyfill';
|
|
||||||
|
|
||||||
import { loadIntlPolyfills } from './intl';
|
import { loadIntlPolyfills } from './intl';
|
||||||
|
|
||||||
function importExtraPolyfills() {
|
function importExtraPolyfills() {
|
||||||
@@ -17,6 +14,7 @@ export function loadPolyfills() {
|
|||||||
const needsExtraPolyfills = !window.requestIdleCallback;
|
const needsExtraPolyfills = !window.requestIdleCallback;
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
|
loadVitePreloadPolyfill(),
|
||||||
loadIntlPolyfills(),
|
loadIntlPolyfills(),
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- those properties might not exist in old browsers, even if they are always here in types
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- those properties might not exist in old browsers, even if they are always here in types
|
||||||
needsExtraPolyfills ? importExtraPolyfills() : Promise.resolve(),
|
needsExtraPolyfills ? importExtraPolyfills() : Promise.resolve(),
|
||||||
@@ -31,5 +29,13 @@ async function loadEmojiPolyfills() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loads Vite's module preload polyfill for older browsers, but not in a Worker context.
|
||||||
|
function loadVitePreloadPolyfill() {
|
||||||
|
if (typeof document === 'undefined') return;
|
||||||
|
// @ts-expect-error -- This is a virtual module provided by Vite.
|
||||||
|
// eslint-disable-next-line import/extensions
|
||||||
|
return import('vite/modulepreload-polyfill');
|
||||||
|
}
|
||||||
|
|
||||||
// Null unless polyfill is needed.
|
// Null unless polyfill is needed.
|
||||||
export let emojiRegexPolyfill: RegExp | null = null;
|
export let emojiRegexPolyfill: RegExp | null = null;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { Reducer } from '@reduxjs/toolkit';
|
import type { Reducer } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import { importPolls } from 'mastodon/actions/importer/polls';
|
import { importPolls } from 'mastodon/actions/importer/polls';
|
||||||
import { makeEmojiMap } from 'mastodon/models/custom_emoji';
|
|
||||||
import { createPollOptionTranslationFromServerJSON } from 'mastodon/models/poll';
|
import { createPollOptionTranslationFromServerJSON } from 'mastodon/models/poll';
|
||||||
import type { Poll } from 'mastodon/models/poll';
|
import type { Poll } from 'mastodon/models/poll';
|
||||||
|
|
||||||
@@ -20,16 +19,11 @@ const statusTranslateSuccess = (state: PollsState, pollTranslation?: Poll) => {
|
|||||||
|
|
||||||
if (!poll) return;
|
if (!poll) return;
|
||||||
|
|
||||||
const emojiMap = makeEmojiMap(poll.emojis);
|
|
||||||
|
|
||||||
pollTranslation.options.forEach((item, index) => {
|
pollTranslation.options.forEach((item, index) => {
|
||||||
const option = poll.options[index];
|
const option = poll.options[index];
|
||||||
if (!option) return;
|
if (!option) return;
|
||||||
|
|
||||||
option.translation = createPollOptionTranslationFromServerJSON(
|
option.translation = createPollOptionTranslationFromServerJSON(item);
|
||||||
item,
|
|
||||||
emojiMap,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -138,10 +138,15 @@ const channelNameWithInlineParams = (channelName, params) => {
|
|||||||
return `${channelName}&${Object.keys(params).map(key => `${key}=${params[key]}`).join('&')}`;
|
return `${channelName}&${Object.keys(params).map(key => `${key}=${params[key]}`).join('&')}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('mastodon/store').AppDispatch} Dispatch
|
||||||
|
* @typedef {import('mastodon/store').GetState} GetState
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} channelName
|
* @param {string} channelName
|
||||||
* @param {Object.<string, string>} params
|
* @param {Object.<string, string>} params
|
||||||
* @param {function(Function, Function): { onConnect: (function(): void), onReceive: (function(StreamEvent): void), onDisconnect: (function(): void) }} callbacks
|
* @param {function(Dispatch, GetState): { onConnect: (function(): void), onReceive: (function(StreamEvent): void), onDisconnect: (function(): void) }} callbacks
|
||||||
* @returns {function(): void}
|
* @returns {function(): void}
|
||||||
*/
|
*/
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
@@ -229,7 +234,7 @@ const handleEventSourceMessage = (e, received) => {
|
|||||||
* @param {string} streamingAPIBaseURL
|
* @param {string} streamingAPIBaseURL
|
||||||
* @param {string} accessToken
|
* @param {string} accessToken
|
||||||
* @param {string} channelName
|
* @param {string} channelName
|
||||||
* @param {{ connected: Function, received: function(StreamEvent): void, disconnected: Function, reconnected: Function }} callbacks
|
* @param {{ connected: function(): void, received: function(StreamEvent): void, disconnected: function(): void, reconnected: function(): void }} callbacks
|
||||||
* @returns {WebSocketClient | EventSource}
|
* @returns {WebSocketClient | EventSource}
|
||||||
*/
|
*/
|
||||||
const createConnection = (streamingAPIBaseURL, accessToken, channelName, { connected, received, disconnected, reconnected }) => {
|
const createConnection = (streamingAPIBaseURL, accessToken, channelName, { connected, received, disconnected, reconnected }) => {
|
||||||
@@ -242,12 +247,9 @@ const createConnection = (streamingAPIBaseURL, accessToken, channelName, { conne
|
|||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`, accessToken);
|
const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`, accessToken);
|
||||||
|
|
||||||
// @ts-expect-error
|
|
||||||
ws.onopen = connected;
|
ws.onopen = connected;
|
||||||
ws.onmessage = e => received(JSON.parse(e.data));
|
ws.onmessage = e => received(JSON.parse(e.data));
|
||||||
// @ts-expect-error
|
|
||||||
ws.onclose = disconnected;
|
ws.onclose = disconnected;
|
||||||
// @ts-expect-error
|
|
||||||
ws.onreconnect = reconnected;
|
ws.onreconnect = reconnected;
|
||||||
|
|
||||||
return ws;
|
return ws;
|
||||||
|
|||||||
@@ -12,16 +12,8 @@ export function isProduction() {
|
|||||||
else return import.meta.env.PROD;
|
else return import.meta.env.PROD;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Features = 'modern_emojis' | 'fasp' | 'http_message_signatures';
|
export type Features = 'fasp' | 'http_message_signatures';
|
||||||
|
|
||||||
export function isFeatureEnabled(feature: Features) {
|
export function isFeatureEnabled(feature: Features) {
|
||||||
return initialState?.features.includes(feature) ?? false;
|
return initialState?.features.includes(feature) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isModernEmojiEnabled() {
|
|
||||||
try {
|
|
||||||
return isFeatureEnabled('modern_emojis');
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4031,6 +4031,7 @@ a.account__display-name {
|
|||||||
background: lighten($ui-highlight-color, 5%);
|
background: lighten($ui-highlight-color, 5%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.follow_requests-unlocked_explanation,
|
||||||
.switch-to-advanced {
|
.switch-to-advanced {
|
||||||
color: $light-text-color;
|
color: $light-text-color;
|
||||||
background-color: $ui-base-color;
|
background-color: $ui-base-color;
|
||||||
@@ -4041,7 +4042,7 @@ a.account__display-name {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
|
|
||||||
.switch-to-advanced__toggle {
|
a {
|
||||||
color: $ui-button-tertiary-color;
|
color: $ui-button-tertiary-color;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
@@ -5223,8 +5224,7 @@ a.status-card {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-column-indicator,
|
.empty-column-indicator {
|
||||||
.follow_requests-unlocked_explanation {
|
|
||||||
color: $dark-text-color;
|
color: $dark-text-color;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
@@ -5263,10 +5263,8 @@ a.status-card {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.follow_requests-unlocked_explanation {
|
.follow_requests-unlocked_explanation {
|
||||||
background: var(--surface-background-color);
|
margin: 16px;
|
||||||
border-bottom: 1px solid var(--background-border-color);
|
margin-bottom: 0;
|
||||||
contain: initial;
|
|
||||||
flex-grow: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-column {
|
.error-column {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ActivityPub::Activity::Create < ActivityPub::Activity
|
class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
include FormattingHelper
|
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
@account.schedule_refresh_if_stale!
|
@account.schedule_refresh_if_stale!
|
||||||
|
|
||||||
@@ -99,9 +97,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||||||
uri: @status_parser.uri,
|
uri: @status_parser.uri,
|
||||||
url: @status_parser.url || @status_parser.uri,
|
url: @status_parser.url || @status_parser.uri,
|
||||||
account: @account,
|
account: @account,
|
||||||
text: converted_object_type? ? converted_text : (@status_parser.text || ''),
|
text: @status_parser.processed_text,
|
||||||
language: @status_parser.language,
|
language: @status_parser.language,
|
||||||
spoiler_text: converted_object_type? ? '' : (@status_parser.spoiler_text || ''),
|
spoiler_text: @status_parser.processed_spoiler_text,
|
||||||
created_at: @status_parser.created_at,
|
created_at: @status_parser.created_at,
|
||||||
edited_at: @status_parser.edited_at && @status_parser.edited_at != @status_parser.created_at ? @status_parser.edited_at : nil,
|
edited_at: @status_parser.edited_at && @status_parser.edited_at != @status_parser.created_at ? @status_parser.edited_at : nil,
|
||||||
override_timestamps: @options[:override_timestamps],
|
override_timestamps: @options[:override_timestamps],
|
||||||
@@ -405,18 +403,6 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||||||
value_or_id(@object['inReplyTo'])
|
value_or_id(@object['inReplyTo'])
|
||||||
end
|
end
|
||||||
|
|
||||||
def converted_text
|
|
||||||
[formatted_title, @status_parser.spoiler_text.presence, formatted_url].compact.join("\n\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
def formatted_title
|
|
||||||
"<h2>#{@status_parser.title}</h2>" if @status_parser.title.present?
|
|
||||||
end
|
|
||||||
|
|
||||||
def formatted_url
|
|
||||||
linkify(@status_parser.url || @status_parser.uri)
|
|
||||||
end
|
|
||||||
|
|
||||||
def unsupported_media_type?(mime_type)
|
def unsupported_media_type?(mime_type)
|
||||||
mime_type.present? && !MediaAttachment.supported_mime_types.include?(mime_type)
|
mime_type.present? && !MediaAttachment.supported_mime_types.include?(mime_type)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ class ActivityPub::Activity::Update < ActivityPub::Activity
|
|||||||
|
|
||||||
if equals_or_includes_any?(@object['type'], %w(Application Group Organization Person Service))
|
if equals_or_includes_any?(@object['type'], %w(Application Group Organization Person Service))
|
||||||
update_account
|
update_account
|
||||||
elsif equals_or_includes_any?(@object['type'], %w(Note Question))
|
elsif supported_object_type? || converted_object_type?
|
||||||
update_status
|
update_status
|
||||||
elsif converted_object_type?
|
|
||||||
Status.find_by(uri: object_uri, account_id: @account.id)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ActivityPub::Parser::StatusParser
|
class ActivityPub::Parser::StatusParser
|
||||||
|
include FormattingHelper
|
||||||
include JsonLdHelper
|
include JsonLdHelper
|
||||||
|
|
||||||
NORMALIZED_LOCALE_NAMES = LanguagesHelper::SUPPORTED_LOCALES.keys.index_by(&:downcase).freeze
|
NORMALIZED_LOCALE_NAMES = LanguagesHelper::SUPPORTED_LOCALES.keys.index_by(&:downcase).freeze
|
||||||
@@ -44,6 +45,16 @@ class ActivityPub::Parser::StatusParser
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def processed_text
|
||||||
|
return text || '' unless converted_object_type?
|
||||||
|
|
||||||
|
[
|
||||||
|
title.presence && "<h2>#{title}</h2>",
|
||||||
|
spoiler_text.presence,
|
||||||
|
linkify(url || uri),
|
||||||
|
].compact.join("\n\n")
|
||||||
|
end
|
||||||
|
|
||||||
def spoiler_text
|
def spoiler_text
|
||||||
if @object['summary'].present?
|
if @object['summary'].present?
|
||||||
@object['summary']
|
@object['summary']
|
||||||
@@ -52,6 +63,12 @@ class ActivityPub::Parser::StatusParser
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def processed_spoiler_text
|
||||||
|
return '' if converted_object_type?
|
||||||
|
|
||||||
|
spoiler_text || ''
|
||||||
|
end
|
||||||
|
|
||||||
def title
|
def title
|
||||||
if @object['name'].present?
|
if @object['name'].present?
|
||||||
@object['name']
|
@object['name']
|
||||||
@@ -147,6 +164,10 @@ class ActivityPub::Parser::StatusParser
|
|||||||
as_array(@object['quoteAuthorization']).first
|
as_array(@object['quoteAuthorization']).first
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def converted_object_type?
|
||||||
|
equals_or_includes_any?(@object['type'], ActivityPub::Activity::CONVERTED_TYPES)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def quote_subpolicy(subpolicy)
|
def quote_subpolicy(subpolicy)
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ module Extractor
|
|||||||
end
|
end
|
||||||
|
|
||||||
def extract_hashtags_with_indices(text, _options = {})
|
def extract_hashtags_with_indices(text, _options = {})
|
||||||
return [] unless text&.index('#')
|
return [] unless text&.index(/[##]/)
|
||||||
|
|
||||||
possible_entries = []
|
possible_entries = []
|
||||||
|
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ class SignedRequest
|
|||||||
|
|
||||||
def initialize(request)
|
def initialize(request)
|
||||||
@signature =
|
@signature =
|
||||||
if Mastodon::Feature.http_message_signatures_enabled? && request.headers['signature-input'].present?
|
if request.headers['signature-input'].present?
|
||||||
HttpMessageSignature.new(request)
|
HttpMessageSignature.new(request)
|
||||||
else
|
else
|
||||||
HttpSignature.new(request)
|
HttpSignature.new(request)
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ class StatusCacheHydrator
|
|||||||
payload[:filtered] = payload[:reblog][:filtered]
|
payload[:filtered] = payload[:reblog][:filtered]
|
||||||
payload[:favourited] = payload[:reblog][:favourited]
|
payload[:favourited] = payload[:reblog][:favourited]
|
||||||
payload[:reblogged] = payload[:reblog][:reblogged]
|
payload[:reblogged] = payload[:reblog][:reblogged]
|
||||||
|
payload[:quote_approval] = payload[:reblog][:quote_approval]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ module Status::FetchRepliesConcern
|
|||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
# debounce fetching all replies to minimize DoS
|
# debounce fetching all replies to minimize DoS
|
||||||
FETCH_REPLIES_COOLDOWN_MINUTES = (ENV['FETCH_REPLIES_COOLDOWN_MINUTES'] || 15).to_i.minutes
|
# Period to wait between fetching replies
|
||||||
FETCH_REPLIES_INITIAL_WAIT_MINUTES = (ENV['FETCH_REPLIES_INITIAL_WAIT_MINUTES'] || 5).to_i.minutes
|
FETCH_REPLIES_COOLDOWN_MINUTES = 15.minutes
|
||||||
|
# Period to wait after a post is first created before fetching its replies
|
||||||
|
FETCH_REPLIES_INITIAL_WAIT_MINUTES = 5.minutes
|
||||||
|
|
||||||
included do
|
included do
|
||||||
scope :created_recently, -> { where(created_at: FETCH_REPLIES_INITIAL_WAIT_MINUTES.ago..) }
|
scope :created_recently, -> { where(created_at: FETCH_REPLIES_INITIAL_WAIT_MINUTES.ago..) }
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class Tag < ApplicationRecord
|
|||||||
HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)'
|
HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)'
|
||||||
HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}".freeze
|
HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}".freeze
|
||||||
|
|
||||||
HASHTAG_RE = %r{(?<![=/)\p{Alnum}])#(#{HASHTAG_NAME_PAT})}
|
HASHTAG_RE = %r{(?<![=/)\p{Alnum}])[##](#{HASHTAG_NAME_PAT})}
|
||||||
HASHTAG_NAME_RE = /\A(#{HASHTAG_NAME_PAT})\z/i
|
HASHTAG_NAME_RE = /\A(#{HASHTAG_NAME_PAT})\z/i
|
||||||
HASHTAG_INVALID_CHARS_RE = /[^[:alnum:]\u0E47-\u0E4E#{HASHTAG_SEPARATORS}]/
|
HASHTAG_INVALID_CHARS_RE = /[^[:alnum:]\u0E47-\u0E4E#{HASHTAG_SEPARATORS}]/
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class InitialStateSerializer < ActiveModel::Serializer
|
|||||||
store[:default_content_type] = object_account_user.setting_default_content_type
|
store[:default_content_type] = object_account_user.setting_default_content_type
|
||||||
store[:system_emoji_font] = object_account_user.setting_system_emoji_font
|
store[:system_emoji_font] = object_account_user.setting_system_emoji_font
|
||||||
store[:show_trends] = Setting.trends && object_account_user.setting_trends
|
store[:show_trends] = Setting.trends && object_account_user.setting_trends
|
||||||
store[:emoji_style] = object_account_user.settings['web.emoji_style'] if Mastodon::Feature.modern_emojis_enabled?
|
store[:emoji_style] = object_account_user.settings['web.emoji_style']
|
||||||
else
|
else
|
||||||
store[:auto_play_gif] = Setting.auto_play_gif
|
store[:auto_play_gif] = Setting.auto_play_gif
|
||||||
store[:display_media] = Setting.display_media
|
store[:display_media] = Setting.display_media
|
||||||
|
|||||||
@@ -172,9 +172,9 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||||||
|
|
||||||
def quote_approval
|
def quote_approval
|
||||||
{
|
{
|
||||||
automatic: object.quote_policy_as_keys(:automatic),
|
automatic: object.proper.quote_policy_as_keys(:automatic),
|
||||||
manual: object.quote_policy_as_keys(:manual),
|
manual: object.proper.quote_policy_as_keys(:manual),
|
||||||
current_user: object.quote_policy_for_account(current_user&.account),
|
current_user: object.proper.quote_policy_for_account(current_user&.account),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
class ActivityPub::FetchAllRepliesService < ActivityPub::FetchRepliesService
|
class ActivityPub::FetchAllRepliesService < ActivityPub::FetchRepliesService
|
||||||
include JsonLdHelper
|
include JsonLdHelper
|
||||||
|
|
||||||
# Limit of replies to fetch per status
|
# Max number of replies to fetch - for a single post
|
||||||
MAX_REPLIES = (ENV['FETCH_REPLIES_MAX_SINGLE'] || 500).to_i
|
MAX_REPLIES = 500
|
||||||
|
|
||||||
def call(status_uri, collection_or_uri, max_pages: 1, batch_id: nil, request_id: nil)
|
def call(status_uri, collection_or_uri, max_pages: 1, batch_id: nil, request_id: nil)
|
||||||
@status_uri = status_uri
|
@status_uri = status_uri
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
|||||||
@request_id = request_id
|
@request_id = request_id
|
||||||
@quote = nil
|
@quote = nil
|
||||||
|
|
||||||
# Only native types can be updated at the moment
|
|
||||||
return @status if !expected_type? || already_updated_more_recently?
|
return @status if !expected_type? || already_updated_more_recently?
|
||||||
|
|
||||||
if @status_parser.edited_at.present? && (@status.edited_at.nil? || @status_parser.edited_at > @status.edited_at)
|
if @status_parser.edited_at.present? && (@status.edited_at.nil? || @status_parser.edited_at > @status.edited_at)
|
||||||
@@ -170,8 +169,8 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update_immediate_attributes!
|
def update_immediate_attributes!
|
||||||
@status.text = @status_parser.text || ''
|
@status.text = @status_parser.processed_text
|
||||||
@status.spoiler_text = @status_parser.spoiler_text || ''
|
@status.spoiler_text = @status_parser.processed_spoiler_text
|
||||||
@status.sensitive = @account.sensitized? || @status_parser.sensitive || false
|
@status.sensitive = @account.sensitized? || @status_parser.sensitive || false
|
||||||
@status.language = @status_parser.language
|
@status.language = @status_parser.language
|
||||||
|
|
||||||
@@ -351,7 +350,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
|||||||
end
|
end
|
||||||
|
|
||||||
def expected_type?
|
def expected_type?
|
||||||
equals_or_includes_any?(@json['type'], %w(Note Question))
|
equals_or_includes_any?(@json['type'], ActivityPub::Activity::SUPPORTED_TYPES) || equals_or_includes_any?(@json['type'], ActivityPub::Activity::CONVERTED_TYPES)
|
||||||
end
|
end
|
||||||
|
|
||||||
def record_previous_edit!
|
def record_previous_edit!
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
selected: current_user.time_zone || Time.zone.tzinfo.name,
|
selected: current_user.time_zone || Time.zone.tzinfo.name,
|
||||||
wrapper: :with_label
|
wrapper: :with_label
|
||||||
|
|
||||||
- if Mastodon::Feature.modern_emojis_enabled?
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.simple_fields_for :settings, current_user.settings do |ff|
|
= f.simple_fields_for :settings, current_user.settings do |ff|
|
||||||
= ff.input :'web.emoji_style',
|
= ff.input :'web.emoji_style',
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ class ActivityPub::FetchAllRepliesWorker
|
|||||||
|
|
||||||
sidekiq_options queue: 'pull', retry: 3
|
sidekiq_options queue: 'pull', retry: 3
|
||||||
|
|
||||||
# Global max replies to fetch per request (all replies, recursively)
|
# Max number of replies to fetch - total, recursively through a whole reply tree
|
||||||
MAX_REPLIES = (ENV['FETCH_REPLIES_MAX_GLOBAL'] || 1000).to_i
|
MAX_REPLIES = 1000
|
||||||
MAX_PAGES = (ENV['FETCH_REPLIES_MAX_PAGES'] || 500).to_i
|
# Max number of replies Collection pages to fetch - total
|
||||||
|
MAX_PAGES = 500
|
||||||
|
|
||||||
def perform(root_status_id, options = {})
|
def perform(root_status_id, options = {})
|
||||||
@batch = WorkerBatch.new(options['batch_id'])
|
@batch = WorkerBatch.new(options['batch_id'])
|
||||||
|
|||||||
@@ -867,7 +867,7 @@ be:
|
|||||||
title: Перадвызначана выключыць карыстальнікаў з індэксацыі пашуковымі рухавікамі
|
title: Перадвызначана выключыць карыстальнікаў з індэксацыі пашуковымі рухавікамі
|
||||||
discovery:
|
discovery:
|
||||||
follow_recommendations: Выконвайце рэкамендацыі
|
follow_recommendations: Выконвайце рэкамендацыі
|
||||||
preamble: Паказ цікавага кантэнту карысны ў прывабліванні новых карыстальнікаў, якія могуць нікога не ведаць у Mastodon. Кантралюйце, як розныя функцыі вынаходства працуюць на Вашым серверы.
|
preamble: Паказ цікавага кантэнту карысны ў прывабліванні новых карыстальнікаў, якія могуць нікога не ведаць у Mastodon. Кантралюйце, як розныя функцыі выяўлення працуюць на Вашым серверы.
|
||||||
privacy: Прыватнасць
|
privacy: Прыватнасць
|
||||||
profile_directory: Дырэкторыя профіляў
|
profile_directory: Дырэкторыя профіляў
|
||||||
public_timelines: Публічная паслядоўнасць публікацый
|
public_timelines: Публічная паслядоўнасць публікацый
|
||||||
@@ -883,6 +883,11 @@ be:
|
|||||||
authenticated: Толькі аўтэнтыфікаваныя карыстальнікі
|
authenticated: Толькі аўтэнтыфікаваныя карыстальнікі
|
||||||
disabled: Запатрабаваць адмысловую ролю карыстальніка
|
disabled: Запатрабаваць адмысловую ролю карыстальніка
|
||||||
public: Усе
|
public: Усе
|
||||||
|
landing_page:
|
||||||
|
values:
|
||||||
|
about: Падрабязна
|
||||||
|
local_feed: Тутэйшая стужка
|
||||||
|
trends: Трэнды
|
||||||
registrations:
|
registrations:
|
||||||
moderation_recommandation: Пераканайцеся, што ў вас ёсць адэкватная і аператыўная каманда мадэратараў, перш чым адчыняць рэгістрацыю для ўсіх жадаючых!
|
moderation_recommandation: Пераканайцеся, што ў вас ёсць адэкватная і аператыўная каманда мадэратараў, перш чым адчыняць рэгістрацыю для ўсіх жадаючых!
|
||||||
preamble: Кантралюйце, хто можа ствараць уліковы запіс на вашым серверы.
|
preamble: Кантралюйце, хто можа ствараць уліковы запіс на вашым серверы.
|
||||||
|
|||||||
@@ -835,6 +835,14 @@ bg:
|
|||||||
all: До всеки
|
all: До всеки
|
||||||
disabled: До никого
|
disabled: До никого
|
||||||
users: До влезнали локални потребители
|
users: До влезнали локални потребители
|
||||||
|
feed_access:
|
||||||
|
modes:
|
||||||
|
authenticated: Само удостоверени потребители
|
||||||
|
disabled: Изисква особена потребителска роля
|
||||||
|
public: Всеки
|
||||||
|
landing_page:
|
||||||
|
values:
|
||||||
|
trends: Пламенности
|
||||||
registrations:
|
registrations:
|
||||||
moderation_recommandation: Уверете се, че имате адекватен и реактивен модераторски екип преди да отворите регистриранията за всеки!
|
moderation_recommandation: Уверете се, че имате адекватен и реактивен модераторски екип преди да отворите регистриранията за всеки!
|
||||||
preamble: Управлява кой може да създава акаунт на сървъра ви.
|
preamble: Управлява кой може да създава акаунт на сървъра ви.
|
||||||
@@ -888,6 +896,7 @@ bg:
|
|||||||
no_status_selected: Няма промяна, тъй като няма избрани публикации
|
no_status_selected: Няма промяна, тъй като няма избрани публикации
|
||||||
open: Отваряне на публикация
|
open: Отваряне на публикация
|
||||||
original_status: Първообразна публикация
|
original_status: Първообразна публикация
|
||||||
|
quotes: Цитати
|
||||||
reblogs: Блогване пак
|
reblogs: Блогване пак
|
||||||
replied_to_html: Отговорено до %{acct_link}
|
replied_to_html: Отговорено до %{acct_link}
|
||||||
status_changed: Публикацията променена
|
status_changed: Публикацията променена
|
||||||
@@ -895,6 +904,7 @@ bg:
|
|||||||
title: Публикации на акаунт - @%{name}
|
title: Публикации на акаунт - @%{name}
|
||||||
trending: Изгряващи
|
trending: Изгряващи
|
||||||
view_publicly: Преглед като публично
|
view_publicly: Преглед като публично
|
||||||
|
view_quoted_post: Преглед на цитираната публикация
|
||||||
visibility: Видимост
|
visibility: Видимост
|
||||||
with_media: С мултимедия
|
with_media: С мултимедия
|
||||||
strikes:
|
strikes:
|
||||||
@@ -1165,7 +1175,10 @@ bg:
|
|||||||
hint_html: Ако желаете да се преместите от друг акаунт към този, тук можете да създадете псевдоним, което се изисква преди да можете да пристъпите към преместване на последователите си от стария акаунт към този. Това действие е <strong>безопасно и възстановимо</strong>. <strong>Миграцията към новия акаунт се инициира от стария акаунт</strong>.
|
hint_html: Ако желаете да се преместите от друг акаунт към този, тук можете да създадете псевдоним, което се изисква преди да можете да пристъпите към преместване на последователите си от стария акаунт към този. Това действие е <strong>безопасно и възстановимо</strong>. <strong>Миграцията към новия акаунт се инициира от стария акаунт</strong>.
|
||||||
remove: Разкачвне на псевдонима
|
remove: Разкачвне на псевдонима
|
||||||
appearance:
|
appearance:
|
||||||
|
advanced_settings: Разширени настройки
|
||||||
animations_and_accessibility: Анимация и достъпност
|
animations_and_accessibility: Анимация и достъпност
|
||||||
|
boosting_preferences: Настройки за подсилване
|
||||||
|
boosting_preferences_info_html: "<strong>Съвет:</strong> Без значение от настройките, <kbd>Shift</kbd> + <kbd>Щрак</kbd> върху иконата %{icon} Подсилване веднага ще подсили."
|
||||||
discovery: Откриване
|
discovery: Откриване
|
||||||
localization:
|
localization:
|
||||||
body: Mastodon е преведено от доброволци.
|
body: Mastodon е преведено от доброволци.
|
||||||
@@ -1567,6 +1580,13 @@ bg:
|
|||||||
expires_at: Изтича на
|
expires_at: Изтича на
|
||||||
uses: Използвания
|
uses: Използвания
|
||||||
title: Поканете хора
|
title: Поканете хора
|
||||||
|
link_preview:
|
||||||
|
author_html: От %{name}
|
||||||
|
potentially_sensitive_content:
|
||||||
|
action: Щракване за показване
|
||||||
|
confirm_visit: Наистина ли искате да отворите тази връзка?
|
||||||
|
hide_button: Скриване
|
||||||
|
label: Възможно деликатно съдържание
|
||||||
lists:
|
lists:
|
||||||
errors:
|
errors:
|
||||||
limit: Достигнахте максималния брой списъци
|
limit: Достигнахте максималния брой списъци
|
||||||
@@ -1719,6 +1739,9 @@ bg:
|
|||||||
self_vote: Не може да гласувате в свои анкети
|
self_vote: Не може да гласувате в свои анкети
|
||||||
too_few_options: трябва да има повече от един елемент
|
too_few_options: трябва да има повече от един елемент
|
||||||
too_many_options: не може да съдържа повече от %{max} елемента
|
too_many_options: не може да съдържа повече от %{max} елемента
|
||||||
|
vote: Гласувам
|
||||||
|
posting_defaults:
|
||||||
|
explanation: Тези настройки ще се употребяват като стандартни, когато създавате нови публикации, но може да ги редактирате за всяка публикация в редактора.
|
||||||
preferences:
|
preferences:
|
||||||
other: Друго
|
other: Друго
|
||||||
posting_defaults: По подразбиране за публикации
|
posting_defaults: По подразбиране за публикации
|
||||||
@@ -1874,6 +1897,9 @@ bg:
|
|||||||
other: "%{count} видеозаписа"
|
other: "%{count} видеозаписа"
|
||||||
boosted_from_html: Раздуто от %{acct_link}
|
boosted_from_html: Раздуто от %{acct_link}
|
||||||
content_warning: 'Предупреждение за съдържание: %{warning}'
|
content_warning: 'Предупреждение за съдържание: %{warning}'
|
||||||
|
content_warnings:
|
||||||
|
hide: Скриване на публ.
|
||||||
|
show: Показване на още
|
||||||
default_language: Същият като езика на интерфейса
|
default_language: Същият като езика на интерфейса
|
||||||
disallowed_hashtags:
|
disallowed_hashtags:
|
||||||
one: 'съдържа непозволен хаштаг: %{tags}'
|
one: 'съдържа непозволен хаштаг: %{tags}'
|
||||||
@@ -1888,9 +1914,22 @@ bg:
|
|||||||
limit: Вече сте закачили максималния брой публикации
|
limit: Вече сте закачили максималния брой публикации
|
||||||
ownership: Публикация на някого другиго не може да бъде закачена
|
ownership: Публикация на някого другиго не може да бъде закачена
|
||||||
reblog: Раздуване не може да бъде закачано
|
reblog: Раздуване не може да бъде закачано
|
||||||
|
quote_error:
|
||||||
|
not_available: Неналична публикация
|
||||||
|
pending_approval: Публикацията чака одобрение
|
||||||
|
revoked: Премахната публикация от автора
|
||||||
|
quote_policies:
|
||||||
|
followers: Само последователи
|
||||||
|
nobody: Само аз
|
||||||
|
public: Някой
|
||||||
|
quote_post_author: Цитирах публикация от %{acct}
|
||||||
title: "%{name}: „%{quote}“"
|
title: "%{name}: „%{quote}“"
|
||||||
visibilities:
|
visibilities:
|
||||||
|
direct: Частно споменаване
|
||||||
|
private: Само последователи
|
||||||
public: Публично
|
public: Публично
|
||||||
|
public_long: Всеки във и извън Mastodon
|
||||||
|
unlisted: Тиха публика
|
||||||
statuses_cleanup:
|
statuses_cleanup:
|
||||||
enabled: Автоматично изтриване на стари публикации
|
enabled: Автоматично изтриване на стари публикации
|
||||||
enabled_hint: От само себе си трие публикациите ви, щом достигнат указания възрастов праг, освен ако не съвпаднат с някое от изключенията долу
|
enabled_hint: От само себе си трие публикациите ви, щом достигнат указания възрастов праг, освен ако не съвпаднат с някое от изключенията долу
|
||||||
|
|||||||
@@ -872,7 +872,7 @@ cs:
|
|||||||
profile_directory: Adresář profilů
|
profile_directory: Adresář profilů
|
||||||
public_timelines: Veřejné časové osy
|
public_timelines: Veřejné časové osy
|
||||||
publish_statistics: Zveřejnit statistiku
|
publish_statistics: Zveřejnit statistiku
|
||||||
title: Objevujte
|
title: Objevování
|
||||||
trends: Trendy
|
trends: Trendy
|
||||||
domain_blocks:
|
domain_blocks:
|
||||||
all: Všem
|
all: Všem
|
||||||
@@ -883,6 +883,11 @@ cs:
|
|||||||
authenticated: Pouze autentifikovaní uživatelé
|
authenticated: Pouze autentifikovaní uživatelé
|
||||||
disabled: Vyžadovat specifickou uživatelskou roli
|
disabled: Vyžadovat specifickou uživatelskou roli
|
||||||
public: Všichni
|
public: Všichni
|
||||||
|
landing_page:
|
||||||
|
values:
|
||||||
|
about: O službě
|
||||||
|
local_feed: Místní kanál
|
||||||
|
trends: Trendy
|
||||||
registrations:
|
registrations:
|
||||||
moderation_recommandation: Před otevřením registrací všem se ujistěte, že máte vhodný a reaktivní moderační tým!
|
moderation_recommandation: Před otevřením registrací všem se ujistěte, že máte vhodný a reaktivní moderační tým!
|
||||||
preamble: Mějte pod kontrolou, kdo může vytvořit účet na vašem serveru.
|
preamble: Mějte pod kontrolou, kdo může vytvořit účet na vašem serveru.
|
||||||
|
|||||||
@@ -855,6 +855,11 @@ da:
|
|||||||
authenticated: Kun godkendte brugere
|
authenticated: Kun godkendte brugere
|
||||||
disabled: Kræv specifik brugerrolle
|
disabled: Kræv specifik brugerrolle
|
||||||
public: Alle
|
public: Alle
|
||||||
|
landing_page:
|
||||||
|
values:
|
||||||
|
about: Om
|
||||||
|
local_feed: Lokalt feed
|
||||||
|
trends: Trends
|
||||||
registrations:
|
registrations:
|
||||||
moderation_recommandation: Sørg for, at der er et tilstrækkeligt og reaktivt moderationsteam, før registrering åbnes for alle!
|
moderation_recommandation: Sørg for, at der er et tilstrækkeligt og reaktivt moderationsteam, før registrering åbnes for alle!
|
||||||
preamble: Styr, hvem der kan oprette en konto på serveren.
|
preamble: Styr, hvem der kan oprette en konto på serveren.
|
||||||
|
|||||||
@@ -852,6 +852,9 @@ de:
|
|||||||
modes:
|
modes:
|
||||||
authenticated: Nur authentifizierte Nutzer*innen
|
authenticated: Nur authentifizierte Nutzer*innen
|
||||||
public: Alle
|
public: Alle
|
||||||
|
landing_page:
|
||||||
|
values:
|
||||||
|
about: Über
|
||||||
registrations:
|
registrations:
|
||||||
moderation_recommandation: Bitte vergewissere dich, dass du ein geeignetes und reaktionsschnelles Moderationsteam hast, bevor du die Registrierungen uneingeschränkt zulässt!
|
moderation_recommandation: Bitte vergewissere dich, dass du ein geeignetes und reaktionsschnelles Moderationsteam hast, bevor du die Registrierungen uneingeschränkt zulässt!
|
||||||
preamble: Lege fest, wer auf deinem Server ein Konto erstellen darf.
|
preamble: Lege fest, wer auf deinem Server ein Konto erstellen darf.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ bg:
|
|||||||
send_paranoid_instructions: Ако вашият имейл адрес съществува в нашата база данни, ще получите имейл с указания как да потвърдите имейл адреса си след няколко минути. Проверете спам папката си, ако не сте получили такъв имейл.
|
send_paranoid_instructions: Ако вашият имейл адрес съществува в нашата база данни, ще получите имейл с указания как да потвърдите имейл адреса си след няколко минути. Проверете спам папката си, ако не сте получили такъв имейл.
|
||||||
failure:
|
failure:
|
||||||
already_authenticated: Вече сте влезли.
|
already_authenticated: Вече сте влезли.
|
||||||
|
closed_registrations: Вашият опит за регистриране е блокиран заради мрежова политика. Ако вярвате, че е грешка, то свържете се с %{email}.
|
||||||
inactive: Акаунтът ви още не е задействан.
|
inactive: Акаунтът ви още не е задействан.
|
||||||
invalid: Невалиден %{authentication_keys} или парола.
|
invalid: Невалиден %{authentication_keys} или парола.
|
||||||
last_attempt: Разполагате с още един опит преди акаунтът ви да се заключи.
|
last_attempt: Разполагате с още един опит преди акаунтът ви да се заключи.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ pt-BR:
|
|||||||
send_paranoid_instructions: Se o seu endereço de e-mail já existir em nosso banco de dados, você receberá um e-mail com instruções para confirmá-lo dentro de alguns minutos. Verifique sua caixa de spam caso ainda não o tenha recebido.
|
send_paranoid_instructions: Se o seu endereço de e-mail já existir em nosso banco de dados, você receberá um e-mail com instruções para confirmá-lo dentro de alguns minutos. Verifique sua caixa de spam caso ainda não o tenha recebido.
|
||||||
failure:
|
failure:
|
||||||
already_authenticated: Você entrou na sua conta.
|
already_authenticated: Você entrou na sua conta.
|
||||||
|
closed_registrations: Sua tentativa de registro foi bloqueada devido a uma política de rede. Se você acredita que isso é um erro, entre em contato com %{email}.
|
||||||
inactive: Sua conta não foi confirmada ainda.
|
inactive: Sua conta não foi confirmada ainda.
|
||||||
invalid: "%{authentication_keys} ou senha inválida."
|
invalid: "%{authentication_keys} ou senha inválida."
|
||||||
last_attempt: Você tem mais uma tentativa antes de sua conta ser bloqueada.
|
last_attempt: Você tem mais uma tentativa antes de sua conta ser bloqueada.
|
||||||
|
|||||||
@@ -855,6 +855,11 @@ el:
|
|||||||
authenticated: Πιστοποιημένοι χρήστες μόνο
|
authenticated: Πιστοποιημένοι χρήστες μόνο
|
||||||
disabled: Να απαιτείται συγκεκριμένος ρόλος χρήστη
|
disabled: Να απαιτείται συγκεκριμένος ρόλος χρήστη
|
||||||
public: Όλοι
|
public: Όλοι
|
||||||
|
landing_page:
|
||||||
|
values:
|
||||||
|
about: Σχετικά
|
||||||
|
local_feed: Τοπική ροή
|
||||||
|
trends: Τάσεις
|
||||||
registrations:
|
registrations:
|
||||||
moderation_recommandation: Παρακαλώ βεβαιώσου ότι έχεις μια επαρκής και ενεργή ομάδα συντονισμού πριν ανοίξεις τις εγγραφές για όλους!
|
moderation_recommandation: Παρακαλώ βεβαιώσου ότι έχεις μια επαρκής και ενεργή ομάδα συντονισμού πριν ανοίξεις τις εγγραφές για όλους!
|
||||||
preamble: Έλεγξε ποιος μπορεί να δημιουργήσει ένα λογαριασμό στον διακομιστή σας.
|
preamble: Έλεγξε ποιος μπορεί να δημιουργήσει ένα λογαριασμό στον διακομιστή σας.
|
||||||
|
|||||||
@@ -855,6 +855,11 @@ es-AR:
|
|||||||
authenticated: Solo usuarios autenticados
|
authenticated: Solo usuarios autenticados
|
||||||
disabled: Requerir un rol de específico de usuario
|
disabled: Requerir un rol de específico de usuario
|
||||||
public: Todos
|
public: Todos
|
||||||
|
landing_page:
|
||||||
|
values:
|
||||||
|
about: Acerca de
|
||||||
|
local_feed: Cronología local
|
||||||
|
trends: Tendencias
|
||||||
registrations:
|
registrations:
|
||||||
moderation_recommandation: Por favor, ¡asegurate de tener un equipo de moderación adecuado y reactivo antes de abrir los registros a todos!
|
moderation_recommandation: Por favor, ¡asegurate de tener un equipo de moderación adecuado y reactivo antes de abrir los registros a todos!
|
||||||
preamble: Controlá quién puede crear una cuenta en tu servidor.
|
preamble: Controlá quién puede crear una cuenta en tu servidor.
|
||||||
|
|||||||
@@ -855,6 +855,11 @@ es-MX:
|
|||||||
authenticated: Solo usuarios autenticados
|
authenticated: Solo usuarios autenticados
|
||||||
disabled: Requerir un rol de usuario específico
|
disabled: Requerir un rol de usuario específico
|
||||||
public: Todos
|
public: Todos
|
||||||
|
landing_page:
|
||||||
|
values:
|
||||||
|
about: Acerca de
|
||||||
|
local_feed: Cronología local
|
||||||
|
trends: Tendencias
|
||||||
registrations:
|
registrations:
|
||||||
moderation_recommandation: "¡Por favor, asegúrate de contar con un equipo de moderación adecuado y activo antes de abrir el registro al público!"
|
moderation_recommandation: "¡Por favor, asegúrate de contar con un equipo de moderación adecuado y activo antes de abrir el registro al público!"
|
||||||
preamble: Controla quién puede crear una cuenta en tu servidor.
|
preamble: Controla quién puede crear una cuenta en tu servidor.
|
||||||
|
|||||||
@@ -855,6 +855,11 @@ es:
|
|||||||
authenticated: Solo usuarios autenticados
|
authenticated: Solo usuarios autenticados
|
||||||
disabled: Requerir un rol de usuario específico
|
disabled: Requerir un rol de usuario específico
|
||||||
public: Todos
|
public: Todos
|
||||||
|
landing_page:
|
||||||
|
values:
|
||||||
|
about: Acerca de
|
||||||
|
local_feed: Cronología local
|
||||||
|
trends: Tendencias
|
||||||
registrations:
|
registrations:
|
||||||
moderation_recommandation: Por favor, ¡asegúrate de tener un equipo de moderación adecuado y reactivo antes de abrir los registros a todo el mundo!
|
moderation_recommandation: Por favor, ¡asegúrate de tener un equipo de moderación adecuado y reactivo antes de abrir los registros a todo el mundo!
|
||||||
preamble: Controla quién puede crear una cuenta en tu servidor.
|
preamble: Controla quién puede crear una cuenta en tu servidor.
|
||||||
|
|||||||
@@ -837,6 +837,7 @@ et:
|
|||||||
title: Otsimootorite indeksitesse kasutajaid vaikimisi ei lisata
|
title: Otsimootorite indeksitesse kasutajaid vaikimisi ei lisata
|
||||||
discovery:
|
discovery:
|
||||||
follow_recommendations: Jälgi soovitusi
|
follow_recommendations: Jälgi soovitusi
|
||||||
|
preamble: Huvitava sisu esiletoomine on oluline uute kasutajate kaasamisel, kes ei pruugi Mastodonist kedagi tunda. Kontrolli, kuidas erinevad avastamisfunktsioonid serveris töötavad.
|
||||||
privacy: Privaatsus
|
privacy: Privaatsus
|
||||||
profile_directory: Kasutajate kataloog
|
profile_directory: Kasutajate kataloog
|
||||||
public_timelines: Avalikud ajajooned
|
public_timelines: Avalikud ajajooned
|
||||||
@@ -850,7 +851,11 @@ et:
|
|||||||
feed_access:
|
feed_access:
|
||||||
modes:
|
modes:
|
||||||
authenticated: Vaid autenditud kasutajad
|
authenticated: Vaid autenditud kasutajad
|
||||||
|
disabled: Eelda konkreetse kasutajarolli olemasolu
|
||||||
public: Kõik
|
public: Kõik
|
||||||
|
landing_page:
|
||||||
|
values:
|
||||||
|
trends: Trendid
|
||||||
registrations:
|
registrations:
|
||||||
moderation_recommandation: Enne kõigi jaoks registreerimise avamist veendu, et oleks olemas adekvaatne ja reageerimisvalmis modereerijaskond!
|
moderation_recommandation: Enne kõigi jaoks registreerimise avamist veendu, et oleks olemas adekvaatne ja reageerimisvalmis modereerijaskond!
|
||||||
preamble: Kes saab serveril konto luua.
|
preamble: Kes saab serveril konto luua.
|
||||||
|
|||||||
@@ -855,6 +855,11 @@ fo:
|
|||||||
authenticated: Einans váttaðir brúkarar
|
authenticated: Einans váttaðir brúkarar
|
||||||
disabled: Krev serstakan brúkaraleiklut
|
disabled: Krev serstakan brúkaraleiklut
|
||||||
public: Øll
|
public: Øll
|
||||||
|
landing_page:
|
||||||
|
values:
|
||||||
|
about: Um
|
||||||
|
local_feed: Lokal rás
|
||||||
|
trends: Rák
|
||||||
registrations:
|
registrations:
|
||||||
moderation_recommandation: Vinarliga tryggja tær, at tú hevur eitt nøktandi og klárt umsjónartoymi, áðreen tú letur upp fyri skrásetingum frá øllum!
|
moderation_recommandation: Vinarliga tryggja tær, at tú hevur eitt nøktandi og klárt umsjónartoymi, áðreen tú letur upp fyri skrásetingum frá øllum!
|
||||||
preamble: Stýr, hvør kann stovna eina kontu á tínum ambætara.
|
preamble: Stýr, hvør kann stovna eina kontu á tínum ambætara.
|
||||||
|
|||||||
@@ -897,6 +897,11 @@ ga:
|
|||||||
authenticated: Úsáideoirí fíordheimhnithe amháin
|
authenticated: Úsáideoirí fíordheimhnithe amháin
|
||||||
disabled: Éiligh ról úsáideora sonrach
|
disabled: Éiligh ról úsáideora sonrach
|
||||||
public: Gach duine
|
public: Gach duine
|
||||||
|
landing_page:
|
||||||
|
values:
|
||||||
|
about: Maidir
|
||||||
|
local_feed: Fotha áitiúil
|
||||||
|
trends: Treochtaí
|
||||||
registrations:
|
registrations:
|
||||||
moderation_recommandation: Cinntigh le do thoil go bhfuil foireann mhodhnóireachta imoibríoch leordhóthanach agat sula n-osclaíonn tú clárúcháin do gach duine!
|
moderation_recommandation: Cinntigh le do thoil go bhfuil foireann mhodhnóireachta imoibríoch leordhóthanach agat sula n-osclaíonn tú clárúcháin do gach duine!
|
||||||
preamble: Rialú cé atá in ann cuntas a chruthú ar do fhreastalaí.
|
preamble: Rialú cé atá in ann cuntas a chruthú ar do fhreastalaí.
|
||||||
|
|||||||
@@ -883,6 +883,11 @@ he:
|
|||||||
authenticated: משתמשים מאומתים בלבד
|
authenticated: משתמשים מאומתים בלבד
|
||||||
disabled: נדרש תפקיד משתמש מסוים
|
disabled: נדרש תפקיד משתמש מסוים
|
||||||
public: כולם
|
public: כולם
|
||||||
|
landing_page:
|
||||||
|
values:
|
||||||
|
about: אודות
|
||||||
|
local_feed: פיד מקומי
|
||||||
|
trends: נושאים חמים
|
||||||
registrations:
|
registrations:
|
||||||
moderation_recommandation: יש לוודא שלאתר יש צוות מנחות ומנחי שיחה מספק ושירותי בטרם תבחרו לפתוח הרשמה לכולם!
|
moderation_recommandation: יש לוודא שלאתר יש צוות מנחות ומנחי שיחה מספק ושירותי בטרם תבחרו לפתוח הרשמה לכולם!
|
||||||
preamble: שליטה בהרשאות יצירת חשבון בשרת שלך.
|
preamble: שליטה בהרשאות יצירת חשבון בשרת שלך.
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ ia:
|
|||||||
edit:
|
edit:
|
||||||
title: Modificar annuncio
|
title: Modificar annuncio
|
||||||
empty: Necun annuncios trovate.
|
empty: Necun annuncios trovate.
|
||||||
live: In directo
|
live: In vivo
|
||||||
new:
|
new:
|
||||||
create: Crear annuncio
|
create: Crear annuncio
|
||||||
title: Nove annuncio
|
title: Nove annuncio
|
||||||
@@ -796,6 +796,8 @@ ia:
|
|||||||
view_dashboard_description: Permitte que usatores accede al tabuliero de instrumentos e a varie statisticas
|
view_dashboard_description: Permitte que usatores accede al tabuliero de instrumentos e a varie statisticas
|
||||||
view_devops: DevOps
|
view_devops: DevOps
|
||||||
view_devops_description: Permitte que usatores accede al tabulieros de instrumentos de Sidekiq e pgHero
|
view_devops_description: Permitte que usatores accede al tabulieros de instrumentos de Sidekiq e pgHero
|
||||||
|
view_feeds: Vider canales thematic e in vivo
|
||||||
|
view_feeds_description: Permitte que usatores acceder al canales thematic e in vivo independentemente del configuration del servitor
|
||||||
title: Rolos
|
title: Rolos
|
||||||
rules:
|
rules:
|
||||||
add_new: Adder regula
|
add_new: Adder regula
|
||||||
@@ -837,6 +839,7 @@ ia:
|
|||||||
title: Excluder le usatores del indexation del motores de recerca per predefinition
|
title: Excluder le usatores del indexation del motores de recerca per predefinition
|
||||||
discovery:
|
discovery:
|
||||||
follow_recommendations: Recommendationes de contos a sequer
|
follow_recommendations: Recommendationes de contos a sequer
|
||||||
|
preamble: Presentar contento interessante es essential pro attraher e retener nove usatores qui pote non cognoscer alcun persona sur Mastodon. Controla como varie optiones de discoperta functiona sur tu servitor.
|
||||||
privacy: Confidentialitate
|
privacy: Confidentialitate
|
||||||
profile_directory: Directorio de profilos
|
profile_directory: Directorio de profilos
|
||||||
public_timelines: Chronologias public
|
public_timelines: Chronologias public
|
||||||
@@ -850,7 +853,13 @@ ia:
|
|||||||
feed_access:
|
feed_access:
|
||||||
modes:
|
modes:
|
||||||
authenticated: Solmente usatores authenticate
|
authenticated: Solmente usatores authenticate
|
||||||
|
disabled: Requirer un rolo de usator specific
|
||||||
public: Omnes
|
public: Omnes
|
||||||
|
landing_page:
|
||||||
|
values:
|
||||||
|
about: A proposito
|
||||||
|
local_feed: Canal local
|
||||||
|
trends: Tendentias
|
||||||
registrations:
|
registrations:
|
||||||
moderation_recommandation: Per favor assecura te de haber un equipa de moderation adequate e reactive ante de aperir le inscription a omnes!
|
moderation_recommandation: Per favor assecura te de haber un equipa de moderation adequate e reactive ante de aperir le inscription a omnes!
|
||||||
preamble: Controla qui pote crear un conto sur tu servitor.
|
preamble: Controla qui pote crear un conto sur tu servitor.
|
||||||
|
|||||||
@@ -857,6 +857,11 @@ is:
|
|||||||
authenticated: Einungis auðkenndir notendur
|
authenticated: Einungis auðkenndir notendur
|
||||||
disabled: Krefjast sérstaks hlutverks notanda
|
disabled: Krefjast sérstaks hlutverks notanda
|
||||||
public: Allir
|
public: Allir
|
||||||
|
landing_page:
|
||||||
|
values:
|
||||||
|
about: Um hugbúnaðinn
|
||||||
|
local_feed: Staðbundið streymi
|
||||||
|
trends: Vinsælt
|
||||||
registrations:
|
registrations:
|
||||||
moderation_recommandation: Tryggðu að þú hafir hæft og aðgengilegt umsjónarteymi til taks áður en þú opnar á skráningar fyrir alla!
|
moderation_recommandation: Tryggðu að þú hafir hæft og aðgengilegt umsjónarteymi til taks áður en þú opnar á skráningar fyrir alla!
|
||||||
preamble: Stýrðu því hverjir geta útbúið notandaaðgang á netþjóninum þínum.
|
preamble: Stýrðu því hverjir geta útbúið notandaaðgang á netþjóninum þínum.
|
||||||
|
|||||||
@@ -693,6 +693,7 @@ lad:
|
|||||||
delete_data_html: Efasa el profil i kontenido de <strong>@%{acct}</strong> en 30 dias si no sea desuspendido en akel tiempo
|
delete_data_html: Efasa el profil i kontenido de <strong>@%{acct}</strong> en 30 dias si no sea desuspendido en akel tiempo
|
||||||
preview_preamble_html: "<strong>@%{acct}</strong> resivira una avertensya komo esta:"
|
preview_preamble_html: "<strong>@%{acct}</strong> resivira una avertensya komo esta:"
|
||||||
record_strike_html: Enrejistra un amonestamiento kontra <strong>@%{acct}</strong> para ke te ayude eskalar las violasyones de reglas de este kuento en el avenir
|
record_strike_html: Enrejistra un amonestamiento kontra <strong>@%{acct}</strong> para ke te ayude eskalar las violasyones de reglas de este kuento en el avenir
|
||||||
|
send_email_html: Embia un mesaj de avertensia a la posta elektronika de <strong>@%{acct}</strong>
|
||||||
warning_placeholder: Adisionalas, opsionalas razones la aksyon de moderasyon.
|
warning_placeholder: Adisionalas, opsionalas razones la aksyon de moderasyon.
|
||||||
target_origin: Orijin del kuento raportado
|
target_origin: Orijin del kuento raportado
|
||||||
title: Raportos
|
title: Raportos
|
||||||
@@ -796,6 +797,7 @@ lad:
|
|||||||
title: Ekskluye utilizadores de la indeksasyon de los bushkadores komo preferensya predeterminada
|
title: Ekskluye utilizadores de la indeksasyon de los bushkadores komo preferensya predeterminada
|
||||||
discovery:
|
discovery:
|
||||||
follow_recommendations: Rekomendasyones de kuentos
|
follow_recommendations: Rekomendasyones de kuentos
|
||||||
|
preamble: Ekspone kontenido enteresante a la superfisie es fundamental para inkorporar muevos utilizadores ke pueden no koneser a dinguno en Mastodon. Kontrola komo fonksionan varias opsiones de diskuvrimiento en tu sirvidor.
|
||||||
privacy: Privasita
|
privacy: Privasita
|
||||||
profile_directory: Katalogo de profiles
|
profile_directory: Katalogo de profiles
|
||||||
public_timelines: Linyas de tiempo publikas
|
public_timelines: Linyas de tiempo publikas
|
||||||
@@ -809,6 +811,10 @@ lad:
|
|||||||
feed_access:
|
feed_access:
|
||||||
modes:
|
modes:
|
||||||
public: Todos
|
public: Todos
|
||||||
|
landing_page:
|
||||||
|
values:
|
||||||
|
about: Sovre esto
|
||||||
|
trends: Trendes
|
||||||
registrations:
|
registrations:
|
||||||
moderation_recommandation: Por favor, asigurate ke tyenes una taifa de moderasyon adekuada i reaktiva antes de avrir los enrejistramyentos a todos!
|
moderation_recommandation: Por favor, asigurate ke tyenes una taifa de moderasyon adekuada i reaktiva antes de avrir los enrejistramyentos a todos!
|
||||||
preamble: Kontrola ken puede kriyar un kuento en tu sirvidor.
|
preamble: Kontrola ken puede kriyar un kuento en tu sirvidor.
|
||||||
@@ -846,6 +852,7 @@ lad:
|
|||||||
back_to_account: Retorna al kuento
|
back_to_account: Retorna al kuento
|
||||||
back_to_report: Retorna a la pajina del raporto
|
back_to_report: Retorna a la pajina del raporto
|
||||||
batch:
|
batch:
|
||||||
|
add_to_report: 'Adjusta al raporto #%{id}'
|
||||||
remove_from_report: Kita del raporto
|
remove_from_report: Kita del raporto
|
||||||
report: Raporto
|
report: Raporto
|
||||||
contents: Kontenidos
|
contents: Kontenidos
|
||||||
@@ -860,10 +867,12 @@ lad:
|
|||||||
no_status_selected: No se troko dinguna publikasyon al no eskojer dinguna
|
no_status_selected: No se troko dinguna publikasyon al no eskojer dinguna
|
||||||
open: Avre publikasyon
|
open: Avre publikasyon
|
||||||
original_status: Publikasyon orijinala
|
original_status: Publikasyon orijinala
|
||||||
|
quotes: Sitas
|
||||||
reblogs: Repartajasyones
|
reblogs: Repartajasyones
|
||||||
status_changed: Publikasyon trokada
|
status_changed: Publikasyon trokada
|
||||||
trending: Trendes
|
trending: Trendes
|
||||||
view_publicly: Ve puvlikamente
|
view_publicly: Ve puvlikamente
|
||||||
|
view_quoted_post: Ve puvlikasyon sitada
|
||||||
visibility: Vizivilita
|
visibility: Vizivilita
|
||||||
with_media: Kon multimedia
|
with_media: Kon multimedia
|
||||||
strikes:
|
strikes:
|
||||||
@@ -1094,7 +1103,9 @@ lad:
|
|||||||
hint_html: Si keres migrar de otro kuento a este, aki puedes kriyar un alias, kale proseder antes de ampesar a mover suivantes del kuento anterior a este. Esta aksion por si mezma es <strong>inofensiva i reversivle</strong>. <strong>La migrasyon del kuento se inisya dizde el kuento viejo</strong>.
|
hint_html: Si keres migrar de otro kuento a este, aki puedes kriyar un alias, kale proseder antes de ampesar a mover suivantes del kuento anterior a este. Esta aksion por si mezma es <strong>inofensiva i reversivle</strong>. <strong>La migrasyon del kuento se inisya dizde el kuento viejo</strong>.
|
||||||
remove: Dezata alias
|
remove: Dezata alias
|
||||||
appearance:
|
appearance:
|
||||||
|
advanced_settings: Konfigurasyon avansada
|
||||||
animations_and_accessibility: Animasyones i aksesivilita
|
animations_and_accessibility: Animasyones i aksesivilita
|
||||||
|
boosting_preferences: Preferensias de repartajar
|
||||||
discovery: Diskuvrimiento
|
discovery: Diskuvrimiento
|
||||||
localization:
|
localization:
|
||||||
body: Mastodon es trezladado por volontarios.
|
body: Mastodon es trezladado por volontarios.
|
||||||
@@ -1199,6 +1210,7 @@ lad:
|
|||||||
example_title: Teksto de enshemplo
|
example_title: Teksto de enshemplo
|
||||||
more_from_html: Mas de %{name}
|
more_from_html: Mas de %{name}
|
||||||
s_blog: Blog de %{name}
|
s_blog: Blog de %{name}
|
||||||
|
title: Atribusyon del otor
|
||||||
challenge:
|
challenge:
|
||||||
confirm: Kontinua
|
confirm: Kontinua
|
||||||
hint_html: "<strong>Konsejo:</strong> No retornaremos a demandarte por el kod durante la sigiente ora."
|
hint_html: "<strong>Konsejo:</strong> No retornaremos a demandarte por el kod durante la sigiente ora."
|
||||||
@@ -1734,6 +1746,7 @@ lad:
|
|||||||
preferences: Preferensyas
|
preferences: Preferensyas
|
||||||
profile: Profil publiko
|
profile: Profil publiko
|
||||||
relationships: Segidos i suivantes
|
relationships: Segidos i suivantes
|
||||||
|
severed_relationships: Relasyones kortadas
|
||||||
statuses_cleanup: Efasasyon otomatika de publikasyones
|
statuses_cleanup: Efasasyon otomatika de publikasyones
|
||||||
strikes: Amonestamientos de moderasyon
|
strikes: Amonestamientos de moderasyon
|
||||||
two_factor_authentication: Autentifikasyon en dos pasos
|
two_factor_authentication: Autentifikasyon en dos pasos
|
||||||
@@ -1741,6 +1754,8 @@ lad:
|
|||||||
severed_relationships:
|
severed_relationships:
|
||||||
download: Abasha (%{count})
|
download: Abasha (%{count})
|
||||||
event_type:
|
event_type:
|
||||||
|
account_suspension: Suspensyon de kuento (%{target_name})
|
||||||
|
domain_block: Suspensyon de sirvidor (%{target_name})
|
||||||
user_domain_block: Blokates a %{target_name}
|
user_domain_block: Blokates a %{target_name}
|
||||||
lost_followers: Suivantes pedridos
|
lost_followers: Suivantes pedridos
|
||||||
lost_follows: Segimyentos pedridos
|
lost_follows: Segimyentos pedridos
|
||||||
@@ -1776,10 +1791,15 @@ lad:
|
|||||||
limit: Ya tienes fiksado el numero maksimo de publikasyones
|
limit: Ya tienes fiksado el numero maksimo de publikasyones
|
||||||
ownership: La publikasyon de otra persona no puede fiksarse
|
ownership: La publikasyon de otra persona no puede fiksarse
|
||||||
reblog: No se puede fixar una repartajasyon
|
reblog: No se puede fixar una repartajasyon
|
||||||
|
quote_error:
|
||||||
|
not_available: Puvlikasyon no desponivle
|
||||||
|
pending_approval: Puvlikasyon esta asperando
|
||||||
|
revoked: Puvlikasyon kitada por el otor
|
||||||
quote_policies:
|
quote_policies:
|
||||||
followers: Solo suivantes
|
followers: Solo suivantes
|
||||||
nobody: Solo yo
|
nobody: Solo yo
|
||||||
public: Todos
|
public: Todos
|
||||||
|
quote_post_author: Sito una puvlikasyon de %{acct}
|
||||||
title: '%{name}: "%{quote}"'
|
title: '%{name}: "%{quote}"'
|
||||||
visibilities:
|
visibilities:
|
||||||
direct: Enmentadura privada
|
direct: Enmentadura privada
|
||||||
@@ -1893,6 +1913,8 @@ lad:
|
|||||||
subject: Tu kuento fue aksedido dizde un muevo adreso IP
|
subject: Tu kuento fue aksedido dizde un muevo adreso IP
|
||||||
title: Una mueva koneksyon kon tu kuento
|
title: Una mueva koneksyon kon tu kuento
|
||||||
terms_of_service_changed:
|
terms_of_service_changed:
|
||||||
|
sign_off: La taifa de %{domain}
|
||||||
|
subject: Aktualizasyones de muestros terminos de sirvisyo
|
||||||
title: Aktualizasyon emportante
|
title: Aktualizasyon emportante
|
||||||
warning:
|
warning:
|
||||||
appeal: Embia una apelasyon
|
appeal: Embia una apelasyon
|
||||||
|
|||||||
@@ -855,6 +855,11 @@ nl:
|
|||||||
authenticated: Alleen ingelogde gebruikers
|
authenticated: Alleen ingelogde gebruikers
|
||||||
disabled: Specifieke gebruikersrol vereisen
|
disabled: Specifieke gebruikersrol vereisen
|
||||||
public: Iedereen
|
public: Iedereen
|
||||||
|
landing_page:
|
||||||
|
values:
|
||||||
|
about: Over
|
||||||
|
local_feed: Lokale tijdlijn
|
||||||
|
trends: Trends
|
||||||
registrations:
|
registrations:
|
||||||
moderation_recommandation: Zorg ervoor dat je een adequaat en responsief moderatieteam hebt voordat je registraties voor iedereen openstelt!
|
moderation_recommandation: Zorg ervoor dat je een adequaat en responsief moderatieteam hebt voordat je registraties voor iedereen openstelt!
|
||||||
preamble: Toezicht houden op wie een account op deze server kan registreren.
|
preamble: Toezicht houden op wie een account op deze server kan registreren.
|
||||||
|
|||||||
@@ -847,6 +847,16 @@ pt-BR:
|
|||||||
all: Para todos
|
all: Para todos
|
||||||
disabled: Para ninguém
|
disabled: Para ninguém
|
||||||
users: Para usuários locais logados
|
users: Para usuários locais logados
|
||||||
|
feed_access:
|
||||||
|
modes:
|
||||||
|
authenticated: Apenas usuários autenticados
|
||||||
|
disabled: Exige função específica de usuário
|
||||||
|
public: Todos
|
||||||
|
landing_page:
|
||||||
|
values:
|
||||||
|
about: Sobre
|
||||||
|
local_feed: Feed local
|
||||||
|
trends: Em alta
|
||||||
registrations:
|
registrations:
|
||||||
moderation_recommandation: Por favor, certifique-se de ter uma equipe de moderação adequada e reativa antes de abrir as inscrições para todos!
|
moderation_recommandation: Por favor, certifique-se de ter uma equipe de moderação adequada e reativa antes de abrir as inscrições para todos!
|
||||||
preamble: Controle quem pode criar uma conta no seu servidor.
|
preamble: Controle quem pode criar uma conta no seu servidor.
|
||||||
@@ -900,6 +910,7 @@ pt-BR:
|
|||||||
no_status_selected: Nenhuma publicação foi modificada porque nenhuma estava selecionada
|
no_status_selected: Nenhuma publicação foi modificada porque nenhuma estava selecionada
|
||||||
open: Publicação aberta
|
open: Publicação aberta
|
||||||
original_status: Publicação original
|
original_status: Publicação original
|
||||||
|
quotes: Citações
|
||||||
reblogs: Reblogs
|
reblogs: Reblogs
|
||||||
replied_to_html: Respondeu à %{acct_link}
|
replied_to_html: Respondeu à %{acct_link}
|
||||||
status_changed: Publicação alterada
|
status_changed: Publicação alterada
|
||||||
@@ -907,6 +918,7 @@ pt-BR:
|
|||||||
title: Publicações da conta - @%{name}
|
title: Publicações da conta - @%{name}
|
||||||
trending: Em alta
|
trending: Em alta
|
||||||
view_publicly: Ver publicamente
|
view_publicly: Ver publicamente
|
||||||
|
view_quoted_post: Visualizar citação publicada
|
||||||
visibility: Visibilidade
|
visibility: Visibilidade
|
||||||
with_media: Com mídia
|
with_media: Com mídia
|
||||||
strikes:
|
strikes:
|
||||||
@@ -1181,7 +1193,9 @@ pt-BR:
|
|||||||
hint_html: Se você quiser migrar de uma outra conta para esta, você pode criar um atalho aqui, o que é necessário antes que você possa migrar os seguidores da conta antiga para esta. Esta ação por si só é <strong>inofensiva e reversível</strong>. <strong>A migração da conta é iniciada pela conta antiga</strong>.
|
hint_html: Se você quiser migrar de uma outra conta para esta, você pode criar um atalho aqui, o que é necessário antes que você possa migrar os seguidores da conta antiga para esta. Esta ação por si só é <strong>inofensiva e reversível</strong>. <strong>A migração da conta é iniciada pela conta antiga</strong>.
|
||||||
remove: Desvincular alias
|
remove: Desvincular alias
|
||||||
appearance:
|
appearance:
|
||||||
|
advanced_settings: Configurações avançadas
|
||||||
animations_and_accessibility: Animações e acessibilidade
|
animations_and_accessibility: Animações e acessibilidade
|
||||||
|
boosting_preferences: Adicionar preferências
|
||||||
discovery: Descobrir
|
discovery: Descobrir
|
||||||
localization:
|
localization:
|
||||||
body: Mastodon é traduzido por voluntários.
|
body: Mastodon é traduzido por voluntários.
|
||||||
@@ -1583,6 +1597,13 @@ pt-BR:
|
|||||||
expires_at: Expira em
|
expires_at: Expira em
|
||||||
uses: Usos
|
uses: Usos
|
||||||
title: Convidar pessoas
|
title: Convidar pessoas
|
||||||
|
link_preview:
|
||||||
|
author_html: Por %{name}
|
||||||
|
potentially_sensitive_content:
|
||||||
|
action: Clique para mostrar
|
||||||
|
confirm_visit: Tem certeza que deseja abrir esse link?
|
||||||
|
hide_button: Ocultar
|
||||||
|
label: Conteúdo potencialmente sensível
|
||||||
lists:
|
lists:
|
||||||
errors:
|
errors:
|
||||||
limit: Você atingiu o número máximo de listas
|
limit: Você atingiu o número máximo de listas
|
||||||
@@ -1893,6 +1914,9 @@ pt-BR:
|
|||||||
other: "%{count} vídeos"
|
other: "%{count} vídeos"
|
||||||
boosted_from_html: Impulso de %{acct_link}
|
boosted_from_html: Impulso de %{acct_link}
|
||||||
content_warning: 'Aviso de conteúdo: %{warning}'
|
content_warning: 'Aviso de conteúdo: %{warning}'
|
||||||
|
content_warnings:
|
||||||
|
hide: Ocultar publicação
|
||||||
|
show: Exibir mais
|
||||||
default_language: Igual ao idioma da interface
|
default_language: Igual ao idioma da interface
|
||||||
disallowed_hashtags:
|
disallowed_hashtags:
|
||||||
one: 'continha hashtag não permitida: %{tags}'
|
one: 'continha hashtag não permitida: %{tags}'
|
||||||
@@ -1907,15 +1931,22 @@ pt-BR:
|
|||||||
limit: Você alcançou o número limite de publicações fixadas
|
limit: Você alcançou o número limite de publicações fixadas
|
||||||
ownership: As publicações dos outros não podem ser fixadas
|
ownership: As publicações dos outros não podem ser fixadas
|
||||||
reblog: Um impulso não pode ser fixado
|
reblog: Um impulso não pode ser fixado
|
||||||
|
quote_error:
|
||||||
|
not_available: Publicação indisponível
|
||||||
|
pending_approval: Publicação pendente
|
||||||
|
revoked: Publicação removida pelo autor
|
||||||
quote_policies:
|
quote_policies:
|
||||||
followers: Apenas seguidores
|
followers: Apenas seguidores
|
||||||
nobody: Apenas eu
|
nobody: Apenas eu
|
||||||
public: Qualquer um
|
public: Qualquer um
|
||||||
|
quote_post_author: Publicação citada por %{acct}
|
||||||
title: '%{name}: "%{quote}"'
|
title: '%{name}: "%{quote}"'
|
||||||
visibilities:
|
visibilities:
|
||||||
direct: Citação privada
|
direct: Citação privada
|
||||||
|
private: Apenas seguidores
|
||||||
public: Público
|
public: Público
|
||||||
public_long: Qualquer um dentro ou fora do Mástodon
|
public_long: Qualquer um dentro ou fora do Mástodon
|
||||||
|
unlisted: Publicação silenciada
|
||||||
unlisted_long: Oculto aos resultados de pesquisa em Mástodon
|
unlisted_long: Oculto aos resultados de pesquisa em Mástodon
|
||||||
statuses_cleanup:
|
statuses_cleanup:
|
||||||
enabled: Excluir publicações antigas automaticamente
|
enabled: Excluir publicações antigas automaticamente
|
||||||
|
|||||||
@@ -796,6 +796,8 @@ pt-PT:
|
|||||||
view_dashboard_description: Permite aos utilizadores acederem ao painel de controlo e a várias estatísticas
|
view_dashboard_description: Permite aos utilizadores acederem ao painel de controlo e a várias estatísticas
|
||||||
view_devops: DevOps
|
view_devops: DevOps
|
||||||
view_devops_description: Permite aos utilizadores aceder aos painéis de controlo do Sidekiq e pgHero
|
view_devops_description: Permite aos utilizadores aceder aos painéis de controlo do Sidekiq e pgHero
|
||||||
|
view_feeds: Ver cronologia em tempo real e de etiquetas
|
||||||
|
view_feeds_description: Permitir aos utilizadores aceder às cronologias em tempo real e de etiquetas independentemente das definições do servidor
|
||||||
title: Funções
|
title: Funções
|
||||||
rules:
|
rules:
|
||||||
add_new: Adicionar regra
|
add_new: Adicionar regra
|
||||||
@@ -851,7 +853,13 @@ pt-PT:
|
|||||||
feed_access:
|
feed_access:
|
||||||
modes:
|
modes:
|
||||||
authenticated: Apesar utilizadores autenticados
|
authenticated: Apesar utilizadores autenticados
|
||||||
|
disabled: Requerer função de utilizador especifica
|
||||||
public: Todos
|
public: Todos
|
||||||
|
landing_page:
|
||||||
|
values:
|
||||||
|
about: Sobre
|
||||||
|
local_feed: Cronologia local
|
||||||
|
trends: Tendências
|
||||||
registrations:
|
registrations:
|
||||||
moderation_recommandation: Certifique-se de que dispõe de uma equipa de moderação adequada e reativa antes de abrir as inscrições a todos!
|
moderation_recommandation: Certifique-se de que dispõe de uma equipa de moderação adequada e reativa antes de abrir as inscrições a todos!
|
||||||
preamble: Controle quem pode criar uma conta no seu servidor.
|
preamble: Controle quem pode criar uma conta no seu servidor.
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ be:
|
|||||||
content_cache_retention_period: Усе допісы з іншых сервераў (разам з пашырэннямі і адказамі) будуць выдалены праз паказаную колькасць дзён, незалежна ад таго, як лакальны карыстальнік узаемадзейнічаў з гэтымі допісамі. Гэта датычыцца і тых допісаў, якія лакальны карыстальнік пазначыў у закладкі або ўпадабанае. Прыватныя згадванні паміж карыстальнікамі з розных экзэмпляраў сервераў таксама будуць страчаны і іх нельга будзе аднавіць. Выкарыстанне гэтай налады прызначана для экзэмпляраў сервераў спецыяльнага прызначэння і парушае многія чаканні карыстальнікаў пры выкарыстанні ў агульных мэтах.
|
content_cache_retention_period: Усе допісы з іншых сервераў (разам з пашырэннямі і адказамі) будуць выдалены праз паказаную колькасць дзён, незалежна ад таго, як лакальны карыстальнік узаемадзейнічаў з гэтымі допісамі. Гэта датычыцца і тых допісаў, якія лакальны карыстальнік пазначыў у закладкі або ўпадабанае. Прыватныя згадванні паміж карыстальнікамі з розных экзэмпляраў сервераў таксама будуць страчаны і іх нельга будзе аднавіць. Выкарыстанне гэтай налады прызначана для экзэмпляраў сервераў спецыяльнага прызначэння і парушае многія чаканні карыстальнікаў пры выкарыстанні ў агульных мэтах.
|
||||||
custom_css: Вы можаце прымяняць карыстальніцкія стылі ў вэб-версіі Mastodon.
|
custom_css: Вы можаце прымяняць карыстальніцкія стылі ў вэб-версіі Mastodon.
|
||||||
favicon: WEBP, PNG, GIF ці JPG. Замяняе прадвызначаны favicon Mastodon на ўласны значок.
|
favicon: WEBP, PNG, GIF ці JPG. Замяняе прадвызначаны favicon Mastodon на ўласны значок.
|
||||||
|
landing_page: Выбірае, якую старонку бачаць новыя наведвальнікі, калі прыходзяць на Ваш сервер. Калі выбераце "Трэнды", тады неабходна іх уключыць у наладах Выяўленне. Калі выбераце "Тутэйшая стужка", тады ў наладах Выяўленне ў налады "Доступ да жывых стужак з лакальнымі допісамі" мусіць стаяць варыянт "Усе".
|
||||||
mascot: Замяняе ілюстрацыю ў пашыраным вэб-інтэрфейсе.
|
mascot: Замяняе ілюстрацыю ў пашыраным вэб-інтэрфейсе.
|
||||||
media_cache_retention_period: Медыяфайлы з допісаў, зробленых карыстальнікамі з іншых сервераў, кэшыруюцца на вашым серверы. Пры станоўчым значэнні медыяфайлы будуць выдалены праз пазначаную колькасць дзён. Калі медыяданыя будуць запытаныя пасля выдалення, яны будуць спампаваныя зноў, калі зыходнае змесціва усё яшчэ даступнае. У сувязі з абмежаваннямі на частату абнаўлення картак перадпрагляду іншых сайтаў, рэкамендуецца ўсталяваць значэнне не менш за 14 дзён, інакш гэтыя карткі не будуць абнаўляцца па запыце раней за гэты тэрмін.
|
media_cache_retention_period: Медыяфайлы з допісаў, зробленых карыстальнікамі з іншых сервераў, кэшыруюцца на вашым серверы. Пры станоўчым значэнні медыяфайлы будуць выдалены праз пазначаную колькасць дзён. Калі медыяданыя будуць запытаныя пасля выдалення, яны будуць спампаваныя зноў, калі зыходнае змесціва усё яшчэ даступнае. У сувязі з абмежаваннямі на частату абнаўлення картак перадпрагляду іншых сайтаў, рэкамендуецца ўсталяваць значэнне не менш за 14 дзён, інакш гэтыя карткі не будуць абнаўляцца па запыце раней за гэты тэрмін.
|
||||||
min_age: Карыстальнікі будуць атрымліваць запыт на пацвярджэнне даты нараджэння падчас рэгістрацыі
|
min_age: Карыстальнікі будуць атрымліваць запыт на пацвярджэнне даты нараджэння падчас рэгістрацыі
|
||||||
@@ -288,6 +289,7 @@ be:
|
|||||||
content_cache_retention_period: Перыяд захоўвання змесціва з іншых сервераў
|
content_cache_retention_period: Перыяд захоўвання змесціва з іншых сервераў
|
||||||
custom_css: CSS карыстальніка
|
custom_css: CSS карыстальніка
|
||||||
favicon: Значок сайта
|
favicon: Значок сайта
|
||||||
|
landing_page: Старонка прыбыцця для новых наведвальнікаў
|
||||||
local_live_feed_access: Доступ да жывых стужак з лакальнымі допісамі
|
local_live_feed_access: Доступ да жывых стужак з лакальнымі допісамі
|
||||||
local_topic_feed_access: Доступ да хэштэгавых і спасылачных стужак з лакальнымі допісамі
|
local_topic_feed_access: Доступ да хэштэгавых і спасылачных стужак з лакальнымі допісамі
|
||||||
mascot: Уласны маскот(спадчына)
|
mascot: Уласны маскот(спадчына)
|
||||||
|
|||||||
@@ -242,6 +242,7 @@ bg:
|
|||||||
setting_emoji_style: Стил на емоджито
|
setting_emoji_style: Стил на емоджито
|
||||||
setting_expand_spoilers: Винаги разширяване на публикации, отбелязани с предупреждения за съдържание
|
setting_expand_spoilers: Винаги разширяване на публикации, отбелязани с предупреждения за съдържание
|
||||||
setting_hide_network: Скриване на социалния ви свързан граф
|
setting_hide_network: Скриване на социалния ви свързан граф
|
||||||
|
setting_quick_boosting: Включване на бързо подсилване
|
||||||
setting_reduce_motion: Обездвижване на анимациите
|
setting_reduce_motion: Обездвижване на анимациите
|
||||||
setting_system_font_ui: Употреба на стандартния шрифт на системата
|
setting_system_font_ui: Употреба на стандартния шрифт на системата
|
||||||
setting_system_scrollbars_ui: Употреба на системната подразбираща се лента за превъртане
|
setting_system_scrollbars_ui: Употреба на системната подразбираща се лента за превъртане
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ cs:
|
|||||||
content_cache_retention_period: Všechny příspěvky z jiných serverů (včetně boostů a odpovědí) budou po uplynutí stanoveného počtu dní smazány bez ohledu na interakci místního uživatele s těmito příspěvky. To se týká i příspěvků, které místní uživatel přidal do záložek nebo oblíbených. Soukromé zmínky mezi uživateli z různých instancí budou rovněž ztraceny a nebude možné je obnovit. Použití tohoto nastavení je určeno pro instance pro speciální účely a při implementaci pro obecné použití porušuje mnohá očekávání uživatelů.
|
content_cache_retention_period: Všechny příspěvky z jiných serverů (včetně boostů a odpovědí) budou po uplynutí stanoveného počtu dní smazány bez ohledu na interakci místního uživatele s těmito příspěvky. To se týká i příspěvků, které místní uživatel přidal do záložek nebo oblíbených. Soukromé zmínky mezi uživateli z různých instancí budou rovněž ztraceny a nebude možné je obnovit. Použití tohoto nastavení je určeno pro instance pro speciální účely a při implementaci pro obecné použití porušuje mnohá očekávání uživatelů.
|
||||||
custom_css: Můžete použít vlastní styly ve verzi Mastodonu.
|
custom_css: Můžete použít vlastní styly ve verzi Mastodonu.
|
||||||
favicon: WEBP, PNG, GIF nebo JPG. Nahradí výchozí favicon Mastodonu vlastní ikonou.
|
favicon: WEBP, PNG, GIF nebo JPG. Nahradí výchozí favicon Mastodonu vlastní ikonou.
|
||||||
|
landing_page: Vybere stránku, kterou návštěvníci uvidí, když prvně přijdou na tvůj server. Pokud zvolíte "Trendy", je třeba povolit trendy v nastavení objevování. Pokud zvolíte "Místní kanál", je třeba v nastavení Objevování nastavit "Přístup k živým kanálům s lokálními příspěvky" na "Všichni".
|
||||||
mascot: Přepíše ilustraci v pokročilém webovém rozhraní.
|
mascot: Přepíše ilustraci v pokročilém webovém rozhraní.
|
||||||
media_cache_retention_period: Mediální soubory z příspěvků vzdálených uživatelů se ukládají do mezipaměti na vašem serveru. Pokud je nastaveno na kladnou hodnotu, budou média po zadaném počtu dní odstraněna. Pokud jsou mediální data vyžádána po jejich odstranění, budou znovu stažena, pokud je zdrojový obsah stále k dispozici. Vzhledem k omezením týkajícím se četnosti dotazů karet náhledů odkazů na weby třetích stran se doporučuje nastavit tuto hodnotu alespoň na 14 dní, jinak nebudou karty náhledů odkazů na vyžádání aktualizovány dříve.
|
media_cache_retention_period: Mediální soubory z příspěvků vzdálených uživatelů se ukládají do mezipaměti na vašem serveru. Pokud je nastaveno na kladnou hodnotu, budou média po zadaném počtu dní odstraněna. Pokud jsou mediální data vyžádána po jejich odstranění, budou znovu stažena, pokud je zdrojový obsah stále k dispozici. Vzhledem k omezením týkajícím se četnosti dotazů karet náhledů odkazů na weby třetích stran se doporučuje nastavit tuto hodnotu alespoň na 14 dní, jinak nebudou karty náhledů odkazů na vyžádání aktualizovány dříve.
|
||||||
min_age: Uživatelé budou požádáni, aby při registraci potvrdili datum svého narození
|
min_age: Uživatelé budou požádáni, aby při registraci potvrdili datum svého narození
|
||||||
@@ -288,7 +289,8 @@ cs:
|
|||||||
content_cache_retention_period: Doba uchovávání vzdáleného obsahu
|
content_cache_retention_period: Doba uchovávání vzdáleného obsahu
|
||||||
custom_css: Vlastní CSS
|
custom_css: Vlastní CSS
|
||||||
favicon: Favicon
|
favicon: Favicon
|
||||||
local_live_feed_access: Přístup k live kanálům s lokálními příspěvky
|
landing_page: Úvodní stránka pro nové návštěvníky
|
||||||
|
local_live_feed_access: Přístup k živým kanálům s lokálními příspěvky
|
||||||
local_topic_feed_access: Přístup ke kanálům s hashtagy a odkazy s lokálními příspěvky
|
local_topic_feed_access: Přístup ke kanálům s hashtagy a odkazy s lokálními příspěvky
|
||||||
mascot: Vlastní maskot (zastaralé)
|
mascot: Vlastní maskot (zastaralé)
|
||||||
media_cache_retention_period: Doba uchovávání mezipaměti médií
|
media_cache_retention_period: Doba uchovávání mezipaměti médií
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ da:
|
|||||||
content_cache_retention_period: Alle indlæg fra andre servere (herunder fremhævelser og besvarelser) slettes efter det angivne antal dage uden hensyn til lokal brugerinteraktion med disse indlæg. Dette omfatter indlæg, hvor en lokal bruger har markeret dem som bogmærker eller favoritter. Private omtaler mellem brugere fra forskellige instanser vil også være tabt og umulige at gendanne. Brugen af denne indstilling er beregnet til særlige formål instanser og bryder mange brugerforventninger ved implementering til almindelig brug.
|
content_cache_retention_period: Alle indlæg fra andre servere (herunder fremhævelser og besvarelser) slettes efter det angivne antal dage uden hensyn til lokal brugerinteraktion med disse indlæg. Dette omfatter indlæg, hvor en lokal bruger har markeret dem som bogmærker eller favoritter. Private omtaler mellem brugere fra forskellige instanser vil også være tabt og umulige at gendanne. Brugen af denne indstilling er beregnet til særlige formål instanser og bryder mange brugerforventninger ved implementering til almindelig brug.
|
||||||
custom_css: Man kan anvende tilpassede stilarter på Mastodon-webversionen.
|
custom_css: Man kan anvende tilpassede stilarter på Mastodon-webversionen.
|
||||||
favicon: WEBP, PNG, GIF eller JPG. Tilsidesætter standard Mastodon favikonet på mobilenheder med et tilpasset ikon.
|
favicon: WEBP, PNG, GIF eller JPG. Tilsidesætter standard Mastodon favikonet på mobilenheder med et tilpasset ikon.
|
||||||
|
landing_page: Vælger, hvilken side nye besøgende ser, når de først ankommer til din server. Hvis du vælger "Trender", skal trends være aktiveret i Opdagelse-indstillingerne. Hvis du vælger "Lokalt feed", skal "Adgang til live feeds med lokale indlæg" være indstillet til "Alle" i Opdagelse-indstillingerne.
|
||||||
mascot: Tilsidesætter illustrationen i den avancerede webgrænseflade.
|
mascot: Tilsidesætter illustrationen i den avancerede webgrænseflade.
|
||||||
media_cache_retention_period: Mediefiler fra indlæg oprettet af eksterne brugere er cachet på din server. Når sat til positiv værdi, slettes medier efter det angivne antal dage. Anmodes om mediedata efter de er slettet, gendownloades de, hvis kildeindholdet stadig er tilgængeligt. Grundet begrænsninger på, hvor ofte linkforhåndsvisningskort forespørger tredjeparts websteder, anbefales det at sætte denne værdi til mindst 14 dage, ellers opdateres linkforhåndsvisningskort ikke efter behov før det tidspunkt.
|
media_cache_retention_period: Mediefiler fra indlæg oprettet af eksterne brugere er cachet på din server. Når sat til positiv værdi, slettes medier efter det angivne antal dage. Anmodes om mediedata efter de er slettet, gendownloades de, hvis kildeindholdet stadig er tilgængeligt. Grundet begrænsninger på, hvor ofte linkforhåndsvisningskort forespørger tredjeparts websteder, anbefales det at sætte denne værdi til mindst 14 dage, ellers opdateres linkforhåndsvisningskort ikke efter behov før det tidspunkt.
|
||||||
min_age: Brugere anmodes om at bekræfte deres fødselsdato under tilmelding
|
min_age: Brugere anmodes om at bekræfte deres fødselsdato under tilmelding
|
||||||
@@ -286,6 +287,7 @@ da:
|
|||||||
content_cache_retention_period: Opbevaringsperiode for eksternt indhold
|
content_cache_retention_period: Opbevaringsperiode for eksternt indhold
|
||||||
custom_css: Tilpasset CSS
|
custom_css: Tilpasset CSS
|
||||||
favicon: Favikon
|
favicon: Favikon
|
||||||
|
landing_page: Landingside for nye besøgende
|
||||||
local_live_feed_access: Adgang til live feeds med lokale indlæg
|
local_live_feed_access: Adgang til live feeds med lokale indlæg
|
||||||
local_topic_feed_access: Adgang til hashtag- og link-feeds med lokale indlæg
|
local_topic_feed_access: Adgang til hashtag- og link-feeds med lokale indlæg
|
||||||
mascot: Tilpasset maskot (ældre funktion)
|
mascot: Tilpasset maskot (ældre funktion)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user